diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php index 9f6e0580aaa9802c4f4358ac21102832bffee178..b49548023628bfa707d0d92b143e4236772ada05 100644 --- a/classes/cronlog-renderer.class.php +++ b/classes/cronlog-renderer.class.php @@ -244,11 +244,13 @@ class cronlogrenderer extends cronlog{ } $sReturn=''; $sServer=isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : false; + $sReturn.='<li class="nav-item d-none d-sm-inline-block"><a href="#" class="nav-link">Instances:</a></li>'; foreach($this->_aInstances as $sInstance => $sUrl){ $sHost=parse_url($sUrl, PHP_URL_HOST); - $sClass=($sServer && $sServer==$sHost) ? 'active' : ''; - $sReturn.='<a class="'.$sClass.'" href="'.$sUrl.'" title="'.$sUrl.'">'.$sInstance.'</a> '; + $sClass=($sServer && $sServer==$sHost) ? 'active bg-gray' : ''; + // $sReturn.='<a class="'.$sClass.'" href="'.$sUrl.'" title="'.$sUrl.'">'.$sInstance.'</a> '; + $sReturn.='<li class="nav-item d-none d-sm-inline-block '.$sClass.'"><a href="'.$sUrl.'" class="nav-link">'.$sInstance.'</a></li>'; } return $sReturn; } @@ -494,7 +496,7 @@ class cronlogrenderer extends cronlog{ */ public function renderLogfile($sLogfile){ $sHtml='' - . '<button style="position: fixed;" onclick="closeOverlay();"><i class="fas fa-chevron-left"></i> back</button><br><br>' + . '<button style="position: fixed;" onclick="closeOverlay();" class="btn btn-default"><i class="fas fa-chevron-left"></i> back</button><br><br>' . '<h3>Logfile '.basename($sLogfile).'</h3>' ; if(!$sLogfile){ @@ -561,11 +563,14 @@ class cronlogrenderer extends cronlog{ .'>'.$sServer.'</option>'; } $sHtml=$sHtml - ? '<input id="serverfiltertext" type="text" placeholder="filter server" value="" + ? '' + /* + .'<input id="serverfiltertext" type="text" placeholder="filter server" value="" onchange="filterServers();" onkeypress="filterServers();" onkeyup="filterServers();" ><button onclick="$(\'#serverfiltertext\').val(\'\'); filterServers();">X</button><br><br>' + */ .'<select' . ' size="'.( min(array(count($this->getServers())+1 , $iMaxItemsToShow)) ).'"' // . ' size="1"' diff --git a/classes/htmlelements.class.php b/classes/htmlelements.class.php new file mode 100755 index 0000000000000000000000000000000000000000..91943c7c97d4a3226b6a2c457496ed85b08ef447 --- /dev/null +++ b/classes/htmlelements.class.php @@ -0,0 +1,222 @@ +<?php + +/** + * Generator for HTML Tags + * + * see getTag() ... + * it generates html code for tags + * - with and without label + * - with and without closing tag + * + * see _setAttributes($aAttributes) ... + * attribute will be inserted with given key-value array + * BUT with special attribute keys: + * - label - will be used as label + * - icon - will be added as <i class="[icon value]"></i> to the label + * + * @author Axel + */ +class htmlelements { + + /** + * set of auto generated icon prefixes + * @var type + */ + var $_aIcons=array( + // 'fa-'=>'fa ', + ); + + var $_sLabel = ''; + var $_aAttributes = array(); + + + // ---------------------------------------------------------------------- + // CONSTRUCTOR + // ---------------------------------------------------------------------- + + public function __construct() { + return true; + } + + // ---------------------------------------------------------------------- + // + // PRIVATE FUNCTIONS + // + // ---------------------------------------------------------------------- + + + /** + * generate html attibutes with all internal attributes key -> values + * @return string + */ + protected function _addAttributes() { + $sReturn = ''; + foreach ($this->_aAttributes as $sAttr => $sValue) { + if(is_array($sValue)){ + echo "ERROR: an html tag was defined with array in attribute [$sAttr]:<br><pre>".print_r($this->_aAttributes, 1)."</pre>"; + } + $sReturn .= ' '.$sAttr . '="' . $sValue . '"'; + + } + return $sReturn; + } + + + /** + * internal helper: fetch all attributes from key-value hash; + * Specialties here: + * - label will be extracted from key 'label' + * - and optional existing key 'icon' will be added at beginning of a label + * + * @param array $aAttributes + * @return boolean + */ + protected function _setAttributes($aAttributes){ + $this->_sLabel=''; + if(isset($aAttributes['icon']) && $aAttributes['icon']){ + $this->_sLabel.=$this->getIcon($aAttributes['icon']); + unset($aAttributes['icon']); + } + if(isset($aAttributes['label']) && $aAttributes['label']){ + $this->_sLabel .= $aAttributes['label']; + unset($aAttributes['label']); + } + $this->_aAttributes=$aAttributes; + return true; + } + + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS + // HTML GENERIC + // + // ---------------------------------------------------------------------- + + /** + * generic function to get html code for a single tag + * + * @param string $sTag tag name + * @param array $aAttributes array with attributes (optional including 'icon' and 'label') + * @param boolean $bCloseTag optional: set false if tag has no closing tag (= ending with "/>") + * @return type + */ + public function getTag($sTag, $aAttributes, $bCloseTag=true){ + $sTpl = $bCloseTag ? "<$sTag%s>%s</$sTag>" : "<$sTag %s/>%s"; + $this->_setAttributes($aAttributes); + return sprintf($sTpl, $this->_addAttributes(), $this->_sLabel); + } + + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS + // SIMPLE HTML ELEMENTS + // + // ---------------------------------------------------------------------- + + /** + * helper detect prefix of a string add prefix of a framework + * i.e. value "fa-close" detects font awesome and adds "fa " as prefix + * + * @param string $sIconclass + * @return boolean + */ + public function getIcon($sIconclass=false){ + if(!$sIconclass){ + return ''; + } + $sPrefix=''; + foreach ($this->_aIcons as $sPrefix =>$add) { + if (strpos($sIconclass, $sPrefix)===0){ + $sPrefix=$add; + continue; + } + } + // do not use this .. it overrides internal attribute vars + // return $this->getTag('i', array('class'=>$sPrefix.$sIconclass)); + return '<i class="'.$sPrefix.$sIconclass.'"></i> '; + } + + + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS + // HTML COMPONENTS + // + // ---------------------------------------------------------------------- + + /** + * get html code for an input field + * + * @param array $aAttributes attributes of the select tag + * @return string + */ + public function getFormInput($aAttributes){ + $sTpl = '<input %s/>'; + $this->_setAttributes($aAttributes); + return sprintf($sTpl, $this->_addAttributes()); + } + /** + * get html code for an option field in a select drop down + * + * @param array $aAttributes attributes of the option tag + * @return string + */ + public function getFormOption($aAttributes){ + $sTpl = '<option %s>%s</option>'; + $this->_setAttributes($aAttributes); + return sprintf($sTpl, $this->_addAttributes(), $this->_sLabel); + } + /** + * get html code for a select drop down + * + * @param array $aAttributes attributes of the select tag + * @param array $aOptions array for all option fields + * @return string + */ + public function getFormSelect($aAttributes, $aOptions=array()){ + // $sTpl = '<select %s>%s</select>'; + + if(!count($aOptions)){ + return false; + } + $sOptions=''; + foreach($aOptions as $aOptionAttributes){ + // $sOptions.=$this->getFormOption($aOptionAttributes); + $sOptions.=$this->getTag('option', $aOptionAttributes); + } + $aAttributes['label']=$sOptions; + return $this->getTag('select', $aAttributes); + /* + $this->_setAttributes($aAttributes); + return sprintf($sTpl, $this->_addAttributes(), $sOptions); + * + */ + } + + public function getTable($aHead, $aBody, $aTableAttributes=array()){ + $sReturn=''; + $sTdata=''; + $sThead=''; + $sTpl = '<table %s>' + . '<thead><tr>%s</tr></thead>' + . '<tbody>%s</tbody>' + . '</table>'; + + foreach($aHead as $sTh){ + $sThead.='<th>'.$sTh.'</th>'; + } + foreach($aBody as $aTr){ + $sTdata.='<tr>'; + foreach($aTr as $sTd){ + $sTdata.='<td>'.$sTd.'</td>'; + } + $sTdata.='</tr>'; + } + $this->_setAttributes($aTableAttributes); + return sprintf($sTpl, + $this->_addAttributes(), + $sThead, + $sTdata + ); + } +} diff --git a/classes/render-adminlte.class.php b/classes/render-adminlte.class.php new file mode 100755 index 0000000000000000000000000000000000000000..0f25163c89698484a71b800007d0633e8fbf0991 --- /dev/null +++ b/classes/render-adminlte.class.php @@ -0,0 +1,1138 @@ +<?php +require_once 'htmlelements.class.php'; +/** + * ====================================================================== + * + * RENDERER FOR ADNINLTE template https://adminlte.io + * DOCS: https://adminlte.io/docs/3.2/ + * https://adminlte.io/themes/v3/index3.html + * + * ====================================================================== + * + * @author Axel + */ +class renderadminlte { + + var $aPresets=[ + + 'bgcolor'=>[ + 'description'=>'background colors', + 'group'=>'styling', + 'values'=>[ + // https://adminlte.io/themes/v3/pages/UI/general.html + ''=>'no value', + 'indigo'=>'indigo', + 'lightblue'=>'', + 'navy'=>'', + 'purple'=>'', + 'fuchsia'=>'', + 'pink'=>'', + 'maroon'=>'', + 'orange'=>'', + 'lime'=>'', + 'teal'=>'', + 'olive'=>'', + + 'black'=>'black', + 'dark'=>'dark gray', + 'gray'=>'gray', + 'light'=>'light gray', + ] + ], + + 'type'=>[ + 'description'=>'type or status like info/ warning/ danger to define a color', + 'group'=>'styling', + 'values'=>[ + ''=>'no value', + 'danger'=>'red', + 'info'=>'aqua', + 'primary'=>'blue', + 'secondary'=>'gray', + 'success'=>'green', + 'warning'=>'yellow', + 'dark'=>'dark gray', + 'gray'=>'gray', + ] + ], + 'shadow'=>[ + 'description'=>'use a shadow', + 'group'=>'styling', + 'values'=>[ + ''=>'no value', + 'none'=>'none', + 'small'=>'small', + 'regular'=>'regular', + 'large'=>'large' + ] + ], + 'size'=>[ + 'description'=>'set a size', + 'group'=>'styling', + 'values'=>[ + ''=>'no value', + 'lg'=>'', + 'sm'=>'', + 'xs'=>'', + 'flat'=>'', + ] + ], + 'variant'=>[ + 'description'=>'coloring style', + 'group'=>'styling', + 'values'=>[ + ''=>'no value', + 'outline'=>'small stripe on top', + 'solid'=>'full filled widget', + 'gradient'=>'full filled with gradient', + ] + ], + 'visibility'=>[ + 'description'=>'', + 'group'=>'customizing', + 'values'=>[ + ''=>'no value', + '0'=>'hide', + '1'=>'show', + ] + ], + // for keys: state + 'windowstate'=>[ + 'description'=>'state of a resizable widget', + 'group'=>'customizing', + 'values'=>[ + ''=>'no value', + 'collapsed'=>'header only', + 'maximized'=>'full window', + ] + ], + // for keys: dismissable + 'yesno'=>[ + 'description'=>'', + 'group'=>'customizing', + 'values'=>[ + ''=>'no value', + '0'=>'no', + '1'=>'yes', + ] + ], + ]; + + var $_aValueMappings=[ + 'shadow'=>[ + 'default' => '', + 'none' => 'shadow-none', + 'small' => 'shadow-small', + 'regular' => 'shadow', + 'large' => 'shadow-lg', + ] + ]; + + var $_aElements=[]; + + /** + * instance of htmlelements + * @var object + */ + var $_oHtml=false; + + + // ---------------------------------------------------------------------- + // + // CONSTRUCTOR + // + // ---------------------------------------------------------------------- + public function __construct() { + $this->_oHtml=new htmlelements(); + $this->_initElements(); + return true; + } + + // ---------------------------------------------------------------------- + // + // PRIVATE FUNCTIONS + // + // ---------------------------------------------------------------------- + + /** + * verify if an item has a correct value + * it returns false if a key is not defined to be checked + * it returns true if it was validated successfully + * it dies with an errror, if a value check failed + * + * @param string $sType type; key in $_aValidItems; one of bgcolor|color|type|size + * @param string $sValue value to check + * @param string $sReferrer optional: method that called this function + */ + protected function _DELETE_ME___checkValue($sType, $sValue, $sReferrer=false){ + if (!$sValue || !array_key_exists($sType, $this->_aValidItems)){ + return false; + } + if(array_search($sValue, $this->_aValidItems[$sType])===false){ + echo "ERROR: ".($sReferrer ? $sReferrer.' - ' : '')."value [$sValue] is not a valid for type [$sType]; it must be one of ".implode("|", $this->_aValidItems[$sType]).'<br>'; + } + return true; + } + + /** + * used in cosntructor + * initialize all element definitions + */ + protected function _initElements(){ + $this->_aElements=[ + + // ------------------------------------------------------------ + 'alert'=>[ + 'label'=>'Alert', + 'description'=>'Colored box with title and a text', + 'method'=>'getAlert', + + 'params'=>[ + 'type' => ['select'=>$this->aPresets['type'], 'example_value'=>'warning'], + 'dismissible' => ['select'=>$this->aPresets['yesno'], 'example_value'=>''], + 'title' => [ + 'description'=>'Title in a bit bigger font', + 'group'=>'content', + 'example_value'=>'Alert title' + ], + 'text' => [ + 'description'=>'Message text', + 'group'=>'content', + 'example_value'=>'I am a message. Read me, please.' + ], + ] + ], + // ------------------------------------------------------------ + 'badge'=>[ + 'label'=>'Badge', + 'description'=>'Tiny additional info; mostly as counter', + 'method'=>'getBadge', + + 'params'=>[ + 'type' => ['select'=>$this->aPresets['type'], 'example_value'=>'danger'], + 'bgcolor' => ['select'=>$this->aPresets['bgcolor'], 'example_value'=>''], + 'class' => [ + 'group'=>'styling', + 'description'=>'optional: css classes', + 'example_value'=>'' + ], + 'id' => [ + 'group'=>'customizing', + 'description'=>'optional: id attribute', + 'example_value'=>'' + ], + 'title' => [ + 'group'=>'content', + 'description'=>'optional: title attribute for mouseover', + 'example_value'=>'Errors: 5' + ], + 'text' => [ + 'group'=>'content', + 'description'=>'Text or value in the badge', + 'example_value'=>'5' + ], + ] + ], + // ------------------------------------------------------------ + 'button'=>[ + 'label'=>'Button', + 'description'=>'Buttons<br>In this component you can add other parmeter keys too - these will be added as attributes in the button tag.', + 'method'=>'getButton', + + 'params'=>[ + 'type' => ['select'=>$this->aPresets['type'], 'example_value'=>'primary'], + 'size' => ['select'=>$this->aPresets['size'], 'example_value'=>''], + 'class' => [ + 'group'=>'styling', + 'description'=>'optional: css classes', + 'example_value'=>'' + ], + 'text' => [ + 'group'=>'content', + 'description'=>'Text/ html code on the button', + 'example_value'=>'Click me' + ], + ] + ], + // ------------------------------------------------------------ + 'callout'=>[ + 'label'=>'Callout', + 'description'=>'Kind of infobox', + 'method'=>'getCallout', + + 'params'=>[ + 'type' => ['select'=>$this->aPresets['type'], 'example_value'=>'danger'], + 'class' => [ + 'group'=>'styling', + 'description'=>'optional: css classes', + 'example_value'=>'' + ], + 'title' => [ + 'group'=>'content', + 'description'=>'Title in a bit bigger font', + 'example_value'=>'I am a callout' + ], + 'text' => [ + 'group'=>'content', + 'description'=>'Message text', + 'example_value'=>'Here is some description to whatever.' + ], + ] + ], + // ------------------------------------------------------------ + 'card'=>[ + 'label'=>'Card', + 'description'=>'Content box with header, text, footer', + 'method'=>'getCard', + + 'params'=>[ + 'type' => ['select'=>$this->aPresets['type'], 'example_value'=>'primary'], + 'variant' => ['select'=>$this->aPresets['variant'], 'example_value'=>'outline'], + 'class' => [ + 'group'=>'styling', + 'description'=>'optional: css classes', + 'example_value'=>'' + ], + 'state' => [ + 'group'=>'customizing', + 'select'=>$this->aPresets['windowstate'], + 'example_value'=>'' + ], + + 'tb-collapse' => ['description'=>'show minus symbol as collapse button', 'select'=>$this->aPresets['visibility'], 'example_value'=>''], + 'tb-expand' => ['description'=>'show plus symbol to expand card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''], + 'tb-maximize' => ['description'=>'show maximize button for fullscreen', 'select'=>$this->aPresets['visibility'], 'example_value'=>''], + 'tb-minimize' => ['description'=>'show minimize button to minimize', 'select'=>$this->aPresets['visibility'], 'example_value'=>''], + 'tb-remove' => ['description'=>'show cross symbol to remove card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''], + + 'title' => [ + 'group'=>'content', + 'description'=>'Title in the top row', + 'example_value'=>'I am a card' + ], + 'tools' => [ + 'group'=>'content', + 'description'=>'Html code for the top right', + 'example_value'=>'' + ], + 'text' => [ + 'group'=>'content', + 'description'=>'Main content', + 'example_value'=>'Here is some beautiful content.' + ], + 'footer' => [ + 'group'=>'content', + 'description'=>'optional: footer content', + 'example_value'=>'Footer' + ], + ] + ], + // ------------------------------------------------------------ + 'infobox'=>[ + 'label'=>'Info box', + 'description'=>'Box with icon to highlight a single value; optional with a progress bar', + 'method'=>'getInfobox', + + 'params'=>[ + 'type'=>['select'=>$this->aPresets['type'], 'example_value'=>''], + 'iconbg'=>['select'=>$this->aPresets['type'], 'example_value'=>'info'], + 'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''], + 'icon'=>[ + 'group'=>'content', + 'description'=>'css class for an icon', + 'example_value'=>'far fa-thumbs-up' + ], + 'text'=>[ + 'group'=>'content', + 'description'=>'short information text', + 'example_value'=>'Likes' + ], + 'number'=>[ + 'group'=>'content', + 'description'=>'a number to highlight', + 'example_value'=>"41,410" + ], + 'progressvalue'=>[ + 'group'=>'content', + 'description'=>'optional: progress value 0..100 to draw a progress bar', + 'example_value'=>70 + ], + 'progresstext'=>[ + 'group'=>'content', + 'description'=>'optional: text below progress bar', + 'example_value'=>'70% Increase in 30 Days' + ] + ] + ], + // ------------------------------------------------------------ + 'smallbox'=>[ + 'label'=>'Small box', + 'description'=>'Solid colored box to highlight a single value; optional with a link', + 'method'=>'getSmallbox', + + 'params'=>[ + 'type'=>['select'=>$this->aPresets['type'], 'example_value'=>'info'], + 'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''], + + 'icon'=>['group'=>'content', 'description'=>'css class for an icon', 'example_value'=>'fas fa-shopping-cart'], + + 'text'=>['group'=>'content', 'description'=>'short information text', 'example_value'=>'New orders'], + 'number'=>['group'=>'content', 'description'=>'a number to highlight', 'example_value'=>"150"], + 'url'=>['group'=>'content', 'description'=>'optional: url to set a link on the bottom', 'example_value'=>'#'], + 'linktext'=>['group'=>'content', 'optional: description'=>'linktext', 'example_value'=>'More info'] + ] + ], + ]; + } + /** + * helper function: a shortcut for $this->_oHtml->getTag + * @param string $sTag name of html tag + * @param array $aAttributes array of its attributes + */ + protected function _tag($sTag, $aAttributes, $sContent=false){ + if ($sContent){ + $aAttributes['label']=(isset($aAttributes['label']) ? $aAttributes['label'] : '') . $sContent; + } + return $this->_oHtml->getTag($sTag, $aAttributes); + } + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS AdminLTE 3.2 + // + // ---------------------------------------------------------------------- + + /** + * render a page by using template + * @param string $stemplate html template with placeholders + * @param array $aReplace key = what to replace .. value = new value + */ + public function render($sTemplate, $aReplace){ + return str_replace( + array_keys($aReplace), + array_values($aReplace), + $sTemplate + ); + } + + + /** + * add a wrapper: wrap some content into a tag + * + * @param string $sContent html content inside + * @return string + */ + public function addWrapper($sTag, $aOptions, $sContent){ + $aOptions['label']=$sContent; + return $this->_tag($sTag, $aOptions)."\n"; + } + + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS :: NAVIGATION + // + // ---------------------------------------------------------------------- + + + /** + * get a single navigation item on top bar + * + * @param array $aLink Array of html attributes; key children can contain subitems + * @param integer $iLevel Menu level; 1=top bar; 2=pupup menu with subitems + * @return string + */ + public function getNavItem($aLink, $iLevel=1){ + static $iCounter; + if(!isset($iCounter)){ + $iCounter=0; + } + + // special menu entry: horizontal bar (label is "-") + if($aLink['label']=='-'){ + return '<div class="dropdown-divider"></div>'; + } + + $aChildren=isset($aLink['children']) && is_array($aLink['children']) && count($aLink['children']) ? $aLink['children'] : false; + + $aLink['class']='nav-link' + . (isset($aLink['class']) ? ' '.$aLink['class'] : '') + . ($aChildren ? ' dropdown-toggle' : '') + ; + if($aChildren){ + $iCounter++; + $sNavId="navbarDropdown".$iCounter; + $aLink=array_merge($aLink,[ + 'id'=>$sNavId, + 'role'=>"button", + 'data-toggle'=>"dropdown", + 'aria-haspopup'=>"true", + 'aria-expanded'=>"false", + ]); + unset($aLink['children']); // remove from html attributes to draw + } + $sReturn=$this->_tag('a', $aLink)."\n"; + if($aChildren){ + $iLevel++; + $sReturn.=$this->addWrapper( + 'div', + [ + 'aria-labelledby'=> $sNavId, + 'class'=>'dropdown-menu' + ], + $this->getNavItems($aChildren, $iLevel) + )."\n"; + $iLevel--; + } + + if($iLevel==1){ + $sLiClass='nav-item'.($aChildren ? ' dropdown' : ''); + $sReturn=$this->addWrapper( + 'li', + ['class'=>$sLiClass], + $sReturn + ); + } + return $sReturn; + } + /** + * get a navigation item for top bar + * + */ + public function getNavItems($aLinks, $iLevel=1){ + $sReturn=''; + if (!$aLinks || !is_array($aLinks) || !count($aLinks)){ + return false; + } + foreach($aLinks as $aLink){ + $sReturn.=$this->getNavItem($aLink, $iLevel); + } + return $sReturn; + } + /** + * get a top left navigation for a top navigation bar + * + * @example + * <code> + * $aTopnav=[ + * ['href'=>'/index.php', 'label'=>'MyHome' , 'class'=>'bg-gray'], + * ['href'=>'#', 'label'=>'Contact'], + * ['href'=>'#', 'label'=>'Help', + * 'children'=>[ + * ['href'=>'#', 'label'=>'FAQ'], + * ['label'=>'-'], + * ['href'=>'#', 'label'=>'Support'], + * ] + * ] + * ]; + * echo '<nav class="main-header navbar navbar-expand navbar-white navbar-light">' + * .$renderAdminLTE->getTopNavigation($aTopnav) + * .'</nav>' + * </code> + * + * @param array $aNavitems array of navigation items/ tree + * @param array $aUlOptions array of html attrubutes for wrapping UL tag + * @return string + */ + public function getTopNavigation($aNavItems, $aUlOptions=['class'=>'navbar-nav']){ + array_unshift($aNavItems, ['class'=>'nav-link', 'data-widget'=>'pushmenu', 'href'=>'#', 'role'=>'button', 'label'=>'<i class="fas fa-bars"></i>']); + return $this->addWrapper('ul', $aUlOptions, $this->getNavItems($aNavItems)); + } + + // ---------------------------------------------------------------------- + + + /** + * get a navigation items for sidebar + * @param array $aLinks list of link items + */ + public function getNavi2Items($aLinks){ + $sReturn=''; + if (!$aLinks || !is_array($aLinks) || !count($aLinks)){ + return false; + } + foreach($aLinks as $aLink){ + // to render active or open links: + $aLink['class']='nav-link' . (isset($aLink['class']) ? ' '.$aLink['class'] : ''); + + $aChildren=isset($aLink['children']) ? $aLink['children'] : false; + $aLiClass='nav-item' . ($aChildren && strstr($aLink['class'], 'active') ? ' menu-open' : ''); + $sSubmenu=''; + if($aChildren){ + unset($aLink['children']); + $aLink['label'].='<i class="right fas fa-angle-left"></i>'; + $sSubmenu.=$this->getSidebarNavigation($aChildren, ['class'=>'nav nav-treeview']); + } + $aLink['label']=$this->addWrapper('p', [], $aLink['label']); + $sReturn.=$this->addWrapper( + 'li', ['class'=>$aLiClass], + $this->_tag('a', $aLink).$sSubmenu + )."\n"; + } + return $sReturn; + } + + /** + * + */ + public function getSidebarNavigation($aNavItems, $aUlOptions=[ + 'class'=>'nav nav-pills nav-sidebar flex-column nav-flat_ nav-child-indent', + 'data-widget'=>'treeview', + 'role'=>'menu', + 'data-accordion'=>'false' + ]) + { + return $this->addWrapper( + 'ul', $aUlOptions, + $this->getNavi2Items($aNavItems) + )."\n"; + } + + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS :: CONTENT - BASIC FUNCTIONS + // + // ---------------------------------------------------------------------- + + /** + * add page row + * + * @param string $sContent html content inside + * @return string + */ + public function addRow($sContent){ + return $this->addWrapper('div', ['class'=>'row'], $sContent); + } + + /** + * add page column + * + * @param string $sContent html content inside + * @param integer $iCols column width; 12 = full width + * @param string $sFloat css value for float attribute; default=false + * @return string + */ + public function addCol($sContent, $iCols=6, $sFloat=false){ + return $this->addWrapper('div', ['class'=>'col-md-'.$iCols, 'style'=>'float:'.$sFloat], $sContent); + } + + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS :: CONTENT - WIDGET HELPERS + // + // ---------------------------------------------------------------------- + + + /** + * get a list of all defined components that can be rendered + * @param {bool} $bSendData flag: send including subkeys of the hash; default: false (keys only) + * @return {array} + */ + public function getComponents($bSendData=false){ + return $bSendData + ? $this->_aElements + : array_keys($this->_aElements) + ; + } + /** + * get data of a component + * @param {string} $sComponent id of the component + * @return {array} + */ + public function getComponent($sComponent){ + if(!isset($this->_aElements[$sComponent])){ + return false; + } + $aReturn=array_merge(['id'=>$sComponent], $this->_aElements[$sComponent]); + unset($aReturn['params']); + return $aReturn; + } + + /** + * get parameter keys of a component + * @param {string} $sComponent id of the component + * @param {bool} $bSendData flag: send including subkeys of the hash; default: false (keys only) + * @return {array} + */ + public function getComponentParamkeys($sComponent, $bSendData=false){ + if(!isset($this->_aElements[$sComponent])){ + return false; + } + $aKeys=array_keys($this->_aElements[$sComponent]['params']); + if(!$bSendData){ + return $aKeys; + } + $aReturn=[]; + foreach($aKeys as $sKey){ + $aReturn[$sKey]=$this->getComponentParamkey($sComponent, $sKey); + } + // $aReturn=$this->_aElements[$sComponent]['params']; + return $aReturn; + } + + /** + * get information a parameter keys of a component + * @param {string} $sComponent id of the component + * @param {string} $sKey key in the options array + * @return {array} + */ + public function getComponentParamkey($sComponent, $sKey){ + if(!isset($this->_aElements[$sComponent]['params'][$sKey])){ + return false; + } + $aReturn=$this->_aElements[$sComponent]['params'][$sKey]; + // get description from a preset + if(!isset($aReturn['description']) && isset($aReturn['select']['description'])){ + $aReturn['description']=$aReturn['select']['description']; + } + if(!isset($aReturn['group']) && isset($aReturn['select']['group'])){ + $aReturn['group']=$aReturn['select']['group']; + } + return $aReturn; + } + + /** + * get a flat list of valid parameters for a key in a component + * @param {string} $sComponent id of the component + * @param {string} $sKey key in the options array + * @return {array} + */ + public function getValidParamValues($sComponent, $sKey){ + $aOptionkey=$this->getComponentParamkey($sComponent, $sKey); + if(!$aOptionkey || !isset($aOptionkey['select']['values'])){ + return false; + } + return array_keys($aOptionkey['select']['values']); + } + + + // ---------------------------------------------------------------------- + + /** + * helper: add a css value with prefix + * this handles option keys in get[COMPONENT] methods + * if a value is set then this function returns a space + prefix (param 2) + value + * @param {string} $sValue option value + * @param {string} $sPrefix prefix in front of css value + * @return {string} + */ + protected function _addClassValue($sValue, $sPrefix=''){ + return $sValue ? ' '.$sPrefix.$sValue : ''; + } + + /** + * helper function for get[COMPONENTNAME] methods: + * ensure that all wanted keys exist in an array. Non existing keys will be added with value false + * @return array + */ + protected function _ensureOptions($sComponent, $aOptions=[]){ + + $aParams=$this->getComponentParamkeys($sComponent, 0); + if(!$aParams){ + $aOptions['_infos'][]="Warning: no definition was found for component $sComponent."; + return $aOptions; + } + foreach ($aParams as $sKey){ + if(!isset($aOptions) || !isset($aOptions[$sKey])){ + $aOptions[$sKey]=false; + if(!isset($aOptions['_infos'])){ + $aOptions['_infos']=[]; + } + $aOptions['_infos'][]="added missing key: $sKey"; + } + + // $aParamdata + $aValidvalues=$this->getValidParamValues($sComponent, $sKey); + if($aValidvalues){ + if(array_search($aOptions[$sKey], $aValidvalues)===false){ + echo "ERROR: [".$sComponent."] value "".$aOptions[$sKey]."" is not a valid for param key [".$sKey."]; it must be one of ".implode("|", $aValidvalues).'<br>'; + } + } + + // $this->_checkValue($sKey, $aOptions[$sKey], __METHOD__); + } + // echo '<pre>' . print_r($aOptions, 1) . '</pre>'; + return $aOptions; + } + // ---------------------------------------------------------------------- + // + // PUBLIC FUNCTIONS :: CONTENT - WIDGETS + // + // ---------------------------------------------------------------------- + + /** + * return a alert box + * https://adminlte.io/themes/v3/pages/UI/general.html + * + * @param type $aOptions hash with keys for all options + * - type - one of [none]|danger|info|primary|success|warning + * - dismissible - if dismissible - one of true|false; default: false + * - title + * - text + * @return string + */ + public function getAlert($aOptions){ + $aOptions=$this->_ensureOptions('alert', $aOptions); + $aAlertIcons=[ + 'danger'=>'icon fas fa-ban', + 'info'=>'icon fas fa-info', + 'warning'=>'icon fas fa-exclamation-triangle', + 'success'=>'icon fas fa-check', + ]; + + $aElement=[ + 'class'=>'alert' + . $this->_addClassValue($aOptions['type'], 'alert-') + . $this->_addClassValue($aOptions['dismissible'], 'alert-') + , + 'label'=>'' + . ($aOptions['dismissible'] ? '<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' : '') + . $this->_tag('h5', [ + 'label'=> '' + .(isset($aAlertIcons[$aOptions['type']]) ? '<i class="'.$aAlertIcons[$aOptions['type']].'"></i> ' : '') + .$aOptions['title'] + ]) + .$aOptions['text'] + ]; + + return $this->_tag('div', $aElement); + } + + /** + * get html code for a badge + * + * Examples + * <span class="badge badge-danger navbar-badge">3</span> + * <span title="3 New Messages" class="badge badge-warning">3</span> + * <span class="right badge badge-danger">New</span> + * <span class="badge badge-danger float-right">$350</span> + * + * @param array $aOptions hash with keys for all options + * - bgcolor - background color (without prefix "bg") + * - class - css class + * - id - optional: id attribute + * - text - visible text + * - title - optional: title attribute + * - type - one of [none]|danger|dark|info|primary|secondary|success|warning + */ + public function getBadge($aOptions){ + $aOptions=$this->_ensureOptions('badge', $aOptions); + $aElement=[]; + $aElement['class']='badge' + . $this->_addClassValue($aOptions['class'], '') + . $this->_addClassValue($aOptions['type'], 'badge-') + . $this->_addClassValue($aOptions['bgcolor'], 'bg-') + ; + if ($aOptions['id']){ + $aElement['id']=$aOptions['id']; + } + $aElement['title']=$aOptions['title']; + $aElement['label']=$aOptions['text']; + + return $this->_tag('span', $aElement); + } + + /** + * Get a button. + * You can use any other key that are not named here. Those keys will be rendered + * as additional html attributes without modification. + * https://adminlte.io/themes/v3/pages/UI/buttons.html + * + * <button type="button" class="btn btn-block btn-default">Default</button> + * @param type $aOptions hash with keys for all options + * - type - one of [none]|danger|dark|info|primary|secondary|success|warning + * - size - one of [none]|lg|sm|xs|flat + * - class - any css class for customizing, eg. "disabled" + * - icon - not supported yet + * - text - text on button + * @return string + */ + public function getButton($aOptions){ + $aOptions=$this->_ensureOptions('button', $aOptions); + $aElement=$aOptions; + $aElement['class']='btn' + . $this->_addClassValue($aOptions['type'], 'btn-') + . $this->_addClassValue($aOptions['size'], 'btn-') + . $this->_addClassValue($aOptions['class'], '') + ; + $aElement['label']=$aOptions['text'] ? $aOptions['text'] : ' '; + foreach(['_infos', 'type', 'size', 'icon', 'text'] as $sDeleteKey){ + unset($aElement[$sDeleteKey]); + } + return $this->_tag('button', $aElement); + } + + /** + * get a callout (box with coloered left border; has type, title + text) + * https://adminlte.io/themes/v3/pages/UI/general.html + * + * @param type $aOptions hash with keys for all options + * >> styling + * - type - one of [none]|danger|dark|info|primary|secondary|success|warning + * - class - optional css class + * + * >> texts/ html content + * - title - text: title of the card + * - text - text: content of the card + * @return string + */ + public function getCallout($aOptions){ + $aOptions=$this->_ensureOptions('callout', $aOptions); + $sClass='callout' + . $this->_addClassValue($aOptions['type'], 'callout-') + . $this->_addClassValue($aOptions['class'], '') + ; + + return $this->addWrapper( + 'div', ['class'=>$sClass], + ($aOptions['title'] ? $this->_tag('h5', ['label'=>$aOptions['title']]) : '') + .($aOptions['text'] ? $this->_tag('p', ['label'=>$aOptions['text']]) : '') + ); + } + + /** + * get a card + * https://adminlte.io/docs/3.2/components/cards.html + * https://adminlte.io/docs/3.2/javascript/card-widget.html + * + * @param type $aOptions hash with keys for all options + * >> styling + * - variant: "default" - titlebar is colored + * "outline" - a small stripe on top border is colored + * "solid" - whole card is colored + * "gradient" - whole card is colored with a gradient + * - type - one of [none]|danger|dark|info|primary|secondary|success|warning + * - class - any css class for customizing, eg. "disabled" + * - state - one of [none]|collapsed|maximized + * + * >> toolbar icons - set to true to show it; default: none of it is visible + * - tb-collapse + * - tb-expand it is added automatically if you set 'state'=>'collapsed' + * - tb-maximize + * - tb-minimize it is added automatically if you set 'state'=>'maximized' + * - tb-remove + * + * >> texts/ html content + * - title - text: title of the card + * - tools - text: titlebar top right elements + * - text - text: content of the card + * - footer - text: footer of the card + * @return string + */ + public function getCard($aOptions){ + $aOptions=$this->_ensureOptions('card', $aOptions); + // css class prefixes based on "variant" value + $aVariants=[ + 'default' => 'card-', + 'outline' => 'card-outline card-', + 'solid' => 'bg-', + 'gradient' => 'bg-gradient-', + ]; + + // window states: css class and toolbar buttons to add + $aStates=[ + 'collapsed'=>[ 'class'=>'collapsed-card', 'tool'=>'tb-expand'], + 'maximized'=>[ 'class'=>'maximized-card', 'tool'=>'tb-minimize'], + ]; + $aTools=[ + 'tb-collapse'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>', + 'tb-expand'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-plus"></i></button>', + + 'tb-maximize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-expand"></i></button>', + 'tb-minimize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-compress"></i></button>', + + 'tb-remove'=>'<button type="button" class="btn btn-tool" data-card-widget="remove"><i class="fas fa-times"></i></button>', + ]; + + // print_r($aOptions); + + $sVariantPrefix=$aVariants[$aOptions['variant']] ? $aVariants[$aOptions['variant']] : $aVariants['default']; + $sClass='card' + . $this->_addClassValue($aOptions['type'], $sVariantPrefix) + . $this->_addClassValue($aOptions['class'], '') + ; + + // check window state + foreach($aStates as $sStatus=>$aStatus){ + if($aOptions['state']===$sStatus){ + $sClass.=' '.$aStatus['class']; + $aOptions[$aStatus['tool']]=1; + } + } + + // add toolbar buttons - from given options or by window state + foreach($aTools as $sTool=>$sHtml){ + $aOptions['tools'].=($aOptions[$sTool] ? $sHtml : ''); + } + // build parts of the card + $sCardHeader=$this->addWrapper('div', ['class'=>'card-header'], + $this->_tag('h3', ['class'=>'card-title', 'label'=>$aOptions['title']]) + . ($aOptions['tools'] ? $this->_tag('div', ['class'=>'card-tools', 'label'=>$aOptions['tools']]) : '') + ); + + $sCardBody=$this->_tag('div', ['class'=>'card-body', 'label'=>$aOptions['text']]); + $sCardFooter=$aOptions['footer'] ? $this->_tag('div', ['class'=>'card-footer', 'label'=>$aOptions['footer']]) : ''; + + // merge all + return $this->addWrapper('div', ['class'=>$sClass], $sCardHeader.$sCardBody.$sCardFooter); + } + + + /** + * return an info-box: + * A colored box with large icon, text and a value. + * https://adminlte.io/docs/3.2/components/boxes.html + * + * @param type $aOptions hash with keys for all options + * styling: + * - type - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning + * - iconbg - background color or type of icon; use it for default type (type="") + * - shadow - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large + * + * content + * - icon - icon class + * - text - information text + * - number - value (comes in bold text) + * - progressvalue - integer: progress bar; range: 0..100 + * - progresstext - text below progress bar + * @return string + */ + public function getInfobox($aOptions){ + $aOptions=$this->_ensureOptions('infobox', $aOptions); + + // print_r($aOptions); + $sClass='info-box' + . $this->_addClassValue($aOptions['type'], 'bg-') + . $this->_addClassValue($aOptions['class'], '') + .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) + ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '') + ; + + // build parts + $sIcon=$aOptions['icon'] + ? $this->addWrapper("span", [ + 'class'=>'info-box-icon'.($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : '') + ], $this->_tag('i',['class'=>$aOptions['icon']])) + : '' + ; + $sContent=$this->addWrapper("div", ['class'=>'info-box-content'], + '' + . ($aOptions['text'] ? $this->_tag('span', ['class'=>'info-box-text', 'label'=>$aOptions['text']]) : '') + . ($aOptions['number'] ? $this->_tag('span', ['class'=>'info-box-number', 'label'=>$aOptions['number']]) : '') + . ($aOptions['progressvalue']!==false && $aOptions['progressvalue']!=='' + ? $this->addWrapper('div', ['class'=>'progress'], + $this->_tag('div', ['class'=>'progress-bar'. ($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : ''), 'style'=>'width: '.(int)$aOptions['progressvalue'].'%' ]) + ) + . ($aOptions['progresstext'] ? $this->_tag('span', ['class'=>'progress-description', 'label'=>$aOptions['progresstext']]) : '' ) + : '' + ) + ); + + // merge all + return $this->_tag('div', ['class'=>$sClass], $sIcon.$sContent); + } + + + /** + * return an info-box: + * A colored box with large icon, text and a value. + * https://adminlte.io/docs/3.2/components/boxes.html + * https://adminlte.io/themes/v3/pages/widgets.html + * + * @param type $aOptions hash with keys for all options + * styling: + * - type - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning + + * content + * - icon - icon class for icon on the right + * - text - information text + * - number - value (comes in bold text) + * - url - integer: progress bar; range: 0..100 + * - linktext- text below progress bar + * @return string + */ + public function getSmallbox($aOptions){ + $aOptions=$this->_ensureOptions('smallbox', $aOptions); + $aShadows=[ + 'default' => '', + 'none' => 'shadow-none', + 'small' => 'shadow-small', + 'regular' => 'shadow', + 'large' => 'shadow-lg', + ]; + // print_r($aOptions); + $sClass='small-box' + . $this->_addClassValue($aOptions['type'], 'bg-') + .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) + ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '') + ; + + // build parts + $sContent=$this->addWrapper("div", ['class'=>'inner'], + '' + . ($aOptions['number'] ? $this->_tag('h3', ['label'=>$aOptions['number']]) : '') + . ($aOptions['text'] ? $this->_tag('p', ['class'=>'info-box-text', 'label'=>$aOptions['text']]) : '') + ); + $sIcon=$aOptions['icon'] + ? $this->addWrapper("div", ['class'=>'icon'], + $this->_tag('i',['class'=>$aOptions['icon']])) + : '' + ; + $sFooter=($aOptions['url'] + ? $this->addWrapper("a", [ + 'class'=>'small-box-footer', + 'href'=>$aOptions['url'], + ], + '' + . ($aOptions['linktext'] ? $aOptions['linktext'] : $aOptions['url']) + . ' ' + . $this->_tag('i',['class'=>'fas fa-arrow-circle-right']) + ) + : '' + ); + + // merge all + return $this->_tag('div', ['class'=>$sClass], $sContent.$sIcon.$sFooter); + } + + +/* + +<div class="info-box"> + <span class="info-box-icon bg-info"><i class="far fa-bookmark"></i></span> + <div class="info-box-content"> + <span class="info-box-text">Bookmarks</span> + <span class="info-box-number">41,410</span> + <div class="progress"> + <div class="progress-bar bg-info" style="width: 70%"></div> + </div> + <span class="progress-description"> + 70% Increase in 30 Days + </span> + </div> +</div> + + + + <div class="info-box bg-success"> + <span class="info-box-icon"><i class="far fa-thumbs-up"></i></span> + <div class="info-box-content"> + <span class="info-box-text">Likes</span> + <span class="info-box-number">41,410</span> + <div class="progress"> + <div class="progress-bar" style="width: 70%"></div> + </div> + <span class="progress-description"> + 70% Increase in 30 Days + </span> + </div> + </div> +*/ + + + + + + + + + +} diff --git a/config/page.tpl.php b/config/page.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..3659b4545b783f4ff1f1201e17d01e6230c1ca9b --- /dev/null +++ b/config/page.tpl.php @@ -0,0 +1,455 @@ +<!DOCTYPE html> + +<html lang="en"> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>{{PAGE_TITLE}}</title> + + <script type="text/javascript" src="js/asimax.class.js"></script> + + <script type="text/javascript" src="vendor/jquery/3.6.1/jquery.min.js"></script> + + <!-- visjs --> + <script type="text/javascript" src="vendor/vis/4.21.0/vis.min.js"></script> + <link href="vendor/vis/4.21.0/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css"/> + + <!-- datatable --> + <script type="text/javascript" src="vendor/datatables/1.10.15/js/jquery.dataTables.min.js"></script> + <link href="vendor/datatables/1.10.15/css/jquery.dataTables.min.css" rel="stylesheet" type="text/css"/> + + <!-- fontawesome --> + <link href="vendor/font-awesome/5.15.4/css/all.min.css" rel="stylesheet" type="text/css"/> + + + <!-- + <link rel="stylesheet" href="{{DIR_ADMINLTEPLUGINS}}/fontawesome-free/css/all.min.css"> + --> + <!-- Adminlte --> + <link rel="stylesheet" href="{{DIR_ADMINLTE}}/css/adminlte.min.css?v=3.2.0"> + + <link rel="stylesheet" href="/main.css"> + + <!-- + <script nonce="b14481d0-1a31-4cec-b6e8-b5ad6020a5a9"> + (function(w, d) { + ! function(cM, cN, cO, cP) { + cM.zarazData = cM.zarazData || {}; + cM.zarazData.executed = []; + cM.zaraz = { + deferred: [], + listeners: [] + }; + cM.zaraz.q = []; + cM.zaraz._f = function(cQ) { + return function() { + var cR = Array.prototype.slice.call(arguments); + cM.zaraz.q.push({ + m: cQ, + a: cR + }) + } + }; + for (const cS of ["track", "set", "debug"]) cM.zaraz[cS] = cM.zaraz._f(cS); + cM.zaraz.init = () => { + var cT = cN.getElementsByTagName(cP)[0], + cU = cN.createElement(cP), + cV = cN.getElementsByTagName("title")[0]; + cV && (cM.zarazData.t = cN.getElementsByTagName("title")[0].text); + cM.zarazData.x = Math.random(); + cM.zarazData.w = cM.screen.width; + cM.zarazData.h = cM.screen.height; + cM.zarazData.j = cM.innerHeight; + cM.zarazData.e = cM.innerWidth; + cM.zarazData.l = cM.location.href; + cM.zarazData.r = cN.referrer; + cM.zarazData.k = cM.screen.colorDepth; + cM.zarazData.n = cN.characterSet; + cM.zarazData.o = (new Date).getTimezoneOffset(); + if (cM.dataLayer) + for (const cZ of Object.entries(Object.entries(dataLayer).reduce(((c_, da) => ({ + ...c_[1], + ...da[1] + }))))) zaraz.set(cZ[0], cZ[1], { + scope: "page" + }); + cM.zarazData.q = []; + for (; cM.zaraz.q.length;) { + const db = cM.zaraz.q.shift(); + cM.zarazData.q.push(db) + } + cU.defer = !0; + for (const dc of [localStorage, sessionStorage]) Object.keys(dc || {}).filter((de => de.startsWith("_zaraz_"))).forEach((dd => { + try { + cM.zarazData["z_" + dd.slice(7)] = JSON.parse(dc.getItem(dd)) + } catch { + cM.zarazData["z_" + dd.slice(7)] = dc.getItem(dd) + } + })); + cU.referrerPolicy = "origin"; + cU.src = "/cdn-cgi/zaraz/s.js?z=" + btoa(encodeURIComponent(JSON.stringify(cM.zarazData))); + cT.parentNode.insertBefore(cU, cT) + }; + ["complete", "interactive"].includes(cN.readyState) ? zaraz.init() : cM.addEventListener("DOMContentLoaded", zaraz.init) + }(w, d, 0, "script"); + })(window, document); + </script> + --> +</head> + +<body class="hold-transition {{PAGE_SKIN}} {{PAGE_LAYOUT}}"> + <div class="wrapper"> + + {{NAVI_TOP}} + <!-- + <nav class="main-header navbar navbar-expand navbar-white navbar-light"> + + <ul class="navbar-nav"> + <li class="nav-item"> + <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> + </li> + <li class="nav-item d-none d-sm-inline-block"> + <a href="index3.html" class="nav-link">Home</a> + </li> + <li class="nav-item d-none d-sm-inline-block"> + <a href="#" class="nav-link">Contact</a> + </li> + </ul> + + <ul class="navbar-nav ml-auto"> + + <li class="nav-item"> + <a class="nav-link" data-widget="navbar-search" href="#" role="button"> + <i class="fas fa-search"></i> + </a> + <div class="navbar-search-block"> + <form class="form-inline"> + <div class="input-group input-group-sm"> + <input class="form-control form-control-navbar" type="search" placeholder="Search" + aria-label="Search"> + <div class="input-group-append"> + <button class="btn btn-navbar" type="submit"> + <i class="fas fa-search"></i> + </button> + <button class="btn btn-navbar" type="button" data-widget="navbar-search"> + <i class="fas fa-times"></i> + </button> + </div> + </div> + </form> + </div> + </li> + + <li class="nav-item dropdown"> + <a class="nav-link" data-toggle="dropdown" href="#"> + <i class="far fa-comments"></i> + <span class="badge badge-danger navbar-badge">3</span> + </a> + <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> + <a href="#" class="dropdown-item"> + + <div class="media"> + <img src="dist/img/user1-128x128.jpg" alt="User Avatar" + class="img-size-50 mr-3 img-circle"> + <div class="media-body"> + <h3 class="dropdown-item-title"> + Brad Diesel + <span class="float-right text-sm text-danger"><i class="fas fa-star"></i></span> + </h3> + <p class="text-sm">Call me whenever you can...</p> + <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p> + </div> + </div> + + </a> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item"> + + <div class="media"> + <img src="dist/img/user8-128x128.jpg" alt="User Avatar" + class="img-size-50 img-circle mr-3"> + <div class="media-body"> + <h3 class="dropdown-item-title"> + John Pierce + <span class="float-right text-sm text-muted"><i class="fas fa-star"></i></span> + </h3> + <p class="text-sm">I got your message bro</p> + <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p> + </div> + </div> + + </a> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item"> + + <div class="media"> + <img src="dist/img/user3-128x128.jpg" alt="User Avatar" + class="img-size-50 img-circle mr-3"> + <div class="media-body"> + <h3 class="dropdown-item-title"> + Nora Silvester + <span class="float-right text-sm text-warning"><i + class="fas fa-star"></i></span> + </h3> + <p class="text-sm">The subject goes here</p> + <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p> + </div> + </div> + + </a> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item dropdown-footer">See All Messages</a> + </div> + </li> + + <li class="nav-item dropdown"> + <a class="nav-link" data-toggle="dropdown" href="#"> + <i class="far fa-bell"></i> + <span class="badge badge-warning navbar-badge">15</span> + </a> + <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> + <span class="dropdown-header">15 Notifications</span> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item"> + <i class="fas fa-envelope mr-2"></i> 4 new messages + <span class="float-right text-muted text-sm">3 mins</span> + </a> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item"> + <i class="fas fa-users mr-2"></i> 8 friend requests + <span class="float-right text-muted text-sm">12 hours</span> + </a> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item"> + <i class="fas fa-file mr-2"></i> 3 new reports + <span class="float-right text-muted text-sm">2 days</span> + </a> + <div class="dropdown-divider"></div> + <a href="#" class="dropdown-item dropdown-footer">See All Notifications</a> + </div> + </li> + <li class="nav-item"> + <a class="nav-link" data-widget="fullscreen" href="#" role="button"> + <i class="fas fa-expand-arrows-alt"></i> + </a> + </li> + <li class="nav-item"> + <a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#" role="button"> + <i class="fas fa-th-large"></i> + </a> + </li> + </ul> + </nav> + --> + + <aside class="main-sidebar sidebar-dark-primary elevation-4"> + + {{BRAND}} + <!-- + <a href="index3.html" class="brand-link"> + <img src="dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" + style="opacity: .8"> + <span class="brand-text font-weight-light">AdminLTE 3</span> + </a> + --> + + <div class="sidebar"> + + <!-- + <div class="user-panel mt-3 pb-3 mb-3 d-flex"> + <div class="image"> + <img src="dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image"> + </div> + <div class="info"> + <a href="#" class="d-block">Alexander Pierce</a> + </div> + </div> + + <div class="form-inline"> + <div class="input-group" data-widget="sidebar-search"> + <input class="form-control form-control-sidebar" type="search" placeholder="Search" + aria-label="Search"> + <div class="input-group-append"> + <button class="btn btn-sidebar"> + <i class="fas fa-search fa-fw"></i> + </button> + </div> + </div> + </div> + --> + {{NAVI_LEFT}} + <br><br> + + <!-- + <nav class="mt-2"> + <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> + + <li class="nav-item menu-open"> + <a href="#" class="nav-link active"> + <i class="nav-icon fas fa-tachometer-alt"></i> + <p> + Starter Pages + <i class="right fas fa-angle-left"></i> + </p> + </a> + <ul class="nav nav-treeview"> + <li class="nav-item"> + <a href="#" class="nav-link active"> + <i class="far fa-circle nav-icon"></i> + <p>Active Page</p> + </a> + </li> + <li class="nav-item"> + <a href="#" class="nav-link"> + <i class="far fa-circle nav-icon"></i> + <p>Inactive Page</p> + </a> + </li> + </ul> + </li> + <li class="nav-item"> + <a href="#" class="nav-link"> + <i class="nav-icon fas fa-th"></i> + <p> + Simple Link + <span class="right badge badge-danger">New</span> + </p> + </a> + </li> + </ul> + </nav> + --> + + </div> + + </aside> + + <div class="content-wrapper px-4 py-2"> + + + <div class="content-header"> + <div class="container-fluid"> + <div class="row mb-2"> + <div class="col-sm-6"> + {{PAGE_HEADER_LEFT}} + + <!-- <h1 class="m-0">Starter Page</h1> --> + </div> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + {{PAGE_HEADER_RIGHT}} + + <!-- + <li class="breadcrumb-item"><a href="#">Home</a></li> + <li class="breadcrumb-item active">Starter Page</li> + --> + </ol> + </div> + </div> + </div> + </div> + + + + <div class="content"> + + + <div class="container-fluid"> + + {{PAGE_BODY}} + + <!-- + <div class="row"> + <div class="col-lg-6"> + <div class="card"> + <div class="card-body"> + <h5 class="card-title">Card title</h5> + <p class="card-text"> + Some quick example text to build on the card title and make up the bulk of the + card's + content. + </p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + <div class="card card-primary card-outline"> + <div class="card-body"> + <h5 class="card-title">Card title</h5> + <p class="card-text"> + Some quick example text to build on the card title and make up the bulk of the + card's + content. + </p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + + <div class="col-lg-6"> + <div class="card"> + <div class="card-header"> + <h5 class="m-0">Featured</h5> + </div> + <div class="card-body"> + <h6 class="card-title">Special title treatment</h6> + <p class="card-text">With supporting text below as a natural lead-in to additional + content.</p> + <a href="#" class="btn btn-primary">Go somewhere</a> + </div> + </div> + <div class="card card-primary card-outline"> + <div class="card-header"> + <h5 class="m-0">Featured</h5> + </div> + <div class="card-body"> + <h6 class="card-title">Special title treatment</h6> + <p class="card-text">With supporting text below as a natural lead-in to additional + content.</p> + <a href="#" class="btn btn-primary">Go somewhere</a> + </div> + </div> + </div> + + </div> + --> + </div> + </div> + + </div> + + + <aside class="control-sidebar control-sidebar-dark"> + + <div class="p-3"> + <h5>Title</h5> + <p>Sidebar content</p> + </div> + </aside> + + + <footer class="main-footer"> + + <div class="float-right d-none d-sm-inline"> + {{PAGE_FOOTER_RIGHT}} + <!-- + Anything you want + --> + </div> + + {{PAGE_FOOTER_LEFT}} + <!-- + <strong>Copyright © 2014-2021 <a href="https://adminlte.io">AdminLTE.io</a>.</strong> All rights + reserved. + --> + </footer> + </div> + + <script src="{{DIR_ADMINLTEPLUGINS}}/bootstrap/js/bootstrap.bundle.min.js"></script> + <script src="{{DIR_ADMINLTE}}/js/adminlte.min.js?v=3.2.0"></script> + <script type="text/javascript" src="js/functions.js"></script> +</body> + +</html> \ No newline at end of file diff --git a/config/page_replacements.php b/config/page_replacements.php new file mode 100644 index 0000000000000000000000000000000000000000..b2cb42a0384f8f569a930e0067a27ff6561250b7 --- /dev/null +++ b/config/page_replacements.php @@ -0,0 +1,76 @@ +<?php + +return [ + '{{DIR_ADMINLTE}}' =>'/vendor/admin-lte/3.2.0', + '{{DIR_ADMINLTEPLUGINS}}' =>'/vendor/admin-lte-plugins', + '{{PAGE_SKIN}}' =>'', + '{{PAGE_TITLE}}' =>'Cronlog Viewer', + '{{PAGE_LAYOUT}}' =>'layout-navbar-fixed layout-fixed sidebar-mini', + '{{NAVI_TOP}}' =>'<nav class="main-header navbar navbar-expand navbar-white navbar-light"><ul class="navbar-nav" id="instances"></ul></nav>', + '{{BRAND}}' =>'<a href="/index.php" class="brand-link bg-red"> + Cronlog viewer + <span class="brand-text font-weight-light">v2.0</span> + </a>', + '{{NAVI_LEFT}}' =>' + <br> + <div class="form-inline"> + <div class="input-group"> + <input id="serverfiltertext" class="form-control form-control-sidebar" type="search" placeholder="Search" + + onchange="filterServers();" + onkeypress="filterServers();" + onkeyup="filterServers();" + + > + <div class="input-group-append"> + <button class="btn btn-sidebar" + onclick="$(\'#serverfiltertext\').val(\'\'); filterServers();" > + × + </button> + </div> + </div> + </div> + <div class="user-panel mt-3 pb-3 mb-3 d-flex"> + + <div id="selectserver" class="active"></div> + </div>', + '{{PAGE_HEADER_LEFT}}'=>'<h2 id="lblServername">Server wählen...</h2> + <nav id="subnav" class="navbar navbar-expand navbar-light tabs"> + <ul class="navbar-nav"> + <li class="nav-item"> + <a href="#cronlogs" onclick="setTab(this); return false;" class="nav-link active"> + <i class="far fa-file-alt"></i> Logs</a></li> + <li class="nav-item"> + <a href="#cronhistory" onclick="setTab(this); return false;" class="nav-link"> + <i class="fas fa-history"></i> History</a></li> + <li class="nav-item"> + <a href="#graph" onclick="setTab(this); return false;" class="nav-link"> + <i class="far fa-chart-bar"></i> Timeline</a></li> + </ul> + </nav>', + '{{PAGE_HEADER_RIGHT}}'=>'<span id="counter" style="float: right;"></span>', + '{{PAGE_BODY}}'=>'<div id="tabcontent"> + + <div id="cronlogs"> + <div class="card card-primary card-outline active"> + <div class="card-body" id="cronlogs-content"><br>Moment ...</div></div> + </div> + <div id="cronhistory"> + <div class="card card-info card-outline"> + <div class="card-body" id="cronhistory-content"><br>Moment ...</div></div> + </div> + <div id="graph"> + <div class="card card-success card-outline"> + <div class="card-body" id="graph-content"><br>Moment ...</div></div> + </div> + </div> + + + <div id="overlay" ondblclick="closeOverlay();"> + <div id="showlog" class="active"><br>Moment ...</div> + </div> + + ', + '{{PAGE_FOOTER_LEFT}}'=>'2018 -2022 // University of Bern * Institute for Medical education', + '{{PAGE_FOOTER_RIGHT}}'=>'Source: <a href="https://git-repo.iml.unibe.ch/iml-open-source/cronlog-viewer/">git-repo.iml.unibe.ch</a>', +]; \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 40e3f99b835b102ea6625e9e28e34d2c4c33d37f..0000000000000000000000000000000000000000 --- a/index.html +++ /dev/null @@ -1,62 +0,0 @@ -<!doctype html> -<html lang="de"> - <head> - <meta charset="utf-8" /> - <meta http-equiv="Pragma" content="no-cache"> - <meta name="robots" content="noindex,nofollow"> - - <title>Cronjob-Viewer</title> - - <script type="text/javascript" src="vendor/jquery/3.6.1/jquery.min.js"></script> - <script type="text/javascript" src="js/asimax.class.js"></script> - <script type="text/javascript" src="js/functions.js"></script> - - <script type="text/javascript" src="vendor/vis/4.21.0/vis.min.js"></script> - <link href="vendor/vis/4.21.0/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css"/> - - <script type="text/javascript" src="vendor/datatables/1.10.15/js/jquery.dataTables.min.js"></script> - <link href="vendor/datatables/1.10.15/css/jquery.dataTables.min.css" rel="stylesheet" type="text/css"/> - - <link href="vendor/font-awesome/5.15.4/css/all.min.css" rel="stylesheet" type="text/css"/> - <link rel="stylesheet" type="text/css" href="main.css"> - </head> - <body> - <div id="errorlog"></div> - <header> - <div id="instances">...</div> - <h1><a href="?"><span class="imllogo"></span> CronjobViewer</a></h1> - </header> - <nav class="servers"> - <div id="selectserver" class="active">...</div> - </nav> - <div id="content"> - - <h2 id="lblServername">Server wählen...</h2> - <span id="counter" style="float: right;"></span> - <nav class="tabs"> - <a href="#cronlogs" onclick="setTab(this); return false;" class="active"><i class="far fa-file-alt"></i> Logs</a> - <a href="#cronhistory" onclick="setTab(this); return false;"><i class="fas fa-history"></i> History</a> - <a href="#graph" onclick="setTab(this); return false;"><i class="far fa-chart-bar"></i> Timeline</a> - - </nav><br><br> - - <!-- - <button onclick="refreshPage(); return false;"><i class="fas fa-sync"></i> Reload</button> - --> - - <div id="tabcontent"> - <div id="cronlogs" class="active"><br>Moment ...</div> - <div id="cronhistory"><br>Moment ...</div> - <div id="graph"><br>Moment ...</div> - </div> - - </div> - <div style="clear: both"></div><br><br><br><br> - <footer> - © 2018 -2022<br> - Source: <a href="https://git-repo.iml.unibe.ch/iml-open-source/cronlog-viewer/" target="_blank">git-repo.iml.unibe.ch</a> - </footer> - <div id="overlay" ondblclick="closeOverlay();"> - <div id="showlog" class="active"><br>Moment ...</div> - </div> -</body></html> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000000000000000000000000000000000000..64a0288bd10fe97cc05a032e5207c18301e67028 --- /dev/null +++ b/index.php @@ -0,0 +1,10 @@ +<?php + +require_once('classes/render-adminlte.class.php'); +$renderAdminLTE=new renderadminlte(); + +$aReplace=include("./config/page_replacements.php"); +$sTemplate=file_get_contents('config/page.tpl.php'); + +// ---------- send content +echo $renderAdminLTE->render($sTemplate,$aReplace); diff --git a/main.css b/main.css index 47b0e1746ff92be5e9a82650d36354542db2c6db..eb39b828548ec376ea4c4b306c66d5f7d4a4a497 100644 --- a/main.css +++ b/main.css @@ -1,76 +1,31 @@ -/* - Author : hahn -*/ -:root{ - --bg-main:#fff; - --bg-header:#eee; - --border: #cde; -} -a {color: #38a;text-decoration: none;} -a:hover {text-decoration: underline;} -body{background:var(--bg-main); color:#456; font-family: verdana,arial; font-size:1.0em; margin: 0;} -button{background:#46a; border: none; color:#fff; padding: 0.2em 1em; border-radius: 0.3em; border: 2px solid rgba(0,0,0,0.1); box-shadow: 0.2em 0.2em 0.5em #aaa;} -button.add{background:#8c8;} -button.del{background:#c88;} -button:hover{background:#68c;} -button:active{box-shadow: none; } - -footer{position: fixed; bottom: 1em; right: 1em; background: var(--bg-header); border-top: 1px solid var(--border); padding: 1em; text-align: right;} - -header{border-bottom: 1px solid var(--border); background:var(--bg-header); margin: 0 0 0.3em; padding: 1.5em 1em;} -h1{margin: 0 0 0.3em; padding: 0; margin: 0;} -h1 a{color: #38a;} -h1 a:hover{text-decoration: none;} - #instances{float: right;} - #instances a{border: 0px solid; padding: 0.4em;} - #instances a:hover{background: var(--bg-main);} - #instances a.active{background: var(--bg-main);border-top: 1px solid var(--border); border-left: 1px solid var(--border);} - -h2{color:#379;} -h2 i{color:#abc; font-size: 140%;} -h3{color: #39b;} -h3:before{content: ':: '} -i.fa{font-size: 150%; } -i.fa.lookup{font-size: 100%; opacity: 0.4;} - -nav.servers{float: left; margin-right: 1em;} -#selectserver{min-height: 30em; border-right: 0px dashed #cde; padding: 0 0.5em; background: #fff; } - -nav.tabs a{display: block; float: left; background: rgba(0,60,60,0.05); padding: 0.5em 1em; color:#345; text-decoration: none; margin-right: 2px; border-top: 4px solid rgba(0,0,0,0.01);} -nav.tabs a.active{background: #d0e0e0; border-color: rgb(255,0,51); } -nav.tabs a:hover{border-color: #abc;} - -input{border: 1px solid #ddd; padding: 0.3em;} -option{font-family: verdana,arial; font-size:1.2em; } -option:hover{background: #eee;} -#selectserver select {border: 0; width: 100%;} +/* ----- serverlist on the left side */ +#selectserver{min-height: 20em;max-height: 30em; } +#selectserver select{background: none; border: none; max-height: 100%; color: #ddd;} -p.hint{margin-bottom: 1em; background:#fed; padding: 1em; } -table{border: 1px solid #ccc;} -tr:hover{background:#eee;} -th{background:#eee; padding:0.5em;} -td{padding:0.3em;} +/* ----- head area */ + +#counter{border-bottom: 1px solid #ccc;} +#counter div{background:#bcd; background:#aaa; height: 5px; transition: width 0.3s ;} -.imllogo:before {background: rgb(255,0,51);color: #fff;padding: 0.5em 0.3em;content: 'IML'; font-family: "arial"; text-shadow: none;} + +#subnav .nav-link.active{ + background: #e0e8f0; + border-top: 0px solid; +} /* ----- tabbed content */ #cronhistory,#cronlogs,#graph,#overlay{display: none;} #tabcontent div.active{display: block;} -#overlay{position: fixed; width: 100%; height: 100%; background: rgba(0,0,0,0.4); top:0; left: 0;} -#showlog{background:#fff; border: 3px solid #888; padding: 1em; margin: 2%; height: 90%; overflow: scroll;} -.log-rem{color:#aaa; font-style: italic;} -.log-var{color:#088;} -.log-value{color:#008;} -pre div:hover{background:rgba(0,0,0,0.1);} - -#counter{color:#abc;border-bottom: 1px solid #eee; margin-right: 1em;} -#counter div{background:#bcd; height: 5px; transition: width 1s ;} #errorlog {background:#fcc; color:#800;} -/* ----- override datatable defaukts */ + +p.hint{margin-bottom: 1em; background:#fed; padding: 1em; } + + +/* ----- override datatable defaults */ .dataTables_wrapper{clear: none;float: left; margin: auto 1px;} table.dataTable tbody tr.even{background: #f0f4f8;} /* table.dataTable tbody tr.odd{} */ @@ -86,8 +41,7 @@ table.dataTable tbody tr:hover{background:rgba(0,0,0,0.1);} table.dataTable tbody td ul{margin: 0; padding-left: 0.8em;} -/* timeline -*/ +/* ----- timeline */ .vis-item.timeline-result-error{background:#fcc; border-color: #800} .vis-item.timeline-result-ok{background:#cfc;border-color: #080} @@ -106,31 +60,12 @@ table.dataTable tbody td ul{margin: 0; padding-left: 0.8em;} border-bottom: 4px solid #abc; } - /* - .vis-h0, .vis-h1,.vis-h2,.vis-h3, .vis-h4, .vis-h5, .vis-h6,.vis-h7,.vis-h18,.vis-h19,.vis-h20,.vis-h21,.vis-h22,.vis-h23{ - background: #e0e4e8; - } - .vis-h8, .vis-h9,.vis-h10,.vis-h11, .vis-h12, .vis-h13, .vis-h14,.vis-h15,.vis-h16,.vis-h17{ - background: #f0f4f8; - } - alternating column backgrounds - .vis-time-axis .vis-odd { - background: #fff; - } - .vis-time-axis .vis-even { - background: #f0f4f8; - } - .vis-time-axis{ - background: #fea; - } +/* ----- overlay */ -gray background in weekends, white text color - .vis-time-axis .vis-grid.vis-saturday, - .vis-time-axis .vis-grid.vis-sunday { - background: #ed8 !important; - } - .vis-time-axis .vis-text.vis-saturday, - .vis-time-axis .vis-text.vis-sunday { - } +#overlay{position: fixed; width: 100%; height: 100%; background: rgba(0,0,0,0.4); top:0; left: 0; z-index: 10000;} +#showlog{background:#fff; border: 3px solid #888; padding: 1em; margin: 2%; height: 90%; overflow: scroll;} -*/ +.log-rem{color:#aaa; font-style: italic;} +.log-var{color:#088;} +.log-value{color:#008;} +pre div:hover{background:rgba(0,0,0,0.1);}