├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── docs ├── commands.md ├── design.md ├── env.md ├── extend.md ├── pinPannel.md ├── plugins.md └── tabs.md ├── images ├── demo1.png ├── demo2.png └── web-cli-demo.gif ├── lib ├── acli.css └── acli.js └── samples ├── basic ├── README.md ├── bower.json ├── console.js └── index.html └── plugins ├── README.md ├── api └── someplugin.js ├── index.js ├── package.json └── static ├── bower.json ├── console.js ├── index.html └── plugins ├── someplugin.css └── someplugin.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | bower_components 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional REPL history 34 | .node_repl_history 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Saar Yahalom 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Anode Command Line Interface 2 | ============================= 3 | ACLI is a command line interface, developed mainly for the use in the [anode project](http://anodejs.org). 4 | 5 | __A [Polymer](https://www.polymer-project.org/1.0/) control wrapping this component is now available [here](https://github.com/amiturgman/web-cli)__ 6 | 7 | Why developing a new CLI component? 8 | ----------------------------------- 9 | Before starting to work on this module, few other existing open source CLI (command line interface) components were evaluated, like 10 | 11 | * [GCLI](https://github.com/mozilla/gcli) 12 | * [JQuery Terminal](http://terminal.jcubic.pl/examples.php) 13 | * [JQuery Console](http://neocotic.com/jquery-console) 14 | * [JS Sandbox Console](http://josscrowcroft.github.com/javascript-sandbox-console) 15 | * and more... 16 | 17 | Each of the above components and the others that were examined had some of the required functionality, but did not support other features that we needed. 18 | The most suitable library for this task was the [GCLI](https://github.com/mozilla/gcli) component, which was the main inspiration for implementing ACLI, mostly at the area of the commands structure. 19 | 20 | Features 21 | -------- 22 | * Manages environment variables and use them as part of the command line. 23 | * Supports plugins- remote commands integrated into the console, using [docRouter](https://github.com/anodejs/node-docrouter) metadata. 24 | Supporting plugins with client side processing and styling. 25 | * Keeps command line history, plugins and environment variables in offline storage. 26 | * _Pin Panel_ feature to keep command execution results on-screen. 27 | * Visualizes json data as an html table with auto collapsing deep elements. 28 | * Supports working in parallel with few instances/tabs of the console. 29 | * Supports broadcasting requests when working on a farm. 30 | 31 | __Extended documentation is available in the [docs](docs) folder__ 32 | 33 | ![Example for 'Pin Panel' feature](https://github.com/amiturgman/aCLI/raw/master/images/demo1.png "aCLI demo") 34 | 35 | ![Animated Demo](https://github.com/amiturgman/aCLI/raw/master/images/web-cli-demo.gif "animated demo") 36 | 37 | Getting Started 38 | --------------- 39 | The following is an example of how to quickly start using the component. 40 | See the `basic` sample under the `samples` directory. 41 | 42 | html file: 43 | 44 | 45 |
46 | 47 | 48 | js file: 49 | 50 | var cli = $(".cli-control").cli( 51 | { 52 | environment: {user: { type: 'string', value: '', description: 'The current user' }}, 53 | commands: getCommands(), 54 | context: { some: 'object' }, 55 | welcomeMessage: "Welcome to anode console!
. Type help to start exploring the commands currently supported!
" 56 | } 57 | ); 58 | 59 | // create default commands 60 | function getCommands() { 61 | var commands = []; 62 | 63 | var addCommand = { 64 | name: 'add', 65 | description: 'add two numbers', 66 | usage: 'add num1 num2', 67 | example: 'add 4 5', 68 | params: [ 69 | { 70 | name: 'num1', 71 | description: 'first number', 72 | type: 'number' 73 | }, 74 | { 75 | name: 'num2', 76 | description: 'second number', 77 | type: 'number' 78 | } 79 | ], 80 | exec: function(args, context) { 81 | return args.num1 + args.num2; 82 | } 83 | } 84 | 85 | var asyncCommand = { 86 | name: 'async', 87 | description: 'simulates async command', 88 | usage: 'async [text]', 89 | example: 'async', 90 | params: [ 91 | { 92 | name: 'text', 93 | description: 'some text to show', 94 | type: 'string', 95 | defaultValue: null // make this an optional argument 96 | } 97 | ], 98 | exec: function(args, context) { 99 | var promise = context.createPromise(); 100 | 101 | // simulating some async long-processing action 102 | setTimeout(function(){ 103 | var result = args.text || "you didn't enter text"; 104 | promise.resolve(result); 105 | }, 1000); 106 | 107 | return promise; 108 | } 109 | } 110 | 111 | commands.push(addCommand); 112 | commands.push(asyncCommand); 113 | 114 | return commands; 115 | } 116 | 117 | 118 | Advanced Use 119 | ------------ 120 | See `plugins` under `samples` folder for a node JS sample application that extends the console with commands from the server (plug-ins)! 121 | 122 | 123 | Main Requirements 124 | ----------------- 125 | Main requirements and detailed design can be found in the [design](docs/design.md) file. 126 | 127 | Enjoy! 128 | 129 | 130 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acli", 3 | "version": "0.0.12", 4 | "homepage": "https://github.com/amiturgman/aCLI", 5 | "authors": [ 6 | "Ami Turgman " 7 | ], 8 | "description": "web based command line interface", 9 | "main": "lib/acli.js", 10 | "license": "MIT", 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "jquery": "2.2.4", 20 | "jquery-ui": "1.12.1", 21 | "query-object": "2.0.3", 22 | "underscore": "1.9.1", 23 | "urijs": "1.19.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | Command Line 2 | ============ 3 | Each command has its own format based on the command definition. 4 | A command may have a single action, such as the `log` command that gets logs, or multiple actions (sub-commands) such as the `pipeline` command (`pipeline trigger`, `pipeline rescore` and more). 5 | To see the list of parameters for a command, type `help commandName`, or `help commandName subCommand` for commands with more than one action. 6 | Typing `help command` on a command with more than one actions, will display the list of sub commands. 7 | 8 | Command Parameters 9 | ------------------ 10 | Each command may define parameters, used by the command to execute an action. 11 | Each parameter defines 12 | 13 | * `name`- the name of the parameter. 14 | * `description`- short description for the parameter. 15 | * `type`- currently supporting `string`, `number` and `boolean` types. 16 | * an optional `default value`- if defined, the parameter is automatically considered as optional, otherwise it is required. 17 | * an optional `Default Environment Variable`- if defined, the environment variable defined will be used to get the value needed for the command. If it does not exist in the environment variables, the `default value` will be used as described above. 18 | 19 | Executing a Command 20 | ------------------- 21 | To execute a command, simply type its name, ie. `log`. If the command has sub-commands, the name will be composed of the command name and the sub-command name, ie. `deploy status`. 22 | Following the command name, type the values for the command parameters, as described in the command's help parameters list. 23 | 24 | There are several ways to provide parameters to a command- 25 | 26 | * __By parameter index__- base on the order of the parameters defined for a command, type the command name and after that provide the values for each parameter, for example `log rp.sys warn anodejs.cloudapp.net` will match the `rp.sys` to the `app` parameter, the `warn` to the `level` parameter, and the `anodejs` to the `farm` parameter. Indexed values should always be provided first after the command name. 27 | * __By the parameter name__- base on the parameter's name, type `--`+parameter name to provide its value. This can be anywhere in the command line, but always after the indexed parameters values if provided. 28 | * __By the parameter switch__- base on the parameter's switch, type `-`+parameter switch to provide its value. This can be anywhere in the command line, but always after the indexed parameters values if provided. 29 | 30 | The following commands are equivalent: 31 | 32 | plugins install plugin1 api/plugin1/!! --persist 33 | plugins install --url api/plugin1/!! --name plugin1 -p 34 | 35 | * A `boolean` parameter is always optional and defaults to `false`. 36 | To set an optional parameter to `true`, simply add `--`+parameter name or `-`+parameter switch to the command line. No need to provide `true` after that. 37 | for example `set farm -c` or `set farm --clear` will clear the farm environment value, by setting the `clear` boolean parameter to `true`. 38 | * A string value containing space charachters can be provided by wrapping it with the `"` or `'` charachters as the following 39 | 40 | log --message "some text to filter" 41 | log --message 'some text to filter' 42 | 43 | Using Environment Variables 44 | --------------------------- 45 | Providing a value for a command line parameter is easy as typing `$`+environment variable name. 46 | For example, the following will use the `mytop` environment variable as a value for the log command 47 | 48 | set mytop 10 49 | log --top $mytop 50 | 51 | -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | Anode Command Line Interface 2 | ============================= 3 | ACLI is a command line interface, developed mainly for the use in the anode project (link was removed since project is end-of-life). 4 | 5 | **This document content was mostly copied from the original [aCLI repository](https://github.com/amiturgman/aCLI)** 6 | 7 | 8 | Main Requirements 9 | ----------------- 10 | In addition to the obvious requirements like adding commands, executing commands, command line history and so on, the following are the main requirements from this component 11 | 12 | 8. __Basic Commands__ 13 | Basic implementation for the `help`, `clear`, `cls` commands should be available in the component. 14 | 15 | 2. __Command Response__ 16 | A command can return any type of data like `html`, `text`, `json` or `jquery` object. 17 | 18 | 4. __Generic json view control__ 19 | 1. Provide a generic json-view control that visualizes a json object as an html table, that will be used when commands return `json` object as the execution result. 20 | 2. The json view control will auto collapse data items that are deeper than a defined environment variable. 21 | 22 | 8. __Context__ 23 | The prompt label which is the label on the left side of the input text box, will show the current context of the console. 24 | The context itself will be set by the code hosting acli, and will be copied to the results panel on each command execution to indicate the context in which the command was executed in. 25 | 26 | 1. __Manage environment variables__ 27 | Using the known `set` command 28 | 1. Publish/Subscribe to environment variables `Change` event. 29 | 2. Use these variables as part of commands, using the `$` sign. 30 | 3. Define these variables as default values for command arguments. 31 | 4. User can define new environment variables. 32 | 5. User can not delete environment variables not created by him. 33 | 6. Environment variables can be _read-only_ to users, which means that the user will not be able to change them. 34 | 7. Environment variable can be defined to be auto added to web requests when executing commands. 35 | 36 | 5. __Persistency__ 37 | 1. Command line history is persistent and loaded whenever the console is started, to reflect last history state. 38 | 2. Plugins should be persistent, allowing each developer to plug-in his own commands. 39 | 3. Environment variables should be persistent, and loaded when the console is started to reflect the last settings state. 40 | 41 | 3. __Extending the console with remote APIs exposing [docRouter](https://github.com/anodejs/node-docrouter) metadata__ 42 | 1. Provide the ability to integrate server-side commands (plugins) exposing docRouter meta data into the console. 43 | 1. Plugins expose meta data with all required information for the console to compile it into a command. 44 | 2. Commands can be invoked using `GET` or `POST` methods. 45 | 3. Commands can define parameters that are sent as part of the `url template`, `query string` or in the `body`. 46 | 2. Plugin commands can return any type of value (json, html). 47 | 3. Plugin commands can define a client side handler method that will get the execution result and do some more processing before sending the result to be shown in the console. 48 | 4. Plugin commands can define their style classes, if they return `html` result by either providing the style classes as part of the meta data, or providing the url to a `css` file. 49 | 2. Manage plugins (install/uninstall) with persistency. 50 | 51 | 6. __Tabs__ 52 | 1. A user will be able to work with few tabs _(sessions)_ of the console in parallel. 53 | 2. All persistent data is stored per session. 54 | 3. Sessions(and their persistent data) can be restored easilty based on the session id. 55 | 4. Session can be created by opening a new browser tab, or by a programatically initialize another `acli` jquery plugin, with a new session id. 56 | 57 | 7. __Broadcast requrests__ 58 | [anode](http://anodejs.org) hosts its apps on top of azure, distributing the apps runing on top of it to all servers on the farm. 59 | We need a way to invoke requests to all instances of the farm, collect the data and present it after it is collected. 60 | This feature will mostly be used when collecting performance statistics, when we want to know the state of the app on all servers all together, and so. 61 | 62 | 8. __Docker panel__ 63 | Sometimes, we would want to keep some of the results in front of us, rather than let them scroll out of the window and disapear. 64 | We need a way to dock such desired command results to keep them _always on_ screen. 65 | 66 | Design Solution 67 | --------------- 68 | The `web-cli` component is a Polymer component wrapping the aCLI jquery plugin. 69 | The following option arguments may be provided when initializing the plugin: 70 | 71 | * `environmentVars`- a collection of a pre-defined set of environment variables. 72 | * `plugins`- a collection of a pre-defined urls for plugins to load. 73 | * `commands`- a collection of client-sode commands to add to the console. 74 | * `welcomeMessage`- a message that will appear whenever the console starts. 75 | 76 | The following is an example for a basic initialization: 77 | 78 | 79 | 80 | 81 | corpus to graph admin console 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 199 | 200 | 201 | 202 | Web-cli API 203 | -------- 204 | The following APIs are exposed to the application hosting the component: 205 | 206 | * `env(setting, val, appOriginated, isEmpty)`- used to control the environment variables. 207 | When called with no parameters, returns a copy of the current variables collection. 208 | * `setting`- the name of the environment variable. When this is the only parameter that is passed, this is a `get` action which returns the variable value. 209 | * `val`- the value of the environment variable. Used to set an existing environment variable or create a new one. 210 | * `appOriginated`- applicative flag indicating whether the environment value was set by the application code or by the user. This is used to prevent users from changing _read-only_ environment variables. 211 | * `isEmpty`- applicative flag indicating if the value should be set to an empty value. 212 | * 'addEventListener'- listening for events emmited by the control, for example- `envChanged` event 213 | 214 | var webCli = $('web-cli')[0]; 215 | webCli.addEventListener('envChanged', function(e) { 216 | console.log('envChanged!', e.detail); 217 | updatePrompt(); 218 | }); 219 | 220 | * `prompt(val)`- used to change the cli's prompt value. 221 | 222 | Commands 223 | -------- 224 | 225 | ### Writing a command 226 | 227 | A command contains the following properties: 228 | 229 | * `name`- the name of the command. When a command is part of a group of commands, the name of the command will be preceded by the group name : 'commandGroupName commandName' (example in the above code). 230 | * `description`- description of the command, which will be displayed when the user view the command help. 231 | * `usage`- description of how to use the command. 232 | * `example`- an example of a concrete command execution. 233 | * `params`- a hash of parameters the command supports (detailed explanation later). 234 | * `exec`- the function that will be called when the command was invoked by the user, passing the parsed arguments and context. 235 | * `hidden`- boolean value indicates whether the command is listed when runing `help`. 236 | 237 | #### The _params_ property 238 | Each parameter defines the following properties: 239 | 240 | * `name`- the name of the parameter. 241 | * `short`- an optional switch that will be used to reference the parameter. 242 | * `description`- description for the parameter. 243 | * `type`- the type of the parameter, now supporting `string`, `number` and `boolean`. 244 | * `defaultValue`- a default value for an argument, in case it was not provided in the command line 245 | __Notes__ 246 | * `boolean` parameter always defaults to `false` and ignores `defaultValue` property. 247 | * A parameter with no `defaultValue` is considered as `required`, while by providing a `defaultValue`, a parameter is considered as `optional`. 248 | 249 | #### The _exec_ method 250 | The exec method is the handler that is called to handle the command execution, and have two arguments: 251 | 252 | * `args`- a collection of the arguments and their values, after parsing the command line and validating their type. 253 | * `context`- contains the following objects and methods 254 | * `appcontext`- the context that the `cli` component was initialized with. 255 | * `command`- a copy of the command object. 256 | * `env`- a copy of the environment variables collection. 257 | * `createPromise`- returns an object that supports long-processing/async commands, contains the following methods 258 | * `setProgress(progress)`- sets an indicator with a number in the range [0, 100] to indicate the progress. 259 | * `resolve(result)`- when the process completes, this methods trasfers the final result to be displayed on the results panel. 260 | The result can be `text`, `html`, `jquery object` which will be displayed as is, or a `json` object which will be displayed using a generic internal json-view control. 261 | * `scrollDown()`- scrolls the console to the bottom of the screen. 262 | 263 | #### Group of commands 264 | To add a group of commands, we define a virtual _parent_ command, containing the group command name, and then all the sub-commands' name, will be preceeded by the group command name. 265 | The following is an example for a group command with two subcommands: 266 | 267 | // this is a group of commands, under the `group` name 268 | var groupCommand = { 269 | name: 'group', 270 | description: 'some group command' 271 | } 272 | var subCommand1 = { 273 | name: 'group command1', 274 | description: 'first command in group', 275 | usage: 'group command [param]', 276 | example: 'group command someParamValue', 277 | params: [{ 278 | name: 'param', 279 | type: 'string', 280 | defaultValue: null // make it optional argument 281 | }], 282 | exec: function(args, context) { 283 | return args.param || 'param not provided'; 284 | } 285 | } 286 | var subCommand2 = { 287 | name: 'group command2', 288 | description: 'second command in group', 289 | usage: 'group command2', 290 | example: 'group command2', 291 | exec: function(args, context) { 292 | return 'no parameters here, hello from exec method!'; 293 | } 294 | } 295 | 296 | // add commands to array 297 | commands.push(groupCommand); 298 | commands.push(subCommand1); 299 | commands.push(subCommand2); 300 | 301 | The following command will be used in further discussions and examples. It is also part of the basic sample package. 302 | 303 | var sampleCommand = { 304 | name: 'sample', 305 | description: 'Sample command', 306 | usage: 'sample [requiredString1] [requiredString2] [requiredNumber1] [optionalNumber1] [optionalNumber2]', 307 | example: 'sample aa bb 444 333 -l ffff -r', 308 | params: [ 309 | { 310 | name: 'requiredString1', 311 | description: 'required string 1', 312 | type: 'string' 313 | }, 314 | { 315 | name: 'requiredString2', 316 | description: 'required string 2', 317 | type: 'string' 318 | }, 319 | { 320 | name: 'requiredNumber1', 321 | description: 'required number 1', 322 | type: 'number' 323 | }, 324 | { 325 | name: 'optString1', 326 | description: 'optional string 1', 327 | type: 'string', 328 | defaultValue: 'optString1' 329 | }, 330 | { 331 | name: 'optString2', 332 | short: 'a', 333 | description: 'optional string 2', 334 | type: 'string', 335 | defaultValue: 'optString2' 336 | }, 337 | { 338 | name: 'optionalNumber1', 339 | short: 'b', 340 | description: 'optional number 1', 341 | type: 'number', 342 | defaultValue: 1234 343 | }, 344 | { 345 | name: 'optionalNumber2', 346 | short: 'c', 347 | description: 'optional number 2', 348 | type: 'number', 349 | defaultValue: 222 350 | }, 351 | { 352 | name: 'optionalNullNumber', 353 | short: 'd', 354 | description: 'optional null number 1', 355 | type: 'number', 356 | defaultValue: null 357 | }, 358 | { 359 | name: 'switchBool1', 360 | short: 'e', 361 | description: 'boolean switch', 362 | type: 'boolean' 363 | }, 364 | { 365 | name: 'switchBool2', 366 | short: 'f', 367 | description: 'boolean switch', 368 | type: 'boolean' 369 | } 370 | 371 | ], 372 | exec: function(args, context) { 373 | return "sample command args: " + JSON.stringify(args); 374 | } 375 | } 376 | 377 | ### Displaying a command execution result 378 | 379 | Text, html or jquery objects returned from command execution methods are automatically added to a container that was pre-allocated for the 380 | command execution and was placed on the DOM where we would expect the result to be displayed. 381 | This cli should also support `json` result (hash object) to be returned from a command execution method. In this case, a generic json-view control should display the json object. 382 | 383 | The json-view control logic: 384 | 385 | * for each property, create a new table line with a name and value cells. 386 | * if the value is another hash object, it recursively calls the same method for the value and displays the result table in the value cell, otherwise, it prints the value. 387 | 388 | In addition to that, there's a pre-defined environment variable called `jsonViewCollapseLevel` which can be set, and controls the level of depth, 389 | after which the results in a json-view control are collapsed automatically. 390 | 391 | ### Executing a command 392 | 393 | To execute a command, simply type its name, ie. `clear`. If the command has sub-commands, the name will be composed of the command name and the sub-command name, ie. `GroupCommandName subCommand`. 394 | Following the command name, type the values for the command parameters, as described in the command's help parameters list. 395 | 396 | There are several ways to provide parameters to a command- 397 | 398 | * __By parameter index__- base on the order of the parameters defined for a command, type the command name and after that provide the values for each parameter, for example `sample aa bb 444` will match the `aa` to the `requiredString1` parameter, the `bb` to the `requiredString2` parameter, and the `444` to the `requiredNumber1` parameter. __Indexed values should always be provided first after the command name__. 399 | * __By the parameter name__- base on the parameter's name, type `--`+parameter name to provide its value. This can be anywhere in the command line, but always after the indexed parameters values if provided. 400 | * __By the parameter switch__- base on the parameter's switch, type `-`+parameter switch to provide its value. This can be anywhere in the command line, but always after the indexed parameters values if provided. 401 | 402 | The following commands are equivalent: 403 | 404 | sample aa bb 444 cc -e --optionalNumber1 10 --switchBool2 405 | sample aa bb 444 -b 10 --optString1 cc -e -f 406 | sample aa bb --requiredNumber1 444 -b 10 --optString1 cc -e -f 407 | 408 | * A `boolean` parameter is always optional and defaults to `false`. 409 | To set an optional parameter to `true`, simply add `--`+parameter name or `-`+parameter switch to the command line. No need to provide `true` after that. 410 | for example `set envParam -c` or `set envParam --clear` will clear the envParam environment value, by setting the `clear` boolean parameter to `true`. 411 | * A string value containing space charachters can be provided by wrapping it with the `"` or `'` charachters as the following 412 | 413 | 414 | sample 'string 1' "string 2" 444 415 | 416 | 417 | ### Live command 418 | When executing commands, you can use the `live` system flag to keep refreshing the command's result every few seconds. 419 | `live` is a special flag that can be used with all commands. 420 | To use it, add `---live x` where `x` is the number of seconds between each refresh. 421 | The number of times the command refreshes itself is defined in the `liveIterationsCount` environment variable. 422 | Change this variable to control the number of times the command refreshes itself. 423 | 424 | pipeline status ---live 5 425 | 426 | 427 | Environment 428 | ----------- 429 | The console maintains environment variables, used to control its behaviour as well as by the different commands. 430 | The following list is the current predefined environment variables designed to be included in the cli: 431 | 432 | * `maxResults` used to control the max number of results being displayed. 433 | * `maxHistory` used to control the max number of commands kept in history. 434 | * `jsonViewCollapseLevel` used to control the level of depth after which the result in a json-view control are collapsed automatically. 435 | * `liveIterationsCount` used to control the number of command refreshes when using the `---live` flag. 436 | 437 | ### Managing environment variables 438 | The idea is to use the known `set` command from other consoles. For that, an internal `set` command will be implemented in the cli component. 439 | The `set` command will work in 3 modes, _list_, _set_ and _get_: 440 | * `set`- get list of all existing environment variables 441 | * `set varname varvalue`- create a new environment variables `varname` and set it to `varvalue`. 442 | * `set varname`- display the current value of `varname`. 443 | 444 | More options in the `set command` 445 | * use `set varname -c`, `set varname --clear` or `set varname ''` to clear an environment variable. 446 | * use `set varname -d` or `set varname --delete` to remove an environment variable from the list. 447 | * use `set -r` or `set --restore` to restore the environment variables collection to the original values. 448 | * use `set -o` or `set --online` to get a settings panel that gets updated online when an environment variable is changed. 449 | * use `set -x` or `set --extend` to get an extended version of the settings collection. 450 | 451 | ### Environment variables as inputs for the command line 452 | Environment variables will also be used as inputs for a command line parameter values. 453 | For example, the following will use the `str1` environment variable as a value for the `sample` command 454 | 455 | set str1 "some string" 456 | sample $str1 "string 2" 444 457 | 458 | ### Environment Variables Options 459 | When the cli is initialized, a pre-defined set of environment variables can be provided. Each of the environment variable can contain the following properties: 460 | 461 | * `type`- one of the following `string`, `number` or `boolean`. 462 | * `value`- the initial value. 463 | * `description`- short description for the use of this variable. 464 | * `options`- an array of valid options which will be validated before changing the setting value, mostly used with `string` variables. 465 | * `min`- minimum value for a `number` variable. 466 | * `max`- maximum value for a `number` variable. 467 | * `userReadOnly`- indicates that the user will not be able to change the value of this setting, and its value is managed by the application itself. 468 | * `useAtQuery`- indicates that the value, if not empty or null, will be added to the query string when calling a plugin API via a web request. 469 | 470 | ### Persistency 471 | The environment variables will be persistent. Whenever the console is started, the environment variables will be initialized with the last state from the 472 | browser's local storage. 473 | 474 | ### Context 475 | It is reasonable that the console's context will be dependant on the environment values. 476 | For that, after the application starts the cli component, it will be able to register for environment settings state change events, and then construct the `prompt` label based on them. 477 | The above code shows an example for doing this. 478 | 479 | 480 | Tabs / Sessions and Persistency 481 | ------------------------------- 482 | As any other jQuery component, ACLI should support initializing few parallel controls at the same time, on the same page. 483 | Since the component persists its state (command line history, environment variables and plugins data) in the browser's local storage, it needs a way to partition this data per each component instance. 484 | For that, when initializing a component, we can transfer a unique `sid` options variable for each instance, and it will be used to partition the offline storage data. 485 | This will be mostly used when creating few instances of the component on the same page. 486 | 487 | In addition to that, in cases that there's only one ACLI component initialized on a page, the component will support an internal command named `tab` 488 | that will use a new browser tab for creating a new session, passing the session id value as part of the command. 489 | The new page will look for the value provided in the query string, and use this value for partitioning the offline data as described above. 490 | Having this value on the query string, gives the developer the option to keep the session in his browser's favourites, and create different sessions for different applications. 491 | 492 | 493 | Docking Elements 494 | ---------------- 495 | Sometimes, we want to keep command execution results on screen. For that, a panel called `Pin Panel` is implemented. 496 | An example for such use is the `set --online` command that results in the environment variables table, which is updated on every environment state change. 497 | The internal command `panel` will toggle the _Pin Panel_, which will be positioned on the right side of the screen. 498 | 499 | There should be two ways of docking items in the _Pin Panel_: 500 | * run `panel --pin` to take the last command result and move it into the _Pin Panel_. 501 | * _experimental_- use the `CTRL` + mouse double click to make any command result draggable, and then drag it into the _Pin Panel_. 502 | 503 | To remove an item from the _Pin Panel_, `CTRL` + mouse double click the item. 504 | 505 | Clicking the _Pin Panel_ title, will maximize/minimize the panel, while keeping it on screen. 506 | To close _Pin Panel_, click the `X` button, that will appear when hovering the panel. 507 | 508 | An example: 509 | 510 | panel 511 | set --online 512 | panel --pin 513 | 514 | 515 | Plugins 516 | ------- 517 | The console will enable extending it with commands generated out of metadata defined by the docRouter decorating a server side API. 518 | 519 | The sample package contains a `plugins` directory, with a _node_ application, using ACLI and extending it with a `someplugin` command. 520 | run `node index.js` and navigate your browser to `localhost:4000`, which will open the command line. 521 | 522 | The following scenarios and examples are taken from the sample application: 523 | 524 | ### Adding Simple Commands 525 | 526 | Simple commands are commands that get parameters and return json/html to be displayed in the console as a result of executing the command with the provided parameters. 527 | These commands do not require any client side logics. They are called by the console with the neccessary parameters provided, process the request and return the __final__ result which is displayed as is. 528 | A command that returns a `json` result, will use a generic json-view control to display the object, any other result will be displayed as is (`html`). 529 | 530 | In this manual we will refer to the `someplugin.js` file as an example to extending the console. 531 | 532 | As can be seen, the file begins with the docRouter definition: 533 | 534 | app.use(docRouter(app, '/api/someplugin', function(app) { 535 | 536 | 537 | Each method in the api file defines 538 | 539 | * a Verb- `GET` or `POST`- The console uses this info to invoke the method 540 | * a set of parameters- Parameters are optional. Each parameter defines its 541 | * `type`- The parameter type: `string`, `int`, `bool`. 542 | * `required`- `true`/`false`. 543 | * `style`- `template`- this parameter is expected to be provided as part if the url, `query`- as part of the querystring or `body`- as part of the post body. 544 | * `short`- an optional switch that will be used to refer that parameter in the command line. 545 | * `defaultValue`- a value that will be used if the parameter was not provided as part of the command line. In this case, the value is automatically considered as an optional parameter. 546 | * `defaultEnvVar`- defines an environment variable parameter name that will be used as a default value if the parameter was not provided. If it does not exist or empty, the default value will be used if provided as described above. 547 | 548 | #### Sample 1: 549 | 550 | The following is an example for a command that gets 3 parameters: `tparam1` and `tparam2` which are defined as a `template` parameters, and `qparam` which is defined as a `qurtystring` parameter. 551 | The `tparam2` parameter has a `defaultValue` set, which means that as far of the console's concern, this is an optional parameter that will get the value provided in the `defaultValue` if not provided 552 | in the command line. 553 | 554 | app.get('/json/:tparam1/:tparam2', function(req, res) { 555 | var tparam1 = req.params.tparam1; 556 | var tparam2 = req.params.tparam2; 557 | var qparam = req.query['qparam']; 558 | 559 | var o = {tparam1: tparam1, tparam2: tparam2, qparam: qparam}; 560 | res.writeHead(200, {'Content-Type': 'application/json' }); 561 | res.end(JSON.stringify(o)); 562 | }, 563 | { 564 | id: 'sample_json', 565 | name: 'json', 566 | usage: 'json tparam1 qparam [tparam2]', 567 | example: 'json tparam1 qparamValue', 568 | doc: 'sample for a GET command getting template params and a query param', 569 | params: { 570 | "tparam1" : { 571 | "short": "b", 572 | "type": "string", 573 | "doc": "template param", 574 | "style": "template", 575 | "required": "true" 576 | }, 577 | "qparam" : { 578 | "short": "q", 579 | "type": "string", 580 | "doc": "querty string param", 581 | "style": "query", 582 | "required": "true" 583 | }, 584 | tparam2 : { 585 | "short": "a", 586 | "type": "string", 587 | "doc": "template param", 588 | "style": "template", 589 | "required": "true", 590 | "defaultValue": "someTemplateValue" 591 | } 592 | } 593 | } 594 | ); 595 | 596 | 597 | * The following command: `someplugin json 1 2` calls `GET /api/someplugin/json/1/someTemplateValue?qparam=2` and returns the result `{"tparam1":"1","tparam2":"someTemplateValue","qparam":"2"}`. 598 | * The following command: `someplugin json 1 2 3` calls `GET /api/someplugin/json/1/3?qparam=2` and returns the following result `{"tparam1":"1","tparam2":"3","qparam":"2"}`. 599 | 600 | __Required parameters should always be provided at the begining of the command__ 601 | 602 | #### Sample 2: 603 | 604 | The following `someplugin html` command defines a required body parameter `bparam` and also an optional boolean parameter `flag`. 605 | Boolean parameters are always considered as optional and defaults to `false`. To set them to `true`, we should only add them to the command line. 606 | Notice that this time, the method uses the `POST` verb, and that the `bparam` parameter will be sent as part of the request's body. 607 | 608 | app.post('/html', function(req, res) { 609 | var flag = req.query.flag; 610 | res.writeHead(200, {'Content-Type': 'text/html' }); 611 | res.end("
HTML output: Template Param: "+req.params.tparam+ 612 | " Flag: " + flag +"
"); 613 | }, 614 | { 615 | id: 'sample_html', 616 | name: 'html', 617 | usage: 'html tparam qparam', 618 | example: 'html tparam qparam', 619 | doc: 'sample for a POST command getting a template param returning html', 620 | params: { 621 | "bparam" : { 622 | "short": "b", 623 | "type": "string", 624 | "doc": "template param", 625 | "style": "body", 626 | "required": "true" 627 | }, 628 | "flag" : { 629 | "short": "f", 630 | "type": "bool", 631 | "doc": "some boolean flag", 632 | "style": "query", 633 | "required": "true" 634 | }, 635 | 636 | } 637 | } 638 | ); 639 | 640 | * The following command: `someplugin html 1 -f` calls `POST /api/someplugin/html?flag=true` with a body `{bparam: 1}` and returns the result `
HTML output: Template Param: 1 Flag: true
`. 641 | * The following command: `someplugin html 1` calls `/api/someplugin/html` with a body `{bparam: 1}` and returns the following result `
HTML output: Template Param: 1 Flag: undefined
`. 642 | 643 | 644 | ### Advanced Commands 645 | 646 | Commands that need any processing on the client side, are considered as advanced commands. 647 | Such commands may be for example, commands that get `json` and present charts, or any other command that gets the results and make some further processing before displaying it to the user. 648 | 649 | Advanced command will define a `controller` attribute, in which a javascript file and a handler method name will be set. 650 | The following `sample handler` command, will get two parameters, and the returned result will be transfered to the `handler` method in the `/plugins/someplugin.js` file. 651 | The `controller.handler` attribute is optional and will override the default handler name which is the command name if defined. 652 | 653 | 654 | app.get('/handler/:tparam', function(req, res) { 655 | var tparam = req.params.tparam; 656 | var qparam = req.query['qparam']; 657 | 658 | var o = {tparam: tparam}; 659 | res.writeHead(200, {'Content-Type': 'application/json' }); 660 | res.end(JSON.stringify(o)); 661 | }, 662 | { 663 | id: 'sample_handler', 664 | name: 'handler', 665 | usage: 'handler', 666 | example: 'handler', 667 | doc: 'sample for a GET command using external handler', 668 | params: { 669 | "tparam" : { 670 | "short": "t", 671 | "type": "string", 672 | "doc": "template param", 673 | "style": "template", 674 | "required": "true" 675 | } 676 | }, 677 | controller: { 678 | url: '../../plugins/someplugin.js', 679 | handler: 'handler' // this is optional, the default will be the command name 680 | } 681 | } 682 | ); 683 | 684 | The following is the `/plugins/someplugin.js` file. 685 | 686 | console.info('sample handler loaded'); 687 | 688 | this.handler = function(error, response){ 689 | if ($.query.get('debug')) debugger; 690 | var args = response.args; 691 | var context = response.context; 692 | var url = context.url; 693 | var env = context.env; 694 | var command = context.command; 695 | var data = response.data; 696 | 697 | var progress = 0; 698 | var intrvl = setInterval(function(){ 699 | response.promise.setProgress(progress); 700 | if(progress==100) { 701 | clearInterval(intrvl); 702 | response.promise.resolve( 703 | $("
").addClass('anode-sample').html( 704 | 'this is the client side plugin handler speaking.. got the following parametrs:' + JSON.stringify(args) + 705 | '. got the following response: ' + JSON.stringify(data))); 706 | } 707 | progress+=10; 708 | }, 200); 709 | } 710 | 711 | When the script is loaded, it is wrapped in a function and called with a `call` method, providing the context object. All handlers will be added to this context when executing the wrapped code. 712 | Since the script is loaded dynamically, you wan't be able to see it in the developer tools of Chrome/FireFox. One solution for this issue, is adding `if ($.query.get('debug')) debugger;` 713 | statement to the begining of the method, and load the console with `?debug` at the query string. This will make the browser to stop at this line and allow you to debug it. 714 | When the script is loaded, it is attached as the command execute method. 715 | When the console calls this method, it will pass an `error` and a `response` object. 716 | The `response` object contains the following parameters: 717 | 718 | * `args`- contains all arguments provided as part of the command line. 719 | * `data`- contains the data returned from the api call. 720 | * `promise`- an object that helps us manage the command response. This object contains the following methods: 721 | * `setProgress(percent)`- set a percentage indicator, use when the processing might be long and we would like to update the user on the progress that is made. 722 | * `resolve(res)`- used to end the command processing by passing the final result to the console. The result may be any string or html element. 723 | * `context`- contains command execution related objects such as 724 | * `url`- the url that was called . 725 | * `env`- a copy of the environment variable collection (so it won't be able to change it). 726 | * `command`- a copy of the command object if there's any need for details from it. 727 | 728 | Try executing `someplugin handler val` to see how it behaves. 729 | 730 | ### Adding A Command Custom Style 731 | 732 | If the controller needs any special stylesheet, on the server side, use the `cssUrl` or the `css` attributes as can be seen in the sample file for the `htmlbcast` and `handler` commands. 733 | 734 | controller: { 735 | css: '.anode-sample-class{ background-color: green; }' 736 | } 737 | 738 | controller: { 739 | url: '../../plugins/someplugin.js', 740 | cssUrl: '../../plugins/someplugin.css' 741 | } 742 | 743 | 744 | __Note:__ It is very important to namespace your css classes to avoid impact on other elements in the DOM. In the example above, the style is namespaced with the `anode-sample` class. 745 | -------------------------------------------------------------------------------- /docs/env.md: -------------------------------------------------------------------------------- 1 | Environment Variables 2 | ===================== 3 | The console maintains environment variables, used to control its behaviour as well as by the different commands. 4 | The following list is the current predefined environment variables, type `set` to see it: 5 | 6 | * `maxResults` used to control the max number of results being displayed. 7 | * `maxHistory` used to control the max number of commands kept in history. 8 | * `jsonViewCollapseLevel` used to control the level of depth after which the result in a json-view control are collapsed automatically. 9 | * `liveIterationsCount` used to control the number of command refreshes when using the `---live` flag. 10 | * `app` defines the application on which we would like to act upon; used by most of the app-related commands as a default value. 11 | 12 | Console's Context 13 | ----------------- 14 | The prompt label on the left side of the command line, is the __context__ of the commands invoked. 15 | 16 | For example, a command which defines a required parameter named `app`, will automatically use the value 17 | from the environment variable if available, without the need to provide it as part of the command line. 18 | If the environment variable is not present or contains an empty value, a value will have to be provided 19 | as part of the command line. 20 | 21 | ` 22 | There's currently no validations for the environment values. 23 | ` 24 | 25 | Adding Environment Variables 26 | ---------------------------- 27 | You can create new environment variables by simply typing `set name value`. 28 | Using environment values as part of a command is easy as typing `$varname`, for example `log --top $mytop` will use the `mytop` value from 29 | the environment variable when parsing and executing the command. 30 | 31 | Managing Environment Variables 32 | ------------------------------ 33 | Type `set varname -c`, `set varname --clear` or `set varname ''` to clear an environment variable. 34 | Type `set varname -d` or `set varname --delete` to remove an environment variable from the list. 35 | Type `set -r` or `set --restore` to restore the environment variables collection to the original values. 36 | 37 | Persistency 38 | ----------- 39 | The environment variables are persistent. Whenever the console is started, the environment variables are initialized with the last state from the 40 | browser's local storage, keeping the context used last time. 41 | 42 | -------------------------------------------------------------------------------- /docs/extend.md: -------------------------------------------------------------------------------- 1 | Extending The Console 2 | ===================== 3 | __Extending the console is easy!__ 4 | 5 | The console uses metadata defined by the docRouter module to generate the commands. 6 | In order to create another command, add a new file under the `repo/sys/console/api` folder. The command will be named after the new api file name. 7 | 8 | The api file should expose the following structure: 9 | 10 | module.exports = { console: { autoLoad: true, router: expressRouter } }; 11 | 12 | When the `autoLoad` flag is set to `true`, it will make the plugin to be automatically integrated into the console when it is started. 13 | The `router` parameter is the express `router` object. Look at the api files for an example of how the apis are exposed. 14 | 15 | Each API can contain any number of commands, all namespaced by the api file name. The console will generate a command for each of the methods in the api file. 16 | To execute a method (command), type the api file name and then the method name. ie, to execute `command1` in file `api1`, type `api1 command1` and then provide the params if required. 17 | 18 | Adding Simple Commands 19 | ---------------------- 20 | Simple commands are commands that get parameters and return json/html to be displayed in the console as a result of executing the command with the provided parameters. 21 | These commands do not require any client side logics. They are called by the console with the neccessary parameters provided, process the request and return the __final__ result which is displayed as is. 22 | A command that returns a `json` result, will use a generic json-view control to display the object, any other result will be displayed as is (`html`). 23 | 24 | Most of the commands currently implemented are _simple_ commands. 25 | 26 | In this manual we will refer to the `sample.js` file as an example to extending the console. 27 | 28 | As can be seen, the file begins with the docRouter definition, defining `/api/sample` as the root for this api: 29 | 30 | docRouter(router, '/api/sample', function (router) { 31 | 32 | 33 | Each method in the api file defines 34 | 35 | * a Verb- `GET` or `POST`- The console uses this info to invoke the method 36 | * a set of parameters- Parameters are optional. Each parameter defines its 37 | * `type`- The parameter type: `string`, `int`, `bool`. 38 | * `required`- `true`/`false`. 39 | * `style`- `template`- this parameter is expected to be provided as part if the url, `query`- as part of the querystring or `body`- as part of the post body. 40 | * `short`- an optional switch that will be used to refer that parameter in the command line. 41 | * `defaultValue`- a value that will be used if the parameter was not provided as part of the command line. In this case, the value is automatically considered as an optional parameter. 42 | * `defaultEnvVar`- defines an environment variable parameter name that will be used as a default value if the parameter was not provided. If it does not exist or empty, the default value will be used if provided as described above. 43 | An example to using this option can be found in the `log.js` api file. 44 | 45 | 46 | Sample 1: 47 | --------- 48 | The following is an example for a command that gets 3 parameters: `tparam1` and `tparam2` which are defined as a `template` parameters, and `qparam` which is defined as a `qurtystring` parameter. 49 | The `tparam2` parameter has a `defaultValue` set, which means that as far of the console's concern, this is an optional parameter that will get the value provided in the `defaultValue` if not provided 50 | in the command line. 51 | 52 | app.get('/json/:tparam1/:tparam2', function(req, res) { 53 | var tparam1 = req.params.tparam1; 54 | var tparam2 = req.params.tparam2; 55 | var qparam = req.query['qparam']; 56 | 57 | var o = {tparam1: tparam1, tparam2: tparam2, qparam: qparam}; 58 | res.writeHead(200, {'Content-Type': 'application/json' }); 59 | res.end(JSON.stringify(o)); 60 | }, 61 | { 62 | id: 'sample_json', 63 | name: 'json', 64 | usage: 'json tparam1 qparam [tparam2]', 65 | example: 'json tparam1 qparamValue', 66 | doc: 'sample for a GET command getting template params and a query param', 67 | params: { 68 | "tparam1" : { 69 | "short": "b", 70 | "type": "string", 71 | "doc": "template param", 72 | "style": "template", 73 | "required": "true" 74 | }, 75 | "qparam" : { 76 | "short": "q", 77 | "type": "string", 78 | "doc": "querty string param", 79 | "style": "query", 80 | "required": "true" 81 | }, 82 | tparam2 : { 83 | "short": "a", 84 | "type": "string", 85 | "doc": "template param", 86 | "style": "template", 87 | "required": "true", 88 | "defaultValue": "someTemplateValue" 89 | } 90 | } 91 | } 92 | ); 93 | 94 | 95 | * The following command: `sample json 1 2` calls `GET /api/sample/json/1/someTemplateValue?qparam=2` and returns the result `{"tparam1":"1","tparam2":"someTemplateValue","qparam":"2"}`. 96 | * The following command: `sample json 1 2 3` calls `GET /api/sample/json/1/3?qparam=2` and returns the following result `{"tparam1":"1","tparam2":"3","qparam":"2"}`. 97 | 98 | __Required parameters should always be provided at the begining of the command__ 99 | 100 | Sample 2: 101 | --------- 102 | The following `sample html` command defines a required bosy parameter `bparam` and also an optional boolean parameter `flag`. 103 | Boolean parameters are always considered as optional and defaults to `false`. To set them to `true`, we should only add them to the command line. 104 | Notice that this time, the method uses the `POST` verb, and that the `bparam` parameter will be sent as part of the request's body. 105 | 106 | app.post('/html', function(req, res) { 107 | var flag = req.query.flag; 108 | res.writeHead(200, {'Content-Type': 'text/html' }); 109 | res.end("
HTML output: Template Param: "+req.params.tparam+ 110 | " Flag: " + flag +"
"); 111 | }, 112 | { 113 | id: 'sample_html', 114 | name: 'html', 115 | usage: 'html tparam qparam', 116 | example: 'html tparam qparam', 117 | doc: 'sample for a POST command getting a template param returning html', 118 | params: { 119 | "bparam" : { 120 | "short": "b", 121 | "type": "string", 122 | "doc": "template param", 123 | "style": "body", 124 | "required": "true" 125 | }, 126 | "flag" : { 127 | "short": "f", 128 | "type": "bool", 129 | "doc": "some boolean flag", 130 | "style": "query", 131 | "required": "true" 132 | }, 133 | 134 | } 135 | } 136 | ); 137 | 138 | * The following command: `sample html 1 -f` calls `POST /api/sample/html?flag=true` with a body `{bparam: 1}` and returns the result `
HTML output: Template Param: 1 Flag: true
`. 139 | * The following command: `sample html 1` calls `/api/sample/html` with a body `{bparam: 1}` and returns the following result `
HTML output: Template Param: 1 Flag: undefined
`. 140 | 141 | Advanced Commands 142 | ================= 143 | Commands that need any processing on the client side, are considered as advanced commands. 144 | Such commands may be for example, commands that get `json` and present charts, or the current log command that gets the results and make some further processing before displaying it to the user. 145 | 146 | Advanced command will define a `controller` attribute, in which a javascript file and a handler method name will be set. 147 | The following `sample handler` command, will get two parameters, and the returned result will be transfered to the `handler` method in the `/plugins/sample.js` file. 148 | The `controller.handler` attribute is optional and will override the default handler name which is the command name if defined. 149 | 150 | 151 | app.get('/handler/:tparam', function(req, res) { 152 | var tparam = req.params.tparam; 153 | var qparam = req.query['qparam']; 154 | 155 | var o = {tparam: tparam}; 156 | res.writeHead(200, {'Content-Type': 'application/json' }); 157 | res.end(JSON.stringify(o)); 158 | }, 159 | { 160 | id: 'sample_handler', 161 | name: 'handler', 162 | usage: 'handler', 163 | example: 'handler', 164 | doc: 'sample for a GET command using external handler', 165 | params: { 166 | "tparam" : { 167 | "short": "t", 168 | "type": "string", 169 | "doc": "template param", 170 | "style": "template", 171 | "required": "true" 172 | } 173 | }, 174 | controller: { 175 | url: '../../plugins/sample.js', 176 | handler: 'handler' // this is optional, the default will be the command name 177 | } 178 | } 179 | ); 180 | 181 | The following is the `/plugins/sample.js` file. 182 | The `anode.loadCss` and `anode.addCss` are discussed later on in the _Adding A Command Custom Style_ section. 183 | 184 | console.info('sample handler loaded'); 185 | 186 | // the following 2 lines are possible. in this case, the css are defined on the server side 187 | //anode.loadCss('plugins/sample.css'); 188 | //anode.addCss(".anode-sample {width: 300px;border: 1px solid gray;background-color: rgba(236, 255, 117, 0.60);padding: 4px;}") 189 | 190 | // anode will always be in the global context in this stage as this script always loaded after anode is already initialized 191 | this.handler = function(error, response){ 192 | if ($.query.get('debug')) debugger; 193 | var args = response.args; 194 | var context = response.context; 195 | var url = context.url; 196 | var env = context.env; 197 | var command = context.command; 198 | var data = response.data; 199 | 200 | var progress = 0; 201 | var intrvl = setInterval(function(){ 202 | response.promise.setProgress(progress); 203 | if(progress==100) { 204 | clearInterval(intrvl); 205 | response.promise.resolve( 206 | $("
").addClass('anode-sample').html( 207 | 'this is the client side plugin handler speaking.. got the following parametrs:' + JSON.stringify(args) + 208 | '. got the following response: ' + JSON.stringify(data))); 209 | } 210 | progress+=10; 211 | }, 200); 212 | } 213 | 214 | When the script is loaded, it is wrapped in a function and called with a `call` method, providing the context object. All handlers will be added to this context when executing the wrapped code. 215 | Since the script is loaded dynamically, you wan't be able to see it in the developer tools of Chrome/FireFox. One solution for this issue, is adding `if ($.query.get('debug')) debugger;` 216 | statement to the begining of the method, and load the console with `?debug` at the query string. This will make the browser to stop at this line and allow you to debug it. 217 | When the script is loaded, it is attached as the command execute method. 218 | When the console calls this method, it will pass an `error` and a `response` object. 219 | The `response` object contains the following parameters: 220 | 221 | 222 | * `args`- contains all arguments provided as part of the command line. 223 | * `data`- contains the data returned from the api call. 224 | * `promise`- an object that helps us manage the command response. This object contains the following methods: 225 | * `setProgress(percent)`- set a percentage indicator, use when the processing might be long and we would like to update the user on the progress that is made. 226 | * `resolve(res)`- used to end the command processing by passing the final result to the console. The result may be any string or html element. 227 | * `context`- contains command execution related objects such as 228 | * `url`- the url that was called . 229 | * `env`- a copy of the environment variable collection (so it won't be able to change it). 230 | * `command`- a copy of the command object if there's any need for details from it. 231 | 232 | Try executing `sample handler val` to see how it behaves. 233 | 234 | Adding A Command Custom Style 235 | ----------------------------- 236 | If the controller needs any special stylesheet, it can do so in few ways 237 | 238 | * Use the `anode.loadCss(url)` command to load a css file and add it to the DOM. 239 | * Use the `anode.addCss(style)` command to embed style classes. 240 | * On the server side, use the `cssUrl` or the `css` attributes as can be seen in the sample file for the `htmlbcast` and `handler` commands. 241 | 242 | 243 | controller: { 244 | css: '.anode-sample-class{ background-color: green; }' 245 | } 246 | 247 | controller: { 248 | url: '../../plugins/sample.js', 249 | cssUrl: '../../plugins/sample.css' 250 | } 251 | 252 | 253 | __Note:__ It is very important to namespace your css classes to avoid impact on other elements in the DOM. In the example above, the style is namespaced with the `anode-sample` class. 254 | 255 | 256 | Adding Remote Management API 257 | ============================ 258 | Please type `man plugins` for information about how to create your own remote management API and integrate it into the console. 259 | 260 | -------------------------------------------------------------------------------- /docs/pinPannel.md: -------------------------------------------------------------------------------- 1 | Managing The Pin Panel 2 | ========================= 3 | 4 | _Pin Panel_ is a panel on the right side of the screen, letting you keep command results always visible to you. 5 | Think of Performance monitor charts, long processing test execution commands, placing these manuals always-on and more. 6 | 7 | To make the board visible, execute the `panel` command. Whenever executed, this command will toggle the visibility state of the board. 8 | 9 | There are two ways to pin command results on _Pin Panel': 10 | 11 | * After the command execution, type `panel --pin`. This will take the last result and move it to the _Pin Panel' panel. 12 | * While pressing the `ctrl` key, double click the result item. This will make the item _draggable_. You are now able to drag the item and drop on _Pin Panel' panel. 13 | 14 | 15 | Actions 16 | ------- 17 | * To cancel an item from being _draggable_, just `ctrl`+double click on that item again. 18 | * To delete an item from _Pin Panel' panel, `ctrl`+double click that item. 19 | 20 | 21 | You can leave _Pin Panel' opened on screen, minimized or maximize by clicking the `panel` title. 22 | To close _Pin Panel' panel, click the X button which will apear when hovering the panel. 23 | 24 | Example 25 | ------- 26 | Try the following 27 | 28 | ``` 29 | panel 30 | man pinPanel 31 | pinPanel --pin 32 | ``` 33 | 34 | Notice that this manual is now always on in the _Pin Panel'. 35 | 36 | Another Example 37 | --------------- 38 | Type `set -o`. `ctrl`+double click the result and release the buttons. Now, drag the result panel to _Pin Panel' panel, and relase the mouse. 39 | Notice the settings panel is pinned on _Pin Panel' panel. 40 | 41 | Now try to change a setting variable, for example, change the application context by typing `set app console.sys`. 42 | Notice that the settings panel is updated and always displays the updated settings state. 43 | 44 | Have fun! 45 | 46 | -------------------------------------------------------------------------------- /docs/plugins.md: -------------------------------------------------------------------------------- 1 | Creating Management API For My App 2 | ================================== 3 | __Note:__ Please make sure to read the _extend_ manual (type `man extend`) before you continue. 4 | 5 | Creating your own management API and integrating it into the console is as easy as extending the console, as described in the `extend` manual. 6 | The only diffrence is that the API is implemented as part of your application and exposes the docRouter information as discussed earlier. 7 | 8 | In order to _plug-in_ the API into the console, use the `plugins install` command. 9 | In the following example, we're installing `mysample` API: 10 | 11 | ` 12 | plugins install mysample http://mysample.com/!! -p 13 | ` 14 | 15 | After executing this command, the console will get the docRouter data, compile the commands and add them to the commands available for execution. 16 | Type `help` or `help mysample` to see how this command is now part of the console command list. 17 | The `-p` switch makes the plugin persistent. This means that next time when opening the console (from the same machine), the plugin will be automatically be integrated into the console. 18 | Not using this switch will make the command available only for this session. Closing and opening the console will not load this command. 19 | 20 | This feature allows you to create your own set of APIs integrated into the console. 21 | 22 | * In order to _uninstall_ a plugin, use the `plugins uninstall` command. 23 | * In order to remove all plugins currently installed, use the `plugins reset` command. 24 | 25 | __Notes:__ 26 | 27 | * The API exposed to the console should be publicly available to the console. No authentication is currently supported. 28 | * This works best in Chrome browser. IE for example has Cross Site Origin issue loading plugins from other domains. 29 | -------------------------------------------------------------------------------- /docs/tabs.md: -------------------------------------------------------------------------------- 1 | Managing Parallel Console Sessions 2 | ================================== 3 | As you probably already know by now, the console keeps the commands history, environment variables and the installed plugins in the browser's local storage, used to restore its state when initialized. 4 | 5 | The `tabs` feature allows you to open few console `sessions`. Each such session will keep all of the storage data listed above, partitioned by the optional `name` argument. 6 | Any time, you can go back to a session by just executing the command with the same session name. 7 | This way you will be able to create few sessions, each with its own set of environment variables, history and plugins related to it. 8 | 9 | For example, the following commands will create two sessions 10 | 11 | ``` 12 | tabs app1 13 | tabs app2 14 | 15 | ``` 16 | 17 | Now, in each _session_ you are now able to create your own set of environment variables and install plugins. 18 | 19 | -------------------------------------------------------------------------------- /images/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiturgman/aCLI/e133c0cb043433da2b65c318448780d41a8d0a1d/images/demo1.png -------------------------------------------------------------------------------- /images/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiturgman/aCLI/e133c0cb043433da2b65c318448780d41a8d0a1d/images/demo2.png -------------------------------------------------------------------------------- /images/web-cli-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiturgman/aCLI/e133c0cb043433da2b65c318448780d41a8d0a1d/images/web-cli-demo.gif -------------------------------------------------------------------------------- /lib/acli.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .cli-control { 4 | font-family: courier; 5 | font-size: 1.1em; 6 | margin: 0; 7 | color: #3d3d3d; 8 | display: block; 9 | height: 100%; 10 | } 11 | 12 | .cli-promptText { 13 | color: #808080; 14 | margin-right: 5px; 15 | float: left; 16 | } 17 | 18 | .cli-input-wrapper { 19 | overflow: hidden; 20 | } 21 | 22 | .cli-input-container-clear { 23 | clear: both; 24 | } 25 | 26 | .cli-commandRow { 27 | margin-top: 10px; 28 | } 29 | 30 | .cli-commandText { 31 | font-weight: bold; 32 | } 33 | 34 | .cli-output-warn { 35 | color: orange; 36 | } 37 | 38 | .cli-output-info { 39 | color: auto; 40 | } 41 | .cli-output-error { 42 | font-weight: bold; 43 | color: red; 44 | } 45 | 46 | .cli-tab-container { 47 | border: 1px solid #008000; 48 | background-color: red; 49 | position: absolute; 50 | top: 100px; 51 | left: 100px; 52 | width: 300px; 53 | height: 500px; 54 | } 55 | 56 | 57 | 58 | .cli-output { 59 | font-size: 1.1em; 60 | position: absolute; 61 | top: 0; 62 | bottom: 26px; 63 | left: 0; 64 | right: 0; 65 | width: 100%; 66 | overflow: auto; 67 | } 68 | 69 | .cli-output > div { 70 | padding: 5px; 71 | } 72 | 73 | .cli-pinPanel { 74 | position: absolute; 75 | top: 0; 76 | bottom: 26px; 77 | width: 40%; 78 | right: 10px; 79 | padding: 3px; 80 | background-color: #ffffef; 81 | border: 1px solid #d3d3d3; 82 | opacity: 0.9; 83 | display: none; 84 | overflow: auto; 85 | } 86 | 87 | .cli-pinPanel-close { 88 | display: none; 89 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAABkWlDQ1BJQ0MgUHJvZmlsZQAAeJyVkT1LHFEUhp87SCwS1gSnUotrJ7obFkWwSKNTLJIUy6Kyu93szLgujLOXO9evIm2aCJp0gSD6D2wShEDQOrESt0yTxlIEtVAYi5uwjSK+cOA5LwfOFzi/faViB1hOjK6UZmS1Vpe9HXoZBAA/SNV0ufyOB3XVQQCcFHyl4ofr7tULXa3VQbiA27Q8ArgNy28Ad80oA6IMuMGSH4IIgbyeq3ggtoFc0/IukGtY3gdyq0HTgDgEiknYSkCcA1NhlAbgjAAmUNqA8xUoVGt1aUebG4NCDziHXe/DFhz9hIGJrjc0BK/ew/fRrnfxCwGI5Eu6ODEOgBjsh+edLDvbg77PcHuUZdeXWXZzCs++wafNYEWv/ruLEMfwWG53s7n9Adi+97PdH4Ai7PyFhWFYmIePKcgDePkHSifw9gfiWPwPeysABjw/bjW0b6JQVkoz0mvHbZ0qP4ie9PbHZaJ1A+C11YZuNZeMnFYqjqTXXlYrJtJ5OZsEr/NyvFicBLgDENJ1NIrxLpQAAAAJcEhZcwAACxMAAAsTAQCanBgAAAbMSURBVEiJlZdfTFTZHce/M/fegbnMzGUY/u0w6wAKMzQwLSotCe7UxASNaHVLJsZWWzYYTA19M/VF00xaXzQaEwKGpAb71hirMawPNNGCiZq6pIjRRBtdQRk6AtKF+Xtn7r2/PnAue2ccyXqS38vJ+Z3P+f0955iw8TAxwc6dO82xWMyUTqdNsiybjh8/zouiaBoaGsoAQHFxMdntdhofH9eYLjEpOPgNgGYmnM/nE/x+v9Xn84npdJqfn58njuOcPM9zoijONzc3w+v10uzsrPzs2bPU4uKiAkAFoDH54ACmj0A5AFxFRYXQ2toqdnV1Vba0tGxqaWn5uSRJQUEQ2o0KiqI8jsVi30xNTY0ODg4+efToUUyW5czi4mKWHUDdyHowC3kA1tra2tJQKLTpypUrO2Kx2N8pb0SjUXVmZkbNn19ZWfmbz+era21tdTudTgmAle1p/hjUxBaIHo+n7MiRI1tu3br1C1VVZ4iIMpmMNjw8nOns7EwCiBmlra0tceHChUwqldLY2sd37tw5vG3btk2SJDkBiGzvDzysQ60Oh6Ns9+7dW65du/YlEclERGNjY4rX643nA/PF7/cnJicnFd36sbGxX/v9/loGtxaCcwCKAEiBQKDu3LlzexRFmSUiGhkZyXActyHQKBzHxc6ePSvr8MuXL3/pdrs/ByAxBqfDTQAEALby8nL3iRMndiwvL3+tW/opUKMMDQ1liIhisdgDAD8CUA3AxlhmPaGKHQ5HWTAYbLp69epXRESJREL1eDw57t26dWuio6PjgxhXVlbGe3t706Iors9ZLJZYJBLJEhFNTEz80ePxNAAoA1DMrIYZQIkoiu5QKLTj9evXfyUiGhgYyBg3b25uTujuC4fDsj5fX1+fiEQiGhHR8PBwjk5fX1+aiGhpaemfAAIAPmNW83p8JUmS6vfu3bsnmUw+JiJqb2/Psczr9caTySQZ4Q0NDetQIqLz58/LRh2XyxVXlLVcKy0tDQKoA1AKwAJGd1VVVTUfOHDgV0REq6uraqG4dXV1JY3w1dXVdeiNGzeyhfJhenpaJiI6derU7+12ux9AOYBiMwCTzWbjysvLrRUVFeUAEI1GC3aZ27dvq6FQKCXLMgDAbrebAODmzZtKKBRKq6r6gc7s7CwAoLa21qNp2npWmwFA0zTOZDIJqqoWA0A2my3EBQC8evWK4vF4zsGePn2qFYICgD6vaVpRIpHgGNhsBoBkMmlaWFjAy5cvUwBQXV1dcBO/328eHx+3ulwuk3HTM2fOWMLhsKWQjtvt1gBgZmYmZrVacxqIAMAtCMLP6urqejOZzAIRUX4pSZIUj0ajOTHdv39/Tsz7+/vTRh2O42IrKytZIqJAIPA7QRB+CqAGQIneuAmAJoqi8vbt2+cA0N3dnXNlNjU1mauqqnJiOjo6qoZCoVQqlQIABINBzqiza9cuzuFw8O/evXv65MmT77LZbM4tJQCoFAThJ42NjYcuXrz4F3b7ZI0NAawu+/v70/nZ29bWlgiHw3K+l+7du5cgIhodHR0F8EsAPwZQhbW+vVZOAJqKior2ejyeP0QikalCdfkp0tPTk2Itc97lcp0CsAdAE1g5AayBAKjjef6LsrKy3mPHjg3rcevp6Ul9KrSjoyOZzWZVIqLBwcGbAL4C8AUMDUS/KTgAnKZpQiaTKZ6bm+NKSkpW29ramg4ePMgTESYmJgrXS944evQof/369SKLxWJ+8ODBN93d3f8AMAdgAcAKgDSALMAuCaw18C0AghzH/dbpdP55YGDglm758+fP04cOHUrlx12Xzs7O5MOHD9dT/P79+/8G8CcAvwEQZHuvXxL6K5LH2l1pYzGo4TjOa7PZ6g8fPtx0+vTpppqamgYAkGVZe/HihTI5OUkAEAgE0NjYyDkcDh4AlpeX50dGRv5z8uTJfwH4FsAsgAiAJQBxADIAVS9mjsGLATgY/DOO4zxFRUWfJ5PJmkuXLjXu27evfPPmzb5CLn7z5s23d+/efd/X1/csm83OAXjLXPxfBl1lblYAaDpYj7PA4Hbmliom1VartTKVSpVJklS6ffv2ivb2dicATE1NrUxPTy9FIpH3AJZZLKMA3jFZZuHQY6sCIOP7R4dbDG6XADixVm5lWMtIG8/zVgC8oijgeV7RNC2laVocwHcM9B7A/1gy6e7NwPDMzX/16Y94gR3ACqCEecDODiPi+7cT2GYygCSD6AmXAJBiwCy+f9yvW5k/9F8Ez0T3gC4W5L6TNazFLcMOoEuGzSso8JsoBDbO64cwfmlMBfT0f5Lxy2KE/aAvzMfGD1274VdFH/8HBejinPCjSboAAAAASUVORK5CYII=); 90 | height: 30px; 91 | width: 30px; 92 | background-color: transparent; 93 | position: absolute; 94 | left: 3px; 95 | top: 2px; 96 | } 97 | 98 | .cli-pinPanel:hover .cli-pinPanel-close { /* when hovering a note- display its close button */ 99 | display: block; 100 | } 101 | 102 | .cli-pinPanel-title { 103 | background-color: #ffffad; 104 | font-weight: bold;; 105 | text-align: center; 106 | font-size: 1.2em; 107 | cursor: pointer; 108 | padding: 5px; 109 | } 110 | 111 | .cli-pinPanel-item { 112 | margin-top: 5px; 113 | } 114 | 115 | .cli-input-container { 116 | position: absolute; 117 | height: 22px; 118 | bottom: 0; 119 | left: 0; 120 | right: 0; 121 | border: 1px solid #808080; 122 | vertical-align: bottom; 123 | background-color: white; 124 | padding: 3px 2px 0 2px; 125 | text-align: left; 126 | } 127 | 128 | .cli-input{ 129 | font-size: 0.9em; 130 | border: 0px solid black; 131 | outline: none; 132 | background-color: transparent; 133 | font-family: courier new; 134 | font-weight: bold; 135 | width: 100%; 136 | color: black; 137 | } 138 | 139 | .cli-table { 140 | border-collapse: collapse; 141 | empty-cells: show; 142 | border: 1px solid gray; 143 | } 144 | 145 | .cli-table td, th { 146 | padding: 2px 3px 2px 3px; 147 | vertical-align: top; 148 | text-align: left; 149 | } 150 | .cli-table td { 151 | background-color: #eeeeee; 152 | } 153 | .cli-table th { 154 | color: blue; 155 | background-color: #eeeeda; 156 | } 157 | 158 | .cli-jsonview-control .metadataField 159 | { 160 | background-color: #eeeeee; 161 | font-weight: bold; 162 | } 163 | 164 | .cli-jsonview-control .metadataFieldCollapsed 165 | { 166 | background-color: #edd3d6; 167 | } 168 | 169 | .cli-jsonview-control .metadataValue 170 | { 171 | background-color: #eeeeda; 172 | } 173 | 174 | .cli-jsonview-control table { 175 | border-collapse:collapse; 176 | empty-cells:show; 177 | font-family: courier; 178 | } 179 | 180 | .cli-jsonview-control table td { 181 | border: 1px silver solid; 182 | padding: 2px 3px 2px 3px; 183 | vertical-align: top; 184 | text-align: left; 185 | word-wrap: normal; 186 | } 187 | -------------------------------------------------------------------------------- /lib/acli.js: -------------------------------------------------------------------------------- 1 | 2 | (function( $ ){ 3 | var defaultSessionIdCounter = 0; 4 | 5 | function invoke(context, method) { 6 | methods[method]() 7 | } 8 | 9 | var methods = { 10 | init: function(opts){ 11 | 12 | // for each selected control, initialize. 13 | // return the wrapped collection 14 | return this.each(function(){ 15 | 16 | var $this = $(this), 17 | data = $this.data('cli'); 18 | 19 | // control already initialized, return it 20 | if (data) return $this; 21 | 22 | // construct internal elements 23 | var cliOutput = $('
').addClass('cli-output'); 24 | $this.append(cliOutput); 25 | 26 | var pinPanel = $('
').addClass('cli-pinPanel') 27 | .append($('
').addClass('cli-pinPanel-close')) 28 | .append($('
').addClass('cli-pinPanel-title').text('Pin Panel')); 29 | 30 | $this.append(pinPanel); 31 | 32 | var cliInput = $('').addClass('cli-input').attr('type', 'text'); 33 | var cliPrompt = $('
').addClass('cli-promptText'); 34 | var inputContainer = $('
').addClass('cli-input-container') 35 | .append(cliPrompt) 36 | .append($('
').addClass('cli-input-wrapper').append(cliInput)) 37 | .append($('
').addClass('cli-input-container-clear')); 38 | 39 | $this.append(inputContainer); 40 | 41 | opts.promptControl = cliPrompt; 42 | var options = $.extend(true, {}, $.fn.cli.defaults, opts); 43 | 44 | // the plugin hasn't been initialized yet 45 | var sessionId = (queryObject.get('sid') || options.sid || 'auto'+(defaultSessionIdCounter++)) + '_', 46 | envStorageKey = sessionId + 'env', 47 | pluginsStorageKey = sessionId + 'commands', 48 | historyStorageKey = sessionId + "history", 49 | resultsContainer = $("
").appendTo(cliOutput), 50 | promptControl = cliPrompt, 51 | panel = new PinPanel(), 52 | history = new History(), 53 | env = new Environment(), 54 | commands = new Commands(options.commands), 55 | systemParams = [], 56 | plugins = new Plugins(); 57 | 58 | data = { 59 | input : cliInput, 60 | options: options, 61 | history: history, 62 | commands: commands, 63 | plugins: plugins, 64 | env: env, 65 | writeMessage: writeMessage 66 | }; 67 | 68 | // store control data 69 | $(this).data('cli', data); 70 | 71 | // register keyboard events 72 | cliInput.keydown(function(e) { 73 | if(e.keyCode==9) { // ignore tab keys 74 | e.preventDefault(); 75 | return; 76 | } 77 | handleKeyPress(e.keyCode); 78 | }); 79 | 80 | writeMessage('info', options.welcomeMessage); 81 | writeMessage('info', ' '); 82 | 83 | addDefaultCommands(); 84 | addSystemParams(); 85 | 86 | plugins.init(); 87 | 88 | cliInput.focus(); 89 | 90 | console.log("cli control initialized"); 91 | 92 | 93 | // ********************* 94 | // private methods 95 | // ********************* 96 | 97 | function writeMessage(type, msg) { 98 | $("
").addClass('cli-output-'+type).html(msg).appendTo(resultsContainer); 99 | scrollDown(); 100 | } 101 | 102 | function handleKeyPress(key) { 103 | switch(key) { 104 | case 9: // tab- do nothing 105 | break 106 | case 13: // enter 107 | var command = getCommand(); 108 | if(!command) return; 109 | var row = $("
") 110 | .append($("").html(new Date().toLocaleTimeString() +'>')) 111 | .append($("").html(command)); 112 | resultsContainer.append(row); 113 | scrollDown(); 114 | history.add(command); 115 | // todo: validate command 116 | clearCommand(); 117 | parseAndExecuteCommand(command); 118 | break; 119 | case 27: // esc 120 | clearCommand(); 121 | break; 122 | case 38: // up in history 123 | setCommand(history.getPrevious()); 124 | break; 125 | case 40: // down in history 126 | setCommand(history.getNext()); 127 | break; 128 | default: 129 | //alert(e.keyCode); 130 | } 131 | } 132 | 133 | function getCommand() { 134 | return cliInput.val().trim(); 135 | } 136 | 137 | function setCommand(command) { 138 | cliInput.focus().val(''); 139 | setTimeout(function(){ 140 | cliInput.val(command); 141 | }, 1); 142 | } 143 | 144 | function clearCommand() { 145 | cliInput.val(""); 146 | } 147 | 148 | function scrollDown() { 149 | resultsContainer.parent().animate({scrollTop: resultsContainer.height()}, 0); 150 | 151 | // remove rows based on maxResults 152 | var maxResults = parseInt(env.env('maxResults')); 153 | var rows = resultsContainer.find('.cli-commandRow'); 154 | while(rows.length > maxResults) { 155 | removeLastLine(); 156 | rows = resultsContainer.find('.cli-commandRow'); 157 | } 158 | 159 | function removeLastLine() { 160 | resultsContainer.find('div').first().remove(); 161 | } 162 | } 163 | 164 | function parseAndExecuteCommand(commandLine) { 165 | 166 | var commandInfo; 167 | try { 168 | commandInfo = parseCommand(commandLine); 169 | if(commandInfo) { 170 | executeCommand(commandInfo.command, commandInfo.args); 171 | } 172 | } 173 | catch(error) { 174 | console.error(error.message); 175 | writeMessage('error', error.message); 176 | return; 177 | } 178 | } 179 | 180 | function parseCommand(commandLine) { 181 | console.log('parsing command line:', commandLine); 182 | 183 | var tokens = tokenize(commandLine), 184 | commandName = tokens[0], 185 | command = commands.get(commandName), 186 | isGroupCommand = command && !command.exec; 187 | 188 | // check if this is a sub-command 189 | if(isGroupCommand) { 190 | if(tokens.length<2) { 191 | writeMessage('info', 'please select command for: '+commandName); 192 | parseAndExecuteCommand('help '+commandName); 193 | return; 194 | } 195 | commandName += ' ' +tokens[1]; 196 | command = commands.get(commandName); 197 | } 198 | 199 | if(!command) { 200 | writeMessage('error', "command '"+commandName+"' not found"); 201 | return; 202 | } 203 | 204 | var args = {}, paramsFromCommand = {}; 205 | tokens = tokens.slice(isGroupCommand ? 2 : 1); 206 | console.log(command, tokens); 207 | 208 | var i =0; 209 | 210 | // get params from command line 211 | while(i").appendTo(resultsContainer) 479 | 480 | // todo: move following code to PinPanel class 481 | .dblclick(function (e) { 482 | if (!e.ctrlKey) return; 483 | 484 | if ($(this).parent().is('.cli-pinPanel-item')) { 485 | if (liveInterval) clearInterval(liveInterval); 486 | $(this).remove(); 487 | } 488 | else { 489 | if ($(this).data("isDraggable")) { 490 | $(this).draggable('destroy'); 491 | $(this).data("isDraggable", false); 492 | } 493 | else { 494 | $(this) 495 | .draggable({ 496 | appendTo: 'body', 497 | scroll: false, 498 | helper: 'clone', 499 | cursorAt: { left: 0, top: 0 }, 500 | delay: 100, 501 | opacity: 0.5, 502 | revert: 'invalid', 503 | revertDuration: 300 504 | }); 505 | $(this).data("isDraggable", true); 506 | } 507 | } 508 | }); 509 | 510 | var progressControl = $(""); 511 | var isLive = args.live; 512 | var isPin = args.pin; 513 | 514 | var liveInterval; 515 | var liveCount = 0; 516 | var maxLiveCount = env.env('liveIterationsCount'); 517 | if (isLive) 518 | liveInterval = setInterval(function () { 519 | liveCount++; 520 | execCommandInternal(); 521 | } 522 | ,args.live * 1000); 523 | 524 | execCommandInternal(); 525 | function execCommandInternal() { 526 | if (liveCount >= maxLiveCount) { 527 | clearInterval(liveInterval); 528 | console.log('stopping live interval for command', command); 529 | return; 530 | } 531 | var promise = { 532 | resolve: function(result) { 533 | appendExecutionResult(result); 534 | }, 535 | setProgress: function (progress) { 536 | if (liveCount === 0) 537 | progressControl.html( (progress*100).toString().substr(0, 2) + '% completed... '); 538 | } 539 | }; 540 | 541 | var execContext = { appcontext: options.context } 542 | execContext.createPromise = function() { 543 | return promise; 544 | } 545 | 546 | // we copy the command so that handlers will not be able to change its properties 547 | execContext.command = jQuery.extend(true, {}, command); 548 | execContext.env = jQuery.extend(true, {}, env.env()); 549 | execContext.scrollDown = scrollDown; 550 | 551 | var res = command.exec(args, execContext); 552 | var img = $("").attr('src', $.fn.cli.images.processing); 553 | 554 | if(res!==promise) 555 | appendExecutionResult(res); 556 | else if (liveCount === 0) 557 | appendExecutionResult($("
").html('processing... ') 558 | .append(progressControl) 559 | .append(img)); 560 | 561 | function appendExecutionResult(result) { 562 | 563 | if(typeof result == 'object' && !(result instanceof jQuery)) 564 | result = jsonToHtml(result, true); 565 | 566 | resultContainer.html("").append(result); 567 | 568 | if (isPin) { 569 | panel.addItemToPinPanel(resultContainer); 570 | pinPanel.show(); 571 | } 572 | else 573 | if (!isLive || liveCount === 0) scrollDown(); 574 | } 575 | } 576 | } 577 | 578 | function addSystemParams() { 579 | systemParams.push({ 580 | name: 'live', 581 | description: 'makes command refresh every X seconds', 582 | type: 'number', 583 | defaultValue: 5 584 | }); 585 | 586 | systemParams.push({ 587 | name: 'pin', 588 | description: 'pins the result on the panel', 589 | type: 'boolean' 590 | }); 591 | 592 | systemParams.forEach(function(param){ 593 | param.system = true; 594 | }); 595 | } 596 | 597 | function addDefaultCommands() { 598 | 599 | var helpCommand = { 600 | name: 'help', 601 | description: 'Gets help for working with the console', 602 | usage: 'help [command name]', 603 | manual: '', 604 | params: [ 605 | { 606 | name: 'command', 607 | description: 'The command to get help for', 608 | type: 'string', 609 | defaultValue: '' 610 | }, 611 | { 612 | name: 'subCommand', 613 | description: 'The sub-command to get help for', 614 | type: 'string', 615 | defaultValue: '' 616 | } 617 | ], 618 | exec: function(args, context) { 619 | 620 | var tbl = $(""); 621 | 622 | // if someone for some reason uses the named version, this should be checked... 623 | if(args.subCommand && !args.command) { 624 | return "please specify command group name in addition to sub-command '"+args.subCommand+"'"; 625 | } 626 | 627 | var commandName = args.command + (args.subCommand ? ' ' + args.subCommand : ''); 628 | 629 | if(commandName) { 630 | var command = commands.get(commandName); 631 | 632 | if(!command) { 633 | return "command '"+commandName+"' not found"; 634 | } 635 | 636 | if(command.exec) { // this is a regular command 637 | 638 | var descr = command.description || ''; 639 | if(options.descriptionHandler) descr = options.descriptionHandler(command, descr); 640 | tbl.append($("").append($("").append($("").append($("
").html('Description')).append($("").html(descr))); 641 | 642 | tbl.append($("
").html('Usage')).append($("").html(command.usage || ''))); 643 | tbl.append($("
").html('Example')).append($("").html(command.example || ''))); 644 | 645 | if(command.params && command.params.length) { 646 | 647 | var paramsTbl = $(""); 648 | 649 | paramsTbl.append($("") 650 | .append($("").append($("") 679 | .append($("") 700 | .append($("
").html('Name')) 651 | .append($("").html('Description')) 652 | .append($("").html('Type')) 653 | .append($("").attr("nowrap", "nowrap").html('Default Value')) 654 | .append($("").attr("nowrap", "nowrap").html('Default Env Var')) 655 | .append($("").html('Optional')) 656 | .append($("").html('Switch'))); 657 | 658 | for(var i=0; i") 661 | .append($("").html(param.name)) 662 | .append($("").html(param.description || '')) 663 | .append($("").html(param.type)) 664 | .append($("").html(param.hasOwnProperty('defaultValue') ? param.defaultValue : (param.type=='boolean' ? 'false' : ''))) 665 | .append($("").html(param.hasOwnProperty('defaultEnvVar') ? '$' + param.defaultEnvVar : '')) 666 | .append($("").html(param.hasOwnProperty('defaultValue') || param.type=='boolean' ? 'optional' : 'required')) 667 | .append($("").html(param.short || '')); 668 | 669 | row.appendTo(paramsTbl); 670 | } 671 | 672 | tbl.append($("
").html('Parameters')).append($("").append(paramsTbl))); 673 | 674 | } 675 | } 676 | else { // this is a group command 677 | 678 | tbl.append($("
").html('Sub Command')) 680 | .append($("").html('Description'))); 681 | 682 | var cmds = getSortedCommands(); 683 | for(var i=0; i") 689 | .append($("").html(command.name)) 690 | .append($("").html(command.description || '')); 691 | 692 | row.appendTo(tbl); 693 | } 694 | } 695 | } 696 | } 697 | else { // list all commands 698 | 699 | tbl.append($("
").html('Command')) 701 | .append($("").html('Description')) 702 | .append($("").html('Usage'))); 703 | 704 | var sortedArray = getSortedCommands(); 705 | 706 | for(var i=0;i") 712 | .append($("").html(command.name)) 713 | .append($("").html(command.description)) 714 | .append($("").html(command.usage || '')); 715 | 716 | row.appendTo(tbl); 717 | } 718 | } 719 | } 720 | 721 | return tbl; 722 | 723 | function getSortedCommands() { 724 | return _.toArray(commands.get()).sort(function(a,b) { 725 | if(a.name > b.name) return 1; 726 | if(a.name < b.name) return -1; 727 | return 0; 728 | } 729 | ); 730 | } 731 | } 732 | } 733 | 734 | var clsCommand = { 735 | name: 'cls', 736 | description: 'Clears the console', 737 | usage: 'cls', 738 | example: 'cls', 739 | exec: function() { 740 | resultsContainer.html(""); 741 | writeMessage('info', options.welcomeMessage); 742 | writeMessage('info', ' '); 743 | } 744 | } 745 | 746 | var clearCommand = { 747 | name: 'clear', 748 | description: 'Clears the console', 749 | usage: 'clear', 750 | example: 'clear', 751 | exec: function() { 752 | resultsContainer.html(""); 753 | writeMessage('info', options.welcomeMessage); 754 | writeMessage('info', ' '); 755 | } 756 | } 757 | 758 | var pinPanelCommand = { 759 | name: 'panel', 760 | description: 'Manages the panel panel. Using with no params will toggle the panel visibility.', 761 | usage: 'panel [--off] [--on] [--pin]', 762 | example: 'panel', 763 | params: [ 764 | { 765 | name: 'off', 766 | description: 'turn panel visibility off', 767 | type: 'boolean' 768 | }, 769 | { 770 | name: 'on', 771 | description: 'turn panel visibility on', 772 | type: 'boolean' 773 | }, 774 | { 775 | name: 'pin', 776 | description: 'pin last result', 777 | type: 'boolean' 778 | } 779 | ], 780 | //hidden: true, 781 | exec: function(args) { 782 | // var pinPanel = options.pinPanel; 783 | if(!args.on && !args.off && !args.pin) { 784 | if (pinPanel.css("display")=='none') 785 | args.on = true; 786 | else args.off = true; 787 | } 788 | 789 | if(args.off) { 790 | pinPanel.hide(); 791 | } 792 | else if (args.on) 793 | pinPanel.show(); 794 | else if(args.pin) { 795 | var item = resultsContainer.find(".cli-commandResult").last().prevAll(".cli-commandResult")[0]; 796 | panel.addItemToPinPanel(item); 797 | } 798 | } 799 | } 800 | 801 | var sampleCommand = { 802 | name: 'sample', 803 | description: 'Sample command', 804 | usage: 'sample [requiredString1] [requiredString2] [requiredNumber1] [optionalNumber1] [optionalNumber2]', 805 | example: 'sample aa bb 444 333 -l ffff -r', 806 | params: [ 807 | { 808 | name: 'requiredString1', 809 | description: 'required string 1', 810 | type: 'string' 811 | }, 812 | { 813 | name: 'requiredString2', 814 | description: 'required string 2', 815 | type: 'string' 816 | }, 817 | { 818 | name: 'requiredNumber1', 819 | description: 'required number 1', 820 | type: 'number' 821 | }, 822 | { 823 | name: 'optString1', 824 | description: 'optional string 1', 825 | type: 'string', 826 | defaultValue: 'optString1' 827 | }, 828 | { 829 | name: 'optString2', 830 | short: 'a', 831 | description: 'optional string 2', 832 | type: 'string', 833 | defaultValue: 'optString2' 834 | }, 835 | { 836 | name: 'optionalNumber1', 837 | short: 'b', 838 | description: 'optional number 1', 839 | type: 'number', 840 | defaultValue: 1234 841 | }, 842 | { 843 | name: 'optionalNumber2', 844 | short: 'c', 845 | description: 'optional number 2', 846 | type: 'number', 847 | defaultValue: 222 848 | }, 849 | { 850 | name: 'optionalNullNumber', 851 | short: 'd', 852 | description: 'optional null number 1', 853 | type: 'number', 854 | defaultValue: null 855 | }, 856 | { 857 | name: 'switchBool1', 858 | short: 'e', 859 | description: 'boolean switch', 860 | type: 'boolean' 861 | }, 862 | { 863 | name: 'switchBool2', 864 | short: 'f', 865 | description: 'boolean switch', 866 | type: 'boolean' 867 | } 868 | 869 | ], 870 | exec: function(args, context) { 871 | return "sample command args: " + JSON.stringify(args); 872 | } 873 | } 874 | 875 | // group commands does not have an exec method 876 | var sampleGroupCommand = { 877 | name: "group", 878 | description: "group of commands", 879 | usage: "type 'help group' for sub-commands" 880 | } 881 | 882 | var sampleGroupCommand1 = { 883 | name: "group action1", 884 | description: "action1 of group of commands", 885 | usage: 'group action1 requiredString1 [--boolParam]', 886 | example: 'group action1 "some string" --boolParam', 887 | params: [ 888 | { 889 | name: 'requiredString1', 890 | description: 'required string 1', 891 | type: 'string' 892 | }, 893 | { 894 | name: 'boolParam', 895 | short: 'b', 896 | description: 'bool param', 897 | type: 'boolean' 898 | } 899 | ], 900 | exec: function(args, context) { 901 | return "action1 of group of commands: " + JSON.stringify(args); 902 | } 903 | } 904 | 905 | var sampleGroupCommand2 = { 906 | name: "group action2", 907 | description: "action2 of group of commands", 908 | usage: 'group action2 [optionalStr] [numberParam]', 909 | example: 'group action2 "optional string" 11', 910 | params: [ 911 | { 912 | name: 'optionalStr', 913 | short: 'o', 914 | description: 'optional string 1', 915 | type: 'string', 916 | defaultValue: 'default!!!' 917 | }, 918 | { 919 | name: 'numberParam', 920 | short: 'n', 921 | description: 'number param', 922 | type: 'number', 923 | defaultValue: 33 924 | } 925 | ], 926 | exec: function(args, context) { 927 | return "action2 of group of commands: " + JSON.stringify(args); 928 | } 929 | } 930 | 931 | var pluginsGroupCommand = { 932 | name: "plugins", 933 | description: "Manages plugins", 934 | usage: "type 'help plugins' for more info" 935 | } 936 | 937 | var installPluginCommand = { 938 | name: "plugins install", 939 | description: "Installs a plugin from remote url", 940 | usage: 'plugins install name url [--persist/-p]', 941 | example: 'plugins install management http://some.url/management -p', 942 | params: [ 943 | { 944 | name: 'name', 945 | description: 'commands group name', 946 | type: 'string' 947 | }, 948 | { 949 | name: 'url', 950 | description: 'commands url (docrouter metadata)', 951 | type: 'string' 952 | }, 953 | { 954 | name: 'persist', 955 | short: 'p', 956 | description: 'persists plugin and automatically load next time the console is started', 957 | type: 'boolean' 958 | } 959 | ], 960 | exec: function(args, context) { 961 | 962 | var resp = $("
"); 963 | 964 | var plugin = { url: args.url, userCommand: true, name: args.name }; 965 | plugin.groupCommandName = args.name; 966 | 967 | if(commands.get(args.name)) { 968 | resp.addClass('cli-output-error').html('plugin name \''+args.name+'\' already exists, please choose another plugin name'); 969 | return resp; 970 | } 971 | 972 | plugins.loadPlugin(plugin, args.persist, function(){}); 973 | } 974 | } 975 | 976 | var uninstallPluginCommand = { 977 | name: "plugins uninstall", 978 | description: "Uninstalls a plugin", 979 | usage: 'plugins uninstall name', 980 | example: 'plugins uninstall management', 981 | params: [ 982 | { 983 | name: 'name', 984 | description: 'name of plugin to uninstall', 985 | type: 'string' 986 | } 987 | ], 988 | exec: function(args, context) { 989 | 990 | if(!commands.get(args.name)) { 991 | writeMessage('error', 'plugin \''+args.name+'\' does not exist'); 992 | return; 993 | } 994 | 995 | try { 996 | commands.delete(args.name); 997 | } 998 | catch(err) { 999 | writeMessage('error', err.message); 1000 | return; 1001 | } 1002 | 1003 | // delete from local storage 1004 | plugins.removePlugin(args.name); 1005 | 1006 | writeMessage('info', 'plugin \''+args.name+'\' uninstalled successfully'); 1007 | } 1008 | } 1009 | 1010 | var resetPluginsCommand = { 1011 | name: "plugins reset", 1012 | description: "Uninstalls all installed plugins", 1013 | usage: 'plugins reset', 1014 | example: 'plugins reset', 1015 | params: [], 1016 | exec: function(args, context) { 1017 | var userCommands = plugins.list(); 1018 | 1019 | try { 1020 | for(var i=0; iset: display the list of all environment variables
' + 1058 | 'set app: display current value of app environment variable
'+ 1059 | 'set app appname: set current value of app environment variable to appname
'+ 1060 | 'set xxx -c: sets variable xxx to an empty value
'+ 1061 | 'set xxx -d: deletes the xxx environment variable
', 1062 | params: [ 1063 | { 1064 | name: 'name', 1065 | description: 'the name of the environment variable', 1066 | type: 'string', 1067 | defaultValue: null 1068 | }, 1069 | { 1070 | name: 'value', 1071 | description: 'the value of the setting', 1072 | isOptional: true, 1073 | type: 'dynamic', 1074 | defaultValue: null 1075 | }, 1076 | { 1077 | short: 'r', 1078 | name: 'restore', 1079 | description: 'restores env variables and ignores the rest of the parameters if provided', 1080 | type: 'boolean' 1081 | }, 1082 | { 1083 | short: 'x', 1084 | name: 'extend', 1085 | description: 'displays extended information about the environment variables', 1086 | type: 'boolean' 1087 | }, 1088 | { 1089 | short: 'c', 1090 | name: 'clear', 1091 | description: 'delete the variable value- make it empty', 1092 | type: 'boolean' 1093 | }, 1094 | { 1095 | short: 'd', 1096 | name: 'delete', 1097 | description: 'remove the variable from the environment collection', 1098 | type: 'boolean' 1099 | }, 1100 | { 1101 | short: 'o', 1102 | name: 'online', 1103 | description: 'presents a real-time settings pannel when used alone (set -o)', 1104 | type: 'boolean' 1105 | } 1106 | ], 1107 | exec: function(args) { 1108 | 1109 | if(args.restore) env.reset(); 1110 | if(args.value=='') { 1111 | args.clear = true; 1112 | } 1113 | 1114 | if(args.clear || args.delete) { 1115 | if(!args.name) { 1116 | writeMessage('error', 'plsease spcify a param name'); 1117 | return; 1118 | } 1119 | 1120 | if(args.clear) env.clear(args.name); 1121 | if(args.delete) env.delete(args.name); 1122 | } 1123 | 1124 | var res = {}; 1125 | if(!args.name && !args.value) { 1126 | 1127 | if(!args.extend) { 1128 | var _env = env.env(); 1129 | for(var key in _env) { 1130 | res[key] = _env[key].value; 1131 | } 1132 | } 1133 | else res = env.env(); 1134 | 1135 | var container = $("
").append(jsonToHtml(res, true)); 1136 | if(args.online) 1137 | { 1138 | $(env.events).bind('state', function(e, state){ 1139 | var res = {}; 1140 | var _env = env.env(); 1141 | for(var key in _env) { 1142 | res[key] = _env[key].value; 1143 | } 1144 | container.html('').append(jsonToHtml(res, true)); 1145 | }); 1146 | } 1147 | return container; 1148 | } 1149 | 1150 | if(args.name && args.value) { // set 1151 | env.env(args.name, args.value); 1152 | } 1153 | 1154 | res[args.name] = !args.extend ? env.env(args.name) : env.env()[args.name]; 1155 | return res; 1156 | } 1157 | } 1158 | 1159 | var tabCommand = { 1160 | name: 'tab', 1161 | description: "Creates a console session in a new tab", 1162 | usage: 'tab name', 1163 | example: 'tab myNewSessionName', 1164 | params: [ 1165 | { 1166 | name: 'name', 1167 | description: 'The name of the new tab. When used, all storage (history, plugins, environment variables) '+ 1168 | 'is automatically persisted under this name and can be restored the next time the tab is opened with this name.', 1169 | type: 'string', 1170 | defaultValue: null 1171 | } 1172 | ], 1173 | exec: function (args, context) { 1174 | var url = new URI(window.location.href); 1175 | url = args.name ? url.addQuery('sid', args.name) : url.removeQuery('sid'); 1176 | window.open(url); 1177 | } 1178 | } 1179 | 1180 | var getCommand = { 1181 | name: 'get', 1182 | description: "Invokes a web GET request to a url that returns json object", 1183 | usage: 'get url', 1184 | example: 'get http://some.url.that.returns.json', 1185 | params: [ 1186 | { 1187 | short: 'u', 1188 | name: 'url', 1189 | description: 'Url that returns json object', 1190 | type: 'string' 1191 | } 1192 | ], 1193 | exec: function(args, context) { 1194 | var promise = context.createPromise(); 1195 | 1196 | if(!args.url) return 'please provide a url'; 1197 | 1198 | getJson(args.url, function(error, data) { 1199 | if(error){ 1200 | promise.resolve($("
").html('Error getting json: '+error)); 1201 | return; 1202 | } 1203 | 1204 | var jsonView = jsonToHtml(data); 1205 | promise.resolve(jsonView); 1206 | }); 1207 | 1208 | return promise; 1209 | } 1210 | } 1211 | 1212 | commands.add(tabCommand); 1213 | commands.add(helpCommand); 1214 | commands.add(clsCommand); 1215 | commands.add(clearCommand); 1216 | commands.add(setCommand); 1217 | commands.add(getCommand); 1218 | commands.add(pluginsGroupCommand); 1219 | commands.add(listPluginsCommand); 1220 | commands.add(installPluginCommand); 1221 | commands.add(uninstallPluginCommand); 1222 | commands.add(resetPluginsCommand); 1223 | commands.add(pinPanelCommand); 1224 | //commands.add(sampleCommand); 1225 | //commands.add(sampleGroupCommand); 1226 | //commands.add(sampleGroupCommand1); 1227 | //commands.add(sampleGroupCommand2); 1228 | } 1229 | 1230 | 1231 | 1232 | function jsonToHtml(obj, ignoreJQuery) { 1233 | 1234 | var html = _jsonToHtml(obj, ignoreJQuery); 1235 | 1236 | var level = env.env('jsonViewCollapseLevel'); 1237 | html.find("table").each(function(){ 1238 | var field = $(this).parent("td.metadataValue").prev("td.metadataField") 1239 | .click(function(){ 1240 | console.log("User click detected"); 1241 | $(this).next("td.metadataValue").toggle(); 1242 | $(this).toggleClass("metadataFieldCollapsed"); 1243 | 1244 | // If the value of the clicked field is an array, and this is the first click, 1245 | // we want the content of the array to be displayed, not just its indices 1246 | if (field.next("td.metadataValue").data("isArray") && !$(this).data("uncollapsed")) { 1247 | $(this).data("uncollapsed", true); 1248 | $(this).next("td.metadataValue").children("table").each(function(){ 1249 | $(this).children("tbody").children("tr").each(function(){ 1250 | $(this).children("td.metadataField").click(); 1251 | }); 1252 | }); 1253 | } 1254 | }) 1255 | .attr("title", 'click to toggle'); 1256 | 1257 | // Hide all the elements which level is larger than jsonViewCollapseLevel, 1258 | // unless the last non hidden elements are the indices or an array. In this case 1259 | // show the values of those elements as well 1260 | var fieldLevel = field.parents("table").length; 1261 | var containingElement = field.closest("table").parent("td.metadataValue"); 1262 | 1263 | if ((fieldLevel <= level) || 1264 | ( fieldLevel === level + 1 && containingElement.length !== 0 && containingElement.data("isArray")) ) { 1265 | field.data("uncollapsed", true); 1266 | } 1267 | else { 1268 | field.addClass('metadataFieldCollapsed'); 1269 | field.next("td.metadataValue").hide(); 1270 | } 1271 | 1272 | }); 1273 | return $("
").addClass('cli-jsonview-control').append(html); 1274 | 1275 | function _jsonToHtml(obj, ignoreJQuery) { 1276 | 1277 | var tbl = $(""); 1278 | for (var key in obj) 1279 | { 1280 | if(key.indexOf('jQuery')==0 || typeof obj[key] == 'function') 1281 | continue; 1282 | 1283 | var val; 1284 | if (typeof obj[key] === 'object' && obj[key]) val = _jsonToHtml(obj[key], ignoreJQuery); 1285 | else if (typeof obj[key] === 'boolean') val = obj[key] ? 'true' : 'false'; 1286 | else if (typeof obj[key] === 'number') val = obj[key]; 1287 | else val = obj[key] || ''; 1288 | 1289 | var isArray = (obj[key] instanceof Array ); 1290 | 1291 | $("") 1292 | .append($("
").html(key)).data("uncollapsed", false) 1293 | .append($("").append(val).data("isArray", isArray)) 1294 | .appendTo(tbl); 1295 | } 1296 | 1297 | return tbl; 1298 | } 1299 | } 1300 | 1301 | 1302 | // ********************* 1303 | // private classes 1304 | // ********************* 1305 | 1306 | // manages commands history 1307 | function History(){ 1308 | 1309 | var self = this; 1310 | this.commands = loadHistory(); 1311 | var currIndex = this.commands.length; 1312 | 1313 | this.add = function(command) { 1314 | if(command == _.last(this.commands)) { 1315 | currIndex = this.commands.length; 1316 | return; 1317 | } 1318 | this.commands.push(command); 1319 | var maxHistory = parseInt(env.env('maxHistory')) || $.fn.cli.env.maxHistory; 1320 | if(this.commands.length > maxHistory){ 1321 | this.commands = _.last(this.commands, maxHistory); 1322 | } 1323 | currIndex = this.commands.length; 1324 | saveHistory(); 1325 | } 1326 | 1327 | this.getPrevious = function() { 1328 | if(currIndex>=0) currIndex--; 1329 | return (this.commands.length && currIndex > -1) ? this.commands[currIndex] : null; 1330 | } 1331 | 1332 | this.getNext = function() { 1333 | if(currIndex<(this.commands.length)) currIndex++; 1334 | return (this.commands.length && currIndex < this.commands.length) ? this.commands[currIndex] : null; 1335 | } 1336 | 1337 | function saveHistory () { 1338 | localStorage.setItem(historyStorageKey, JSON.stringify(self.commands)); 1339 | } 1340 | function loadHistory () { 1341 | var history = localStorage.getItem(historyStorageKey); 1342 | return history ? JSON.parse(history) : []; 1343 | } 1344 | } 1345 | 1346 | // manages commands 1347 | function Commands(commands) { 1348 | this.commands = {}; 1349 | 1350 | this.add = function(command) { 1351 | // todo: validate command parameters 1352 | this.commands[command.name] = command; 1353 | } 1354 | 1355 | this.delete = function(name) { 1356 | if(this.commands.hasOwnProperty(name)) { 1357 | var cmd = this.commands[name]; 1358 | if(cmd.userCommand) 1359 | { 1360 | if(cmd.exec) { 1361 | throw new Error('Can not delete sub command, only group command'); 1362 | } 1363 | 1364 | // delete all sub commands 1365 | for(var c in this.commands) { 1366 | if(c.indexOf(name+' ')==0) 1367 | delete this.commands[c]; 1368 | } 1369 | //delete group command 1370 | delete this.commands[name]; 1371 | } 1372 | else 1373 | throw new Error('Can not delete command that was not added by you'); 1374 | } 1375 | } 1376 | 1377 | for(var i=0; commands && i= ' + typeDef.min); 1418 | if(isValid && typeDef.hasOwnProperty('max') && val>typeDef.max) throw new Error('value should be <= ' + typeDef.max); 1419 | break; 1420 | case 'boolean': 1421 | var validTrue = (val === true || val.toLowerCase() === 'true' || val === 1 || val === '1'); 1422 | var validFalse = ( val === false || val.toLowerCase() === 'false' || val === 0 || val === '0' || val===''); 1423 | isValid = validTrue || validFalse; 1424 | if(validTrue) val = 'true'; 1425 | if(validFalse) val = 'false'; 1426 | break; 1427 | default: // string or dynamic 1428 | isValid = true; 1429 | } 1430 | if(!isValid) throw new Error('value should be of type ' + type); // value is not valid 1431 | } 1432 | var options = _env[setting].options; 1433 | if(options) { 1434 | var exists = false; 1435 | for(var i=0; i").appendTo(pinPanel); 1592 | 1593 | $(item).appendTo(conatiner); 1594 | setTimeout(function () { 1595 | $(item).draggable('destroy'); 1596 | }, 500); 1597 | } 1598 | 1599 | pinPanel.find(".cli-pinPanel-close").click(function(){ 1600 | pinPanel.hide(); 1601 | }); 1602 | } 1603 | 1604 | // plugins manager 1605 | function Plugins() { 1606 | var self = this; 1607 | this.plugins = []; 1608 | 1609 | this.init = function() { 1610 | if(options.plugins) this.plugins = this.plugins.concat(options.plugins); 1611 | this.plugins = this.plugins.concat(loadPluginsFromStorage()); 1612 | this.loadPlugins(this.plugins); 1613 | } 1614 | 1615 | function loadPluginsFromStorage() { 1616 | var pluginsFromStorage = localStorage.getItem(pluginsStorageKey); // returns string 1617 | pluginsFromStorage = pluginsFromStorage ? JSON.parse(pluginsFromStorage) : []; // convert to array object 1618 | return pluginsFromStorage; 1619 | } 1620 | 1621 | function storePlugin(plugin) { 1622 | var pluginsFromStorage = loadPluginsFromStorage(); 1623 | pluginsFromStorage.push(plugin); 1624 | localStorage.setItem(pluginsStorageKey, JSON.stringify(pluginsFromStorage)); 1625 | } 1626 | 1627 | this.loadPlugins = function(plugins) { 1628 | 1629 | var loadPluginsFuncs = [], 1630 | handlerObjNamePrefix = 'acli_plugin_handler_', 1631 | handlerIndex = 0; 1632 | 1633 | $(plugins).each(function(i ,plugin) { 1634 | loadPlugin(plugin, function(){}); 1635 | }); 1636 | 1637 | function loadPlugin(plugin, persist, callback) { 1638 | if(!callback) { 1639 | callback = persist; 1640 | persist = null; 1641 | } 1642 | var loadPluginDiv = $("
").addClass('cli-output-info').html('loading plugin ' + plugin.url + '... ').appendTo(resultsContainer); 1643 | loadPluginDiv.append($("").attr('src', $.fn.cli.images.processing)); 1644 | 1645 | loadPluginInfo(plugin, function(error, pluginInfo){ 1646 | if (error) { 1647 | writeMessage('error', 'error loading plugin:' + plugin.url + '. error: ' + error); 1648 | loadPluginDiv.find('img').remove(); 1649 | return callback(); 1650 | } 1651 | 1652 | // load plugin resources first- js, css files 1653 | loadPluginResources(pluginInfo, function(){ 1654 | 1655 | // all resources loaded, we can now compile the commands 1656 | var newCommands = compilePlugin(pluginInfo); 1657 | 1658 | _.each(newCommands, function(cmd) { 1659 | commands.add(cmd); 1660 | }); 1661 | 1662 | if(persist) storePlugin(plugin); 1663 | loadPluginDiv.find('img').remove(); 1664 | callback(); // all resources were loaded 1665 | }); 1666 | }); 1667 | } 1668 | 1669 | this.loadPlugin = loadPlugin; 1670 | 1671 | function loadPluginResources(pluginInfo, callback) { 1672 | var files = {}, 1673 | rootUrl = pluginInfo.rootUrl; 1674 | 1675 | $(pluginInfo.commands).each(function(i ,command) { 1676 | 1677 | if(command.controller) { 1678 | 1679 | if (command.controller.url) { 1680 | var scriptUrl = command.controller.url.indexOf('http') == 0 ? command.controller.url : 1681 | rootUrl + '/' + command.controller.url; 1682 | command.controller._url = scriptUrl; 1683 | files[scriptUrl] = 'js'; 1684 | } 1685 | 1686 | if (command.controller.cssUrl) { 1687 | var cssUrl = command.controller.cssUrl.indexOf('http') == 0 ? command.controller.cssUrl : 1688 | rootUrl + '/' + command.controller.cssUrl; 1689 | files[cssUrl] = 'css'; 1690 | } 1691 | 1692 | if(command.controller.css) addCss(command.controller.css); 1693 | } 1694 | }); 1695 | 1696 | var loadFileFuncs = []; 1697 | 1698 | for(var url in files) { 1699 | loadFileFuncs.push( 1700 | (function (url, type) { 1701 | return function(cb) { 1702 | if(type == 'js') 1703 | loadJsFile(url, cb); 1704 | else 1705 | loadCssFile(url, cb); 1706 | } 1707 | })(url, files[url])); 1708 | } 1709 | 1710 | if (loadFileFuncs.length) { 1711 | 1712 | function _loadFileFuncsCb() { 1713 | loadFileFuncsIndex++; 1714 | if(loadFileFuncsIndex").text(str).appendTo("head"); 1737 | var context = {}; 1738 | window[scriptNameObj].call(context); 1739 | 1740 | var cmds = _.filter(pluginInfo.commands, function(c){ 1741 | return c.controller && c.controller._url == url; 1742 | }); 1743 | _.each(cmds, function(cmd) { 1744 | if(cmd.controller.clientOnly) 1745 | cmd.exec = context[cmd.name]; 1746 | else { 1747 | if(!cmd.options) cmd.options = {}; 1748 | cmd.options.handler = context[cmd.name]; 1749 | } 1750 | }); 1751 | 1752 | console.log('added handler script:', url); 1753 | 1754 | return cb(); 1755 | }) 1756 | .error(function(err){ 1757 | writeMessage('error', 'error loading file: ' + url + '. error: ' + err.status + ' (' + err.statusText + ') ' + err.responseText); 1758 | cb(); 1759 | }); 1760 | } 1761 | 1762 | function loadCssFile(url, cb) { 1763 | console.log('loading css file', url); 1764 | loadCss(url); 1765 | return cb(); 1766 | } 1767 | } 1768 | 1769 | // plugin: { url: url } 1770 | // returns {url: url, plugin: plugin, commands: data} 1771 | function loadPluginInfo(plugin, callback) { 1772 | var url = plugin.url; 1773 | console.info('loading plugins from ', url); 1774 | 1775 | getJson(url, function(err, resp){ 1776 | if(err) 1777 | callback(err); 1778 | else { 1779 | 1780 | var pluginInfo = { 1781 | plugin: plugin 1782 | }; 1783 | 1784 | if(resp instanceof Array) { // docRouter returns array 1785 | pluginInfo.commands = resp; 1786 | pluginInfo.rootUrl = url.replace('/!!',''); 1787 | } 1788 | else { 1789 | pluginInfo.commands = resp.commands; 1790 | pluginInfo.rootUrl = resp.rootUrl || url; 1791 | 1792 | } 1793 | 1794 | callback(null, pluginInfo); 1795 | } 1796 | 1797 | }); 1798 | } 1799 | } 1800 | 1801 | //commandsInfo: {url: url, plugin: plugin, commands: data} 1802 | function compilePlugin(commandsInfo) { 1803 | 1804 | var newCommands = {}; 1805 | var cmds = commandsInfo.commands; 1806 | if(!cmds || !cmds.length) return; 1807 | 1808 | // group command is the last element of the url 1809 | var commandRootUrl = commandsInfo.rootUrl; 1810 | var commandName = commandsInfo.plugin.groupCommandName || commandRootUrl.split('/').pop(); 1811 | 1812 | // we create group for apis with more than 1 command or 1813 | // for remote commads which were loaded by the user 1814 | var isGroup = cmds.length > 1 || commandsInfo.plugin.userCommand; 1815 | if(isGroup) { 1816 | var gCommand = createGroupCommand(commandName); 1817 | if(commandsInfo.plugin.userCommand) gCommand.userCommand=true; 1818 | newCommands[commandName] = gCommand; 1819 | } 1820 | 1821 | var autoSubCommandNameIndex = 1; 1822 | for(var i=0; i id --> create auto name 1829 | if(isGroup) { 1830 | var subCommandName = cmd.name || cmd.id || ('autoCommand'+(autoSubCommandNameIndex++)); 1831 | cmdName += ' ' + subCommandName.toLowerCase(); 1832 | } 1833 | newCommands[cmdName] = createCommand(cmdName, cmd, commandRootUrl, commandsInfo.plugin); 1834 | } 1835 | 1836 | return newCommands; 1837 | 1838 | 1839 | function getType(type) { 1840 | switch(type) { 1841 | case 'bool': return 'boolean'; 1842 | case 'date': 1843 | case 'text': return 'string'; 1844 | case 'int': return 'number'; 1845 | 1846 | default: return type; 1847 | } 1848 | } 1849 | 1850 | function createCommand(name, cmd, rootUrl, plugin) { 1851 | 1852 | var exclueAndOverride = cmd.options ? cmd.options.excludeAndOverideParams : {}; 1853 | 1854 | var newCommand = { 1855 | name: name, 1856 | usage: cmd.usage, 1857 | example: cmd.example, 1858 | response: cmd.response || 'json', 1859 | method: cmd.method, 1860 | rootUrl: rootUrl, 1861 | path: rootUrl + cmd.path, 1862 | plugin: plugin, 1863 | ignoredParams: {}, 1864 | options: cmd.options 1865 | } 1866 | 1867 | if(cmd.hasOwnProperty('doc')) newCommand.description = cmd.doc; 1868 | if(cmd.hasOwnProperty('usage')) newCommand.usage = cmd.usage; 1869 | if(cmd.hasOwnProperty('example')) newCommand.example = cmd.example; 1870 | if(cmd.hasOwnProperty('hidden'))newCommand.hidden = cmd.hidden; 1871 | 1872 | var params = []; 1873 | for(var paramName in cmd.params) { 1874 | var param = cmd.params[paramName]; 1875 | var newParam = 1876 | { 1877 | name: paramName, 1878 | type: getType(param.type), 1879 | style: param.style 1880 | } 1881 | if(param.hasOwnProperty('doc')) newParam.description = param.doc; 1882 | if(param.hasOwnProperty('options')) newParam.options = param.options; 1883 | if(param.hasOwnProperty('short')) newParam.short = param.short; 1884 | if(param.hasOwnProperty('defaultEnvVar')) newParam.defaultEnvVar = param.defaultEnvVar; 1885 | if(param.hasOwnProperty('defaultValue')) 1886 | newParam.defaultValue = param.defaultValue; 1887 | else if(!param.required) newParam.defaultValue = null; 1888 | 1889 | if(!exclueAndOverride || !exclueAndOverride[paramName]) 1890 | params.push(newParam); 1891 | else 1892 | newCommand.ignoredParams[newParam.name] = newParam; 1893 | } 1894 | newCommand.params = params; 1895 | 1896 | // add the rest of the command fields 1897 | for(var field in cmd) { 1898 | if(!newCommand.hasOwnProperty(field)) 1899 | newCommand[field] = cmd[field]; 1900 | }; 1901 | 1902 | if(!newCommand.exec) { 1903 | newCommand.exec = function(args, context) { 1904 | console.log(args, this); 1905 | 1906 | // construct url 1907 | var url = this.path; 1908 | var body = null; 1909 | var method = this.method; 1910 | 1911 | for(var i=0; ib.instance) return 1; 2002 | if(b.instance>a.instance) return -1; 2003 | return 0; 2004 | }); 2005 | 2006 | 2007 | 2008 | var resp = {data: responses, args: args, context:context, promise: promise}; 2009 | 2010 | if(handler) return handler(resp); 2011 | else { 2012 | var isJson = true; 2013 | _.each(responses, function(_resp){ 2014 | if(_resp.response && typeof _resp.response !== 'object') isJson = false; 2015 | }); 2016 | 2017 | if(isJson) 2018 | promise.resolve(resp.data); 2019 | else 2020 | // just combine the html blocks 2021 | promise.resolve( 2022 | _.map(responses, function(_resp) { 2023 | return _resp.error || _resp.response; 2024 | }).join('') 2025 | ); 2026 | } 2027 | } 2028 | } 2029 | ); 2030 | 2031 | $.ajax(ajaxRequest); 2032 | 2033 | }); 2034 | 2035 | return promise; 2036 | 2037 | } 2038 | 2039 | 2040 | function addDefaultQueryVariables(url, env) { 2041 | // TODO: change to use url plugin 2042 | for(var envVarName in env) { 2043 | var envVar = env[envVarName]; 2044 | if(envVar.useAtQuery && envVar.value) { 2045 | addVariable(envVarName, envVar.value); 2046 | } 2047 | } 2048 | 2049 | return url; 2050 | 2051 | function addVariable(name, value) { 2052 | var pair = '$' + name+'='+value; 2053 | if(url.indexOf("?")>0) 2054 | url+='&'+pair; 2055 | else 2056 | url+='?'+pair; 2057 | } 2058 | } 2059 | 2060 | 2061 | 2062 | } 2063 | 2064 | return newCommand; 2065 | } 2066 | 2067 | function createGroupCommand(name) { 2068 | return { 2069 | name: name, 2070 | description: "Group of commands for " + name, 2071 | usage: "type 'help "+name+"' for sub-commands" 2072 | } 2073 | } 2074 | } 2075 | 2076 | // todo: change groupCommandName to plugin name 2077 | this.removePlugin = function(plugin) { 2078 | var pluginsFromStorage = loadPluginsFromStorage(); 2079 | var newPluginsArr = _.filter(pluginsFromStorage, function(c) { return c.groupCommandName!=plugin; }); 2080 | localStorage.setItem(pluginsStorageKey, JSON.stringify(newPluginsArr)); 2081 | } 2082 | 2083 | this.reset = function() { 2084 | localStorage.setItem(pluginsStorageKey, JSON.stringify([])); 2085 | } 2086 | 2087 | this.list = loadPluginsFromStorage; 2088 | } 2089 | }); 2090 | }, 2091 | 2092 | // get or set environment variable 2093 | // get : env('setting') 2094 | // set : env('setting', 'value', appOriginated, isEmpty) 2095 | // get env hash : env() 2096 | env: function(setting, val, appOriginated, isEmpty) { 2097 | return $(this).data('cli').env.env(setting, val, appOriginated, isEmpty); 2098 | }, 2099 | 2100 | // get environment events broker 2101 | envEventsBroker: function() { 2102 | return $(this).data('cli').env.events; 2103 | }, 2104 | 2105 | /* 2106 | // add plugin external handler 2107 | addPluginHandler: function(id, handler) { 2108 | 2109 | var handlers = $(this).data('cli').handlers; 2110 | if(handlers.hasOwnProperty(id)) 2111 | throw new Error('script handler already exists', id, ' not loading script'); 2112 | 2113 | handlers[id] = handler; 2114 | }, 2115 | */ 2116 | 2117 | // get or set the prompt value 2118 | // get : prompt() 2119 | // set : prompt('value') 2120 | prompt: function(val) { 2121 | var promptControl = $(this).data('cli').options.promptControl; 2122 | if(val) { // set 2123 | promptControl.html(val); 2124 | } 2125 | 2126 | return promptControl.html(); 2127 | }, 2128 | 2129 | writeMessage: function(type, msg) { 2130 | var writeMessage = $(this).data('cli').writeMessage; 2131 | writeMessage(type, msg); 2132 | }, 2133 | 2134 | loadCss: function(url) { 2135 | loadCss(url); 2136 | }, 2137 | 2138 | addCss: function(css) { 2139 | addCss(css); 2140 | }, 2141 | 2142 | getJson: function(url, callback) { 2143 | getJson(url, callback); 2144 | } 2145 | } 2146 | 2147 | function loadCss(url){ 2148 | if(($("link[href='"+url+"']")).length){ 2149 | console.warn('css already loaded', url); 2150 | return; // script already loaded 2151 | } 2152 | 2153 | $("").attr({ 2154 | rel: 'stylesheet', 2155 | type: 'text/css', 2156 | href: url 2157 | }).appendTo("head"); 2158 | } 2159 | 2160 | function addCss(css) { 2161 | $(" 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/plugins/README.md: -------------------------------------------------------------------------------- 1 | Command Line Interface- plugins sample 2 | =============================================================== 3 | 4 | Running 5 | ------- 6 | 7 | * Run `npm install` to install node dependencies 8 | * Run `bower install` from the `static` folder to install bower dependencies 9 | * Run `npm start` and browse to `http://localhost:4000` 10 | * Type `help` to start exploring the commands supported 11 | 12 | Type `help someplugin` to get the list of the remote commands 13 | Try some of the commands to see how they react. Use `help [commandName]` to get the documentation for each command. For example `help someplugin handler`. 14 | -------------------------------------------------------------------------------- /samples/plugins/api/someplugin.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var api = { console: { autoLoad: false} }; 4 | 5 | var express = require('express'), 6 | router = api.router = express.Router(), 7 | docRouter = require('docrouter').docRouter; 8 | 9 | module.exports = api; 10 | 11 | docRouter(router, '/api/someplugin', function(router) { 12 | 13 | router.get('/json/:tparam1/:tparam2', function(req, res) { 14 | var tparam1 = req.params.tparam1; 15 | var tparam2 = req.params.tparam2; 16 | var qparam = req.query['qparam']; 17 | 18 | var o = {tparam1: tparam1, tparam2: tparam2, qparam: qparam}; 19 | res.writeHead(200, {'Content-Type': 'application/json' }); 20 | res.end(JSON.stringify(o)); 21 | }, 22 | { 23 | id: 'sample_json', 24 | name: 'json', 25 | usage: 'json tparam1 qparam [tparam2]', 26 | example: 'json tparam1 qparamValue', 27 | doc: 'sample for a GET command getting template params and a query param', 28 | params: { 29 | "tparam1" : { 30 | "short": "b", 31 | "type": "string", 32 | "doc": "template param", 33 | "style": "template", 34 | "required": "true" 35 | }, 36 | "qparam" : { 37 | "short": "q", 38 | "type": "string", 39 | "doc": "query string param", 40 | "style": "query", 41 | "required": "true" 42 | }, 43 | tparam2 : { 44 | "short": "a", 45 | "type": "string", 46 | "doc": "template param", 47 | "style": "template", 48 | "required": "true", 49 | "defaultValue": "someTemplateValue" 50 | } 51 | } 52 | } 53 | ); 54 | 55 | router.post('/html', function (req, res) { 56 | console.log(req.body); 57 | var flag = req.query.flag; 58 | res.writeHead(200, { 'Content-Type': 'text/html' }); 59 | res.end("
HTML output: body param: " + req.body.bparam + 60 | " Flag: " + flag + "
"); 61 | }, 62 | { 63 | id: 'sample_html', 64 | name: 'html', 65 | usage: 'html tparam qparam', 66 | example: 'html tparam qparam', 67 | doc: 'sample for a POST command getting a body param returning html', 68 | params: { 69 | "bparam": { 70 | "short": "b", 71 | "type": "string", 72 | "doc": "post body param", 73 | "style": "body", 74 | "required": "true" 75 | }, 76 | "flag": { 77 | "short": "f", 78 | "type": "bool", 79 | "doc": "some boolean flag", 80 | "style": "query", 81 | "required": "true" 82 | } 83 | } 84 | } 85 | ); 86 | 87 | router.post('/htmlbcast', function(req, res) { 88 | var flag = req.query.flag; 89 | res.writeHead(200, { 'Content-Type': 'text/html' }); 90 | res.end("
HTML output: (" + req.body.bparam + "): POST Param: " + req.body.bparam + 91 | " Flag: " + flag + "
"); 92 | }, 93 | { 94 | id: 'sample_htmlbcast', 95 | name: 'htmlbcast', 96 | usage: 'htmlbcast tparam qparam', 97 | example: 'htmlbcast tparam qparam', 98 | doc: 'sample for a broadcasting POST command getting a body param returning html', 99 | broadcast: true, 100 | params: { 101 | "bparam": { 102 | "short": "b", 103 | "type": "string", 104 | "doc": "POST param", 105 | "style": "body", 106 | "required": "true" 107 | }, 108 | "flag": { 109 | "short": "f", 110 | "type": "bool", 111 | "doc": "some boolean flag", 112 | "style": "query", 113 | "required": "true" 114 | } 115 | }, 116 | controller: { 117 | css: '.anode-sample-class{ background-color: green; }' 118 | } 119 | } 120 | ); 121 | 122 | router.post('/htmlbcasthandler', function(req, res) { 123 | var flag = req.query.flag; 124 | res.writeHead(200, {'Content-Type': 'text/html' }); 125 | res.end("
HTML output: ("+req.query['server']+"): Template Param: "+req.body.bparam+ 126 | " Flag: " + flag +"
"); 127 | }, 128 | { 129 | id: 'sample_htmlbcasthandler', 130 | name: 'htmlbcasthandler', 131 | usage: 'htmlbcasthandler tparam qparam', 132 | example: 'htmlbcasthandler tparam qparam', 133 | doc: 'sample for a broadcasting POST command getting a template param returning html with handler', 134 | broadcast: true, 135 | params: { 136 | "bparam" : { 137 | "short": "b", 138 | "type": "string", 139 | "doc": "body param", 140 | "style": "body", 141 | "required": "true" 142 | }, 143 | "flag" : { 144 | "short": "f", 145 | "type": "bool", 146 | "doc": "some boolean flag", 147 | "style": "query", 148 | "required": "true" 149 | } 150 | }, 151 | controller: { 152 | url: '../../plugins/someplugin.js' // relative to /api/sample 153 | } 154 | } 155 | ); 156 | 157 | router.get('/bcast', function(req, res) { 158 | res.writeHead(200, {'Content-Type': 'application/json' }); 159 | res.end(JSON.stringify({server: req.query['server']})); 160 | }, 161 | { 162 | id: 'sample_bcast', 163 | name: 'bcast', 164 | usage: 'sample bcast', 165 | example: 'sample bcast', 166 | doc: 'sample for a broadcasting command', 167 | broadcast: true 168 | }); 169 | 170 | router.get('/handler/:tparam', function(req, res) { 171 | var tparam = req.params.tparam; 172 | var qparam = req.query['qparam']; 173 | 174 | var o = {tparam: tparam}; 175 | res.writeHead(200, {'Content-Type': 'application/json' }); 176 | res.end(JSON.stringify(o)); 177 | }, 178 | { 179 | id: 'sample_handler', 180 | name: 'handler', 181 | usage: 'handler', 182 | example: 'handler', 183 | doc: 'sample for a GET command using external handler', 184 | params: { 185 | "tparam" : { 186 | "short": "t", 187 | "type": "string", 188 | "doc": "template param", 189 | "style": "template", 190 | "required": "true" 191 | } 192 | }, 193 | controller: { 194 | url: '../../plugins/someplugin.js', // relative to /api/sample 195 | cssUrl: '../../plugins/someplugin.css' // relative to /api/sample 196 | } 197 | } 198 | ); 199 | 200 | }); 201 | -------------------------------------------------------------------------------- /samples/plugins/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | bodyParser = require('body-parser'), 3 | cookieParser = require('cookie-parser'), 4 | path = require('path'), 5 | somePlugin = require('./api/someplugin'), 6 | port = process.env.PORT || 4000, 7 | app = express(); 8 | 9 | process.chdir(__dirname); 10 | 11 | app.use(cookieParser()); 12 | app.use(bodyParser.json()); 13 | app.use(bodyParser.urlencoded({ extended: false })); 14 | 15 | // adding plugin to app 16 | app.use('/someplugin', somePlugin.router); 17 | 18 | app.use(express.static(path.join(__dirname, "static"))); 19 | 20 | app.listen(port); 21 | 22 | console.log('listening on port', port); -------------------------------------------------------------------------------- /samples/plugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acli-plugins", 3 | "version": "1.0.0", 4 | "description": "sample for extending the console with remote commands (aka plugins)", 5 | "main": "index.js", 6 | "dependencies": { 7 | "body-parser": "^1.15.0", 8 | "cookie-parser": "^1.4.1", 9 | "docrouter": "^0.7.0", 10 | "express": "^4.13.4", 11 | "jade": "^1.11.0" 12 | }, 13 | "devDependencies": {}, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "start": "node index.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/amiturgman/aCLI.git" 21 | }, 22 | "author": "Ami Turgman ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/amiturgman/aCLI/issues" 26 | }, 27 | "homepage": "https://github.com/amiturgman/aCLI#readme" 28 | } 29 | -------------------------------------------------------------------------------- /samples/plugins/static/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugins", 3 | "homepage": "https://github.com/amiturgman/aCLI", 4 | "authors": [ 5 | "Ami Turgman " 6 | ], 7 | "description": "plugins sample for aCLI library", 8 | "main": "index.html", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "acli": "^0.0.10" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/plugins/static/console.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by JetBrains WebStorm. 3 | * User: amitu 4 | * Date: 1/15/12 5 | * Time: 1:50 PM 6 | * To change this template use File | Settings | File Templates. 7 | */ 8 | 9 | $(function () { 10 | 11 | var env = { 12 | user: { type: 'string', value: '', description: 'The current user'} 13 | } 14 | 15 | var commands = getCommands(); 16 | 17 | var opts = { 18 | environment: env, 19 | commands: commands, 20 | sid: 'aaaa', 21 | context: { some: 'object' }, 22 | welcomeMessage: "Welcome to the console!
Type help to start exploring the commands currently supported!
" 23 | }; 24 | 25 | var cli = $(".cli-control").cli(opts); 26 | 27 | // get environment events broker 28 | var ebEnv = cli.cli('envEventsBroker'); 29 | 30 | // bind to environment change events 31 | $(ebEnv).bind({ 32 | userChanged: function(e, state) { 33 | updatePrompt(); 34 | } 35 | }); 36 | 37 | updatePrompt(); 38 | 39 | // returns an array of ajaxOptions used by cli when broadcasting a request 40 | function broadcastUrlGenerator(ajaxRequestOptions) { 41 | 42 | // assuming there are 3 servers, 43 | // and that when adding their name to the query string the requests are redirected to the specific server 44 | var servers = ['server1', 'server2', 'server3']; 45 | 46 | var urlWithQuestionMark = ajaxRequestOptions.url.indexOf('?') > -1; 47 | 48 | var ajaxRequestsOptions = _.map(servers, function(server) { 49 | var serverRequest = $.extend(true, {}, ajaxRequestOptions, 50 | {server: server, url: ajaxRequestOptions.url + (urlWithQuestionMark ? '&' : '?') + 'server='+server }); 51 | return serverRequest; 52 | }); 53 | return ajaxRequestsOptions; 54 | } 55 | 56 | function updatePrompt() { 57 | var prompt = cli_env('user') + '>'; 58 | cli_prompt(prompt); 59 | } 60 | 61 | // create default anode commands 62 | function getCommands() { 63 | var commands = []; 64 | 65 | var addCommand = { 66 | name: 'add', 67 | description: 'add two numbers', 68 | usage: 'add num1 num2', 69 | example: 'add 4 5', 70 | params: [ 71 | { 72 | name: 'num1', 73 | description: 'first number', 74 | type: 'number' 75 | }, 76 | { 77 | name: 'num2', 78 | description: 'second number', 79 | type: 'number' 80 | } 81 | ], 82 | exec: function(args, context) { 83 | return args.num1 + args.num2; 84 | } 85 | } 86 | 87 | var asyncCommand = { 88 | name: 'async', 89 | description: 'simulates async command', 90 | usage: 'async [text]', 91 | example: 'async', 92 | params: [ 93 | { 94 | name: 'text', 95 | description: 'some text to show', 96 | type: 'string', 97 | defaultValue: null // make this an optional argument 98 | } 99 | ], 100 | exec: function(args, context) { 101 | var promise = context.createPromise(); 102 | 103 | // simulating some async long-processing action 104 | setTimeout(function(){ 105 | var result = args.text || "you didn't enter text"; 106 | promise.resolve(result); 107 | }, 1000); 108 | 109 | return promise; 110 | } 111 | } 112 | 113 | var sampleCommand = { 114 | name: 'sample', 115 | description: 'Sample command', 116 | usage: 'sample [requiredString1] [requiredString2] [requiredNumber1] [optionalNumber1] [optionalNumber2]', 117 | example: 'sample aa bb 444 333 -l ffff -r', 118 | params: [ 119 | { 120 | name: 'requiredString1', 121 | description: 'required string 1', 122 | type: 'string' 123 | }, 124 | { 125 | name: 'requiredString2', 126 | description: 'required string 2', 127 | type: 'string' 128 | }, 129 | { 130 | name: 'requiredNumber1', 131 | description: 'required number 1', 132 | type: 'number' 133 | }, 134 | { 135 | name: 'optString1', 136 | description: 'optional string 1', 137 | type: 'string', 138 | defaultValue: 'optString1' 139 | }, 140 | { 141 | name: 'optString2', 142 | short: 'a', 143 | description: 'optional string 2', 144 | type: 'string', 145 | defaultValue: 'optString2' 146 | }, 147 | { 148 | name: 'optionalNumber1', 149 | short: 'b', 150 | description: 'optional number 1', 151 | type: 'number', 152 | defaultValue: 1234 153 | }, 154 | { 155 | name: 'optionalNumber2', 156 | short: 'c', 157 | description: 'optional number 2', 158 | type: 'number', 159 | defaultValue: 222 160 | }, 161 | { 162 | name: 'optionalNullNumber', 163 | short: 'd', 164 | description: 'optional null number 1', 165 | type: 'number', 166 | defaultValue: null 167 | }, 168 | { 169 | name: 'switchBool1', 170 | short: 'e', 171 | description: 'boolean switch', 172 | type: 'boolean' 173 | }, 174 | { 175 | name: 'switchBool2', 176 | short: 'f', 177 | description: 'boolean switch', 178 | type: 'boolean' 179 | } 180 | 181 | ], 182 | exec: function(args, context) { 183 | return "sample command args: " + JSON.stringify(args); 184 | } 185 | } 186 | 187 | // this is a group of commands, under the `group` name 188 | var groupCommand = { 189 | name: 'group', 190 | description: 'some group command' 191 | } 192 | var subCommand1 = { 193 | name: 'group command1', 194 | description: 'first command in group', 195 | usage: 'group command [param]', 196 | example: 'group command someParamValue', 197 | params: [{ 198 | name: 'param', 199 | type: 'string', 200 | defaultValue: null // make it optional argument 201 | }], 202 | exec: function(args, context) { 203 | return args.param || 'param not provided'; 204 | } 205 | } 206 | var subCommand2 = { 207 | name: 'group command2', 208 | description: 'second command in group', 209 | usage: 'group command2', 210 | example: 'group command2', 211 | exec: function(args, context) { 212 | return 'no parameters here, hello from exec method!'; 213 | } 214 | } 215 | 216 | 217 | // See more commands in the cli code under the addDefaultCommands() method 218 | 219 | commands.push(addCommand); 220 | commands.push(asyncCommand); 221 | commands.push(sampleCommand); 222 | 223 | commands.push(groupCommand); 224 | commands.push(subCommand1); 225 | commands.push(subCommand2); 226 | return commands; 227 | } 228 | 229 | // help method for accessing cli env 230 | function cli_env(setting, val, appOriginated, setEmpty) { 231 | return cli.cli('env', setting, val, appOriginated, setEmpty); 232 | } 233 | 234 | // help method for accessing cli prompt 235 | function cli_prompt(val) { 236 | return cli.cli('prompt', val); 237 | } 238 | 239 | }); 240 | -------------------------------------------------------------------------------- /samples/plugins/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | web based command line interface with plugins 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/plugins/static/plugins/someplugin.css: -------------------------------------------------------------------------------- 1 | .anode-sample { 2 | width: 300px; 3 | border: 1px solid gray; 4 | background-color: rgba(236, 255, 117, 0.60); 5 | padding: 4px; 6 | } 7 | -------------------------------------------------------------------------------- /samples/plugins/static/plugins/someplugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by JetBrains WebStorm. 3 | * User: amitu 4 | * Date: 1/15/12 5 | * Time: 1:50 PM 6 | * To change this template use File | Settings | File Templates. 7 | */ 8 | 9 | //debugger; 10 | console.info('sample handler loaded'); 11 | 12 | this.handler = function(error, response){ 13 | if (queryObject.get('debug')) debugger; 14 | var args = response.args; 15 | var context = response.context; 16 | var url = context.url; 17 | var env = context.env; 18 | var command = context.command; 19 | var data = response.data; 20 | 21 | var progress = 0; 22 | var intrvl = setInterval(function(){ 23 | response.promise.setProgress(progress); 24 | if(progress==100) { 25 | clearInterval(intrvl); 26 | response.promise.resolve( 27 | $("
").addClass('anode-sample').html( 28 | 'this is the client side plugin handler speaking.. got the following parametrs:' + JSON.stringify(args) + 29 | '. got the following response: ' + JSON.stringify(data))); 30 | } 31 | progress+=10; 32 | }, 200); 33 | } 34 | 35 | this.htmlbcasthandler = function(response){ 36 | if (queryObject.get('debug')) debugger; 37 | var args = response.args; 38 | var context = response.context; 39 | var env = context.env; 40 | var command = context.command; 41 | var data = response.data; 42 | 43 | var res = $("
"); 44 | _.each(data, function(instanceResult){ 45 | var title = $("
").appendTo(res).html(instanceResult.instance); 46 | var data = $("
").appendTo(res).html(instanceResult.error || instanceResult.response); 47 | }); 48 | response.promise.resolve(res); 49 | } 50 | 51 | --------------------------------------------------------------------------------