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