From 4011ff8de070b5adc8fa8110073398655889f3de Mon Sep 17 00:00:00 2001 From: hahn <axel.hahn@iml.unibe.ch> Date: Tue, 20 Sep 2022 18:47:20 +0200 Subject: [PATCH] enable shell plugins (WIP) --- .../deployment/classes/plugins.class.php | 200 +++++---- .../classes/plugins_renderer.class.php | 119 +++++ public_html/deployment/inc_functions.php | 6 +- public_html/deployment/index.php | 69 ++- public_html/deployment/js/addi.js | 410 ++++++++++++++++++ public_html/deployment/js/functions.js | 11 + public_html/deployment/js/ubd.class.js | 209 +++++++++ public_html/deployment/main.css | 33 +- .../deployment/plugins/build/tgz/info.json | 1 + .../deployment/plugins/build/zip/info.json | 1 + .../deployment/plugins/doc_reminder.md | 55 +++ .../deployment/plugins/shellcmd/getdata.php | 134 +----- .../plugins/shellcmd/load/config.json | 7 + .../plugins/shellcmd/load/info.json | 11 + .../plugins/shellcmd/load/plugin.php | 14 +- .../plugins/shellcmd/load/render.js | 43 +- .../shellcmd/plugins_shellcmd.class.php | 11 +- .../plugins/shellcmd/processes/command.php | 0 .../plugins/shellcmd/processes/config.json | 7 + .../plugins/shellcmd/processes/info.json | 11 + .../plugins/shellcmd/processes/plugin.php | 73 ++++ .../plugins/shellcmd/processes/render.js | 42 ++ 22 files changed, 1243 insertions(+), 224 deletions(-) create mode 100644 public_html/deployment/classes/plugins_renderer.class.php create mode 100644 public_html/deployment/js/addi.js create mode 100644 public_html/deployment/js/ubd.class.js create mode 100644 public_html/deployment/plugins/doc_reminder.md create mode 100644 public_html/deployment/plugins/shellcmd/load/config.json create mode 100644 public_html/deployment/plugins/shellcmd/load/info.json delete mode 100644 public_html/deployment/plugins/shellcmd/processes/command.php create mode 100644 public_html/deployment/plugins/shellcmd/processes/config.json create mode 100644 public_html/deployment/plugins/shellcmd/processes/info.json create mode 100644 public_html/deployment/plugins/shellcmd/processes/plugin.php create mode 100644 public_html/deployment/plugins/shellcmd/processes/render.js diff --git a/public_html/deployment/classes/plugins.class.php b/public_html/deployment/classes/plugins.class.php index 26c5869d..1a52f30c 100644 --- a/public_html/deployment/classes/plugins.class.php +++ b/public_html/deployment/classes/plugins.class.php @@ -13,29 +13,61 @@ * // print_r($CI_plugins->getPlugins()); * print_r($CI_plugins->getPlugins('build')); * - * $CI_plugins->setPlugin('tgz', 'build'); + * $CI_plugins->setPlugin('tgz', 'build'); // plugin name + type * * * @author axel */ class ciplugins { + /** + * start path of all plugin types (as subdirs) + * @var string + */ protected $_sPlugindir=false; /** + * path of the currently set plugin + * @var string + */ + protected $_sSelfdir=false; + + /** + * url of set plugin + * @var string + */ + protected $_sSelfurl=false; + + /** + * current plugin type - can be set via setType or setPlugin * @var string */ protected $_sType=false; /** + * current plugin name - can be set via setPlugin * @var string */ protected $_sPluginname=false; - + /** + * plugin language + * @var string + */ protected $_sLang = "en-en"; + + /** + * plugin language texts (lang*.json) + * @var array + */ protected $_aLang = []; + /** + * plugin configuration data (config.json) + * @var array + */ + protected $_aConfig = []; + // --------------------------------------------------------------- // CONSTRUCTOR @@ -51,43 +83,46 @@ class ciplugins { return true; } - + // --------------------------------------------------------------- - // LANGUAGE TEXTS + // FOR LISTING :: GETTER // --------------------------------------------------------------- - + /** - * get a translated text from lang_XX.json in plugin dir; - * If the key is missed it returns "[KEY :: LANG]" - * - * @see setLang() - * @param string $sKey key to find in lang file - * @return string + * get an array of available plugin types read from filesystem + * @return array */ - protected function _t($sKey){ - return (isset($this->_aLang[$sKey]) && $this->_aLang[$sKey]) - ? $this->_aLang[$sKey] - : "[ $sKey :: $this->_sLang ]" - ; + public function getPluginTypes(){ + $aReturn=[]; + foreach(glob($this->_sPlugindir.'/*', GLOB_ONLYDIR) as $sMydir){ + $aReturn[]=basename($sMydir); + } + return $aReturn; } /** - * set language for output of formdata and other texts. - * This method loads the language file into a hash. The output of - * translated texts can be done with $this->_t("your_key") - * - * @see _t() - * @param string $sLang language code, i.e. "de" - * @return boolean + * get an array of available plugins read from filesystem + * @return array */ - public function setLang($sLang=false){ - $this->_sLang=$sLang ? $sLang : $this->_sLang; - - $oReflection=new ReflectionClass($this); - $sFile=dirname($oReflection->getFileName()) . '/lang_'.$this->_sLang.'.json'; - $this->_aLang=(file_exists($sFile)) ? json_decode(file_get_contents($sFile), 1) : $this->_aLang; - return true; + public function getPlugins($sType=false){ + $aReturn=[]; + if($sType){ + if (!$this->setType($sType)){ + return $aReturn; + } + } + foreach(glob($this->_sPlugindir.'/'.$this->_sType.'/*', GLOB_ONLYDIR) as $sMydir){ + $aReturn[]=basename($sMydir); + } + return $aReturn; } + + // --------------------------------------------------------------- + // + // BELOW ARE METHODS FOR A SET SPECIFIC PLUGIN AND TYPE + // + // --------------------------------------------------------------- + // --------------------------------------------------------------- // SETTER // --------------------------------------------------------------- @@ -97,6 +132,7 @@ class ciplugins { * @param {string} $sType Name of a plugin type, e.g. build|rollout */ public function setType($sType){ + $this->_sType=false; if(!$sType || !is_dir($this->_sPlugindir.'/'.$sType)){ return false; } @@ -104,74 +140,88 @@ class ciplugins { } /** - * set a plugin with autoload + * reset vars before setting a new plugin; + * called in testPlugin() + * @return boolean + */ + protected function _resetPluginData(){ + $this->_sPluginname=false; + $this->_sSelfdir=false; + $this->_sSelfurl=false; + $this->_aLang=[]; + $this->_aConfig=[]; + return true; + } + + /** + * set a plugin without autoload of its php class + * It returns the path of php class for true + * or boolean false if it does not exist + * + * This can be used standalone to embed html code + * without loading any php code of the plugin class. * * @param {string} $sPluginName name of the plugin - * @param {string} $sType optuional: set a type - * @return bool + * @param {string} $sType optional: set a type + * @return bool|string */ - public function setPlugin($sPluginName,$sType=false){ + public function testPlugin($sPluginName,$sType=false){ + $this->_resetPluginData(); if($sType){ if (!$this->setType($sType)){ return false; } } - $sFile=$this->getPluginFilename($sPluginName); + $this->_sSelfdir=$this->_sPlugindir.'/'.$this->_sType.'/'.$sPluginName; + $sFile=$this->_sSelfdir.'/plugin.php'; if(!file_exists($sFile)){ + // die(' MISS '.$sFile); + $this->_sSelfdir=false; return false; } - include_once $sFile; - return $this->_sPluginname=$sPluginName; + $this->_sPluginname=$sPluginName; + $this->_sSelfurl='/deployment/plugins/'.$this->_sType.'/'.$sPluginName; + return $sFile; } - // --------------------------------------------------------------- - // GETTER - // --------------------------------------------------------------- - - /** - * get a location of a plugin file with full path - * The type must be initialized first with setType() + * set a plugin with autoload of its php class + * It returns a boolean * - * @param string $sPluginName optional: Name of plugin - * @return string - */ - public function getPluginFilename($sPluginName=false){ - if(!$sPluginName){ - $sPluginName=$this->_sPluginname; - } - return $this->_sPlugindir.'/'.$this->_sType.'/'.$sPluginName.'/'.$this->_sType.'_'.$sPluginName.'.php'; - } - - - /** - * get an array of available plugin types read from filesystem - * @return array + * @param {string} $sPluginName name of the plugin + * @param {string} $sType optional: set a type + * @return bool */ - public function getPluginTypes(){ - $aReturn=[]; - foreach(glob($this->_sPlugindir.'/*', GLOB_ONLYDIR) as $sMydir){ - $aReturn[]=basename($sMydir); + public function setPlugin($sPluginName,$sType=false){ + $sFile=$this->testPlugin($sPluginName,$sType); + if(!$sFile){ + return false; } - return $aReturn; + include_once $sFile; + return true; } - + // --------------------------------------------------------------- + // getter for plugin + // --------------------------------------------------------------- /** - * get an array of available plugins read from filesystem + * get plugin config from its config.json + * works with + * - shellcmd plugin * @return array */ - public function getPlugins($sType=false){ - $aReturn=[]; - if($sType){ - if (!$this->setType($sType)){ - return $aReturn; - } + public function getPluginConfig(){ + if(count($this->_aConfig)){ + return $this->_aConfig; } - foreach(glob($this->_sPlugindir.'/'.$this->_sType.'/*', GLOB_ONLYDIR) as $sMydir){ - $aReturn[]=basename($sMydir); - } - return $aReturn; + $this->_aConfig=(file_exists($this->_sSelfdir.'/config.json')) + ? json_decode(file_get_contents($this->_sSelfdir.'/config.json'), 1) + : ["error" => "config.json not found in ".$this->_sSelfdir] + ; + return $this->_aConfig; } + // --------------------------------------------------------------- + // access plugin php class + // --------------------------------------------------------------- /** * get a location of a plugin file with full path diff --git a/public_html/deployment/classes/plugins_renderer.class.php b/public_html/deployment/classes/plugins_renderer.class.php new file mode 100644 index 00000000..288452c9 --- /dev/null +++ b/public_html/deployment/classes/plugins_renderer.class.php @@ -0,0 +1,119 @@ +<?php +require_once('plugins.class.php'); +/** + * WIP + * base class for all plugin types to read available plugins + * and its metadata + * + * @example + * $CI_plugins=new ciplugins(); + * print_r($CI_plugins->getPluginTypes()); + * + * // $CI_plugins->setType('build'); + * // print_r($CI_plugins->getPlugins()); + * print_r($CI_plugins->getPlugins('build')); + * + * $CI_plugins->setPlugin('tgz', 'build'); // plugin name + type + * + * + * @author axel + */ +class plugin_renderer extends ciplugins { + + // --------------------------------------------------------------- + // + // BELOW ARE METHODS FOR A SET SPECIFIC PLUGIN AND TYPE + // + // --------------------------------------------------------------- + + + // --------------------------------------------------------------- + // LANGUAGE TEXTS (needed in ui only) + // --------------------------------------------------------------- + + /** + * get a translated text from lang_XX.json in plugin dir; + * If the key is missed it returns "[KEY :: LANG]" + * + * @see setLang() + * @param string $sKey key to find in lang file + * @return string + */ + protected function _t($sKey){ + return (isset($this->_aLang[$sKey]) && $this->_aLang[$sKey]) + ? $this->_aLang[$sKey] + : "[ $sKey :: $this->_sLang ]" + ; + } + + /** + * set language for output of formdata and other texts. + * This method loads the language file into a hash. The output of + * translated texts can be done with $this->_t("your_key") + * + * @see _t() + * @param string $sLang language code, i.e. "de" + * @return boolean + */ + public function setLang($sLang=false){ + $this->_sLang=$sLang ? $sLang : $this->_sLang; + + $oReflection=new ReflectionClass($this); + $sFile=dirname($oReflection->getFileName()) . '/lang_'.$this->_sLang.'.json'; + $this->_aLang=(file_exists($sFile)) ? json_decode(file_get_contents($sFile), 1) : $this->_aLang; + return true; + } + // --------------------------------------------------------------- + // SETTER + // --------------------------------------------------------------- + + + // --------------------------------------------------------------- + // prepare html code + // --------------------------------------------------------------- + + /** + * for shellcmd plugins: get html code to load javascript file + * The file must exist in the plugin directory + * @params string $sFile (basename of) filename, eg. render.js + */ + public function getHtmlLoadScript($sFile){ + return (file_exists($this->_sSelfdir.'/'.$sFile)) + ? '<script src="'.$this->_sSelfurl.'/'.$sFile.'"></script>'."\n" + : '' + ; + } + + /** + * get id for an output div + * @return string + */ + public function getHtmlOutId(){ + return $this->_sPluginname ? 'divPlugin'.$this->_sType.''.$this->_sPluginname : false; + } + /** + * get id for the wrapper div of an output div + * @return string + */ + public function getHtmlOutIdWrapper(){ + return $this->getHtmlOutId().'Wrapper'; + } + public function getHtmlOutwindow(){ + $aConfig=$this->getPluginConfig(); + return '<div id="'.$this->getHtmlOutIdWrapper().'" class="cmdoutbox draggable draggable-onpage">' + // .'<button class="btn-close float-right">X</button>' + .'<div class="header"><i class="'.$aConfig['icon'].'"></i> '.$this->_sPluginname.'</div>' + .'<div id="'.$this->getHtmlOutId().'" ' + .'class="out' + .(isset($aConfig['window-cols']) && $aConfig['window-cols'] ? ' cmd-cols-'.$aConfig['window-cols'] : '' ) + .(isset($aConfig['window-lines']) && $aConfig['window-lines'] ? ' cmd-lines-'.$aConfig['window-lines'] : '' ) + .'"></div>' + .'</div>'; + } + + // --------------------------------------------------------------- + // access plugin php class + // --------------------------------------------------------------- + + +} diff --git a/public_html/deployment/inc_functions.php b/public_html/deployment/inc_functions.php index aa27cdd8..3ec77ca9 100644 --- a/public_html/deployment/inc_functions.php +++ b/public_html/deployment/inc_functions.php @@ -196,7 +196,7 @@ function aGotop($sClass = "scroll-link btn btn-default") { * @global type $aParams * @return type */ -function getTopArea() { +function getTopArea($aEmbed=[]) { global $aParams, $oHtml; $sReturn = ''; require_once("./classes/project.class.php"); @@ -391,6 +391,10 @@ function getTopArea() { </ul> <ul class="nav navbar-nav navbar-right">' + .(isset($aEmbed['right']) + ? $aEmbed['right'] + : '' + ) . ($oUser->getUsername() ? ' <!-- userdata --> diff --git a/public_html/deployment/index.php b/public_html/deployment/index.php index c9a84309..c6d7c2f1 100644 --- a/public_html/deployment/index.php +++ b/public_html/deployment/index.php @@ -23,6 +23,7 @@ ini_set('display_startup_errors', 1); error_reporting(E_ALL); require_once("./classes/page.class.php"); +require_once("./classes/plugins_renderer.class.php"); // detect first run $bFirstRun=!file_exists("../../config/config_custom.php") || !file_exists("../../config/inc_user2roles.php"); @@ -40,7 +41,9 @@ $oHtml=new htmlguielements(); $sPrj = ""; $sAction = "overview"; -// ------ check parameters +// ---------------------------------------------------------------------- +// check params +// ---------------------------------------------------------------------- if (array_key_exists("prj", $aParams)) { $sPrj = $aParams["prj"]; @@ -60,15 +63,55 @@ if($bFirstRun){ $sAction='installer'; } -// ------ Ausgabe -$sHeader = '<style>'; +// ---------------------------------------------------------------------- +// html header +// ---------------------------------------------------------------------- + +$sHeader = "\n<!-- generated CSS for phases -->\n<style>\n"; foreach ($aConfig["phases"] as $sPhase => $aData) { $sHeader.=array_key_exists("bgdark", $aData["css"]) ? 'th.' . $sPhase . '{' . $aData["css"]["bgdark"] . '}' : ''; $sHeader.=array_key_exists("bglight", $aData["css"]) ? 'td.' . $sPhase . ', div.' . $sPhase . '{' . $aData["css"]["bglight"] . '}' : ''; $sHeader.=array_key_exists("bgbutton", $aData["css"]) ? 'a.' . $sPhase . ',a.' . $sPhase . ':hover,button.' . $sPhase . ',button.' . $sPhase . ':hover{' . $aData["css"]["bgbutton"] . '}' : ''; } -$sHeader.='</style>'; -$sTopArea=getTopArea(); +$sHeader.="</style>\n"; + +// add shellcmd files +$sShellOuptut=''; +$sTopRight=''; + +$CI_plugins=new plugin_renderer(); +$CI_plugins->setType('shellcmd'); +$aPluginsShellcmd=$CI_plugins->getPlugins(); +$sHeader = "\n<!-- shellcmd plugins :: js files -->\n"; +$sHeader.='<script src="/deployment/js/ubd.class.js"></script>'."\n" + .'<script src="/deployment/js/addi.js"></script>'."\n" + ; +foreach ($CI_plugins->getPlugins('shellcmd') as $sPlugin){ + if ($CI_plugins->testPlugin($sPlugin)){ + $aPluginConfig=$CI_plugins->getPluginConfig(); + $sHeader.=$CI_plugins->getHtmlLoadScript('render.js'); + $sShellOuptut.=$CI_plugins->getHtmlOutwindow(); + $sTopRight.='' + .'<li >' + .$oHtml->getLink(array( + 'href'=>'#', + // 'onclick'=>'$(\'#'.$CI_plugins->getHtmlOutIdWrapper().'\').slideToggle(100);', + 'onclick'=>'toggleShellWindow(\''.$CI_plugins->getHtmlOutIdWrapper().'\', this);', + 'role'=>'button', + 'aria-expanded'=>'false', + 'icon'=> (isset($aPluginConfig['icon']) ? $aPluginConfig['icon'] : ''), + 'label'=>$sPlugin, + )) + .'</li>' + ; + } +} + +// ---------------------------------------------------------------------- +// html body +// ---------------------------------------------------------------------- + +$sTopArea=getTopArea(['right'=>$sTopRight]); $sBanner=isset($aConfig['banner']) && $aConfig['banner'] ? '<div class="alert alert-info">'.$aConfig['banner'].'</div>' : ''; $sTopAction=getAction(); @@ -109,11 +152,18 @@ if ($oUser->hasPermission('page_'.$sAction)){ // return false; } +// ---------------------------------------------------------------------- +// render page +// ---------------------------------------------------------------------- + $oCLog->add("Finally: rendering page ..."); $sPhpOut = ' <br> - ' . $sTopArea .' + ' + . $sTopArea + . $sShellOuptut + .' <div id="content"> ' . $sBanner . $sTopAction . ' ' . $sPhpOut . ' @@ -122,11 +172,8 @@ $sPhpOut = ' '.t("menu-brand").' © 2013-' . date("Y") . ' <a href="https://git-repo.iml.unibe.ch/iml-open-source/imldeployment/" target="_blank">Institut für Medizinische Lehre; Universität Bern</a> </div> - <!-- - <script src="/deployment/plugins/shellcmd/load/render.js" /> - --> - - '.$oCLog->render(); + ' + .$oCLog->render(); $oPage = new Page(); $oPage->addResponseHeader("Pragma: no-cache"); diff --git a/public_html/deployment/js/addi.js b/public_html/deployment/js/addi.js new file mode 100644 index 00000000..102226a0 --- /dev/null +++ b/public_html/deployment/js/addi.js @@ -0,0 +1,410 @@ +/** + * + * ADDI = Axels Drag and drop implementation + * + * create draggable divs on screen if they have a defined class + * (named "draggable" by default - but you can use any other name) + * + * @author Axel Hahn + * @version 1.0 + * + * @this addi + * + * @example + * <pre> + * // make all divs with class "draggable" be movable on screen<br> + * addi.init(); + * </pre> + * + * @constructor + * @return nothing + */ + var addi = function(){ + + return { + _saveData: [], + _dragClass: 'draggable', + _draggingClass: 'isdragging', + + // last z-index value of last activated div + _addi_zIndex: 100, + + // fixed rectangle earea whe a div can be moved + oFence: { + bFullscreen: true, + top: 0, + left: 0, + width: window.innerWidth, + height: window.innerHeight + }, + + // override existing style values while moving the div + _savstyles:{ + transition: 'auto' + }, + + /** + * detect all draggable objects on a page and init each + * + * @see initDiv() + * @param {string} sClass optional: class of draggable elements; default: "draggable" + * @returns {undefined} + */ + init(sClass){ + if(sClass){ + this._dragClass=sClass; + } + // scan all elements with class draggable and make them movable + var oList = document.getElementsByClassName(this._dragClass); + if(oList && oList.length){ + for (var i = 0; i < oList.length; i++) { + this.initDiv(oList[i], false); + } + } + }, + // ------------------------------------------------------------ + // private functions + // ------------------------------------------------------------ + /** + * get a top left position {xpos, ypos} to fix current position and + * make a div fully visible + * @see move() + * + * @private + * @param {object} oDiv2Drag movable object + * @param {type} xpos + * @param {type} ypos + * @returns {object} + */ + _fixVisiblePosition(oDiv2Drag,xpos,ypos){ + + this._updateFence(oDiv2Drag.style.paddingLeft); + var aStyles = window.getComputedStyle(oDiv2Drag); + + var divDeltaX=0 + + parseInt(aStyles.borderLeftWidth) + + parseInt(aStyles.borderRightWidth) + + parseInt(aStyles.marginLeft) + + parseInt(aStyles.marginRight) + + parseInt(aStyles.paddingLeft) + + parseInt(aStyles.paddingRight) + - (window.innerWidth - document.documentElement.clientWidth) // scrollbar + ; + var divDeltaY=0 + + parseInt(aStyles.borderTopWidth) + + parseInt(aStyles.borderBottomWidth) + + parseInt(aStyles.marginTop) + + parseInt(aStyles.marginBottom) + // + parseInt(aStyles.paddingTop) + //+ parseInt(aStyles.paddingBottom) + ; + + xpos=Math.max(this.oFence.left,xpos); + xpos=Math.min(this.oFence.left+this.oFence.width-oDiv2Drag.clientWidth-divDeltaX,xpos); + + ypos=Math.max(this.oFence.top,ypos); + if (aStyles.position==='fixed') { + ypos=Math.min(this.oFence.top+this.oFence.height-oDiv2Drag.clientHeight-divDeltaY,ypos); + } + + return { + xpos: xpos, + ypos: ypos + }; + + }, + /** + * helper: get a varname for localstorage to save / get last position + * + * @private + * @param {string} s id of movable element + * @returns {String} + */ + _getVarname(s){ + return 'addi.saveddiv-'+s; + }, + /** + * save position of the given dom object + * + * @private + * @param {object} oDiv2Drag movable object + * @returns {undefined} + */ + _save(oDiv2Drag){ + aData={ + left: oDiv2Drag.style.left.replace('px',''), + top: oDiv2Drag.style.top.replace('px','') + }; + localStorage.setItem(this._getVarname(oDiv2Drag.id),JSON.stringify(aData)); + }, + + /** + * helper: save attributes in a variable: transition + * it is used for dragging: a transition slows down all movements + * @see _styleRestore() + * + * @private + * @param {object} oDiv2Drag movable object + * @returns {Boolean} + */ + _styleSave(oDiv2Drag){ + var aStyles = window.getComputedStyle(oDiv2Drag); + // create subitem for div id + if(this._saveData[oDiv2Drag.id] === undefined){ + this._saveData[oDiv2Drag.id]=new Object(); + }; + for (var sAttr in this._savstyles){ + // store value + this._saveData[oDiv2Drag.id][sAttr]=aStyles.getPropertyValue(sAttr); + // apply temp value + oDiv2Drag.style[sAttr]=this._savstyles[sAttr]; + } + return true; + }, + /** + * helper: retore attributes after moving + * @see _styleSave() + * @private + * @param {object} oDiv2Drag movable object + * @returns {Boolean} + */ + _styleRestore(oDiv2Drag){ + for (var sAttr in this._savstyles){ + oDiv2Drag.style[sAttr]=this._saveData[oDiv2Drag.id][sAttr]; + } + return true; + }, + /** + * helper: update size of this.oFence if it is set to fullscreen in + * case of resizing of the browser window + * + * @private + * @returns {undefined} + */ + _updateFence(){ + if(this.oFence.bFullscreen){ + this.oFence={ + bFullscreen: true, + top: 0, + left: 0, + width: window.innerWidth, + height: window.innerHeight + }; + }; + }, + + /** + * generate an id for a draggable div that has no id yet + * + * @private + * @returns {string} + */ + _generateId(){ + var sPrefix='generatedId-'; + var iCount=1; + while (document.getElementById(sPrefix+iCount)){ + iCount++; + } + return sPrefix+iCount; + }, + + // ------------------------------------------------------------ + // public functions + // ------------------------------------------------------------ + + /** + * make a single div movable: + * - add listener and + * - restore last saved position (if class "saveposition" exists) + * + * @param {object} oDiv2Drag movable object + * @param {object} oDiv2Click optional: clickable object + * @returns {undefined} + */ + initDiv(oDiv2Drag,oDiv2Click){ + var sDivId=oDiv2Drag.id; + if(!sDivId){ + sDivId=this._generateId(); + oDiv2Drag.id=sDivId; + } + + // force position: fixed + var aStyles = oDiv2Drag.currentStyle || window.getComputedStyle(oDiv2Drag); + + console.log(oDiv2Drag.id + ' position: ' + aStyles.position); + if(!aStyles.position || aStyles.position=='static' /* || aStyles.position!='fixed' */){ + // oDiv2Drag.style.position='fixed'; + } + + // add events + // using atributes instead of addEventListener + o=(oDiv2Click ? oDiv2Click : oDiv2Drag); + o.onmousedown = function (event) { + addi._isDragging=true; + addi.startMoving(document.getElementById(sDivId),event); + }; + o.onmouseup = function () { + addi._isDragging=false; + addi.stopMoving(document.getElementById(sDivId)); + }; + + // restore last position + if (oDiv2Drag.className.indexOf('saveposition')!==false){ + this.load(oDiv2Drag); + } + }, + /** + * load stored position and apply it to given dom object + * + * @param {object} oDiv2Drag movable object + * @returns {Array|Object|Boolean} + */ + load(oDiv2Drag){ + var id=this._getVarname(oDiv2Drag.id); + + // detect the highest z-index + var aStyles = oDiv2Drag.currentStyle || window.getComputedStyle(oDiv2Drag); + if(aStyles.zIndex && aStyles.zIndex>0){ + this._addi_zIndex=Math.max(parseInt(aStyles.zIndex), this._addi_zIndex); + } + oDiv2Drag.style.zIndex=this._addi_zIndex++; + + var aData=localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : false; + if(aData && aData.left && aData.top){ + this.move(oDiv2Drag,aData.left,aData.top, true); + } + return aData; + }, + /** + * move obj to new position and store position in localstorage + * called from startMoving() and load() + * + * @param {object} oDiv2Drag movable object + * @param {integer} xpos position x + * @param {integer} ypos position y + * @param {boolean} bNoFix flag: skip fixing the position based on window size (is set to true in the load method) + * @returns {undefined} + */ + move(oDiv2Drag,xpos,ypos, bNoFix){ + oDiv2Drag.style.bottom = 'auto'; + + var aPos=bNoFix ? { 'xpos':xpos, 'ypos':ypos } : this._fixVisiblePosition(oDiv2Drag,xpos,ypos); + oDiv2Drag.style.left = aPos['xpos'] + 'px'; + oDiv2Drag.style.top = aPos['ypos'] + 'px'; + this._save(oDiv2Drag); + return true; + }, + + /** + * called by onmousedown event + * + * @param {object} oDiv2Drag movable object + * @param {object} evt event + * @returns {undefined} + */ + startMoving(oDiv2Drag,evt){ + if (oDiv2Drag.className.indexOf(this._dragClass)===false){ + return false; + } + + evt = evt || window.event; + var posX = evt.clientX, + posY = evt.clientY; + + // save some styles + this._styleSave(oDiv2Drag); + + + if(oDiv2Drag.className.indexOf(this._draggingClass)<0){ + oDiv2Drag.className+= ' '+this._draggingClass; + } + + // for FF only: + // if (navigator.appCodeName==='Mozilla' && navigator.userAgent.indexOf('Firefox/')>0){ + document.body.style.userSelect='none'; + + iDivWidth = parseInt(oDiv2Drag.style.width), + iDivHeight = parseInt(oDiv2Drag.style.height); + + oDiv2Drag.style.cursor='move'; + oDiv2Drag.style.zIndex=this._addi_zIndex++; + + iDivLeft = oDiv2Drag.style.left ? oDiv2Drag.style.left.replace('px','') : oDiv2Drag.offsetLeft; + iDivTop = oDiv2Drag.style.top? oDiv2Drag.style.top.replace('px','') : oDiv2Drag.offsetTop; + var diffX = posX - iDivLeft, + diffY = posY - iDivTop; + document.onmousemove = function(evt){ + evt = evt || window.event; + var posX = evt.clientX, + posY = evt.clientY, + aX = posX - diffX, + aY = posY - diffY; + addi.move(oDiv2Drag,aX,aY); + }; + return true; + }, + /** + * called on mouse up event + * + * @param {object} oDiv2Drag movable object + * @returns {undefined} + */ + stopMoving(oDiv2Drag){ + oDiv2Drag.style.cursor='default'; + // retore styles + this._styleRestore(oDiv2Drag); + document.body.style.userSelect='auto'; + oDiv2Drag.className=oDiv2Drag.className.replace(' '+this._draggingClass, ''); + oDiv2Drag.className=oDiv2Drag.className.replace(this._draggingClass, ''); + + document.onmousemove = function(){}; + }, + /** + * reset position + * + * @param {bool} bRemoveLocalstorage flag: remove saved local variable too + * @returns {undefined} + */ + resetPos(){ + var oList = document.getElementsByClassName(this._dragClass); + if(oList && oList.length){ + for (var i = 0; i < oList.length; i++) { + oList[i].style = ''; + this._save(oList[i]); + } + } + }, + /** + * reset style, onmousedown, onmouseup to make divs unmovable again + * + * @param {bool} bRemoveLocalstorage flag: remove saved local variable too + * @returns {undefined} + */ + reset(bRemoveLocalstorage){ + // scan all elements with class draggable and reset + var oList = document.getElementsByClassName(this._dragClass); + if(oList && oList.length){ + for (var i = 0; i < oList.length; i++) { + this._resetDiv(oList[i],bRemoveLocalstorage); + } + } + }, + /** + * reset a single div and make it unmovable + * + * @private + * @param {object} oDiv2Drag movable object + * @param {bool} bRemoveLocalstorage flag: remove saved local variable too + * @returns {undefined} + */ + _resetDiv(oDiv2Drag, bRemoveLocalstorage){ + oDiv2Drag.onmousemove = null; + oDiv2Drag.onmouseup = null; + oDiv2Drag.onmousedown = null; + oDiv2Drag.style = ''; + if(bRemoveLocalstorage){ + localStorage.removeItem(this._getVarname(oDiv2Drag.id)); + } + } + }; +}(); diff --git a/public_html/deployment/js/functions.js b/public_html/deployment/js/functions.js index e95d9e44..a653e7bb 100644 --- a/public_html/deployment/js/functions.js +++ b/public_html/deployment/js/functions.js @@ -42,12 +42,23 @@ function hideModalMessage(){ return false; } +/** + * shellcmd plugin ... toggle output window + * @param {string} idWrapperDiv id of the wrapper + * @param {object} oLink a tag in the navbar with link for toggling window + */ +function toggleShellWindow(idWrapperDiv, oLink){ + $('#'+idWrapperDiv).slideToggle(100); + $(oLink).parent().toggleClass('active'); +} + // ---------------------------------------------------------------------- // general init in each page // ---------------------------------------------------------------------- $(document).ready(function() { initSoftscroll(); + addi.init(); // $(".optionName").popover({trigger: "hover"}); // $("#content").hide().fadeIn(300); }); diff --git a/public_html/deployment/js/ubd.class.js b/public_html/deployment/js/ubd.class.js new file mode 100644 index 00000000..63d102ab --- /dev/null +++ b/public_html/deployment/js/ubd.class.js @@ -0,0 +1,209 @@ +/** + * ====================================================================== + * + * U B D + * + * Url bound to a dom id + * + * ---------------------------------------------------------------------- + * + * This is a helper class to update the content of a dom object by a + * given Url after a ttl. + * + * ---------------------------------------------------------------------- + * 2022-06-26 www.axel-hahn.de first lines... + * 2022-09-19 do not update if domid is hidden using _iWaitIfHidden + * ====================================================================== + */ + +/** + * Url bound to a dom id + * @return class + */ +class ubd { + /** + * initialize data for a dom object + * @param {object} oConfig optional config object with those subkeys: + * domid - id of a dom object + * url - url to an api + * header - http request header data + * renderer - renderer function to visualize data + * ttl - ttl in sec (TODO) + * @returns {undefined} + */ + constructor(oConfig) { + + this._sDomId = ''; + this._oDomObject = false; + this._sUrl2Fetch = false; // static value or reference of a function + this._oHeader = {}; + this._sRenderfunction = false; + this._iTTL = false; + this._iWaitIfHidden = 500; + + this._oTimer = false; + + this._body = ''; + + oConfig = arguments ? arguments[0] : false; + + + if (oConfig) { + if (oConfig['domid']) { + this.setDomid(oConfig['domid']); + } + if (oConfig['url']) { + this.setUrl(oConfig['url']); + } + if (oConfig['header']) { + this.setHeaders(oConfig['header']); + } + if (oConfig['renderer']) { + this.setRenderfunction(oConfig['renderer']); + } + if (oConfig['ttl']) { + this.setTtl(oConfig['ttl']); + } + } + } + + // ---------------------------------------------------------------------- + // public SETTER for properties + // ---------------------------------------------------------------------- + + /** + * set domid that will by updated + * @param {string} sDomid if of a domobject + */ + setDomid(sDomid) { + if (document.getElementById(sDomid)) { + this._sDomId = sDomid; + this._oDomObject = document.getElementById(sDomid); + } else { + this._sDomId = false; + this._oDomObject = false; + console.error('ERROR: setDomid("' + sDomid + '") got an invalid string - this domid does not exist.'); + } + } + + /** + * set a rendering function that visualized data after a http request + * @param {string|function} oFunction reference to a function ... or false to disable rendering + */ + setRenderfunction (oFunction) { + this._sRenderfunction = oFunction; + } + + /** + * Set time to live in seconds + * @param {int} iTTL + */ + setTtl(iTTL) { + this._iTTL = iTTL / 1; + this.resetTimer(); + } + + /** + * set an url to be requested + * @param {string|function} sUrl static value or reference of a function + */ + setUrl(sUrl) { + this._sUrl2Fetch = sUrl; + } + + /** + * set header obejct for 2nd param in javascript fetch() function + * @param {object} oHeader + */ + setHeaders(oHeader) { + this._oHeader = oHeader; + } + + /** + * helper: dump current object instance to console + */ + dumpme() { + console.log('---------- DUMP ubd'); + console.log(this); + console.log('---------- /DUMP ubd'); + } + // ---------------------------------------------------------------------- + // public ACTIONS + // ---------------------------------------------------------------------- + /** + * show rendered html content into set domid using the render function. + * If no rendering function was set then the response will be written + * directly. + * You can override both by giving a parameter (a string with html) + * to write that one directly. It can be used to show an error message. + * + * TODO: + * other output places than innerHTML by detecting the tag e.g. + * to use input or textarea. + * + * @param {string} sHtml optional: htmlcode of an error message + */ + render(sHtml) { + let out = sHtml ? sHtml : + (this._sRenderfunction + ? this._sRenderfunction(this._body) + : this._body + ); + this._oDomObject.innerHTML = out; + } + + /** + * reset timer to update the content in dom id after reaching TTL + * used in setTtl + */ + resetTimer() { + clearTimeout(this._oTimer); + // clearInterval(this._oTimer); + if (this._iTTL) { + var bIsVisble=this._oDomObject.offsetParent !== null; + var iTtl=bIsVisble ? this._iTTL * 1000 : this._iWaitIfHidden; + let self = this; + self._oTimer = window.setTimeout(function () { self.update(); }, iTtl); + // self._oTimer=window.setInterval(self.update, this._iTTL*1000); + } + } + + /** + * make http request and call the renderer + */ + async update() { + var bIsVisble=this._oDomObject.offsetParent !== null; + + let self = this; + let url = (typeof this._sUrl2Fetch == "function") ? this._sUrl2Fetch() : this._sUrl2Fetch; + if (url == undefined) { + console.error("SKIP update - there is no url in this object instance (anymore) :-/"); + this.dumpme(); + return 0; + } + if(bIsVisble){ + try { + let response = await fetch(url, this._oHeader); + if (response.ok) { + this._body = await response.json(); + + this.render(); + } else { + this.render('<div class="app result1">' + + 'ERROR ' + response.status + ': ' + response.statusText + ' - ' + + url + + '</div>'); + } + } catch (e) { + this.render('<div class="app result1">' + + 'UNKNOWN: no response from ' + + this._sUrl2Fetch + + '</div>'); + console.error(e); + } + } + this.resetTimer(); + } + + + } \ No newline at end of file diff --git a/public_html/deployment/main.css b/public_html/deployment/main.css index ced1a280..3a2fb3dc 100644 --- a/public_html/deployment/main.css +++ b/public_html/deployment/main.css @@ -282,10 +282,41 @@ input[type="radio"]:checked+label, input[type="checkbox"]:checked+label{ .tab-pane p {margin: 1em 10px;} +/* ----- plugins ----- */ +.draggable-onpage{position: absolute;} +.draggable-onscreen{position: fixed;} +.isdragging{opacity: 0.9;} + +.cmdoutbox{background: rgba(0,0,0,0.9); color: #f0f0f0; box-shadow: 0.2em 0.2em 0.5em rgba(0,0,0,0.4); display: none;} +.cmdoutbox .header{background:#628;padding: 0.1em 0.5em;} +.cmdoutbox .out{color:#ccf; ;padding: 0.5em; font-family: monospace; overflow: scroll; white-space: nowrap; } +.cmdoutbox button.btn-close{background:#e55; color: #fff; border: none;} + +.cmdoutbox .bar{background: rgba(255,255,255,0.1);} +.cmdoutbox .progress{background: #85a;} + +.float-right{float: right;} + +.cmd-lines-10{height: 10em; max-height: 10em;} +.cmd-lines-20{height: 20em; max-height: 20em;} +.cmd-lines-25{height: 25em; max-height: 25em;} +.cmd-lines-30{height: 30em; max-height: 30em;} +.cmd-lines-35{height: 35em; max-height: 35em;} + +.cmd-cols-10{width: 10em; max-width: 10em;} +.cmd-cols-20{width: 20em; max-width: 20em;} +.cmd-cols-40{width: 40em; max-width: 40em;} +.cmd-cols-60{width: 60em; max-width: 60em;} +.cmd-cols-80{width: 80em; max-width: 80em;} +.cmd-cols-100{width: 100em; max-width: 100em;} + /* ----- visualized process ----- */ .visualprocess{float: left; padding: 1em; box-shadow: 0 0 0em #ddd;} .visualprocess .process{float:left; text-align: center; padding: 0 0 5px; } -.visualprocess .process.box{border: 2px dashed #ddd; padding: 1em; min-height: 25em;} +.visualprocess .process.box{border: 80 dashed #ddd; 80ding: 1em; min-80m;} ng: 1em; min-25em;} + +.visualprocess .process.box{border: 80 dashed #ddd; 80ding: 1em; min-80m;} ng: 1em; min-25em;} +.visualprocess .process img{} .visualprocess .process img{} .visualprocess .action{float:left;padding: 3em 1em 1em 1em; background: #fff;} .visualprocess .process .title{margin-bottom: 2em; font-weight: bold; font-size: 150%; color:#aaa;} diff --git a/public_html/deployment/plugins/build/tgz/info.json b/public_html/deployment/plugins/build/tgz/info.json index dfff108b..c158b6ef 100644 --- a/public_html/deployment/plugins/build/tgz/info.json +++ b/public_html/deployment/plugins/build/tgz/info.json @@ -4,6 +4,7 @@ "author": "Axel Hahn; University of Bern; Institute for Medical education", "version": "1.0", + "date": "2022-09-20", "url": "[included]", "license": "GNU GPL 3.0", diff --git a/public_html/deployment/plugins/build/zip/info.json b/public_html/deployment/plugins/build/zip/info.json index 0d4f6b9a..3357e801 100644 --- a/public_html/deployment/plugins/build/zip/info.json +++ b/public_html/deployment/plugins/build/zip/info.json @@ -4,6 +4,7 @@ "author": "Axel Hahn; University of Bern; Institute for Medical education", "version": "1.0", + "date": "2022-09-20", "url": "[included]", "license": "GNU GPL 3.0", diff --git a/public_html/deployment/plugins/doc_reminder.md b/public_html/deployment/plugins/doc_reminder.md new file mode 100644 index 00000000..30fe339d --- /dev/null +++ b/public_html/deployment/plugins/doc_reminder.md @@ -0,0 +1,55 @@ +# CI server Plugins + +## Structure + +The plugin base dir is `public_html/deployment/plugins` + +Subdirs are the different plugin types + +```txt +build/ +rollout/ +shellcmd/ +``` + +In the 2nd level is 1 subdir per plugin. + +```txt +. +├── build +│ ├── tgz +│ │ ├── build_tgz.php +│ │ ├── info.json +│ │ ├── lang_de-de.json +│ │ └── lang_en-en.json +│ └── zip +│ ├── build_zip.php +│ ├── info.json +│ ├── lang_de-de.json +│ └── lang_en-en.json +├── rollout +: ... +└── shellcmd + ... +``` + +Bisher hatte ich für Build und Rollout je 1 Klasse gehabt. +Shellcmd Plugins kommen gerade dazu. + +Alle sollen von 1 Plugin-Klasse handhabbar sein - und einer Plugin-Renderere-Klasse. + +## Files in a plugin folder + +* **info.json** - meta data with plugin infos +* **plugin.php** - plugin class +* **config.json** - configuration data +* **render.js** - javascript file to load +* **lang_*.js** - language specific texts + +## Config + +For plugins that can handle project data there is an override mechanism: + +* global configuration data are in the ci server config +* the project setting can override / extend the global settings +* each phase can override/ extend the project settings diff --git a/public_html/deployment/plugins/shellcmd/getdata.php b/public_html/deployment/plugins/shellcmd/getdata.php index 12b260e8..fcfd2ff0 100644 --- a/public_html/deployment/plugins/shellcmd/getdata.php +++ b/public_html/deployment/plugins/shellcmd/getdata.php @@ -1,6 +1,11 @@ <?php /* * script to be used as ajax poll request to get current status of an action + * + * WIP + * + * Example: + * http://localhost:8002/deployment/plugins/shellcmd/getdata.php?plugin=load */ header('Content-Type: application/json'); @@ -8,132 +13,3 @@ require_once('plugins_shellcmd.class.php'); $oShell=new shellcmd(); $oShell->sendResponse(); - -/* - -// width of load=1 -$iMaxWidth=100; - -$sPlugin=isset($_GET['plugin']) && $_GET['plugin'] ? preg_replace('/^a-z0-9/', '', $_GET['plugin']) : false; - -// ---------------------------------------------------------------------- -// ---------------------------------------------------------------------- -function execCommand($sCmd){ - echo "DEBUG: ".__FUNCTION__ . "($sCmd)<br>"; - exec($sCmd, $aOut, $iResult); - return [ - 'command'=>$sCmd, - 'exitcode'=>$iResult, - 'output'=>$aOut, - ]; -} - - -if (!$sPlugin){ - echo "DEBUG: Missing param for a plugin.<br>"; - return [ 'error' => 'Missing param for a plugin.' ]; -} - -$sPluginfile=$sPlugin.'/command.php'; -$sPluginclass='shellplugin_'.$sPlugin; - -echo "DEBUG: sPluginfile=$sPluginfile<br>"; - -if (!file_exists($sPluginfile)){ - echo "DEBUG: Plugin seems to be corrupt. File not found: '. $sPluginfile.'<br>"; - return [ 'error' => 'Plugin seems to be corrupt. File not found: '. $sPluginfile ]; -} - - -include($sPluginfile); - -$oPlugin=new $sPluginclass(); - -echo "DEBUG: sCmd=$sCmd<br>"; - -$aResult=execCommand($sCmd); -if (function_exists("parsedata")){ - $aResult=parsedata($aResult); -} - -echo '<pre>$aResult = '.print_r($aResult, 1).'<pre>'; -return $aResult; - - -// ---------------------------------------------------------------------- -function getLoad(){ - $sCmd='uptime'; - $aResult=execCommand($sCmd); - $aTmp1=explode(',', $aResult['output'][0]); - $aResult['data']=[ - 'uptime'=>$aTmp1[0], - 'users'=>$aTmp1[1], - 'load'=>preg_replace('/^.*:/', '', $aTmp1[2]), - 'load5'=>$aTmp1[3], - 'load15'=>$aTmp1[4], - ]; - return $aResult; -} - -function getProcesses($sFilter){ - $sRegex=$sFilter ? $sFilter : 'SomethingThatWontMatchInProcessList'; - $sCmd="ps -f --forest | egrep -v '($sRegex)' | fgrep -v 'ps -f' | fgrep -v grep"; - return execCommand($sCmd); -} - - -// ---------------------------------------------------------------------- -// ---------------------------------------------------------------------- -function load(){ - global $iMaxWidth; - $sReturn=''; - - $aData=getLoad()['data']; - - $iMaxLoad=round(max($aData['load'], $aData['load5'], $aData['load15']))+1; - $iScale=$iMaxWidth/$iMaxLoad; - - $sScalaPart=str_repeat('_', (round($iScale)-1)).'|'; - $sScala=str_repeat($sScalaPart, $iMaxLoad).$iMaxLoad; - - - $sBar1=str_repeat('#', (int)($aData['load']*$iScale)); - $sBar5=str_repeat(' ', (int)($aData['load5']*$iScale - 1)).'|'; - $sBar15=str_repeat(' ', (int)($aData['load15']*$iScale - 1)).'^'; - - $sReturn.= 'LOAD '.$aData['load'].' .. '.$aData['load5'].' .. '.$aData['load15'].'<br>' - . $sScala.'<br>' - . substr($sBar1, 0, $iMaxWidth).'<br>' - . substr($sBar5, 0, $iMaxWidth).'<br>' - . substr($sBar15, 0, $iMaxWidth).'<br>' - ; - - return $sReturn; -} - - -function processes(){ - - return 'PROCESSES:<br>' - .shell_exec("ps -f --forest | egrep -v '(apache|httpd)' | fgrep -v 'ps -f' | fgrep -v grep") - .'<hr>all processes:<br>' - .shell_exec("ps -f --forest") - ; -} - -// ---------------------------------------------------------------------- -// MAIN -// ---------------------------------------------------------------------- - -$sRemove='apache|httpd|php-fpm'; - -echo '<pre>' - . load() - // .'<hr size="1">' - - .'<hr size="1">' - .'API DATA' - .'<pre>getLoad()='.print_r(getLoad(), 1).'</pre>' - .'<pre>getProcesses(\''.$sRemove.'\')='.print_r(getProcesses($sRemove), 1).'</pre>' - ; -*/ \ No newline at end of file diff --git a/public_html/deployment/plugins/shellcmd/load/config.json b/public_html/deployment/plugins/shellcmd/load/config.json new file mode 100644 index 00000000..63c956c5 --- /dev/null +++ b/public_html/deployment/plugins/shellcmd/load/config.json @@ -0,0 +1,7 @@ +{ + "command": "uptime", + "icon": "fas fa-tachometer-alt", + "interval": 2000, + "window-cols": 20, + "window-lines": false +} \ No newline at end of file diff --git a/public_html/deployment/plugins/shellcmd/load/info.json b/public_html/deployment/plugins/shellcmd/load/info.json new file mode 100644 index 00000000..5d4dd8c4 --- /dev/null +++ b/public_html/deployment/plugins/shellcmd/load/info.json @@ -0,0 +1,11 @@ +{ + "name": "Load", + "description": "Show system load", + "author": "Axel Hahn; University of Bern; Institute for Medical education", + + "version": "1.0", + "date": "2022-09-20", + "url": "[included]", + "license": "GNU GPL 3.0" +} + diff --git a/public_html/deployment/plugins/shellcmd/load/plugin.php b/public_html/deployment/plugins/shellcmd/load/plugin.php index ec933804..0188143f 100644 --- a/public_html/deployment/plugins/shellcmd/load/plugin.php +++ b/public_html/deployment/plugins/shellcmd/load/plugin.php @@ -20,7 +20,13 @@ class shellcmd_load { public function __constructor(){ return $this->getCommand(); } - + /** + * get column width + * @return string + */ + public function getColumns(){ + return false; + } /** * get command * @return string @@ -38,7 +44,11 @@ class shellcmd_load { $aTmp1=array_reverse(explode(',', $aResult['output'][0])); // print_r($aTmp1); $aResult['data']=[ - 'uptime'=>trim($aTmp1[4]), + 'uptime'=>(isset($aTmp1[5]) + ? trim(substr($aTmp1[5], 10) . $aTmp1[4]) + : trim(substr($aTmp1[4], 10)) + ).' h' + , 'users'=>trim(str_replace(' users', '', $aTmp1[3])), 'load'=>trim(preg_replace('/^.*:/', '', $aTmp1[2])), 'load5'=>trim($aTmp1[1]), diff --git a/public_html/deployment/plugins/shellcmd/load/render.js b/public_html/deployment/plugins/shellcmd/load/render.js index f4d31060..4bb3ddd4 100644 --- a/public_html/deployment/plugins/shellcmd/load/render.js +++ b/public_html/deployment/plugins/shellcmd/load/render.js @@ -1,7 +1,42 @@ +/** + * + * @param {array} aData + * @returns + */ + +var _iMaxLoad=5; + +/** + * callback function for ubd: render html code + * @param {array} aData json object + * @returns {string} + */ function load_render(aData){ var sReturn=''; - sReturn+=aData['command']+"<br>" - + "Load: "+aData["data"]['load']+"<br>" + + var iWidth=Math.round(aData["data"]['load']/_iMaxLoad*100); + iWidth=(iWidth>100) ? 100 : iWidth; + var sBar='<div class="bar"><div class="progress" style="width: '+iWidth+'%;"></div></div>'; + + return "" + +'<span style="float: right">'+aData['time']+'</span>' + + "Load: "+aData["data"]['load'] + '<br>'// +" /"+aData["data"]['load5']+"<br>" + + sBar + + aData["data"]['uptime'] ; - return sReturn; -} \ No newline at end of file +} + +function load_init(){ + oUbdLoad=new ubd( + { + "domid": "divPluginshellcmdload", + "url": "/deployment/plugins/shellcmd/getdata.php?plugin=load", + "renderer": load_render, + "ttl": 2, + } + ); + // oUbdLoad.render('waiting for first data...'); + oUbdLoad.update(); +} + +window.setTimeout("load_init()", 200); \ No newline at end of file diff --git a/public_html/deployment/plugins/shellcmd/plugins_shellcmd.class.php b/public_html/deployment/plugins/shellcmd/plugins_shellcmd.class.php index 938c8c57..747d07f1 100644 --- a/public_html/deployment/plugins/shellcmd/plugins_shellcmd.class.php +++ b/public_html/deployment/plugins/shellcmd/plugins_shellcmd.class.php @@ -1,4 +1,12 @@ <?php +/** + * GENERAL CLASS TO FETCH DATA FROM A SHELL COMMAND + * FOR THE WEB UI + * + * Used in ./getdata.php + * + * TODO: replace this class with classes/plugins.class.php + */ class shellcmd { @@ -63,10 +71,11 @@ class shellcmd { * @return */ protected function _execCommand($sCmd){ - exec($sCmd, $aOut, $iResult); + exec("$sCmd", $aOut, $iResult); return [ 'command'=>$sCmd, 'exitcode'=>$iResult, + 'time'=>date("H:i:s"), 'output'=>$aOut, ]; } diff --git a/public_html/deployment/plugins/shellcmd/processes/command.php b/public_html/deployment/plugins/shellcmd/processes/command.php deleted file mode 100644 index e69de29b..00000000 diff --git a/public_html/deployment/plugins/shellcmd/processes/config.json b/public_html/deployment/plugins/shellcmd/processes/config.json new file mode 100644 index 00000000..f95219d5 --- /dev/null +++ b/public_html/deployment/plugins/shellcmd/processes/config.json @@ -0,0 +1,7 @@ +{ + "command": "ps -f --forest | egrep -v '[/\\ ](apache|httpd|php-fpm)' | fgrep -v 'ps -f' | fgrep -v grep", + "icon": "far fa-list-alt", + "interval": 2000, + "window-cols": 80, + "window-lines": 30 +} \ No newline at end of file diff --git a/public_html/deployment/plugins/shellcmd/processes/info.json b/public_html/deployment/plugins/shellcmd/processes/info.json new file mode 100644 index 00000000..586c7af7 --- /dev/null +++ b/public_html/deployment/plugins/shellcmd/processes/info.json @@ -0,0 +1,11 @@ +{ + "name": "Processes", + "description": "Show processes of the ci server", + "author": "Axel Hahn; University of Bern; Institute for Medical education", + + "version": "1.0", + "date": "2022-09-20", + "url": "[included]", + "license": "GNU GPL 3.0" +} + diff --git a/public_html/deployment/plugins/shellcmd/processes/plugin.php b/public_html/deployment/plugins/shellcmd/processes/plugin.php new file mode 100644 index 00000000..ae008a51 --- /dev/null +++ b/public_html/deployment/plugins/shellcmd/processes/plugin.php @@ -0,0 +1,73 @@ +<?php +/** + * + * SHELLCMD PLUGIN :: Processes + * + * ---------------------------------------------------------------------- + * 2022-09-19 axel.hahn@iml.unibe.ch + */ +class shellcmd_processes { + /** + * @var command line to exectute + */ + protected $_command="ps -f --forest | egrep -v '[/\ ](apache|httpd|php-fpm)' | fgrep -v 'ps -f' | fgrep -v grep"; + // protected $_command="ps -ef --forest "; + + /** + * constructor ... returns command + * @return string + */ + public function __constructor(){ + return $this->getCommand(); + } + + /** + * get command + * @return string + */ + public function getCommand(){ + return $this->_command; + } + + /** + * parse output and extract wanted values in section "data" + * @return array + */ + public function parsedata($aResult){ + $aResult['data']=[ + 'count'=>count($aResult['output'])-1, + ]; + return $aResult; + } + +} +/* + +EXAMPLE OUTPUT + +{ + "command": "ps -ef --forest ", + "exitcode": 0, + "time": "12:02:47", + "output": [ + "UID PID PPID C STIME TTY TIME CMD", + "www-data 1 0 0 11:21 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 17 1 0 11:21 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 20 1 0 11:21 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 21 1 0 11:21 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 24 1 0 11:21 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 270 1 0 11:27 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 2929 270 0 12:02 ? 00:00:00 \\_ sh -c ps -ef --forest", + "www-data 2930 2929 0 12:02 ? 00:00:00 \\_ ps -ef --forest", + "www-data 278 1 0 11:27 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 279 1 0 11:27 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 2569 1 0 11:59 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 2570 1 0 11:59 ? 00:00:00 apache2 -DFOREGROUND", + "www-data 2571 1 0 11:59 ? 00:00:00 apache2 -DFOREGROUND" + ], + "data": { + "count": 13 + } +} + +*/ \ No newline at end of file diff --git a/public_html/deployment/plugins/shellcmd/processes/render.js b/public_html/deployment/plugins/shellcmd/processes/render.js new file mode 100644 index 00000000..ae6d2d96 --- /dev/null +++ b/public_html/deployment/plugins/shellcmd/processes/render.js @@ -0,0 +1,42 @@ +/** + * + * @param {array} aData + * @returns + */ + +var _iMaxLoad=5; + +/** + * callback function for ubd: render html code + * @param {array} aData json object + * @returns {string} + */ +function processes_render(aData){ + var sReturn=''; + sReturn='<span style="float: right">'+aData['time']+'</span>'; // +' '+aData['command']+'</span>'; + if(aData['data']['count']==0){ + sReturn+='No activity<br><br>Command:<br>' + +aData['command']; + } else { + for(var i=0; i<aData['output'].length; i++){ + // sReturn+='<div>'+aData['output'][i]+"</div>"; + sReturn+=aData['output'][i]+"<br>"; + } + } + return sReturn; +} + +function processes_init(){ + oUbdProcesses=new ubd( + { + "domid": "divPluginshellcmdprocesses", + "url": "/deployment/plugins/shellcmd/getdata.php?plugin=processes", + "renderer": processes_render, + "ttl": 2, + } + ); + // oUbdLoad.render('waiting for first data...'); + oUbdProcesses.update(); +} + +window.setTimeout("processes_init()", 200); \ No newline at end of file -- GitLab