commit
						c4a41bebd9
					
				|  | @ -704,6 +704,10 @@ class ACLComponent extends Component | |||
|                         'home' => [ | ||||
|                             'url' => '/instance/home', | ||||
|                             'label' => __('Home') | ||||
|                         ], | ||||
|                         'migration' => [ | ||||
|                             'url' => '/instance/migrationIndex', | ||||
|                             'label' => __('Database migration') | ||||
|                         ] | ||||
|                     ] | ||||
|                 ], | ||||
|  |  | |||
|  | @ -6,12 +6,18 @@ use App\Controller\AppController; | |||
| use Cake\Utility\Hash; | ||||
| use Cake\Utility\Text; | ||||
| use \Cake\Database\Expression\QueryExpression; | ||||
| use Cake\Event\EventInterface; | ||||
| 
 | ||||
| class InstanceController extends AppController | ||||
| { | ||||
|     public function beforeFilter(EventInterface $event) | ||||
|     { | ||||
|         parent::beforeFilter($event); | ||||
|         $this->set('metaGroup', !empty($this->isAdmin) ? 'Cerebrate' : 'Administration'); | ||||
|     } | ||||
| 
 | ||||
|     public function home() | ||||
|     { | ||||
|         $this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate'); | ||||
|         $this->set('md', file_get_contents(ROOT . '/README.md')); | ||||
|     } | ||||
| 
 | ||||
|  | @ -22,4 +28,77 @@ class InstanceController extends AppController | |||
|         $data['user'] = $this->ACL->getUser(); | ||||
|         return $this->RestResponse->viewData($data, 'json'); | ||||
|     } | ||||
| 
 | ||||
|     public function migrationIndex() | ||||
|     { | ||||
|         $migrationStatus = $this->Instance->getMigrationStatus(); | ||||
| 
 | ||||
|         $this->loadModel('Phinxlog'); | ||||
|         $status = $this->Phinxlog->mergeMigrationLogIntoStatus($migrationStatus['status']); | ||||
| 
 | ||||
|         $this->set('status', $status); | ||||
|         $this->set('updateAvailables', $migrationStatus['updateAvailables']); | ||||
|     } | ||||
| 
 | ||||
