├── tests └── .gitkeep ├── assets ├── webartisan.css ├── app.js ├── jquery.terminal.css └── jquery.terminal-0.8.8.min.js ├── .gitignore ├── screenshot.png ├── routes.php ├── composer.json ├── src ├── WebartisanServiceProvider.php ├── views │ └── index.blade.php └── WebartisanController.php └── README.md /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/webartisan.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: black; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | .DS_Store 4 | Thumbs.db 5 | .idea -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emir/laravel-webartisan/HEAD/screenshot.png -------------------------------------------------------------------------------- /routes.php: -------------------------------------------------------------------------------- 1 | 'webartisan', 'uses' => 'Emir\Webartisan\WebartisanController@index']); 4 | post('artisan/run', ['as' => 'webartisan.run', 'uses' => 'Emir\Webartisan\WebartisanController@actionRpc']); 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emir/laravel-webartisan", 3 | "description": "Web artisan allows to run artisan console commands using a browser", 4 | "keywords": ["webartisan", "artisan", "laravel", "php"], 5 | "homepage": "https://github.com/emir/laravel-webartisan", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Emir Karşıyakalı", 10 | "email": "emirkarsiyakali@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.0", 15 | "illuminate/support": "~5.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Emir\\Webartisan\\": "src/" 20 | } 21 | }, 22 | "minimum-stability": "dev" 23 | } -------------------------------------------------------------------------------- /src/WebartisanServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 15 | __DIR__.'/../assets' => public_path('emir/webartisan'), 16 | ], 'public'); 17 | 18 | $this->loadViewsFrom(__DIR__.'/views', 'webartisan'); 19 | 20 | if (! $this->app->routesAreCached()) { 21 | require __DIR__.'/../routes.php'; 22 | } 23 | } 24 | 25 | /** 26 | * Register the application services. 27 | */ 28 | public function register() 29 | { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/views/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Webartisan 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel 5 Web Artisan 2 | ================= 3 | 4 | Web artisan allows to run `artisan` console commands using a browser. Laravel port for [samdark/yii2-webshell](https://github.com/samdark/yii2-webshell). 5 | 6 | 7 | 8 | Installation 9 | ------------ 10 | 11 | Require this package with composer: 12 | 13 | ``` 14 | composer require emir/laravel-webartisan 15 | ``` 16 | 17 | After updating composer, because of the security reasons you need to check environment is local. 18 | So you can add the ServiceProvider to app/Providers/AppServiceProvider.php like this: 19 | 20 | ```php 21 | public function register() 22 | { 23 | if ($this->app->environment() == 'local') { 24 | $this->app->register('Emir\Webartisan\WebartisanServiceProvider'); 25 | } 26 | } 27 | ``` 28 | 29 | Copy the package assets to your local with the publish command: 30 | 31 | ```php 32 | php artisan vendor:publish --provider="Emir\Webartisan\WebartisanServiceProvider" 33 | ``` 34 | 35 | Usage 36 | ------------ 37 | 38 | After installation you will be able to access web artisan in your browser using 39 | the URL: 40 | 41 | `http://localhost/path/to/artisan` 42 | 43 | License 44 | ------------- 45 | 46 | [MIT License](http://emir.mit-license.org/) -------------------------------------------------------------------------------- /assets/app.js: -------------------------------------------------------------------------------- 1 | jQuery(function($) { 2 | $('#webartisan').terminal( 3 | function(command, term) { 4 | if (command.indexOf('artisan') === 0 || command.indexOf('artisan') === 7) { 5 | $.jrpc(WebArtisanEndpoint, 'artisan', [command.replace(/^artisan ?/, '')], function(json) { 6 | term.echo(json.result); 7 | }); 8 | } else if (command === 'help') { 9 | term.echo('Available commands are:'); 10 | term.echo(''); 11 | term.echo("clear\tClear console"); 12 | term.echo('help\tThis help text'); 13 | term.echo('artisan\tartisan command'); 14 | term.echo('quit\tQuit web artisan'); 15 | } else if (command === 'quit') { 16 | if (exitUrl) { 17 | term.echo('Bye!'); 18 | location.replace(exitUrl); 19 | } else { 20 | term.echo('There is no exit.'); 21 | } 22 | } else { 23 | term.echo('Unknown command.'); 24 | } 25 | }, 26 | { 27 | greetings: greetings, 28 | name: 'laravel-webartisan', 29 | prompt: '$ ' 30 | } 31 | ); 32 | $('html').on('keydown', function(){ 33 | $('#webartisan').click(); 34 | }); 35 | }); -------------------------------------------------------------------------------- /src/WebartisanController.php: -------------------------------------------------------------------------------- 1 | getContent()); 28 | 29 | switch ($options->method) { 30 | case 'artisan': 31 | list($status, $output) = $this->runCommand(implode(' ', $options->params)); 32 | 33 | return ['result' => $output]; 34 | } 35 | } 36 | 37 | /** 38 | * Runs console command. 39 | * 40 | * @param string $command 41 | * 42 | * @return array [status, output] 43 | */ 44 | private function runCommand($command) 45 | { 46 | $cmd = base_path("artisan $command 2>&1"); 47 | 48 | $handler = popen($cmd, 'r'); 49 | $output = ''; 50 | while (!feof($handler)) { 51 | $output .= fgets($handler); 52 | } 53 | $output = trim($output); 54 | $status = pclose($handler); 55 | 56 | return [$status, $output]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /assets/jquery.terminal.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This css file is part of jquery terminal 3 | * 4 | * Licensed under GNU LGPL Version 3 license 5 | * Copyright (c) 2011-2013 Jakub Jankiewicz 6 | * 7 | */ 8 | .terminal .terminal-output .format, .cmd .format, 9 | .cmd .prompt, .cmd .prompt div, .terminal .terminal-output div div{ 10 | display: inline-block; 11 | } 12 | .cmd .clipboard { 13 | position: absolute; 14 | bottom: 0; 15 | left: 0; 16 | opacity: 0.01; 17 | filter: alpha(opacity = 0.01); 18 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0.01); 19 | width: 2px; 20 | } 21 | .cmd > .clipboard { 22 | position: fixed; 23 | } 24 | .terminal { 25 | padding: 10px; 26 | position: relative; 27 | overflow: hidden; 28 | } 29 | .cmd { 30 | padding: 0; 31 | margin: 0; 32 | height: 1.3em; 33 | /*margin-top: 3px; */ 34 | } 35 | .cmd .cursor.blink { 36 | -webkit-animation: blink 1s infinite steps(1, start); 37 | -moz-animation: blink 1s infinite steps(1, start); 38 | -ms-animation: blink 1s infinite steps(1, start); 39 | animation: blink 1s infinite steps(1, start); 40 | } 41 | @keyframes blink { 42 | 0%, 100% { 43 | background-color: #000; 44 | color: #aaa; 45 | } 46 | 50% { 47 | background-color: #bbb; /* not #aaa because it's seem there is Google Chrome bug */ 48 | color: #000; 49 | } 50 | } 51 | @-webkit-keyframes blink { 52 | 0%, 100% { 53 | background-color: #000; 54 | color: #aaa; 55 | } 56 | 50% { 57 | background-color: #bbb; 58 | color: #000; 59 | } 60 | } 61 | @-ms-keyframes blink { 62 | 0%, 100% { 63 | background-color: #000; 64 | color: #aaa; 65 | } 66 | 50% { 67 | background-color: #bbb; 68 | color: #000; 69 | } 70 | } 71 | @-moz-keyframes blink { 72 | 0%, 100% { 73 | background-color: #000; 74 | color: #aaa; 75 | } 76 | 50% { 77 | background-color: #bbb; 78 | color: #000; 79 | } 80 | } 81 | .terminal .terminal-output div div, .cmd .prompt { 82 | display: block; 83 | line-height: 14px; 84 | height: auto; 85 | } 86 | .cmd .prompt { 87 | float: left; 88 | } 89 | .terminal, .cmd { 90 | font-family: FreeMono, monospace; 91 | color: #aaa; 92 | background-color: #000; 93 | font-size: 12px; 94 | line-height: 14px; 95 | } 96 | .terminal-output > div { 97 | /*padding-top: 3px;*/ 98 | min-height: 14px; 99 | } 100 | .terminal .terminal-output div span { 101 | display: inline-block; 102 | } 103 | .cmd span { 104 | float: left; 105 | /*display: inline-block; */ 106 | } 107 | .terminal .inverted, .cmd .inverted, .cmd .cursor.blink { 108 | background-color: #aaa; 109 | color: #000; 110 | } 111 | .terminal .terminal-output div div::-moz-selection, 112 | .terminal .terminal-output div span::-moz-selection, 113 | .terminal .terminal-output div div a::-moz-selection { 114 | background-color: #aaa; 115 | color: #000; 116 | } 117 | .terminal .terminal-output div div::selection, 118 | .terminal .terminal-output div div a::selection, 119 | .terminal .terminal-output div span::selection, 120 | .cmd > span::selection, 121 | .cmd .prompt span::selection { 122 | background-color: #aaa; 123 | color: #000; 124 | } 125 | .terminal .terminal-output div.error, .terminal .terminal-output div.error div { 126 | color: red; 127 | } 128 | .tilda { 129 | position: fixed; 130 | top: 0; 131 | left: 0; 132 | width: 100%; 133 | z-index: 1100; 134 | } 135 | .clear { 136 | clear: both; 137 | } 138 | .terminal a { 139 | color: #0F60FF; 140 | } 141 | .terminal a:hover { 142 | color: red; 143 | } 144 | -------------------------------------------------------------------------------- /assets/jquery.terminal-0.8.8.min.js: -------------------------------------------------------------------------------- 1 | (function(ctx){var sprintf=function(){if(!sprintf.cache.hasOwnProperty(arguments[0])){sprintf.cache[arguments[0]]=sprintf.parse(arguments[0])}return sprintf.format.call(null,sprintf.cache[arguments[0]],arguments)};sprintf.format=function(parse_tree,argv){var cursor=1,tree_length=parse_tree.length,node_type="",arg,output=[],i,k,match,pad,pad_character,pad_length;for(i=0;i>>0;break;case"x":arg=arg.toString(16);break;case"X":arg=arg.toString(16).toUpperCase();break}arg=/[def]/.test(match[8])&&match[3]&&arg>=0?"+"+arg:arg;pad_character=match[4]?match[4]=="0"?"0":match[4].charAt(1):" ";pad_length=match[6]-String(arg).length;pad=match[6]?str_repeat(pad_character,pad_length):"";output.push(match[5]?arg+pad:pad+arg)}}return output.join("")};sprintf.cache={};sprintf.parse=function(fmt){var _fmt=fmt,match=[],parse_tree=[],arg_names=0;while(_fmt){if((match=/^[^\x25]+/.exec(_fmt))!==null){parse_tree.push(match[0])}else if((match=/^\x25{2}/.exec(_fmt))!==null){parse_tree.push("%")}else if((match=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt))!==null){if(match[2]){arg_names|=1;var field_list=[],replacement_field=match[2],field_match=[];if((field_match=/^([a-z_][a-z_\d]*)/i.exec(replacement_field))!==null){field_list.push(field_match[1]);while((replacement_field=replacement_field.substring(field_match[0].length))!==""){if((field_match=/^\.([a-z_][a-z_\d]*)/i.exec(replacement_field))!==null){field_list.push(field_match[1])}else if((field_match=/^\[(\d+)\]/.exec(replacement_field))!==null){field_list.push(field_match[1])}else{throw"[sprintf] huh?"}}}else{throw"[sprintf] huh?"}match[2]=field_list}else{arg_names|=2}if(arg_names===3){throw"[sprintf] mixing positional and named placeholders is not (yet) supported"}parse_tree.push(match)}else{throw"[sprintf] huh?"}_fmt=_fmt.substring(match[0].length)}return parse_tree};var vsprintf=function(fmt,argv,_argv){_argv=argv.slice(0);_argv.splice(0,0,fmt);return sprintf.apply(null,_argv)};function get_type(variable){return Object.prototype.toString.call(variable).slice(8,-1).toLowerCase()}function str_repeat(input,multiplier){for(var output=[];multiplier>0;output[--multiplier]=input){}return output.join("")}ctx.sprintf=sprintf;ctx.vsprintf=vsprintf})(typeof exports!="undefined"?exports:window);(function($,undefined){"use strict";$.omap=function(o,fn){var result={};$.each(o,function(k,v){result[k]=fn.call(o,k,v)});return result};var isLS=typeof window.localStorage!=="undefined";function wls(n,v){var c;if(typeof n==="string"&&typeof v==="string"){localStorage[n]=v;return true}else if(typeof n==="object"&&typeof v==="undefined"){for(c in n){if(n.hasOwnProperty(c)){localStorage[c]=n[c]}}return true}return false}function wc(n,v){var dt,e,c;dt=new Date;dt.setTime(dt.getTime()+31536e6);e="; expires="+dt.toGMTString();if(typeof n==="string"&&typeof v==="string"){document.cookie=n+"="+v+e+"; path=/";return true}else if(typeof n==="object"&&typeof v==="undefined"){for(c in n){if(n.hasOwnProperty(c)){document.cookie=c+"="+n[c]+e+"; path=/"}}return true}return false}function rls(n){return localStorage[n]}function rc(n){var nn,ca,i,c;nn=n+"=";ca=document.cookie.split(";");for(i=0;itimes&×!==0||fn.call(element,counter)===false){jQuery.timer.remove(element,label,fn)}handler.inProgress=false};handler.$timerID=fn.$timerID;if(!element.$timers[label][fn.$timerID]){element.$timers[label][fn.$timerID]=window.setInterval(handler,interval)}if(!this.global[label]){this.global[label]=[]}this.global[label].push(element)},remove:function(element,label,fn){var timers=element.$timers,ret;if(timers){if(!label){for(var lab in timers){if(timers.hasOwnProperty(lab)){this.remove(element,lab,fn)}}}else if(timers[label]){if(fn){if(fn.$timerID){window.clearInterval(timers[label][fn.$timerID]);delete timers[label][fn.$timerID]}}else{for(var _fn in timers[label]){if(timers[label].hasOwnProperty(_fn)){window.clearInterval(timers[label][_fn]);delete timers[label][_fn]}}}for(ret in timers[label]){if(timers[label].hasOwnProperty(ret)){break}}if(!ret){ret=null;delete timers[label]}}for(ret in timers){if(timers.hasOwnProperty(ret)){break}}if(!ret){element.$timers=null}}}}});if(/(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())){jQuery(window).one("unload",function(){var global=jQuery.timer.global;for(var label in global){if(global.hasOwnProperty(label)){var els=global[label],i=els.length;while(--i){jQuery.timer.remove(els[i],label)}}}})}(function(undef){if(!String.prototype.split.toString().match(/\[native/)){return}var nativeSplit=String.prototype.split,compliantExecNpcg=/()??/.exec("")[1]===undef,self;self=function(str,separator,limit){if(Object.prototype.toString.call(separator)!=="[object RegExp]"){return nativeSplit.call(str,separator,limit)}var output=[],flags=(separator.ignoreCase?"i":"")+(separator.multiline?"m":"")+(separator.extended?"x":"")+(separator.sticky?"y":""),lastLastIndex=0,separator2,match,lastIndex,lastLength;separator=new RegExp(separator.source,flags+"g");str+="";if(!compliantExecNpcg){separator2=new RegExp("^"+separator.source+"$(?!\\s)",flags)}limit=limit===undef?-1>>>0:limit>>>0;while(match=separator.exec(str)){lastIndex=match.index+match[0].length;if(lastIndex>lastLastIndex){output.push(str.slice(lastLastIndex,match.index));if(!compliantExecNpcg&&match.length>1){match[0].replace(separator2,function(){for(var i=1;i1&&match.index=limit){break}}if(separator.lastIndex===match.index){separator.lastIndex++}}if(lastLastIndex===str.length){if(lastLength||!separator.test("")){output.push("")}}else{output.push(str.slice(lastLastIndex))}return output.length>limit?output.slice(0,limit):output};String.prototype.split=function(separator,limit){return self(this,separator,limit)};return self})();function str_parts(str,length){var result=[];var len=str.length;if(len0?data[data.length-1]:null}})}$.json_stringify=function(object,level){var result="",i;level=level===undefined?1:level;var type=typeof object;switch(type){case"function":result+=object;break;case"boolean":result+=object?"true":"false";break;case"object":if(object===null){result+="null"}else if(object instanceof Array){result+="[";var len=object.length;for(i=0;i1?",":"";if(level===1){result=result.replace(/,([\]}])/g,"$1")}return result.replace(/([\[{]),/g,"$1")};function History(name,size){var enabled=true;var storage_key="";if(typeof name==="string"&&name!==""){storage_key=name+"_"}storage_key+="commands";var data=$.Storage.get(storage_key);data=data?$.parseJSON(data):[];var pos=data.length-1;$.extend(this,{append:function(item){if(enabled){if(data[data.length-1]!==item){data.push(item);if(size&&data.length>size){data=data.slice(-size)}pos=data.length-1;$.Storage.set(storage_key,$.json_stringify(data))}}},data:function(){return data},reset:function(){pos=data.length-1},last:function(){return data[length-1]},end:function(){return pos===data.length-1},position:function(){return pos},current:function(){return data[pos]},next:function(){if(pos0){--pos}if(old!==-1){return data[pos]}},clear:function(){data=[];this.purge()},enabled:function(){return enabled},enable:function(){enabled=true},purge:function(){$.Storage.remove(storage_key)},disable:function(){enabled=false}})}$.fn.cmd=function(options){var self=this;var maybe_data=self.data("cmd");if(maybe_data){return maybe_data}self.addClass("cmd");self.append(''+' ');var clip=$("