|     public function migrate($version=null) { | ||||
|         if ($this->request->is('post')) { | ||||
|             if (is_null($version)) { | ||||
|                 $migrateResult = $this->Instance->migrate(); | ||||
|             } else { | ||||
|                 $migrateResult = $this->Instance->migrate(['target' => $version]); | ||||
|             } | ||||
|             if ($this->ParamHandler->isRest() || $this->ParamHandler->isAjax()) { | ||||
|                 if ($migrateResult['success']) { | ||||
|                     return $this->RestResponse->saveSuccessResponse('instance', 'migrate', false, false, __('Migration sucessful')); | ||||
|                 } else { | ||||
|                     return $this->RestResponse->saveFailResponse('instance', 'migrate', false, $migrateResult['error']); | ||||
|                 } | ||||
|             } else { | ||||
|                 if ($migrateResult['success']) { | ||||
|                     $this->Flash->success(__('Migration sucessful')); | ||||
|                     $this->redirect(['action' => 'migrationIndex']); | ||||
|                 } else { | ||||
|                     $this->Flash->error(__('Migration fail')); | ||||
|                     $this->redirect(['action' => 'migrationIndex']); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         $migrationStatus = $this->Instance->getMigrationStatus(); | ||||
|         $this->set('title',  __n('Run database update?', 'Run all database updates?', count($migrationStatus['updateAvailables']))); | ||||
|         $this->set('question', __('The process might take some time.')); | ||||
|         $this->set('actionName', __n('Run update', 'Run all updates', count($migrationStatus['updateAvailables']))); | ||||
|         $this->set('path', ['controller' => 'instance', 'action' => 'migrate']); | ||||
|         $this->render('/genericTemplates/confirm'); | ||||
|     } | ||||
| 
 | ||||
|     public function rollback($version=null) { | ||||
|         if ($this->request->is('post')) { | ||||
|             if (is_null($version)) { | ||||
|                 $migrateResult = $this->Instance->rollback(); | ||||
|             } else { | ||||
|                 $migrateResult = $this->Instance->rollback(['target' => $version]); | ||||
|             } | ||||
|             if ($this->ParamHandler->isRest() || $this->ParamHandler->isAjax()) { | ||||
|                 if ($migrateResult['success']) { | ||||
|                     return $this->RestResponse->saveSuccessResponse('instance', 'rollback', false, false, __('Rollback sucessful')); | ||||
|                 } else { | ||||
|                     return $this->RestResponse->saveFailResponse('instance', 'rollback', false, $migrateResult['error']); | ||||
|                 } | ||||
|             } else { | ||||
|                 if ($migrateResult['success']) { | ||||
|                     $this->Flash->success(__('Rollback sucessful')); | ||||
|                     $this->redirect(['action' => 'migrationIndex']); | ||||
|                 } else { | ||||
|                     $this->Flash->error(__('Rollback fail')); | ||||
|                     $this->redirect(['action' => 'migrationIndex']); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         $migrationStatus = $this->Instance->getMigrationStatus(); | ||||
|         $this->set('title',  __('Run database rollback?')); | ||||
|         $this->set('question', __('The process might take some time.')); | ||||
|         $this->set('actionName', __('Run rollback')); | ||||
|         $this->set('path', ['controller' => 'instance', 'action' => 'rollback']); | ||||
|         $this->render('/genericTemplates/confirm'); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ namespace App\Model\Table; | |||
| use App\Model\Table\AppTable; | ||||
| use Cake\ORM\Table; | ||||
| use Cake\Validation\Validator; | ||||
| use Migrations\Migrations; | ||||
| 
 | ||||
| class InstanceTable extends AppTable | ||||
| { | ||||
|  | @ -17,4 +18,43 @@ class InstanceTable extends AppTable | |||
|     { | ||||
|         return $validator; | ||||
|     } | ||||
| 
 | ||||
|     public function getMigrationStatus() | ||||
|     { | ||||
|         $migrations = new Migrations(); | ||||
|         $status = $migrations->status(); | ||||
|         $status = array_reverse($status); | ||||
| 
 | ||||
|         $updateAvailables = array_filter($status, function ($update) { | ||||
|             return $update['status'] != 'up'; | ||||
|         }); | ||||
|         return [ | ||||
|             'status' => $status, | ||||
|             'updateAvailables' => $updateAvailables, | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function migrate($version=null) { | ||||
|         $migrations = new Migrations(); | ||||
|         if (is_null($version)) { | ||||
|             $migrationResult = $migrations->migrate(); | ||||
|         } else { | ||||
|             $migrationResult = $migrations->migrate(['target' => $version]); | ||||
|         } | ||||
|         return [ | ||||
|             'success' => true | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rollback($version=null) { | ||||
|         $migrations = new Migrations(); | ||||
|         if (is_null($version)) { | ||||
|             $migrationResult = $migrations->rollback(); | ||||
|         } else { | ||||
|             $migrationResult = $migrations->rollback(['target' => $version]); | ||||
|         } | ||||
|         return [ | ||||
|             'success' => true | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,48 @@ | |||
| <?php | ||||
| 
 | ||||
| namespace App\Model\Table; | ||||
| 
 | ||||
| use App\Model\Table\AppTable; | ||||
| use Cake\ORM\Table; | ||||
| 
 | ||||
| class PhinxlogTable extends AppTable | ||||
| { | ||||
|     public function initialize(array $config): void | ||||
|     { | ||||
|         parent::initialize($config); | ||||
|     } | ||||
| 
 | ||||
|     public function mergeMigrationLogIntoStatus(array $status): array | ||||
|     { | ||||
|         $logs = $this->find('list', [ | ||||
|             'keyField' => 'version', | ||||
|             'valueField' => function ($entry) { | ||||
|                 return $entry; | ||||
|             } | ||||
|         ])->toArray(); | ||||
|         foreach ($status as &$entry) { | ||||
|             if (!empty($logs[$entry['id']])) { | ||||
|                 $logEntry = $logs[$entry['id']]; | ||||
|                 $startTime = $logEntry['start_time']; | ||||
|                 $startTime->setToStringFormat('yyyy-MM-dd HH:mm:ss'); | ||||
|                 $endTime = $logEntry['end_time']; | ||||
|                 $endTime->setToStringFormat('yyyy-MM-dd HH:mm:ss'); | ||||
|                 $timeTaken = $logEntry['end_time']->diff($logEntry['start_time']); | ||||
|                 $timeTakenFormated = sprintf('%s min %s sec', | ||||
|                     floor(abs($logEntry['end_time']->getTimestamp() - $logEntry['start_time']->getTimestamp()) / 60), | ||||
|                     abs($logEntry['end_time']->getTimestamp() - $logEntry['start_time']->getTimestamp()) % 60 | ||||
|                 ); | ||||
|             } else { | ||||
|                 $startTime = 'N/A'; | ||||
|                 $endTime = 'N/A'; | ||||
|                 $timeTaken = 'N/A'; | ||||
|                 $timeTakenFormated = 'N/A'; | ||||
|             } | ||||
|             $entry['start_time'] = $startTime; | ||||
|             $entry['end_time'] = $endTime; | ||||
|             $entry['time_taken'] = $timeTaken; | ||||
|             $entry['time_taken_formated'] = $timeTakenFormated; | ||||
|         } | ||||
|         return $status; | ||||
|     } | ||||
| } | ||||
|  | @ -52,9 +52,78 @@ class BootstrapHelper extends Helper | |||
|         $bsTabs = new BootstrapTabs($options); | ||||
|         return $bsTabs->tabs(); | ||||
|     } | ||||
| 
 | ||||
|     public function alert($options) | ||||
|     { | ||||
|         $bsAlert = new BoostrapAlert($options); | ||||
|         return $bsAlert->alert(); | ||||
|     } | ||||
| 
 | ||||
|     public function table($options, $data) | ||||
|     { | ||||
|         $bsTable = new BoostrapTable($options, $data); | ||||
|         return $bsTable->table(); | ||||
|     } | ||||
| 
 | ||||
|     public function button($options) | ||||
|     { | ||||
|         $bsButton = new BoostrapButton($options); | ||||
|         return $bsButton->button(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class BootstrapTabs extends Helper | ||||
| class BootstrapGeneric | ||||
| { | ||||
|     public static $variants = ['primary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent']; | ||||
|     protected $allowedOptionValues = []; | ||||
|     protected $options = []; | ||||
| 
 | ||||
|     protected function checkOptionValidity() | ||||
|     { | ||||
|         foreach ($this->allowedOptionValues as $option => $values) { | ||||
|             if (!isset($this->options[$option])) { | ||||
|                 throw new InvalidArgumentException(__('Option `{0}` should have a value', $option)); | ||||
|             } | ||||
|             if (!in_array($this->options[$option], $values)) { | ||||
|                 throw new InvalidArgumentException(__('Option `{0}` is not a valid option for `{1}`. Accepted values: {2}', json_encode($this->options[$option]), $option, json_encode($values))); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected static function genNode($node, $params=[], $content="") | ||||
|     { | ||||
|         return sprintf('<%s %s>%s</%s>', $node, BootstrapGeneric::genHTMLParams($params), $content, $node); | ||||
|     } | ||||
| 
 | ||||
|     protected static function openNode($node, $params=[]) | ||||
|     { | ||||
|         return sprintf('<%s %s>', $node, BootstrapGeneric::genHTMLParams($params)); | ||||
|     } | ||||
| 
 | ||||
|     protected static function closeNode($node) | ||||
|     { | ||||
|         return sprintf('</%s>', $node); | ||||
|     } | ||||
| 
 | ||||
|     protected static function genHTMLParams($params) | ||||
|     { | ||||
|         $html = ''; | ||||
|         foreach ($params as $k => $v) { | ||||
|             $html .= BootstrapGeneric::genHTMLParam($k, $v) . ' '; | ||||
|         } | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     protected static function genHTMLParam($paramName, $values) | ||||
|     { | ||||
|         if (!is_array($values)) { | ||||
|             $values = [$values]; | ||||
|         } | ||||
|         return sprintf('%s="%s"', $paramName, implode(' ', $values)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class BootstrapTabs extends BootstrapGeneric | ||||
| { | ||||
|     private $defaultOptions = [ | ||||
|         'fill' => false, | ||||
|  | @ -73,17 +142,14 @@ class BootstrapTabs extends Helper | |||
|             'content' => [], | ||||
|         ], | ||||
|     ]; | ||||
| 
 | ||||
|     private $allowedOptionValues = [ | ||||
|         'justify' => [false, 'center', 'end'], | ||||
|         'body-variant' => ['primary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent', ''], | ||||
|         'header-variant' => ['primary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent'], | ||||
|     ]; | ||||
| 
 | ||||
|     private $options = null; | ||||
|     private $bsClasses = null; | ||||
| 
 | ||||
|     function __construct($options) { | ||||
|         $this->allowedOptionValues = [ | ||||
|             'justify' => [false, 'center', 'end'], | ||||
|             'body-variant' => array_merge(BootstrapGeneric::$variants, ['']), | ||||
|             'header-variant' => BootstrapGeneric::$variants, | ||||
|         ]; | ||||
|         $this->processOptions($options); | ||||
|     } | ||||
| 
 | ||||
|  | @ -97,6 +163,9 @@ class BootstrapTabs extends Helper | |||
|         $this->options = array_merge($this->defaultOptions, $options); | ||||
|         $this->data = $this->options['data']; | ||||
|         $this->checkOptionValidity(); | ||||
|         if (empty($this->data['navs'])) { | ||||
|             throw new InvalidArgumentException(__('No navigation data provided')); | ||||
|         } | ||||
|         $this->bsClasses = [ | ||||
|             'nav' => [], | ||||
|             'nav-item' => $this->options['nav-item-class'], | ||||
|  | @ -162,21 +231,6 @@ class BootstrapTabs extends Helper | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function checkOptionValidity() | ||||
|     { | ||||
|         foreach ($this->allowedOptionValues as $option => $values) { | ||||
|             if (!isset($this->options[$option])) { | ||||
|                 throw new InvalidArgumentException(__('Option `{0}` should have a value', $option)); | ||||
|             } | ||||
|             if (!in_array($this->options[$option], $values)) { | ||||
|                 throw new InvalidArgumentException(__('Option `{0}` is not a valid option for `{1}`. Accepted values: {2}', json_encode($this->options[$option]), $option, json_encode($values))); | ||||
|             } | ||||
|         } | ||||
|         if (empty($this->data['navs'])) { | ||||
|             throw new InvalidArgumentException(__('No navigation data provided')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function genTabs() | ||||
|     { | ||||
|         $html = ''; | ||||
|  | @ -192,55 +246,55 @@ class BootstrapTabs extends Helper | |||
|     { | ||||
|         $html = ''; | ||||
|         if ($this->options['card']) { | ||||
|             $html .= $this->genNode('div', ['class' => array_merge(['card'], ["border-{$this->options['header-border-variant']}"])]); | ||||
|             $html .= $this->genNode('div', ['class' => array_merge(['card-header'], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}"])]); | ||||
|             $html .= $this->openNode('div', ['class' => array_merge(['card'], ["border-{$this->options['header-border-variant']}"])]); | ||||
|             $html .= $this->openNode('div', ['class' => array_merge(['card-header'], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}"])]); | ||||
|         } | ||||
|         $html .= $this->genNav(); | ||||
|         if ($this->options['card']) { | ||||
|             $html .= '</div>'; | ||||
|             $html .= $this->genNode('div', ['class' => array_merge(['card-body'], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]); | ||||
|             $html .= $this->closeNode('div'); | ||||
|             $html .= $this->openNode('div', ['class' => array_merge(['card-body'], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]); | ||||
|         } | ||||
|         $html .= $this->genContent(); | ||||
|         if ($this->options['card']) { | ||||
|             $html .= '</div>'; | ||||
|             $html .= '</div>'; | ||||
|             $html .= $this->closeNode('div'); | ||||
|             $html .= $this->closeNode('div'); | ||||
|         } | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genVerticalTabs() | ||||
|     { | ||||
|         $html = $this->genNode('div', ['class' => array_merge(['row', ($this->options['card'] ? 'card flex-row' : '')], ["border-{$this->options['header-border-variant']}"])]); | ||||
|             $html .= $this->genNode('div', ['class' => array_merge(['col-' . $this->options['vertical-size'], ($this->options['card'] ? 'card-header border-right' : '')], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}", "border-{$this->options['header-border-variant']}"])]); | ||||
|         $html = $this->openNode('div', ['class' => array_merge(['row', ($this->options['card'] ? 'card flex-row' : '')], ["border-{$this->options['header-border-variant']}"])]); | ||||
|             $html .= $this->openNode('div', ['class' => array_merge(['col-' . $this->options['vertical-size'], ($this->options['card'] ? 'card-header border-right' : '')], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}", "border-{$this->options['header-border-variant']}"])]); | ||||
|                 $html .= $this->genNav(); | ||||
|             $html .= '</div>'; | ||||
|             $html .= $this->genNode('div', ['class' => array_merge(['col-' . (12 - $this->options['vertical-size']), ($this->options['card'] ? 'card-body2' : '')], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]); | ||||
|             $html .= $this->closeNode('div'); | ||||
|             $html .= $this->openNode('div', ['class' => array_merge(['col-' . (12 - $this->options['vertical-size']), ($this->options['card'] ? 'card-body2' : '')], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]); | ||||
|                 $html .= $this->genContent(); | ||||
|             $html .= '</div>'; | ||||
|         $html .= '</div>'; | ||||
|             $html .= $this->closeNode('div'); | ||||
|         $html .= $this->closeNode('div'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genNav() | ||||
|     { | ||||
|         $html = $this->genNode('ul', [ | ||||
|         $html = $this->openNode('ul', [ | ||||
|             'class' => array_merge(['nav'], $this->bsClasses['nav'], $this->options['nav-class']), | ||||
|             'role' => 'tablist', | ||||
|         ]); | ||||
|         foreach ($this->data['navs'] as $navItem) { | ||||
|             $html .= $this->genNavItem($navItem); | ||||
|         } | ||||
|         $html .= '</ul>'; | ||||
|         $html .= $this->closeNode('ul'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genNavItem($navItem) | ||||
|     { | ||||
|         $html = $this->genNode('li', [ | ||||
|         $html = $this->openNode('li', [ | ||||
|             'class' => array_merge(['nav-item'], $this->bsClasses['nav-item'], $this->options['nav-item-class']), | ||||
|             'role' => 'presentation', | ||||
|         ]); | ||||
|         $html .= $this->genNode('a', [ | ||||
|         $html .= $this->openNode('a', [ | ||||
|             'class' => array_merge( | ||||
|                 ['nav-link'], | ||||
|                 [!empty($navItem['active']) ? 'active' : ''], | ||||
|  | @ -258,56 +312,322 @@ class BootstrapTabs extends Helper | |||
|         } else { | ||||
|             $html .= h($navItem['text']); | ||||
|         } | ||||
|         $html .= '</a></li>'; | ||||
|         $html .= $this->closeNode('a'); | ||||
|         $html .= $this->closeNode('li'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genContent() | ||||
|     { | ||||
|         $html = $this->genNode('div', [ | ||||
|         $html = $this->openNode('div', [ | ||||
|             'class' => array_merge(['tab-content'], $this->options['content-class']), | ||||
|         ]); | ||||
|         foreach ($this->data['content'] as $i => $content) { | ||||
|             $navItem = $this->data['navs'][$i]; | ||||
|             $html .= $this->genContentItem($navItem, $content); | ||||
|         } | ||||
|         $html .= '</div>'; | ||||
|         $html .= $this->closeNode('div'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genContentItem($navItem, $content) | ||||
|     { | ||||
|         $html = $this->genNode('div', [ | ||||
|         return $this->genNode('div', [ | ||||
|             'class' => array_merge(['tab-pane', 'fade'], [!empty($navItem['active']) ? 'show active' : '']), | ||||
|             'role' => 'tabpanel', | ||||
|             'id' => $navItem['id'], | ||||
|             'aria-labelledby' => $navItem['id'] . '-tab' | ||||
|         ]); | ||||
|         $html .= $content; | ||||
|         $html .= '</div>'; | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genNode($node, $params) | ||||
|     { | ||||
|         return sprintf('<%s %s>', $node, $this->genHTMLParams($params)); | ||||
|     } | ||||
| 
 | ||||
|     private function genHTMLParams($params) | ||||
|     { | ||||
|         $html = ''; | ||||
|         foreach ($params as $k => $v) { | ||||
|             $html .= $this->genHTMLParam($k, $v) . ' '; | ||||
|         } | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genHTMLParam($paramName, $values) | ||||
|     { | ||||
|         if (!is_array($values)) { | ||||
|             $values = [$values]; | ||||
|         } | ||||
|         return sprintf('%s="%s"', $paramName, implode(' ', $values)); | ||||
|         ], $content); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class BoostrapAlert extends BootstrapGeneric { | ||||
|     private $defaultOptions = [ | ||||
|         'text' => '', | ||||
|         'html' => null, | ||||
|         'dismissible' => true, | ||||
|         'variant' => 'primary', | ||||
|         'fade' => true | ||||
|     ]; | ||||
| 
 | ||||
|     private $bsClasses = null; | ||||
| 
 | ||||
|     function __construct($options) { | ||||
|         $this->allowedOptionValues = [ | ||||
|             'variant' => BootstrapGeneric::$variants, | ||||
|         ]; | ||||
|         $this->processOptions($options); | ||||
|     } | ||||
| 
 | ||||
|     private function processOptions($options) | ||||
|     { | ||||
|         $this->options = array_merge($this->defaultOptions, $options); | ||||
|         $this->checkOptionValidity(); | ||||
|     } | ||||
| 
 | ||||
|     public function alert() | ||||
|     { | ||||
|         return $this->genAlert(); | ||||
|     } | ||||
| 
 | ||||
|     private function genAlert() | ||||
|     { | ||||
|         $html = $this->openNode('div', [ | ||||
|             'class' => [ | ||||
|                 'alert', | ||||
|                 "alert-{$this->options['variant']}", | ||||
|                 $this->options['dismissible'] ? 'alert-dismissible' : '', | ||||
|                 $this->options['fade'] ? 'fade show' : '', | ||||
|             ], | ||||
|             'role' => "alert" | ||||
|         ]); | ||||
| 
 | ||||
|         $html .= $this->genContent(); | ||||
|         $html .= $this->genCloseButton(); | ||||
|         $html .= $this->closeNode('div'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genCloseButton() | ||||
|     { | ||||
|         $html = ''; | ||||
|         if ($this->options['dismissible']) { | ||||
|             $html .= $this->openNode('button', [ | ||||
|                 'type' => 'button', | ||||
|                 'class' => 'close', | ||||
|                 'data-dismiss' => 'alert', | ||||
|                 'arial-label' => 'close' | ||||
|             ]); | ||||
|             $html .= $this->genNode('span', [ | ||||
|                 'arial-hidden' => 'true' | ||||
|             ], '×'); | ||||
|             $html .= $this->closeNode('button'); | ||||
|         } | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genContent() | ||||
|     { | ||||
|         return !is_null($this->options['html']) ? $this->options['html'] : $this->options['text']; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class BoostrapTable extends BootstrapGeneric { | ||||
|     private $defaultOptions = [ | ||||
|         'striped' => true, | ||||
|         'bordered' => true, | ||||
|         'borderless' => false, | ||||
|         'hover' => true, | ||||
|         'small' => false, | ||||
|         'variant' => '', | ||||
|         'tableClass' => [], | ||||
|         'headerClass' => [], | ||||
|         'bodyClass' => [], | ||||
|     ]; | ||||
| 
 | ||||
|     private $bsClasses = null; | ||||
| 
 | ||||
|     function __construct($options, $data) { | ||||
|         $this->allowedOptionValues = [ | ||||
|             'variant' => array_merge(BootstrapGeneric::$variants, ['']) | ||||
|         ]; | ||||
|         $this->processOptions($options); | ||||
|         $this->fields = $data['fields']; | ||||
|         $this->items = $data['items']; | ||||
|         $this->caption = !empty($data['caption']) ? $data['caption'] : ''; | ||||
|     } | ||||
| 
 | ||||
|     private function processOptions($options) | ||||
|     { | ||||
|         $this->options = array_merge($this->defaultOptions, $options); | ||||
|         $this->checkOptionValidity(); | ||||
|     } | ||||
| 
 | ||||
|     public function table() | ||||
|     { | ||||
|         return $this->genTable(); | ||||
|     } | ||||
| 
 | ||||
|     private function genTable() | ||||
|     { | ||||
|         $html = $this->openNode('table', [ | ||||
|             'class' => [ | ||||
|                 'table', | ||||
|                 "table-{$this->options['variant']}", | ||||
|                 $this->options['striped'] ? 'table-striped' : '', | ||||
|                 $this->options['bordered'] ? 'table-bordered' : '', | ||||
|                 $this->options['borderless'] ? 'table-borderless' : '', | ||||
|                 $this->options['hover'] ? 'table-hover' : '', | ||||
|                 $this->options['small'] ? 'table-sm' : '', | ||||
|                 !empty($this->options['variant']) ? "table-{$this->options['variant']}" : '', | ||||
|                 !empty($this->options['tableClass']) ? $this->options['tableClass'] : '' | ||||
|             ], | ||||
|         ]); | ||||
| 
 | ||||
|         $html .= $this->genCaption(); | ||||
|         $html .= $this->genHeader(); | ||||
|         $html .= $this->genBody(); | ||||
|          | ||||
|         $html .= $this->closeNode('table'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genHeader() | ||||
|     { | ||||
|         $head =  $this->openNode('thead', [ | ||||
|             'class' => [ | ||||
|                 !empty($this->options['headerClass']) ? $this->options['headerClass'] : '' | ||||
|             ], | ||||
|         ]); | ||||
|         $head .= $this->openNode('tr'); | ||||
|         foreach ($this->fields as $i => $field) { | ||||
|             if (is_array($field)) { | ||||
|                 $label = !empty($field['label']) ? $field['label'] : Inflector::humanize($field['key']); | ||||
|             } else { | ||||
|                 $label = Inflector::humanize($field); | ||||
|             } | ||||
|             $head .= $this->genNode('th', [], h($label)); | ||||
|         } | ||||
|         $head .= $this->closeNode('tr'); | ||||
|         $head .= $this->closeNode('thead'); | ||||
|         return $head; | ||||
|     } | ||||
| 
 | ||||
|     private function genBody() | ||||
|     { | ||||
|         $body =  $this->openNode('tbody', [ | ||||
|             'class' => [ | ||||
|                 !empty($this->options['bodyClass']) ? $this->options['bodyClass'] : '' | ||||
|             ], | ||||
|         ]); | ||||
|         foreach ($this->items as $i => $row) { | ||||
|             $body .= $this->genRow($row); | ||||
|         } | ||||
|         $body .= $this->closeNode('tbody'); | ||||
|         return $body; | ||||
|     } | ||||
| 
 | ||||
|     private function genRow($row) | ||||
|     { | ||||
|         $html = $this->openNode('tr',[ | ||||
|             'class' => [ | ||||
|                 !empty($row['_rowVariant']) ? "table-{$row['_rowVariant']}" : '' | ||||
|             ] | ||||
|         ]); | ||||
|         if (array_keys($row) !== range(0, count($row) - 1)) { // associative array
 | ||||
|             foreach ($this->fields as $i => $field) { | ||||
|                 if (is_array($field)) { | ||||
|                     $key = $field['key']; | ||||
|                 } else { | ||||
|                     $key = $field; | ||||
|                 } | ||||
|                 $cellValue = $row[$key]; | ||||
|                 $html .= $this->genCell($cellValue, $field, $row); | ||||
|             } | ||||
|         } else { // indexed array
 | ||||
|             foreach ($row as $cellValue) { | ||||
|                 $html .= $this->genCell($cellValue, $field, $row); | ||||
|             } | ||||
|         } | ||||
|         $html .= $this->closeNode('tr'); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genCell($value, $field=[], $row=[]) | ||||
|     { | ||||
|         if (isset($field['formatter'])) { | ||||
|             $cellContent = $field['formatter']($value, $row); | ||||
|         } else { | ||||
|             $cellContent = h($value); | ||||
|         } | ||||
|         return $this->genNode('td', [ | ||||
|             'class' => [ | ||||
|                 !empty($row['_cellVariant']) ? "bg-{$row['_cellVariant']}" : '' | ||||
|             ] | ||||
|         ], $cellContent); | ||||
|     } | ||||
| 
 | ||||
|     private function genCaption() | ||||
|     { | ||||
|         return $this->genNode('caption', [], h($this->caption)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class BoostrapButton extends BootstrapGeneric { | ||||
|     private $defaultOptions = [ | ||||
|         'id' => '', | ||||
|         'text' => '', | ||||
|         'html' => null, | ||||
|         'variant' => 'primary', | ||||
|         'outline' => false, | ||||
|         'size' => '', | ||||
|         'block' => false, | ||||
|         'icon' => null, | ||||
|         'class' => [], | ||||
|         'type' => 'button', | ||||
|         'nodeType' => 'button', | ||||
|         'params' => [] | ||||
|     ]; | ||||
| 
 | ||||
|     private $bsClasses = []; | ||||
| 
 | ||||
|     function __construct($options) { | ||||
|         $this->allowedOptionValues = [ | ||||
|             'variant' => BootstrapGeneric::$variants, | ||||
|             'size' => ['', 'sm', 'lg'], | ||||
|             'type' => ['button', 'submit', 'reset'] | ||||
|         ]; | ||||
|         $options['class'] = !is_array($options['class']) ? [$options['class']] : $options['class']; | ||||
|         $this->processOptions($options); | ||||
|     } | ||||
| 
 | ||||
|     private function processOptions($options) | ||||
|     { | ||||
|         $this->options = array_merge($this->defaultOptions, $options); | ||||
|         $this->checkOptionValidity(); | ||||
| 
 | ||||
|         $this->bsClasses[] = 'btn'; | ||||
|         if ($this->options['outline']) { | ||||
|             $this->bsClasses[] = "btn-outline-{$this->options['variant']}"; | ||||
|         } else { | ||||
|             $this->bsClasses[] = "btn-{$this->options['variant']}"; | ||||
|         } | ||||
|         if (!empty($this->options['size'])) { | ||||
|             $this->bsClasses[] = "btn-$this->options['size']"; | ||||
|         } | ||||
|         if ($this->options['block']) { | ||||
|             $this->bsClasses[] = 'btn-block'; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function button() | ||||
|     { | ||||
|         return $this->genButton(); | ||||
|     } | ||||
| 
 | ||||
|     private function genButton() | ||||
|     { | ||||
|         $html = $this->openNode($this->options['nodeType'], array_merge($this->options['params'], [ | ||||
|             'class' => array_merge($this->options['class'], $this->bsClasses), | ||||
|             'role' => "alert", | ||||
|             'type' => $this->options['type'] | ||||
|         ])); | ||||
| 
 | ||||
|         $html .= $this->genIcon(); | ||||
|         $html .= $this->genContent(); | ||||
|         $html .= $this->closeNode($this->options['nodeType']); | ||||
|         return $html; | ||||
|     } | ||||
| 
 | ||||
|     private function genIcon() | ||||
|     { | ||||
|         return $this->genNode('span', [ | ||||
|             'class' => ['mr-1', "fa fa-{$this->options['icon']}"], | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     private function genContent() | ||||
|     { | ||||
|         return !is_null($this->options['html']) ? $this->options['html'] : $this->options['text']; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,61 @@ | |||
| <?php | ||||
| if (!empty($updateAvailables)) { | ||||
|     $alertHtml = sprintf( | ||||
|         '<h5 class="alert-heading">%s</h5>%s<div>%s</div>', | ||||
|         __n('A new update is available!', 'New updates are available!', count($updateAvailables)), | ||||
|         __('Updating to the latest version is highly recommanded.'), | ||||
|         $this->Bootstrap->button([ | ||||
|             'variant' => 'success', | ||||
|             'icon' => 'arrow-alt-circle-up', | ||||
|             'class' => 'mt-1', | ||||
|             'text' => __n('Run update', 'Run all updates', count($updateAvailables)), | ||||
|             'params' => [ | ||||
|                 'onclick' => 'runAllUpdate()' | ||||
|             ] | ||||
|         ]) | ||||
|     ); | ||||
|     echo $this->Bootstrap->alert([ | ||||
|         'variant' => 'warning', | ||||
|         'html' => $alertHtml, | ||||
|         'dismissible' => false, | ||||
|     ]); | ||||
| } | ||||
| 
 | ||||
| foreach ($status as $i => &$update) { | ||||
|     if ($update['status'] == 'up') { | ||||
|         $update['_rowVariant'] = 'success'; | ||||
|     } else if ($update['status'] == 'down') { | ||||
|         $update['_rowVariant'] = 'danger'; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| echo $this->Bootstrap->table([], [ | ||||
|     'fields' => [ | ||||
|         ['key' => 'id', 'label' => __('ID')], | ||||
|         ['key' => 'name', 'label' => __('Name')], | ||||
|         ['key' => 'end_time', 'label' => __('End Time')], | ||||
|         ['key' => 'time_taken_formated', 'label' => __('Time Taken')], | ||||
|         ['key' => 'status', 'label' => __('Status')] | ||||
|     ], | ||||
|     'items' => $status, | ||||
| ]); | ||||
| ?>
 | ||||
| 
 | ||||
| <script> | ||||
| function runAllUpdate() { | ||||
|     const url = '/instance/migrate' | ||||
|     const reloadUrl = '/instance/migrate-index' | ||||
|     const modalOptions = { | ||||
|         title: '<?= __n('Run database update?', 'Run all database updates?', count($updateAvailables)) ?>', | ||||
|         body: '<?= __('The process might take some time.') ?>', | ||||
|         type: 'confirm-success', | ||||
|         confirmText: '<?= __n('Run update', 'Run all updates', count($updateAvailables)) ?>', | ||||
|         APIConfirm: (tmpApi) => { | ||||
|             tmpApi.fetchAndPostForm(url, {}).then(() => { | ||||
|                 location.reload() | ||||
|             }) | ||||
|         }, | ||||
|     } | ||||
|     UI.modal(modalOptions) | ||||
| } | ||||
| </script> | ||||
|  | @ -142,7 +142,7 @@ class AJAXApi { | |||
|     static async quickFetchAndPostForm(url, dataToMerge={}, options={}) { | ||||
|         const constAlteredOptions = Object.assign({}, {}, options) | ||||
|         const tmpApi = new AJAXApi(constAlteredOptions) | ||||
|         return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks) | ||||
|         return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks, constAlteredOptions.skipFeedback) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -335,14 +335,14 @@ class AJAXApi { | |||
|      * @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped | ||||
|      * @return {Promise<Object>} Promise object resolving to the result of the POST operation | ||||
|      */ | ||||
|     async fetchAndPostForm(url, dataToMerge={}, skipRequestHooks=false) { | ||||
|     async fetchAndPostForm(url, dataToMerge={}, skipRequestHooks=false, skipFeedback=false) { | ||||
|         if (!skipRequestHooks) { | ||||
|             this.beforeRequest() | ||||
|         } | ||||
|         let toReturn | ||||
|         try { | ||||
|             const form = await this.fetchForm(url, true, true); | ||||
|             toReturn = await this.postForm(form, dataToMerge, true, true) | ||||
|             toReturn = await this.postForm(form, dataToMerge, true, skipFeedback) | ||||
|         } catch (error) { | ||||
|             toReturn = Promise.reject(error); | ||||
|         } finally { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Andras Iklody
						Andras Iklody