Update website
This commit is contained in:
parent
a0b0d3dae7
commit
ae7ef6ad45
3151 changed files with 566766 additions and 48 deletions
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Handles viewing binary logs
|
||||
*/
|
||||
class BinlogController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* binary log files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $binaryLogs;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->binaryLogs = $this->dbi->fetchResult(
|
||||
'SHOW MASTER LOGS',
|
||||
'Log_name',
|
||||
null,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $PMA_Theme, $err_url;
|
||||
|
||||
$params = [
|
||||
'log' => $_POST['log'] ?? null,
|
||||
'pos' => $_POST['pos'] ?? null,
|
||||
'is_full_query' => $_POST['is_full_query'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$position = ! empty($params['pos']) ? (int) $params['pos'] : 0;
|
||||
|
||||
$urlParams = [];
|
||||
if (isset($params['log'])
|
||||
&& array_key_exists($params['log'], $this->binaryLogs)
|
||||
) {
|
||||
$urlParams['log'] = $params['log'];
|
||||
}
|
||||
|
||||
$isFullQuery = false;
|
||||
if (! empty($params['is_full_query'])) {
|
||||
$isFullQuery = true;
|
||||
$urlParams['is_full_query'] = 1;
|
||||
}
|
||||
|
||||
$sqlQuery = $this->getSqlQuery(
|
||||
$params['log'] ?? '',
|
||||
$position,
|
||||
(int) $cfg['MaxRows']
|
||||
);
|
||||
$result = $this->dbi->query($sqlQuery);
|
||||
|
||||
$numRows = 0;
|
||||
if (isset($result) && $result) {
|
||||
$numRows = $this->dbi->numRows($result);
|
||||
}
|
||||
|
||||
$previousParams = $urlParams;
|
||||
$fullQueriesParams = $urlParams;
|
||||
$nextParams = $urlParams;
|
||||
if ($position > 0) {
|
||||
$fullQueriesParams['pos'] = $position;
|
||||
if ($position > $cfg['MaxRows']) {
|
||||
$previousParams['pos'] = $position - $cfg['MaxRows'];
|
||||
}
|
||||
}
|
||||
$fullQueriesParams['is_full_query'] = 1;
|
||||
if ($isFullQuery) {
|
||||
unset($fullQueriesParams['is_full_query']);
|
||||
}
|
||||
if ($numRows >= $cfg['MaxRows']) {
|
||||
$nextParams['pos'] = $position + $cfg['MaxRows'];
|
||||
}
|
||||
|
||||
$values = [];
|
||||
while ($value = $this->dbi->fetchAssoc($result)) {
|
||||
$values[] = $value;
|
||||
}
|
||||
|
||||
$this->render('server/binlog/index', [
|
||||
'url_params' => $urlParams,
|
||||
'binary_logs' => $this->binaryLogs,
|
||||
'log' => $params['log'],
|
||||
'sql_message' => Generator::getMessage(Message::success(), $sqlQuery),
|
||||
'values' => $values,
|
||||
'has_previous' => $position > 0,
|
||||
'has_next' => $numRows >= $cfg['MaxRows'],
|
||||
'previous_params' => $previousParams,
|
||||
'full_queries_params' => $fullQueriesParams,
|
||||
'next_params' => $nextParams,
|
||||
'has_icons' => Util::showIcons('TableNavigationLinksMode'),
|
||||
'is_full_query' => $isFullQuery,
|
||||
'image_path' => $PMA_Theme->getImgPath(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $log Binary log file name
|
||||
* @param int $position Position to display
|
||||
* @param int $maxRows Maximum number of rows
|
||||
*/
|
||||
private function getSqlQuery(
|
||||
string $log,
|
||||
int $position,
|
||||
int $maxRows
|
||||
): string {
|
||||
$sqlQuery = 'SHOW BINLOG EVENTS';
|
||||
if (! empty($log)) {
|
||||
$sqlQuery .= ' IN \'' . $log . '\'';
|
||||
}
|
||||
$sqlQuery .= ' LIMIT ' . $position . ', ' . $maxRows;
|
||||
|
||||
return $sqlQuery;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
/**
|
||||
* Handles viewing character sets and collations
|
||||
*/
|
||||
class CollationsController extends AbstractController
|
||||
{
|
||||
/** @var array|null */
|
||||
private $charsets;
|
||||
|
||||
/** @var array|null */
|
||||
private $collations;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
* @param array|null $charsets Array of charsets
|
||||
* @param array|null $collations Array of collations
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$dbi,
|
||||
?array $charsets = null,
|
||||
?array $collations = null
|
||||
) {
|
||||
global $cfg;
|
||||
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->charsets = $charsets ?? Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$this->collations = $collations ?? Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($this->charsets as $charset) {
|
||||
$charsetCollations = [];
|
||||
/** @var Collation $collation */
|
||||
foreach ($this->collations[$charset->getName()] as $collation) {
|
||||
$charsetCollations[] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
'is_default' => $collation->isDefault(),
|
||||
];
|
||||
}
|
||||
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
'collations' => $charsetCollations,
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('server/collations/index', ['charsets' => $charsets]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,509 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Query\Utilities;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\ReplicationInfo;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function mb_strlen;
|
||||
use function mb_strtolower;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
* Handles viewing and creating and deleting databases
|
||||
*/
|
||||
class DatabasesController extends AbstractController
|
||||
{
|
||||
/** @var array array of database details */
|
||||
private $databases = [];
|
||||
|
||||
/** @var int number of databases */
|
||||
private $databaseCount = 0;
|
||||
|
||||
/** @var string sort by column */
|
||||
private $sortBy;
|
||||
|
||||
/** @var string sort order of databases */
|
||||
private $sortOrder;
|
||||
|
||||
/** @var bool whether to show database statistics */
|
||||
private $hasStatistics;
|
||||
|
||||
/** @var int position in list navigation */
|
||||
private $position;
|
||||
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var RelationCleanup */
|
||||
private $relationCleanup;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
Transformations $transformations,
|
||||
RelationCleanup $relationCleanup,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->transformations = $transformations;
|
||||
$this->relationCleanup = $relationCleanup;
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$checkUserPrivileges = new CheckUserPrivileges($dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $server, $dblist, $is_create_db_priv;
|
||||
global $db_to_create, $text_dir, $PMA_Theme, $err_url;
|
||||
|
||||
$params = [
|
||||
'statistics' => $_REQUEST['statistics'] ?? null,
|
||||
'pos' => $_REQUEST['pos'] ?? null,
|
||||
'sort_by' => $_REQUEST['sort_by'] ?? null,
|
||||
'sort_order' => $_REQUEST['sort_order'] ?? null,
|
||||
];
|
||||
|
||||
$this->addScriptFiles(['server/databases.js']);
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$replicationInfo = new ReplicationInfo($this->dbi);
|
||||
$replicationInfo->load($_POST['master_connection'] ?? null);
|
||||
|
||||
$primaryInfo = $replicationInfo->getPrimaryInfo();
|
||||
$replicaInfo = $replicationInfo->getReplicaInfo();
|
||||
|
||||
$this->setSortDetails($params['sort_by'], $params['sort_order']);
|
||||
$this->hasStatistics = ! empty($params['statistics']);
|
||||
$this->position = ! empty($params['pos']) ? (int) $params['pos'] : 0;
|
||||
|
||||
/**
|
||||
* Gets the databases list
|
||||
*/
|
||||
if ($server > 0) {
|
||||
$this->databases = $this->dbi->getDatabasesFull(
|
||||
null,
|
||||
$this->hasStatistics,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
$this->sortBy,
|
||||
$this->sortOrder,
|
||||
$this->position,
|
||||
true
|
||||
);
|
||||
$this->databaseCount = count($dblist->databases);
|
||||
}
|
||||
|
||||
$urlParams = [
|
||||
'statistics' => $this->hasStatistics,
|
||||
'pos' => $this->position,
|
||||
'sort_by' => $this->sortBy,
|
||||
'sort_order' => $this->sortOrder,
|
||||
];
|
||||
|
||||
$databases = $this->getDatabases($primaryInfo, $replicaInfo);
|
||||
|
||||
$charsetsList = [];
|
||||
if ($cfg['ShowCreateDb'] && $is_create_db_priv) {
|
||||
$charsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$serverCollation = $this->dbi->getServerCollation();
|
||||
/** @var Charset $charset */
|
||||
foreach ($charsets as $charset) {
|
||||
$collationsList = [];
|
||||
/** @var Collation $collation */
|
||||
foreach ($collations[$charset->getName()] as $collation) {
|
||||
$collationsList[] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
'is_selected' => $serverCollation === $collation->getName(),
|
||||
];
|
||||
}
|
||||
$charsetsList[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
'collations' => $collationsList,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$headerStatistics = $this->getStatisticsColumns();
|
||||
|
||||
$this->render('server/databases/index', [
|
||||
'is_create_database_shown' => $cfg['ShowCreateDb'],
|
||||
'has_create_database_privileges' => $is_create_db_priv,
|
||||
'has_statistics' => $this->hasStatistics,
|
||||
'database_to_create' => $db_to_create,
|
||||
'databases' => $databases['databases'],
|
||||
'total_statistics' => $databases['total_statistics'],
|
||||
'header_statistics' => $headerStatistics,
|
||||
'charsets' => $charsetsList,
|
||||
'database_count' => $this->databaseCount,
|
||||
'pos' => $this->position,
|
||||
'url_params' => $urlParams,
|
||||
'max_db_list' => $cfg['MaxDbList'],
|
||||
'has_master_replication' => $primaryInfo['status'],
|
||||
'has_slave_replication' => $replicaInfo['status'],
|
||||
'is_drop_allowed' => $this->dbi->isSuperUser() || $cfg['AllowUserDropDatabase'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'text_dir' => $text_dir,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
global $cfg, $db;
|
||||
|
||||
$params = [
|
||||
'new_db' => $_POST['new_db'] ?? null,
|
||||
'db_collation' => $_POST['db_collation'] ?? null,
|
||||
];
|
||||
|
||||
if (! isset($params['new_db']) || mb_strlen($params['new_db']) === 0 || ! $this->response->isAjax()) {
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
if ($this->dbi->getLowerCaseNames() === '1') {
|
||||
$params['new_db'] = mb_strtolower(
|
||||
$params['new_db']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and executes the db creation sql query
|
||||
*/
|
||||
$sqlQuery = 'CREATE DATABASE ' . Util::backquote($params['new_db']);
|
||||
if (! empty($params['db_collation'])) {
|
||||
[$databaseCharset] = explode('_', $params['db_collation']);
|
||||
$charsets = Charsets::getCharsets(
|
||||
$this->dbi,
|
||||
$cfg['Server']['DisableIS']
|
||||
);
|
||||
$collations = Charsets::getCollations(
|
||||
$this->dbi,
|
||||
$cfg['Server']['DisableIS']
|
||||
);
|
||||
if (array_key_exists($databaseCharset, $charsets)
|
||||
&& array_key_exists($params['db_collation'], $collations[$databaseCharset])
|
||||
) {
|
||||
$sqlQuery .= ' DEFAULT'
|
||||
. Util::getCharsetQueryPart($params['db_collation']);
|
||||
}
|
||||
}
|
||||
$sqlQuery .= ';';
|
||||
|
||||
$result = $this->dbi->tryQuery($sqlQuery);
|
||||
|
||||
if (! $result) {
|
||||
// avoid displaying the not-created db name in header or navi panel
|
||||
$db = '';
|
||||
|
||||
$message = Message::rawError((string) $this->dbi->getError());
|
||||
$json = ['message' => $message];
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
} else {
|
||||
$db = $params['new_db'];
|
||||
|
||||
$message = Message::success(__('Database %1$s has been created.'));
|
||||
$message->addParam($params['new_db']);
|
||||
|
||||
$scriptName = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
|
||||
$json = [
|
||||
'message' => $message,
|
||||
'sql_query' => Generator::getMessage('', $sqlQuery, 'success'),
|
||||
'url' => $scriptName . Url::getCommon(
|
||||
['db' => $params['new_db']],
|
||||
strpos($scriptName, '?') === false ? '?' : '&'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles dropping multiple databases
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
global $selected, $err_url, $cfg, $dblist, $reload;
|
||||
|
||||
$params = [
|
||||
'drop_selected_dbs' => $_POST['drop_selected_dbs'] ?? null,
|
||||
'selected_dbs' => $_POST['selected_dbs'] ?? null,
|
||||
];
|
||||
/** @var Message|int $message */
|
||||
$message = -1;
|
||||
|
||||
if (! isset($params['drop_selected_dbs'])
|
||||
|| ! $this->response->isAjax()
|
||||
|| (! $this->dbi->isSuperUser() && ! $cfg['AllowUserDropDatabase'])
|
||||
) {
|
||||
$message = Message::error();
|
||||
$json = ['message' => $message];
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON($json);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($params['selected_dbs'])) {
|
||||
$message = Message::error(__('No databases selected.'));
|
||||
$json = ['message' => $message];
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON($json);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$err_url = Url::getFromRoute('/server/databases');
|
||||
$selected = $_POST['selected_dbs'];
|
||||
$rebuildDatabaseList = false;
|
||||
$sqlQuery = '';
|
||||
$numberOfDatabases = count($selected);
|
||||
|
||||
for ($i = 0; $i < $numberOfDatabases; $i++) {
|
||||
$this->relationCleanup->database($selected[$i]);
|
||||
$aQuery = 'DROP DATABASE ' . Util::backquote($selected[$i]);
|
||||
$reload = true;
|
||||
$rebuildDatabaseList = true;
|
||||
|
||||
$sqlQuery .= $aQuery . ';' . "\n";
|
||||
$this->dbi->query($aQuery);
|
||||
$this->transformations->clear($selected[$i]);
|
||||
}
|
||||
|
||||
if ($rebuildDatabaseList) {
|
||||
$dblist->databases->build();
|
||||
}
|
||||
|
||||
if ($message === -1) { // no error message
|
||||
$message = Message::success(
|
||||
_ngettext(
|
||||
'%1$d database has been dropped successfully.',
|
||||
'%1$d databases have been dropped successfully.',
|
||||
$numberOfDatabases
|
||||
)
|
||||
);
|
||||
$message->addParam($numberOfDatabases);
|
||||
}
|
||||
|
||||
$json = [];
|
||||
if ($message instanceof Message) {
|
||||
$json = ['message' => $message];
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts parameters sort order and sort by
|
||||
*
|
||||
* @param string|null $sortBy sort by
|
||||
* @param string|null $sortOrder sort order
|
||||
*/
|
||||
private function setSortDetails(?string $sortBy, ?string $sortOrder): void
|
||||
{
|
||||
if (empty($sortBy)) {
|
||||
$this->sortBy = 'SCHEMA_NAME';
|
||||
} else {
|
||||
$sortByAllowList = [
|
||||
'SCHEMA_NAME',
|
||||
'DEFAULT_COLLATION_NAME',
|
||||
'SCHEMA_TABLES',
|
||||
'SCHEMA_TABLE_ROWS',
|
||||
'SCHEMA_DATA_LENGTH',
|
||||
'SCHEMA_INDEX_LENGTH',
|
||||
'SCHEMA_LENGTH',
|
||||
'SCHEMA_DATA_FREE',
|
||||
];
|
||||
$this->sortBy = 'SCHEMA_NAME';
|
||||
if (in_array($sortBy, $sortByAllowList)) {
|
||||
$this->sortBy = $sortBy;
|
||||
}
|
||||
}
|
||||
|
||||
$this->sortOrder = 'asc';
|
||||
if (! isset($sortOrder)
|
||||
|| mb_strtolower($sortOrder) !== 'desc'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sortOrder = 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $primaryInfo
|
||||
* @param array $replicaInfo
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDatabases($primaryInfo, $replicaInfo): array
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$databases = [];
|
||||
$totalStatistics = $this->getStatisticsColumns();
|
||||
foreach ($this->databases as $database) {
|
||||
$replication = [
|
||||
'master' => ['status' => $primaryInfo['status']],
|
||||
'slave' => ['status' => $replicaInfo['status']],
|
||||
];
|
||||
|
||||
if ($primaryInfo['status']) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $primaryInfo['Ignore_DB']);
|
||||
$replication['master']['is_replicated'] = false;
|
||||
|
||||
if (strlen((string) $key) === 0) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $primaryInfo['Do_DB']);
|
||||
|
||||
if (strlen((string) $key) > 0 || count($primaryInfo['Do_DB']) === 0) {
|
||||
$replication['master']['is_replicated'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($replicaInfo['status']) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $replicaInfo['Ignore_DB']);
|
||||
$replication['slave']['is_replicated'] = false;
|
||||
|
||||
if (strlen((string) $key) === 0) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $replicaInfo['Do_DB']);
|
||||
|
||||
if (strlen((string) $key) > 0 || count($replicaInfo['Do_DB']) === 0) {
|
||||
$replication['slave']['is_replicated'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statistics = $this->getStatisticsColumns();
|
||||
if ($this->hasStatistics) {
|
||||
foreach (array_keys($statistics) as $key) {
|
||||
$statistics[$key]['raw'] = $database[$key] ?? null;
|
||||
$totalStatistics[$key]['raw'] += (int) $database[$key] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
$url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$url .= Url::getCommonRaw(
|
||||
['db' => $database['SCHEMA_NAME']],
|
||||
strpos($url, '?') === false ? '?' : '&'
|
||||
);
|
||||
$databases[$database['SCHEMA_NAME']] = [
|
||||
'name' => $database['SCHEMA_NAME'],
|
||||
'collation' => [],
|
||||
'statistics' => $statistics,
|
||||
'replication' => $replication,
|
||||
'is_system_schema' => Utilities::isSystemSchema(
|
||||
$database['SCHEMA_NAME'],
|
||||
true
|
||||
),
|
||||
'is_pmadb' => $database['SCHEMA_NAME'] === ($cfg['Server']['pmadb'] ?? ''),
|
||||
'url' => $url,
|
||||
];
|
||||
$collation = Charsets::findCollationByName(
|
||||
$this->dbi,
|
||||
$cfg['Server']['DisableIS'],
|
||||
$database['DEFAULT_COLLATION_NAME']
|
||||
);
|
||||
if ($collation === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$databases[$database['SCHEMA_NAME']]['collation'] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'databases' => $databases,
|
||||
'total_statistics' => $totalStatistics,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the statistics columns
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getStatisticsColumns(): array
|
||||
{
|
||||
return [
|
||||
'SCHEMA_TABLES' => [
|
||||
'title' => __('Tables'),
|
||||
'format' => 'number',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_TABLE_ROWS' => [
|
||||
'title' => __('Rows'),
|
||||
'format' => 'number',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_DATA_LENGTH' => [
|
||||
'title' => __('Data'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_INDEX_LENGTH' => [
|
||||
'title' => __('Indexes'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_LENGTH' => [
|
||||
'title' => __('Total'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_DATA_FREE' => [
|
||||
'title' => __('Overhead'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\StorageEngine;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
/**
|
||||
* Handles viewing storage engine details
|
||||
*/
|
||||
class EnginesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->render('server/engines/index', [
|
||||
'engines' => StorageEngine::getStorageEngines(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays details about a given Storage Engine
|
||||
*
|
||||
* @param array $params Request params
|
||||
*/
|
||||
public function show(array $params): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$page = $params['page'] ?? '';
|
||||
|
||||
$engine = [];
|
||||
if (StorageEngine::isValid($params['engine'])) {
|
||||
$storageEngine = StorageEngine::getEngine($params['engine']);
|
||||
$engine = [
|
||||
'engine' => $params['engine'],
|
||||
'title' => $storageEngine->getTitle(),
|
||||
'help_page' => $storageEngine->getMysqlHelpPage(),
|
||||
'comment' => $storageEngine->getComment(),
|
||||
'info_pages' => $storageEngine->getInfoPages(),
|
||||
'support' => $storageEngine->getSupportInformationMessage(),
|
||||
'variables' => $storageEngine->getHtmlVariables(),
|
||||
'page' => ! empty($page) ? $storageEngine->getPage($page) : '',
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('server/engines/show', [
|
||||
'engine' => $engine,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Export\Options;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_merge;
|
||||
|
||||
final class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Options */
|
||||
private $export;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Options $export, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->export = $export;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $sql_query, $num_tables, $unlim_num_rows;
|
||||
global $tmp_select, $select_item, $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$pageSettings = new PageSettings('Export');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['export.js']);
|
||||
|
||||
$select_item = $tmp_select ?? '';
|
||||
$databases = $this->export->getDatabasesForSelectOptions($select_item);
|
||||
|
||||
if (! isset($sql_query)) {
|
||||
$sql_query = '';
|
||||
}
|
||||
if (! isset($num_tables)) {
|
||||
$num_tables = 0;
|
||||
}
|
||||
if (! isset($unlim_num_rows)) {
|
||||
$unlim_num_rows = 0;
|
||||
}
|
||||
|
||||
$GLOBALS['single_table'] = $_POST['single_table'] ?? $_GET['single_table'] ?? $GLOBALS['single_table'] ?? null;
|
||||
|
||||
$exportList = Plugins::getExport('server', isset($GLOBALS['single_table']));
|
||||
|
||||
if (empty($exportList)) {
|
||||
$this->response->addHTML(Message::error(
|
||||
__('Could not load export plugins, please check your installation!')
|
||||
)->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $this->export->getOptions(
|
||||
'server',
|
||||
$db,
|
||||
$table,
|
||||
$sql_query,
|
||||
$num_tables,
|
||||
$unlim_num_rows,
|
||||
$exportList
|
||||
);
|
||||
|
||||
$this->render('server/export/index', array_merge($options, [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'databases' => $databases,
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function intval;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $max_upload_size, $table, $SESSION_KEY, $cfg, $PMA_Theme, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Import');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['import.js']);
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
[$SESSION_KEY, $uploadId] = Ajax::uploadProgressSetup();
|
||||
|
||||
$importList = Plugins::getImport('server');
|
||||
|
||||
if (empty($importList)) {
|
||||
$this->response->addHTML(Message::error(__(
|
||||
'Could not load import plugins, please check your installation!'
|
||||
))->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$offset = null;
|
||||
if (Core::isValid($_REQUEST['offset'], 'numeric')) {
|
||||
$offset = intval($_REQUEST['offset']);
|
||||
}
|
||||
|
||||
$timeoutPassed = $_REQUEST['timeout_passed'] ?? null;
|
||||
$localImportFile = $_REQUEST['local_import_file'] ?? null;
|
||||
$compressions = Import::getCompressions();
|
||||
|
||||
$allCharsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($allCharsets as $charset) {
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$idKey = $_SESSION[$SESSION_KEY]['handler']::getIdKey();
|
||||
$hiddenInputs = [
|
||||
$idKey => $uploadId,
|
||||
'import_type' => 'server',
|
||||
];
|
||||
|
||||
$this->render('server/import/index', [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'upload_id' => $uploadId,
|
||||
'handler' => $_SESSION[$SESSION_KEY]['handler'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'hidden_inputs' => $hiddenInputs,
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'max_upload_size' => $max_upload_size,
|
||||
'import_list' => $importList,
|
||||
'local_import_file' => $localImportFile,
|
||||
'is_upload' => $GLOBALS['is_upload'],
|
||||
'upload_dir' => $cfg['UploadDir'] ?? null,
|
||||
'timeout_passed_global' => $GLOBALS['timeout_passed'] ?? null,
|
||||
'compressions' => $compressions,
|
||||
'is_encoding_supported' => Encoding::isSupported(),
|
||||
'encodings' => Encoding::listEncodings(),
|
||||
'import_charset' => $cfg['Import']['charset'] ?? null,
|
||||
'timeout_passed' => $timeoutPassed,
|
||||
'offset' => $offset,
|
||||
'can_convert_kanji' => Encoding::canConvertKanji(),
|
||||
'charsets' => $charsets,
|
||||
'is_foreign_key_check' => Util::isForeignKeyCheck(),
|
||||
'user_upload_dir' => Util::userDir($cfg['UploadDir'] ?? ''),
|
||||
'local_files' => Import::getLocalFiles($importList),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Plugins;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_keys;
|
||||
use function ksort;
|
||||
use function mb_strtolower;
|
||||
use function preg_replace;
|
||||
|
||||
/**
|
||||
* Handles viewing server plugin details
|
||||
*/
|
||||
class PluginsController extends AbstractController
|
||||
{
|
||||
/** @var Plugins */
|
||||
private $plugins;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Plugins $plugins, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->plugins = $plugins;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.tablesorter.js', 'server/plugins.js']);
|
||||
|
||||
$plugins = [];
|
||||
$serverPlugins = $this->plugins->getAll();
|
||||
foreach ($serverPlugins as $plugin) {
|
||||
$plugins[$plugin->getType()][] = $plugin->toArray();
|
||||
}
|
||||
ksort($plugins);
|
||||
|
||||
$cleanTypes = [];
|
||||
foreach (array_keys($plugins) as $type) {
|
||||
$cleanTypes[$type] = preg_replace(
|
||||
'/[^a-z]/',
|
||||
'',
|
||||
mb_strtolower($type)
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('server/plugins/index', [
|
||||
'plugins' => $plugins,
|
||||
'clean_types' => $cleanTypes,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,486 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Controllers\Database\PrivilegesController as DatabaseController;
|
||||
use PhpMyAdmin\Controllers\Table\PrivilegesController as TableController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Privileges;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function header;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function str_replace;
|
||||
use function urlencode;
|
||||
|
||||
/**
|
||||
* Server privileges and users manipulations.
|
||||
*/
|
||||
class PrivilegesController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $err_url, $message, $text_dir, $post_patterns, $PMA_Theme;
|
||||
global $username, $hostname, $dbname, $tablename, $routinename, $db_and_table, $dbname_is_wildcard;
|
||||
global $queries, $password, $ret_message, $ret_queries, $queries_for_display, $sql_query, $_add_user_error;
|
||||
global $itemType, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $title, $export, $grants, $one_grant, $url_dbname;
|
||||
|
||||
$checkUserPrivileges = new CheckUserPrivileges($this->dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->addScriptFiles(['server/privileges.js', 'vendor/zxcvbn.js']);
|
||||
|
||||
$relationCleanup = new RelationCleanup($this->dbi, $this->relation);
|
||||
$serverPrivileges = new Privileges($this->template, $this->dbi, $this->relation, $relationCleanup);
|
||||
|
||||
$databaseController = new DatabaseController(
|
||||
$this->response,
|
||||
$this->template,
|
||||
$db,
|
||||
$serverPrivileges,
|
||||
$this->dbi
|
||||
);
|
||||
|
||||
$tableController = new TableController(
|
||||
$this->response,
|
||||
$this->template,
|
||||
$db,
|
||||
$table,
|
||||
$serverPrivileges,
|
||||
$this->dbi
|
||||
);
|
||||
|
||||
if ((isset($_GET['viewing_mode'])
|
||||
&& $_GET['viewing_mode'] === 'server')
|
||||
&& $GLOBALS['cfgRelation']['menuswork']
|
||||
) {
|
||||
$this->response->addHTML('<div class="container-fluid">');
|
||||
$this->render('server/privileges/subnav', [
|
||||
'active' => 'privileges',
|
||||
'is_super_user' => $this->dbi->isSuperUser(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets globals from $_POST patterns, for privileges and max_* vars
|
||||
*/
|
||||
$post_patterns = [
|
||||
'/_priv$/i',
|
||||
'/^max_/i',
|
||||
];
|
||||
|
||||
Core::setPostAsGlobal($post_patterns);
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$_add_user_error = false;
|
||||
/**
|
||||
* Get DB information: username, hostname, dbname,
|
||||
* tablename, db_and_table, dbname_is_wildcard
|
||||
*/
|
||||
[
|
||||
$username,
|
||||
$hostname,
|
||||
$dbname,
|
||||
$tablename,
|
||||
$routinename,
|
||||
$db_and_table,
|
||||
$dbname_is_wildcard,
|
||||
] = $serverPrivileges->getDataForDBInfo();
|
||||
|
||||
/**
|
||||
* Checks if the user is allowed to do what they try to...
|
||||
*/
|
||||
$isGrantUser = $this->dbi->isGrantUser();
|
||||
$isCreateUser = $this->dbi->isCreateUser();
|
||||
|
||||
if (! $this->dbi->isSuperUser() && ! $isGrantUser && ! $isCreateUser) {
|
||||
$this->render('server/sub_page_header', [
|
||||
'type' => 'privileges',
|
||||
'is_image' => false,
|
||||
]);
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No Privileges'))
|
||||
->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
if (! $isGrantUser && ! $isCreateUser) {
|
||||
$this->response->addHTML(Message::notice(
|
||||
__('You do not have the privileges to administrate the users!')
|
||||
)->getDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is using "Change Login Information / Copy User" dialog
|
||||
* only to update the password
|
||||
*/
|
||||
if (isset($_POST['change_copy']) && $username == $_POST['old_username']
|
||||
&& $hostname == $_POST['old_hostname']
|
||||
) {
|
||||
$this->response->addHTML(
|
||||
Message::error(
|
||||
__(
|
||||
"Username and hostname didn't change. "
|
||||
. 'If you only want to change the password, '
|
||||
. "'Change password' tab should be used."
|
||||
)
|
||||
)->getDisplay()
|
||||
);
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / copies a user, part I
|
||||
*/
|
||||
[$queries, $password] = $serverPrivileges->getDataForChangeOrCopyUser();
|
||||
|
||||
/**
|
||||
* Adds a user
|
||||
* (Changes / copies a user, part II)
|
||||
*/
|
||||
[
|
||||
$ret_message,
|
||||
$ret_queries,
|
||||
$queries_for_display,
|
||||
$sql_query,
|
||||
$_add_user_error,
|
||||
] = $serverPrivileges->addUser(
|
||||
$dbname ?? null,
|
||||
$username ?? null,
|
||||
$hostname ?? null,
|
||||
$password ?? null,
|
||||
(bool) $cfgRelation['menuswork']
|
||||
);
|
||||
//update the old variables
|
||||
if (isset($ret_queries)) {
|
||||
$queries = $ret_queries;
|
||||
unset($ret_queries);
|
||||
}
|
||||
if (isset($ret_message)) {
|
||||
$message = $ret_message;
|
||||
unset($ret_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / copies a user, part III
|
||||
*/
|
||||
if (isset($_POST['change_copy'])) {
|
||||
$queries = $serverPrivileges->getDbSpecificPrivsQueriesForChangeOrCopyUser(
|
||||
$queries,
|
||||
$username,
|
||||
$hostname
|
||||
);
|
||||
}
|
||||
|
||||
$itemType = '';
|
||||
if (! empty($routinename)) {
|
||||
$itemType = $serverPrivileges->getRoutineType($dbname, $routinename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates privileges
|
||||
*/
|
||||
if (! empty($_POST['update_privs'])) {
|
||||
if (is_array($dbname)) {
|
||||
foreach ($dbname as $key => $db_name) {
|
||||
[$sql_query[$key], $message] = $serverPrivileges->updatePrivileges(
|
||||
($username ?? ''),
|
||||
($hostname ?? ''),
|
||||
($tablename ?? ($routinename ?? '')),
|
||||
($db_name ?? ''),
|
||||
$itemType
|
||||
);
|
||||
}
|
||||
|
||||
$sql_query = implode("\n", $sql_query);
|
||||
} else {
|
||||
[$sql_query, $message] = $serverPrivileges->updatePrivileges(
|
||||
($username ?? ''),
|
||||
($hostname ?? ''),
|
||||
($tablename ?? ($routinename ?? '')),
|
||||
($dbname ?? ''),
|
||||
$itemType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign users to user groups
|
||||
*/
|
||||
if (! empty($_POST['changeUserGroup']) && $cfgRelation['menuswork']
|
||||
&& $this->dbi->isSuperUser() && $this->dbi->isCreateUser()
|
||||
) {
|
||||
$serverPrivileges->setUserGroup($username, $_POST['userGroup']);
|
||||
$message = Message::success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes Privileges
|
||||
*/
|
||||
if (isset($_POST['revokeall'])) {
|
||||
[$message, $sql_query] = $serverPrivileges->getMessageAndSqlQueryForPrivilegesRevoke(
|
||||
($dbname ?? ''),
|
||||
($tablename ?? ($routinename ?? '')),
|
||||
$username,
|
||||
$hostname,
|
||||
$itemType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the password
|
||||
*/
|
||||
if (isset($_POST['change_pw'])) {
|
||||
$message = $serverPrivileges->updatePassword(
|
||||
$err_url,
|
||||
$username,
|
||||
$hostname
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes users
|
||||
* (Changes / copies a user, part IV)
|
||||
*/
|
||||
if (isset($_POST['delete'])
|
||||
|| (isset($_POST['change_copy']) && $_POST['mode'] < 4)
|
||||
) {
|
||||
$queries = $serverPrivileges->getDataForDeleteUsers($queries);
|
||||
if (empty($_POST['change_copy'])) {
|
||||
[$sql_query, $message] = $serverPrivileges->deleteUser($queries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / copies a user, part V
|
||||
*/
|
||||
if (isset($_POST['change_copy'])) {
|
||||
$queries = $serverPrivileges->getDataForQueries($queries, $queries_for_display);
|
||||
$message = Message::success();
|
||||
$sql_query = implode("\n", $queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the privilege tables into memory
|
||||
*/
|
||||
$message_ret = $serverPrivileges->updateMessageForReload();
|
||||
if ($message_ret !== null) {
|
||||
$message = $message_ret;
|
||||
unset($message_ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are in an Ajax request for Create User/Edit User/Revoke User/
|
||||
* Flush Privileges, show $message and return.
|
||||
*/
|
||||
if ($this->response->isAjax()
|
||||
&& empty($_REQUEST['ajax_page_request'])
|
||||
&& ! isset($_GET['export'])
|
||||
&& (! isset($_POST['submit_mult']) || $_POST['submit_mult'] !== 'export')
|
||||
&& ((! isset($_GET['initial']) || $_GET['initial'] === null
|
||||
|| $_GET['initial'] === '')
|
||||
|| (isset($_POST['delete']) && $_POST['delete'] === __('Go')))
|
||||
&& ! isset($_GET['showall'])
|
||||
&& ! isset($_GET['edit_user_group_dialog'])
|
||||
) {
|
||||
$extra_data = $serverPrivileges->getExtraDataForAjaxBehavior(
|
||||
($password ?? ''),
|
||||
($sql_query ?? ''),
|
||||
($hostname ?? ''),
|
||||
($username ?? '')
|
||||
);
|
||||
|
||||
if (! empty($message) && $message instanceof Message) {
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON($extra_data);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the links
|
||||
*/
|
||||
if (isset($_GET['viewing_mode']) && $_GET['viewing_mode'] === 'db') {
|
||||
$db = $_REQUEST['db'] = $_GET['checkprivsdb'];
|
||||
|
||||
// Gets the database structure
|
||||
$sub_part = '_structure';
|
||||
ob_start();
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
$content = ob_get_clean();
|
||||
$this->response->addHTML($content . "\n");
|
||||
} elseif (! empty($GLOBALS['message'])) {
|
||||
$this->response->addHTML(Generator::getMessage($GLOBALS['message']));
|
||||
unset($GLOBALS['message']);
|
||||
}
|
||||
|
||||
if (! empty($_GET['edit_user_group_dialog']) && $cfgRelation['menuswork']) {
|
||||
$dialog = $serverPrivileges->getHtmlToChooseUserGroup($username ?? null);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('message', $dialog);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML($dialog);
|
||||
}
|
||||
|
||||
// export user definition
|
||||
if (isset($_GET['export'])
|
||||
|| (isset($_POST['submit_mult']) && $_POST['submit_mult'] === 'export')
|
||||
) {
|
||||
[$title, $export] = $serverPrivileges->getListForExportUserDefinition(
|
||||
$username ?? '',
|
||||
$hostname ?? ''
|
||||
);
|
||||
|
||||
unset($username, $hostname, $grants, $one_grant);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('message', $export);
|
||||
$this->response->addJSON('title', $title);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML('<h2>' . $title . '</h2>' . $export);
|
||||
}
|
||||
|
||||
// Show back the form if an error occurred
|
||||
if (isset($_GET['adduser']) || $_add_user_error === true) {
|
||||
// Add user
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForAddUser(($dbname ?? ''))
|
||||
);
|
||||
} elseif (isset($_GET['checkprivsdb'])) {
|
||||
if (isset($_GET['checkprivstable'])) {
|
||||
$this->response->addHTML($tableController->index([
|
||||
'checkprivsdb' => $_GET['checkprivsdb'],
|
||||
'checkprivstable' => $_GET['checkprivstable'],
|
||||
]));
|
||||
} elseif ($this->response->isAjax() === true && empty($_REQUEST['ajax_page_request'])) {
|
||||
$message = Message::success(__('User has been added.'));
|
||||
$this->response->addJSON('message', $message);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$this->response->addHTML($databaseController->index([
|
||||
'checkprivsdb' => $_GET['checkprivsdb'],
|
||||
]));
|
||||
}
|
||||
} else {
|
||||
if (isset($dbname) && ! is_array($dbname)) {
|
||||
$url_dbname = urlencode(
|
||||
str_replace(
|
||||
[
|
||||
'\_',
|
||||
'\%',
|
||||
],
|
||||
[
|
||||
'_',
|
||||
'%',
|
||||
],
|
||||
$dbname
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($username)) {
|
||||
// No username is given --> display the overview
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForUserOverview($PMA_Theme->getImgPath(), $text_dir)
|
||||
);
|
||||
} elseif (! empty($routinename)) {
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForRoutineSpecificPrivileges(
|
||||
$username,
|
||||
$hostname ?? '',
|
||||
$dbname,
|
||||
$routinename,
|
||||
$url_dbname ?? ''
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// A user was selected -> display the user's properties
|
||||
// In an Ajax request, prevent cached values from showing
|
||||
if ($this->response->isAjax()) {
|
||||
header('Cache-Control: no-cache');
|
||||
}
|
||||
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForUserProperties(
|
||||
$dbname_is_wildcard,
|
||||
$url_dbname ?? '',
|
||||
$username,
|
||||
$hostname ?? '',
|
||||
$dbname ?? '',
|
||||
$tablename ?? ''
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ((! isset($_GET['viewing_mode']) || $_GET['viewing_mode'] !== 'server')
|
||||
|| ! $cfgRelation['menuswork']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML('</div>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* Server replications
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ReplicationGui;
|
||||
use PhpMyAdmin\ReplicationInfo;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Server replications
|
||||
*/
|
||||
class ReplicationController extends AbstractController
|
||||
{
|
||||
/** @var ReplicationGui */
|
||||
private $replicationGui;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, ReplicationGui $replicationGui, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->replicationGui = $replicationGui;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $url_params, $err_url;
|
||||
|
||||
$params = [
|
||||
'url_params' => $_POST['url_params'] ?? null,
|
||||
'mr_configure' => $_POST['mr_configure'] ?? null,
|
||||
'sl_configure' => $_POST['sl_configure'] ?? null,
|
||||
'repl_clear_scr' => $_POST['repl_clear_scr'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$replicationInfo = new ReplicationInfo($this->dbi);
|
||||
$replicationInfo->load($_POST['master_connection'] ?? null);
|
||||
|
||||
$primaryInfo = $replicationInfo->getPrimaryInfo();
|
||||
$replicaInfo = $replicationInfo->getReplicaInfo();
|
||||
|
||||
$this->addScriptFiles(['server/privileges.js', 'replication.js', 'vendor/zxcvbn.js']);
|
||||
|
||||
if (isset($params['url_params']) && is_array($params['url_params'])) {
|
||||
$url_params = $params['url_params'];
|
||||
}
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->replicationGui->handleControlRequest();
|
||||
}
|
||||
|
||||
$errorMessages = $this->replicationGui->getHtmlForErrorMessage();
|
||||
|
||||
if ($primaryInfo['status']) {
|
||||
$masterReplicationHtml = $this->replicationGui->getHtmlForMasterReplication();
|
||||
}
|
||||
|
||||
if (isset($params['mr_configure'])) {
|
||||
$masterConfigurationHtml = $this->replicationGui->getHtmlForMasterConfiguration();
|
||||
} else {
|
||||
if (! isset($params['repl_clear_scr'])) {
|
||||
$slaveConfigurationHtml = $this->replicationGui->getHtmlForSlaveConfiguration(
|
||||
$replicaInfo['status'],
|
||||
$replicationInfo->getReplicaStatus()
|
||||
);
|
||||
}
|
||||
if (isset($params['sl_configure'])) {
|
||||
$changeMasterHtml = $this->replicationGui->getHtmlForReplicationChangeMaster('slave_changemaster');
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/replication/index', [
|
||||
'url_params' => $url_params,
|
||||
'is_super_user' => $this->dbi->isSuperUser(),
|
||||
'error_messages' => $errorMessages,
|
||||
'is_master' => $primaryInfo['status'],
|
||||
'master_configure' => $params['mr_configure'],
|
||||
'slave_configure' => $params['sl_configure'],
|
||||
'clear_screen' => $params['repl_clear_scr'],
|
||||
'master_replication_html' => $masterReplicationHtml ?? '',
|
||||
'master_configuration_html' => $masterConfigurationHtml ?? '',
|
||||
'slave_configuration_html' => $slaveConfigurationHtml ?? '',
|
||||
'change_master_html' => $changeMasterHtml ?? '',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlQueryForm;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
/**
|
||||
* Server SQL executor
|
||||
*/
|
||||
class SqlController extends AbstractController
|
||||
{
|
||||
/** @var SqlQueryForm */
|
||||
private $sqlQueryForm;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, SqlQueryForm $sqlQueryForm, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->sqlQueryForm = $sqlQueryForm;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
]);
|
||||
|
||||
$pageSettings = new PageSettings('Sql');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->sqlQueryForm->getHtml());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController as Controller;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
/** @var Data */
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
*/
|
||||
public function __construct($response, Template $template, $data)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\Advisor;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Displays the advisor feature
|
||||
*/
|
||||
class AdvisorController extends AbstractController
|
||||
{
|
||||
/** @var Advisor */
|
||||
private $advisor;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, Advisor $advisor)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->advisor = $advisor;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [];
|
||||
if ($this->data->dataLoaded) {
|
||||
$data = $this->advisor->run();
|
||||
}
|
||||
|
||||
$this->render('server/status/advisor/index', ['data' => $data]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Server\Status\Monitor;
|
||||
use PhpMyAdmin\Server\SysInfo\SysInfo;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function is_numeric;
|
||||
use function microtime;
|
||||
|
||||
class MonitorController extends AbstractController
|
||||
{
|
||||
/** @var Monitor */
|
||||
private $monitor;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param Monitor $monitor
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $monitor, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->monitor = $monitor;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $PMA_Theme, $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'jquery.sortable-table.js',
|
||||
'vendor/jqplot/jquery.jqplot.js',
|
||||
'vendor/jqplot/plugins/jqplot.pieRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.highlighter.js',
|
||||
'vendor/jqplot/plugins/jqplot.cursor.js',
|
||||
'jqplot/plugins/jqplot.byteFormatter.js',
|
||||
'server/status/monitor.js',
|
||||
'server/status/sorter.js',
|
||||
]);
|
||||
|
||||
$form = [
|
||||
'server_time' => (int) (microtime(true) * 1000),
|
||||
'server_os' => SysInfo::getOs(),
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'server_db_isLocal' => $this->data->dbIsLocal,
|
||||
];
|
||||
|
||||
$javascriptVariableNames = [];
|
||||
foreach ($this->data->status as $name => $value) {
|
||||
if (! is_numeric($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$javascriptVariableNames[] = $name;
|
||||
}
|
||||
|
||||
$this->render('server/status/monitor/index', [
|
||||
'image_path' => $PMA_Theme->getImgPath(),
|
||||
'javascript_variable_names' => $javascriptVariableNames,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chartingData(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = ['requiredData' => $_POST['requiredData'] ?? null];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForChartingData(
|
||||
$params['requiredData'] ?? ''
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logDataTypeSlow(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'time_start' => $_POST['time_start'] ?? null,
|
||||
'time_end' => $_POST['time_end'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForLogDataTypeSlow(
|
||||
(int) $params['time_start'],
|
||||
(int) $params['time_end']
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logDataTypeGeneral(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'time_start' => $_POST['time_start'] ?? null,
|
||||
'time_end' => $_POST['time_end'] ?? null,
|
||||
'limitTypes' => $_POST['limitTypes'] ?? null,
|
||||
'removeVariables' => $_POST['removeVariables'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForLogDataTypeGeneral(
|
||||
(int) $params['time_start'],
|
||||
(int) $params['time_end'],
|
||||
(bool) $params['limitTypes'],
|
||||
(bool) $params['removeVariables']
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function loggingVars(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'varName' => $_POST['varName'] ?? null,
|
||||
'varValue' => $_POST['varValue'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForLoggingVars(
|
||||
$params['varName'],
|
||||
$params['varValue']
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function queryAnalyzer(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'database' => $_POST['database'] ?? null,
|
||||
'query' => $_POST['query'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForQueryAnalyzer(
|
||||
$params['database'] ?? '',
|
||||
$params['query'] ?? ''
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function mb_strtolower;
|
||||
use function strlen;
|
||||
use function ucfirst;
|
||||
|
||||
class ProcessesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'showExecuting' => $_POST['showExecuting'] ?? null,
|
||||
'full' => $_POST['full'] ?? null,
|
||||
'column_name' => $_POST['column_name'] ?? null,
|
||||
'order_by_field' => $_POST['order_by_field'] ?? null,
|
||||
'sort_order' => $_POST['sort_order'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['server/status/processes.js']);
|
||||
|
||||
$isChecked = false;
|
||||
if (! empty($params['showExecuting'])) {
|
||||
$isChecked = true;
|
||||
}
|
||||
|
||||
$urlParams = [
|
||||
'ajax_request' => true,
|
||||
'full' => $params['full'] ?? '',
|
||||
'column_name' => $params['column_name'] ?? '',
|
||||
'order_by_field' => $params['order_by_field'] ?? '',
|
||||
'sort_order' => $params['sort_order'] ?? '',
|
||||
];
|
||||
|
||||
$serverProcessList = $this->getList($params);
|
||||
|
||||
$this->render('server/status/processes/index', [
|
||||
'url_params' => $urlParams,
|
||||
'is_checked' => $isChecked,
|
||||
'server_process_list' => $serverProcessList,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only sends the process list table
|
||||
*/
|
||||
public function refresh(): void
|
||||
{
|
||||
$params = [
|
||||
'showExecuting' => $_POST['showExecuting'] ?? null,
|
||||
'full' => $_POST['full'] ?? null,
|
||||
'column_name' => $_POST['column_name'] ?? null,
|
||||
'order_by_field' => $_POST['order_by_field'] ?? null,
|
||||
'sort_order' => $_POST['sort_order'] ?? null,
|
||||
];
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->getList($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function kill(array $params): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$kill = (int) $params['id'];
|
||||
$query = $this->dbi->getKillQuery($kill);
|
||||
|
||||
if ($this->dbi->tryQuery($query)) {
|
||||
$message = Message::success(
|
||||
__('Thread %s was successfully killed.')
|
||||
);
|
||||
$this->response->setRequestStatus(true);
|
||||
} else {
|
||||
$message = Message::error(
|
||||
__(
|
||||
'phpMyAdmin was unable to kill thread %s.'
|
||||
. ' It probably has already been closed.'
|
||||
)
|
||||
);
|
||||
$this->response->setRequestStatus(false);
|
||||
}
|
||||
$message->addParam($kill);
|
||||
|
||||
$this->response->addJSON(['message' => $message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
private function getList(array $params): string
|
||||
{
|
||||
$urlParams = [];
|
||||
|
||||
$showFullSql = ! empty($params['full']);
|
||||
if ($showFullSql) {
|
||||
$urlParams['full'] = '';
|
||||
} else {
|
||||
$urlParams['full'] = 1;
|
||||
}
|
||||
|
||||
// This array contains display name and real column name of each
|
||||
// sortable column in the table
|
||||
$sortableColumns = [
|
||||
[
|
||||
'column_name' => __('ID'),
|
||||
'order_by_field' => 'Id',
|
||||
],
|
||||
[
|
||||
'column_name' => __('User'),
|
||||
'order_by_field' => 'User',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Host'),
|
||||
'order_by_field' => 'Host',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Database'),
|
||||
'order_by_field' => 'db',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Command'),
|
||||
'order_by_field' => 'Command',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Time'),
|
||||
'order_by_field' => 'Time',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Status'),
|
||||
'order_by_field' => 'State',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Progress'),
|
||||
'order_by_field' => 'Progress',
|
||||
],
|
||||
[
|
||||
'column_name' => __('SQL query'),
|
||||
'order_by_field' => 'Info',
|
||||
],
|
||||
];
|
||||
$sortableColCount = count($sortableColumns);
|
||||
|
||||
$sqlQuery = $showFullSql
|
||||
? 'SHOW FULL PROCESSLIST'
|
||||
: 'SHOW PROCESSLIST';
|
||||
if ((! empty($params['order_by_field'])
|
||||
&& ! empty($params['sort_order']))
|
||||
|| ! empty($params['showExecuting'])
|
||||
) {
|
||||
$urlParams['order_by_field'] = $params['order_by_field'];
|
||||
$urlParams['sort_order'] = $params['sort_order'];
|
||||
$urlParams['showExecuting'] = $params['showExecuting'];
|
||||
$sqlQuery = 'SELECT * FROM `INFORMATION_SCHEMA`.`PROCESSLIST` ';
|
||||
}
|
||||
if (! empty($params['showExecuting'])) {
|
||||
$sqlQuery .= ' WHERE state != "" ';
|
||||
}
|
||||
if (! empty($params['order_by_field']) && ! empty($params['sort_order'])) {
|
||||
$sqlQuery .= ' ORDER BY '
|
||||
. Util::backquote($params['order_by_field'])
|
||||
. ' ' . $params['sort_order'];
|
||||
}
|
||||
|
||||
$result = $this->dbi->query($sqlQuery);
|
||||
|
||||
$columns = [];
|
||||
foreach ($sortableColumns as $columnKey => $column) {
|
||||
$is_sorted = ! empty($params['order_by_field'])
|
||||
&& ! empty($params['sort_order'])
|
||||
&& ($params['order_by_field'] == $column['order_by_field']);
|
||||
|
||||
$column['sort_order'] = 'ASC';
|
||||
if ($is_sorted && $params['sort_order'] === 'ASC') {
|
||||
$column['sort_order'] = 'DESC';
|
||||
}
|
||||
if (isset($params['showExecuting'])) {
|
||||
$column['showExecuting'] = 'on';
|
||||
}
|
||||
|
||||
$columns[$columnKey] = [
|
||||
'name' => $column['column_name'],
|
||||
'params' => $column,
|
||||
'is_sorted' => $is_sorted,
|
||||
'sort_order' => $column['sort_order'],
|
||||
'has_full_query' => false,
|
||||
'is_full' => false,
|
||||
];
|
||||
|
||||
if (0 !== --$sortableColCount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns[$columnKey]['has_full_query'] = true;
|
||||
if (! $showFullSql) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns[$columnKey]['is_full'] = true;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
while ($process = $this->dbi->fetchAssoc($result)) {
|
||||
// Array keys need to modify due to the way it has used
|
||||
// to display column values
|
||||
if ((! empty($params['order_by_field']) && ! empty($params['sort_order']))
|
||||
|| ! empty($params['showExecuting'])
|
||||
) {
|
||||
foreach (array_keys($process) as $key) {
|
||||
$newKey = ucfirst(mb_strtolower($key));
|
||||
if ($newKey === $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$process[$newKey] = $process[$key];
|
||||
unset($process[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'id' => $process['Id'],
|
||||
'user' => $process['User'],
|
||||
'host' => $process['Host'],
|
||||
'db' => ! isset($process['db']) || strlen($process['db']) === 0 ? '' : $process['db'],
|
||||
'command' => $process['Command'],
|
||||
'time' => $process['Time'],
|
||||
'state' => ! empty($process['State']) ? $process['State'] : '---',
|
||||
'progress' => ! empty($process['Progress']) ? $process['Progress'] : '---',
|
||||
'info' => ! empty($process['Info']) ? Generator::formatSql(
|
||||
$process['Info'],
|
||||
! $showFullSql
|
||||
) : '---',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->template->render('server/status/processes/list', [
|
||||
'columns' => $columns,
|
||||
'rows' => $rows,
|
||||
'refresh_params' => $urlParams,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
/**
|
||||
* Displays query statistics for the server
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_sum;
|
||||
use function arsort;
|
||||
use function count;
|
||||
use function str_replace;
|
||||
|
||||
class QueriesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'chart.js',
|
||||
'vendor/jqplot/jquery.jqplot.js',
|
||||
'vendor/jqplot/plugins/jqplot.pieRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.highlighter.js',
|
||||
'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'server/status/sorter.js',
|
||||
'server/status/queries.js',
|
||||
]);
|
||||
|
||||
if ($this->data->dataLoaded) {
|
||||
$hourFactor = 3600 / $this->data->status['Uptime'];
|
||||
$usedQueries = $this->data->usedQueries;
|
||||
$totalQueries = array_sum($usedQueries);
|
||||
|
||||
$stats = [
|
||||
'total' => $totalQueries,
|
||||
'per_hour' => $totalQueries * $hourFactor,
|
||||
'per_minute' => $totalQueries * 60 / $this->data->status['Uptime'],
|
||||
'per_second' => $totalQueries / $this->data->status['Uptime'],
|
||||
];
|
||||
|
||||
// reverse sort by value to show most used statements first
|
||||
arsort($usedQueries);
|
||||
|
||||
$chart = [];
|
||||
$querySum = array_sum($usedQueries);
|
||||
$otherSum = 0;
|
||||
$queries = [];
|
||||
foreach ($usedQueries as $key => $value) {
|
||||
// For the percentage column, use Questions - Connections, because
|
||||
// the number of connections is not an item of the Query types
|
||||
// but is included in Questions. Then the total of the percentages is 100.
|
||||
$name = str_replace(['Com_', '_'], ['', ' '], $key);
|
||||
// Group together values that make out less than 2% into "Other", but only
|
||||
// if we have more than 6 fractions already
|
||||
if ($value < $querySum * 0.02 && count($chart) > 6) {
|
||||
$otherSum += $value;
|
||||
} else {
|
||||
$chart[$name] = $value;
|
||||
}
|
||||
|
||||
$queries[$key] = [
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'per_hour' => $value * $hourFactor,
|
||||
'percentage' => $value * 100 / $totalQueries,
|
||||
];
|
||||
}
|
||||
|
||||
if ($otherSum > 0) {
|
||||
$chart[__('Other')] = $otherSum;
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/status/queries/index', [
|
||||
'is_data_loaded' => $this->data->dataLoaded,
|
||||
'stats' => $stats ?? null,
|
||||
'queries' => $queries ?? [],
|
||||
'chart' => $chart ?? [],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ReplicationGui;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* Object the server status page: processes, connections and traffic.
|
||||
*/
|
||||
class StatusController extends AbstractController
|
||||
{
|
||||
/** @var ReplicationGui */
|
||||
private $replicationGui;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, ReplicationGui $replicationGui, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->replicationGui = $replicationGui;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$replicationInfo = $this->data->getReplicationInfo();
|
||||
$primaryInfo = $replicationInfo->getPrimaryInfo();
|
||||
$replicaInfo = $replicationInfo->getReplicaInfo();
|
||||
|
||||
$traffic = [];
|
||||
$connections = [];
|
||||
$replication = '';
|
||||
if ($this->data->dataLoaded) {
|
||||
// In some case the data was reported not to exist, check it for all keys
|
||||
if (isset($this->data->status['Bytes_received'], $this->data->status['Bytes_sent'])) {
|
||||
$networkTraffic = implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'] + $this->data->status['Bytes_sent'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
if (isset($this->data->status['Uptime'])) {
|
||||
$uptime = Util::timespanFormat($this->data->status['Uptime']);
|
||||
}
|
||||
$startTime = Util::localisedDate($this->getStartTime());
|
||||
|
||||
$traffic = $this->getTrafficInfo();
|
||||
|
||||
$connections = $this->getConnectionsInfo();
|
||||
|
||||
if ($primaryInfo['status']) {
|
||||
$replication .= $this->replicationGui->getHtmlForReplicationStatusTable('master');
|
||||
}
|
||||
if ($replicaInfo['status']) {
|
||||
$replication .= $this->replicationGui->getHtmlForReplicationStatusTable('slave');
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/status/status/index', [
|
||||
'is_data_loaded' => $this->data->dataLoaded,
|
||||
'network_traffic' => $networkTraffic ?? null,
|
||||
'uptime' => $uptime ?? null,
|
||||
'start_time' => $startTime ?? null,
|
||||
'traffic' => $traffic,
|
||||
'connections' => $connections,
|
||||
'is_master' => $primaryInfo['status'],
|
||||
'is_slave' => $replicaInfo['status'],
|
||||
'replication' => $replication,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getStartTime(): int
|
||||
{
|
||||
return (int) $this->dbi->fetchValue(
|
||||
'SELECT UNIX_TIMESTAMP() - ' . $this->data->status['Uptime']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getTrafficInfo(): array
|
||||
{
|
||||
$hourFactor = 3600 / $this->data->status['Uptime'];
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => __('Received'),
|
||||
'number' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
'per_hour' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'] * $hourFactor,
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
],
|
||||
[
|
||||
'name' => __('Sent'),
|
||||
'number' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_sent'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
'per_hour' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_sent'] * $hourFactor,
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
],
|
||||
[
|
||||
'name' => __('Total'),
|
||||
'number' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'] + $this->data->status['Bytes_sent'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
'per_hour' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
($this->data->status['Bytes_received'] + $this->data->status['Bytes_sent']) * $hourFactor,
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getConnectionsInfo(): array
|
||||
{
|
||||
$hourFactor = 3600 / $this->data->status['Uptime'];
|
||||
|
||||
$failedAttemptsPercentage = '---';
|
||||
$abortedPercentage = '---';
|
||||
if ($this->data->status['Connections'] > 0) {
|
||||
$failedAttemptsPercentage = Util::formatNumber(
|
||||
$this->data->status['Aborted_connects'] * 100 / $this->data->status['Connections'],
|
||||
0,
|
||||
2,
|
||||
true
|
||||
) . '%';
|
||||
|
||||
$abortedPercentage = Util::formatNumber(
|
||||
$this->data->status['Aborted_clients'] * 100 / $this->data->status['Connections'],
|
||||
0,
|
||||
2,
|
||||
true
|
||||
) . '%';
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => __('Max. concurrent connections'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Max_used_connections'],
|
||||
0
|
||||
),
|
||||
'per_hour' => '---',
|
||||
'percentage' => '---',
|
||||
],
|
||||
[
|
||||
'name' => __('Failed attempts'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Aborted_connects'],
|
||||
4,
|
||||
1,
|
||||
true
|
||||
),
|
||||
'per_hour' => Util::formatNumber(
|
||||
$this->data->status['Aborted_connects'] * $hourFactor,
|
||||
4,
|
||||
2,
|
||||
true
|
||||
),
|
||||
'percentage' => $failedAttemptsPercentage,
|
||||
],
|
||||
[
|
||||
'name' => __('Aborted'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Aborted_clients'],
|
||||
4,
|
||||
1,
|
||||
true
|
||||
),
|
||||
'per_hour' => Util::formatNumber(
|
||||
$this->data->status['Aborted_clients'] * $hourFactor,
|
||||
4,
|
||||
2,
|
||||
true
|
||||
),
|
||||
'percentage' => $abortedPercentage,
|
||||
],
|
||||
[
|
||||
'name' => __('Total'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Connections'],
|
||||
4,
|
||||
0
|
||||
),
|
||||
'per_hour' => Util::formatNumber(
|
||||
$this->data->status['Connections'] * $hourFactor,
|
||||
4,
|
||||
2
|
||||
),
|
||||
'percentage' => Util::formatNumber(100, 0, 2) . '%',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,679 @@
|
|||
<?php
|
||||
/**
|
||||
* Displays a list of server status variables
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function in_array;
|
||||
use function is_numeric;
|
||||
use function mb_strpos;
|
||||
|
||||
class VariablesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'flush' => $_POST['flush'] ?? null,
|
||||
'filterAlert' => $_POST['filterAlert'] ?? null,
|
||||
'filterText' => $_POST['filterText'] ?? null,
|
||||
'filterCategory' => $_POST['filterCategory'] ?? null,
|
||||
'dontFormat' => $_POST['dontFormat'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'server/status/variables.js',
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'server/status/sorter.js',
|
||||
]);
|
||||
|
||||
if (isset($params['flush'])) {
|
||||
$this->flush($params['flush']);
|
||||
}
|
||||
|
||||
if ($this->data->dataLoaded) {
|
||||
$categories = [];
|
||||
foreach ($this->data->sections as $sectionId => $sectionName) {
|
||||
if (! isset($this->data->sectionUsed[$sectionId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories[$sectionId] = [
|
||||
'id' => $sectionId,
|
||||
'name' => $sectionName,
|
||||
'is_selected' => false,
|
||||
];
|
||||
if (empty($params['filterCategory'])
|
||||
|| $params['filterCategory'] !== $sectionId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories[$sectionId]['is_selected'] = true;
|
||||
}
|
||||
|
||||
$links = [];
|
||||
foreach ($this->data->links as $sectionName => $sectionLinks) {
|
||||
$links[$sectionName] = [
|
||||
'name' => 'status_' . $sectionName,
|
||||
'links' => $sectionLinks,
|
||||
];
|
||||
}
|
||||
|
||||
$descriptions = $this->getDescriptions();
|
||||
$alerts = $this->getAlerts();
|
||||
|
||||
$variables = [];
|
||||
foreach ($this->data->status as $name => $value) {
|
||||
$variables[$name] = [
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'is_numeric' => is_numeric($value),
|
||||
'class' => $this->data->allocationMap[$name] ?? null,
|
||||
'doc' => '',
|
||||
'has_alert' => false,
|
||||
'is_alert' => false,
|
||||
'description' => $descriptions[$name] ?? '',
|
||||
'description_doc' => [],
|
||||
];
|
||||
|
||||
// Fields containing % are calculated,
|
||||
// they can not be described in MySQL documentation
|
||||
if (mb_strpos($name, '%') === false) {
|
||||
$variables[$name]['doc'] = Generator::linkToVarDocumentation(
|
||||
$name,
|
||||
$this->dbi->isMariaDB()
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($alerts[$name])) {
|
||||
$variables[$name]['has_alert'] = true;
|
||||
if ($value > $alerts[$name]) {
|
||||
$variables[$name]['is_alert'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($this->data->links[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->data->links[$name] as $linkName => $linkUrl) {
|
||||
$variables[$name]['description_doc'][] = [
|
||||
'name' => $linkName,
|
||||
'url' => $linkUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/status/variables/index', [
|
||||
'is_data_loaded' => $this->data->dataLoaded,
|
||||
'filter_text' => ! empty($params['filterText']) ? $params['filterText'] : '',
|
||||
'is_only_alerts' => ! empty($params['filterAlert']),
|
||||
'is_not_formatted' => ! empty($params['dontFormat']),
|
||||
'categories' => $categories ?? [],
|
||||
'links' => $links ?? [],
|
||||
'variables' => $variables ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush status variables if requested
|
||||
*
|
||||
* @param string $flush Variable name
|
||||
*/
|
||||
private function flush(string $flush): void
|
||||
{
|
||||
$flushCommands = [
|
||||
'STATUS',
|
||||
'TABLES',
|
||||
'QUERY CACHE',
|
||||
];
|
||||
|
||||
if (! in_array($flush, $flushCommands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dbi->query('FLUSH ' . $flush . ';');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getAlerts(): array
|
||||
{
|
||||
// name => max value before alert
|
||||
return [
|
||||
// lower is better
|
||||
// variable => max value
|
||||
'Aborted_clients' => 0,
|
||||
'Aborted_connects' => 0,
|
||||
|
||||
'Binlog_cache_disk_use' => 0,
|
||||
|
||||
'Created_tmp_disk_tables' => 0,
|
||||
|
||||
'Handler_read_rnd' => 0,
|
||||
'Handler_read_rnd_next' => 0,
|
||||
|
||||
'Innodb_buffer_pool_pages_dirty' => 0,
|
||||
'Innodb_buffer_pool_reads' => 0,
|
||||
'Innodb_buffer_pool_wait_free' => 0,
|
||||
'Innodb_log_waits' => 0,
|
||||
'Innodb_row_lock_time_avg' => 10, // ms
|
||||
'Innodb_row_lock_time_max' => 50, // ms
|
||||
'Innodb_row_lock_waits' => 0,
|
||||
|
||||
'Slow_queries' => 0,
|
||||
'Delayed_errors' => 0,
|
||||
'Select_full_join' => 0,
|
||||
'Select_range_check' => 0,
|
||||
'Sort_merge_passes' => 0,
|
||||
'Opened_tables' => 0,
|
||||
'Table_locks_waited' => 0,
|
||||
'Qcache_lowmem_prunes' => 0,
|
||||
|
||||
'Qcache_free_blocks' =>
|
||||
isset($this->data->status['Qcache_total_blocks'])
|
||||
? $this->data->status['Qcache_total_blocks'] / 5
|
||||
: 0,
|
||||
'Slow_launch_threads' => 0,
|
||||
|
||||
// depends on Key_read_requests
|
||||
// normally lower then 1:0.01
|
||||
'Key_reads' => isset($this->data->status['Key_read_requests'])
|
||||
? 0.01 * $this->data->status['Key_read_requests'] : 0,
|
||||
// depends on Key_write_requests
|
||||
// normally nearly 1:1
|
||||
'Key_writes' => isset($this->data->status['Key_write_requests'])
|
||||
? 0.9 * $this->data->status['Key_write_requests'] : 0,
|
||||
|
||||
'Key_buffer_fraction' => 0.5,
|
||||
|
||||
// alert if more than 95% of thread cache is in use
|
||||
'Threads_cached' => isset($this->data->variables['thread_cache_size'])
|
||||
? 0.95 * $this->data->variables['thread_cache_size'] : 0,
|
||||
|
||||
// higher is better
|
||||
// variable => min value
|
||||
//'Handler read key' => '> ',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of variable descriptions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDescriptions(): array
|
||||
{
|
||||
/**
|
||||
* Messages are built using the message name
|
||||
*/
|
||||
return [
|
||||
'Aborted_clients' => __(
|
||||
'The number of connections that were aborted because the client died'
|
||||
. ' without closing the connection properly.'
|
||||
),
|
||||
'Aborted_connects' => __(
|
||||
'The number of failed attempts to connect to the MySQL server.'
|
||||
),
|
||||
'Binlog_cache_disk_use' => __(
|
||||
'The number of transactions that used the temporary binary log cache'
|
||||
. ' but that exceeded the value of binlog_cache_size and used a'
|
||||
. ' temporary file to store statements from the transaction.'
|
||||
),
|
||||
'Binlog_cache_use' => __(
|
||||
'The number of transactions that used the temporary binary log cache.'
|
||||
),
|
||||
'Connections' => __(
|
||||
'The number of connection attempts (successful or not)'
|
||||
. ' to the MySQL server.'
|
||||
),
|
||||
'Created_tmp_disk_tables' => __(
|
||||
'The number of temporary tables on disk created automatically by'
|
||||
. ' the server while executing statements. If'
|
||||
. ' Created_tmp_disk_tables is big, you may want to increase the'
|
||||
. ' tmp_table_size value to cause temporary tables to be'
|
||||
. ' memory-based instead of disk-based.'
|
||||
),
|
||||
'Created_tmp_files' => __(
|
||||
'How many temporary files mysqld has created.'
|
||||
),
|
||||
'Created_tmp_tables' => __(
|
||||
'The number of in-memory temporary tables created automatically'
|
||||
. ' by the server while executing statements.'
|
||||
),
|
||||
'Delayed_errors' => __(
|
||||
'The number of rows written with INSERT DELAYED for which some'
|
||||
. ' error occurred (probably duplicate key).'
|
||||
),
|
||||
'Delayed_insert_threads' => __(
|
||||
'The number of INSERT DELAYED handler threads in use. Every'
|
||||
. ' different table on which one uses INSERT DELAYED gets'
|
||||
. ' its own thread.'
|
||||
),
|
||||
'Delayed_writes' => __(
|
||||
'The number of INSERT DELAYED rows written.'
|
||||
),
|
||||
'Flush_commands' => __(
|
||||
'The number of executed FLUSH statements.'
|
||||
),
|
||||
'Handler_commit' => __(
|
||||
'The number of internal COMMIT statements.'
|
||||
),
|
||||
'Handler_delete' => __(
|
||||
'The number of times a row was deleted from a table.'
|
||||
),
|
||||
'Handler_discover' => __(
|
||||
'The MySQL server can ask the NDB Cluster storage engine if it'
|
||||
. ' knows about a table with a given name. This is called discovery.'
|
||||
. ' Handler_discover indicates the number of time tables have been'
|
||||
. ' discovered.'
|
||||
),
|
||||
'Handler_read_first' => __(
|
||||
'The number of times the first entry was read from an index. If this'
|
||||
. ' is high, it suggests that the server is doing a lot of full'
|
||||
. ' index scans; for example, SELECT col1 FROM foo, assuming that'
|
||||
. ' col1 is indexed.'
|
||||
),
|
||||
'Handler_read_key' => __(
|
||||
'The number of requests to read a row based on a key. If this is'
|
||||
. ' high, it is a good indication that your queries and tables'
|
||||
. ' are properly indexed.'
|
||||
),
|
||||
'Handler_read_next' => __(
|
||||
'The number of requests to read the next row in key order. This is'
|
||||
. ' incremented if you are querying an index column with a range'
|
||||
. ' constraint or if you are doing an index scan.'
|
||||
),
|
||||
'Handler_read_prev' => __(
|
||||
'The number of requests to read the previous row in key order.'
|
||||
. ' This read method is mainly used to optimize ORDER BY … DESC.'
|
||||
),
|
||||
'Handler_read_rnd' => __(
|
||||
'The number of requests to read a row based on a fixed position.'
|
||||
. ' This is high if you are doing a lot of queries that require'
|
||||
. ' sorting of the result. You probably have a lot of queries that'
|
||||
. ' require MySQL to scan whole tables or you have joins that'
|
||||
. ' don\'t use keys properly.'
|
||||
),
|
||||
'Handler_read_rnd_next' => __(
|
||||
'The number of requests to read the next row in the data file.'
|
||||
. ' This is high if you are doing a lot of table scans. Generally'
|
||||
. ' this suggests that your tables are not properly indexed or that'
|
||||
. ' your queries are not written to take advantage of the indexes'
|
||||
. ' you have.'
|
||||
),
|
||||
'Handler_rollback' => __(
|
||||
'The number of internal ROLLBACK statements.'
|
||||
),
|
||||
'Handler_update' => __(
|
||||
'The number of requests to update a row in a table.'
|
||||
),
|
||||
'Handler_write' => __(
|
||||
'The number of requests to insert a row in a table.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_data' => __(
|
||||
'The number of pages containing data (dirty or clean).'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_dirty' => __(
|
||||
'The number of pages currently dirty.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_flushed' => __(
|
||||
'The number of buffer pool pages that have been requested'
|
||||
. ' to be flushed.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_free' => __(
|
||||
'The number of free pages.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_latched' => __(
|
||||
'The number of latched pages in InnoDB buffer pool. These are pages'
|
||||
. ' currently being read or written or that can\'t be flushed or'
|
||||
. ' removed for some other reason.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_misc' => __(
|
||||
'The number of pages busy because they have been allocated for'
|
||||
. ' administrative overhead such as row locks or the adaptive'
|
||||
. ' hash index. This value can also be calculated as'
|
||||
. ' Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free'
|
||||
. ' - Innodb_buffer_pool_pages_data.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_total' => __(
|
||||
'Total size of buffer pool, in pages.'
|
||||
),
|
||||
'Innodb_buffer_pool_read_ahead_rnd' => __(
|
||||
'The number of "random" read-aheads InnoDB initiated. This happens'
|
||||
. ' when a query is to scan a large portion of a table but in'
|
||||
. ' random order.'
|
||||
),
|
||||
'Innodb_buffer_pool_read_ahead_seq' => __(
|
||||
'The number of sequential read-aheads InnoDB initiated. This'
|
||||
. ' happens when InnoDB does a sequential full table scan.'
|
||||
),
|
||||
'Innodb_buffer_pool_read_requests' => __(
|
||||
'The number of logical read requests InnoDB has done.'
|
||||
),
|
||||
'Innodb_buffer_pool_reads' => __(
|
||||
'The number of logical reads that InnoDB could not satisfy'
|
||||
. ' from buffer pool and had to do a single-page read.'
|
||||
),
|
||||
'Innodb_buffer_pool_wait_free' => __(
|
||||
'Normally, writes to the InnoDB buffer pool happen in the'
|
||||
. ' background. However, if it\'s necessary to read or create a page'
|
||||
. ' and no clean pages are available, it\'s necessary to wait for'
|
||||
. ' pages to be flushed first. This counter counts instances of'
|
||||
. ' these waits. If the buffer pool size was set properly, this'
|
||||
. ' value should be small.'
|
||||
),
|
||||
'Innodb_buffer_pool_write_requests' => __(
|
||||
'The number writes done to the InnoDB buffer pool.'
|
||||
),
|
||||
'Innodb_data_fsyncs' => __(
|
||||
'The number of fsync() operations so far.'
|
||||
),
|
||||
'Innodb_data_pending_fsyncs' => __(
|
||||
'The current number of pending fsync() operations.'
|
||||
),
|
||||
'Innodb_data_pending_reads' => __(
|
||||
'The current number of pending reads.'
|
||||
),
|
||||
'Innodb_data_pending_writes' => __(
|
||||
'The current number of pending writes.'
|
||||
),
|
||||
'Innodb_data_read' => __(
|
||||
'The amount of data read so far, in bytes.'
|
||||
),
|
||||
'Innodb_data_reads' => __(
|
||||
'The total number of data reads.'
|
||||
),
|
||||
'Innodb_data_writes' => __(
|
||||
'The total number of data writes.'
|
||||
),
|
||||
'Innodb_data_written' => __(
|
||||
'The amount of data written so far, in bytes.'
|
||||
),
|
||||
'Innodb_dblwr_pages_written' => __(
|
||||
'The number of pages that have been written for'
|
||||
. ' doublewrite operations.'
|
||||
),
|
||||
'Innodb_dblwr_writes' => __(
|
||||
'The number of doublewrite operations that have been performed.'
|
||||
),
|
||||
'Innodb_log_waits' => __(
|
||||
'The number of waits we had because log buffer was too small and'
|
||||
. ' we had to wait for it to be flushed before continuing.'
|
||||
),
|
||||
'Innodb_log_write_requests' => __(
|
||||
'The number of log write requests.'
|
||||
),
|
||||
'Innodb_log_writes' => __(
|
||||
'The number of physical writes to the log file.'
|
||||
),
|
||||
'Innodb_os_log_fsyncs' => __(
|
||||
'The number of fsync() writes done to the log file.'
|
||||
),
|
||||
'Innodb_os_log_pending_fsyncs' => __(
|
||||
'The number of pending log file fsyncs.'
|
||||
),
|
||||
'Innodb_os_log_pending_writes' => __(
|
||||
'Pending log file writes.'
|
||||
),
|
||||
'Innodb_os_log_written' => __(
|
||||
'The number of bytes written to the log file.'
|
||||
),
|
||||
'Innodb_pages_created' => __(
|
||||
'The number of pages created.'
|
||||
),
|
||||
'Innodb_page_size' => __(
|
||||
'The compiled-in InnoDB page size (default 16KB). Many values are'
|
||||
. ' counted in pages; the page size allows them to be easily'
|
||||
. ' converted to bytes.'
|
||||
),
|
||||
'Innodb_pages_read' => __(
|
||||
'The number of pages read.'
|
||||
),
|
||||
'Innodb_pages_written' => __(
|
||||
'The number of pages written.'
|
||||
),
|
||||
'Innodb_row_lock_current_waits' => __(
|
||||
'The number of row locks currently being waited for.'
|
||||
),
|
||||
'Innodb_row_lock_time_avg' => __(
|
||||
'The average time to acquire a row lock, in milliseconds.'
|
||||
),
|
||||
'Innodb_row_lock_time' => __(
|
||||
'The total time spent in acquiring row locks, in milliseconds.'
|
||||
),
|
||||
'Innodb_row_lock_time_max' => __(
|
||||
'The maximum time to acquire a row lock, in milliseconds.'
|
||||
),
|
||||
'Innodb_row_lock_waits' => __(
|
||||
'The number of times a row lock had to be waited for.'
|
||||
),
|
||||
'Innodb_rows_deleted' => __(
|
||||
'The number of rows deleted from InnoDB tables.'
|
||||
),
|
||||
'Innodb_rows_inserted' => __(
|
||||
'The number of rows inserted in InnoDB tables.'
|
||||
),
|
||||
'Innodb_rows_read' => __(
|
||||
'The number of rows read from InnoDB tables.'
|
||||
),
|
||||
'Innodb_rows_updated' => __(
|
||||
'The number of rows updated in InnoDB tables.'
|
||||
),
|
||||
'Key_blocks_not_flushed' => __(
|
||||
'The number of key blocks in the key cache that have changed but'
|
||||
. ' haven\'t yet been flushed to disk. It used to be known as'
|
||||
. ' Not_flushed_key_blocks.'
|
||||
),
|
||||
'Key_blocks_unused' => __(
|
||||
'The number of unused blocks in the key cache. You can use this'
|
||||
. ' value to determine how much of the key cache is in use.'
|
||||
),
|
||||
'Key_blocks_used' => __(
|
||||
'The number of used blocks in the key cache. This value is a'
|
||||
. ' high-water mark that indicates the maximum number of blocks'
|
||||
. ' that have ever been in use at one time.'
|
||||
),
|
||||
'Key_buffer_fraction_%' => __(
|
||||
'Percentage of used key cache (calculated value)'
|
||||
),
|
||||
'Key_read_requests' => __(
|
||||
'The number of requests to read a key block from the cache.'
|
||||
),
|
||||
'Key_reads' => __(
|
||||
'The number of physical reads of a key block from disk. If Key_reads'
|
||||
. ' is big, then your key_buffer_size value is probably too small.'
|
||||
. ' The cache miss rate can be calculated as'
|
||||
. ' Key_reads/Key_read_requests.'
|
||||
),
|
||||
'Key_read_ratio_%' => __(
|
||||
'Key cache miss calculated as rate of physical reads compared'
|
||||
. ' to read requests (calculated value)'
|
||||
),
|
||||
'Key_write_requests' => __(
|
||||
'The number of requests to write a key block to the cache.'
|
||||
),
|
||||
'Key_writes' => __(
|
||||
'The number of physical writes of a key block to disk.'
|
||||
),
|
||||
'Key_write_ratio_%' => __(
|
||||
'Percentage of physical writes compared'
|
||||
. ' to write requests (calculated value)'
|
||||
),
|
||||
'Last_query_cost' => __(
|
||||
'The total cost of the last compiled query as computed by the query'
|
||||
. ' optimizer. Useful for comparing the cost of different query'
|
||||
. ' plans for the same query. The default value of 0 means that'
|
||||
. ' no query has been compiled yet.'
|
||||
),
|
||||
'Max_used_connections' => __(
|
||||
'The maximum number of connections that have been in use'
|
||||
. ' simultaneously since the server started.'
|
||||
),
|
||||
'Not_flushed_delayed_rows' => __(
|
||||
'The number of rows waiting to be written in INSERT DELAYED queues.'
|
||||
),
|
||||
'Opened_tables' => __(
|
||||
'The number of tables that have been opened. If opened tables is'
|
||||
. ' big, your table_open_cache value is probably too small.'
|
||||
),
|
||||
'Open_files' => __(
|
||||
'The number of files that are open.'
|
||||
),
|
||||
'Open_streams' => __(
|
||||
'The number of streams that are open (used mainly for logging).'
|
||||
),
|
||||
'Open_tables' => __(
|
||||
'The number of tables that are open.'
|
||||
),
|
||||
'Qcache_free_blocks' => __(
|
||||
'The number of free memory blocks in query cache. High numbers can'
|
||||
. ' indicate fragmentation issues, which may be solved by issuing'
|
||||
. ' a FLUSH QUERY CACHE statement.'
|
||||
),
|
||||
'Qcache_free_memory' => __(
|
||||
'The amount of free memory for query cache.'
|
||||
),
|
||||
'Qcache_hits' => __(
|
||||
'The number of cache hits.'
|
||||
),
|
||||
'Qcache_inserts' => __(
|
||||
'The number of queries added to the cache.'
|
||||
),
|
||||
'Qcache_lowmem_prunes' => __(
|
||||
'The number of queries that have been removed from the cache to'
|
||||
. ' free up memory for caching new queries. This information can'
|
||||
. ' help you tune the query cache size. The query cache uses a'
|
||||
. ' least recently used (LRU) strategy to decide which queries'
|
||||
. ' to remove from the cache.'
|
||||
),
|
||||
'Qcache_not_cached' => __(
|
||||
'The number of non-cached queries (not cachable, or not cached'
|
||||
. ' due to the query_cache_type setting).'
|
||||
),
|
||||
'Qcache_queries_in_cache' => __(
|
||||
'The number of queries registered in the cache.'
|
||||
),
|
||||
'Qcache_total_blocks' => __(
|
||||
'The total number of blocks in the query cache.'
|
||||
),
|
||||
'Rpl_status' => __(
|
||||
'The status of failsafe replication (not yet implemented).'
|
||||
),
|
||||
'Select_full_join' => __(
|
||||
'The number of joins that do not use indexes. If this value is'
|
||||
. ' not 0, you should carefully check the indexes of your tables.'
|
||||
),
|
||||
'Select_full_range_join' => __(
|
||||
'The number of joins that used a range search on a reference table.'
|
||||
),
|
||||
'Select_range_check' => __(
|
||||
'The number of joins without keys that check for key usage after'
|
||||
. ' each row. (If this is not 0, you should carefully check the'
|
||||
. ' indexes of your tables.)'
|
||||
),
|
||||
'Select_range' => __(
|
||||
'The number of joins that used ranges on the first table. (It\'s'
|
||||
. ' normally not critical even if this is big.)'
|
||||
),
|
||||
'Select_scan' => __(
|
||||
'The number of joins that did a full scan of the first table.'
|
||||
),
|
||||
'Slave_open_temp_tables' => __(
|
||||
'The number of temporary tables currently'
|
||||
. ' open by the slave SQL thread.'
|
||||
),
|
||||
'Slave_retried_transactions' => __(
|
||||
'Total (since startup) number of times the replication slave SQL'
|
||||
. ' thread has retried transactions.'
|
||||
),
|
||||
'Slave_running' => __(
|
||||
'This is ON if this server is a slave that is connected to a master.'
|
||||
),
|
||||
'Slow_launch_threads' => __(
|
||||
'The number of threads that have taken more than slow_launch_time'
|
||||
. ' seconds to create.'
|
||||
),
|
||||
'Slow_queries' => __(
|
||||
'The number of queries that have taken more than long_query_time'
|
||||
. ' seconds.'
|
||||
),
|
||||
'Sort_merge_passes' => __(
|
||||
'The number of merge passes the sort algorithm has had to do.'
|
||||
. ' If this value is large, you should consider increasing the'
|
||||
. ' value of the sort_buffer_size system variable.'
|
||||
),
|
||||
'Sort_range' => __(
|
||||
'The number of sorts that were done with ranges.'
|
||||
),
|
||||
'Sort_rows' => __(
|
||||
'The number of sorted rows.'
|
||||
),
|
||||
'Sort_scan' => __(
|
||||
'The number of sorts that were done by scanning the table.'
|
||||
),
|
||||
'Table_locks_immediate' => __(
|
||||
'The number of times that a table lock was acquired immediately.'
|
||||
),
|
||||
'Table_locks_waited' => __(
|
||||
'The number of times that a table lock could not be acquired'
|
||||
. ' immediately and a wait was needed. If this is high, and you have'
|
||||
. ' performance problems, you should first optimize your queries,'
|
||||
. ' and then either split your table or tables or use replication.'
|
||||
),
|
||||
'Threads_cached' => __(
|
||||
'The number of threads in the thread cache. The cache hit rate can'
|
||||
. ' be calculated as Threads_created/Connections. If this value is'
|
||||
. ' red you should raise your thread_cache_size.'
|
||||
),
|
||||
'Threads_connected' => __(
|
||||
'The number of currently open connections.'
|
||||
),
|
||||
'Threads_created' => __(
|
||||
'The number of threads created to handle connections. If'
|
||||
. ' Threads_created is big, you may want to increase the'
|
||||
. ' thread_cache_size value. (Normally this doesn\'t give a notable'
|
||||
. ' performance improvement if you have a good thread'
|
||||
. ' implementation.)'
|
||||
),
|
||||
'Threads_cache_hitrate_%' => __(
|
||||
'Thread cache hit rate (calculated value)'
|
||||
),
|
||||
'Threads_running' => __(
|
||||
'The number of threads that are not sleeping.'
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\UserGroups;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Displays the 'User groups' sub page under 'Users' page.
|
||||
*/
|
||||
class UserGroupsController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
if (! $cfgRelation['menuswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['server/user_groups.js']);
|
||||
|
||||
/**
|
||||
* Only allowed to superuser
|
||||
*/
|
||||
if (! $this->dbi->isSuperUser()) {
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No Privileges'))->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML('<div class="container-fluid">');
|
||||
$this->render('server/privileges/subnav', [
|
||||
'active' => 'user-groups',
|
||||
'is_super_user' => $this->dbi->isSuperUser(),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Delete user group
|
||||
*/
|
||||
if (! empty($_POST['deleteUserGroup'])) {
|
||||
UserGroups::delete($_POST['userGroup']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new user group
|
||||
*/
|
||||
if (! empty($_POST['addUserGroupSubmit'])) {
|
||||
UserGroups::edit($_POST['userGroup'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user group
|
||||
*/
|
||||
if (! empty($_POST['editUserGroupSubmit'])) {
|
||||
UserGroups::edit($_POST['userGroup']);
|
||||
}
|
||||
|
||||
if (isset($_POST['viewUsers'])) {
|
||||
// Display users belonging to a user group
|
||||
$this->response->addHTML(UserGroups::getHtmlForListingUsersofAGroup($_POST['userGroup']));
|
||||
}
|
||||
|
||||
if (isset($_GET['addUserGroup'])) {
|
||||
// Display add user group dialog
|
||||
$this->response->addHTML(UserGroups::getHtmlToEditUserGroup());
|
||||
} elseif (isset($_POST['editUserGroup'])) {
|
||||
// Display edit user group dialog
|
||||
$this->response->addHTML(UserGroups::getHtmlToEditUserGroup($_POST['userGroup']));
|
||||
} else {
|
||||
// Display user groups table
|
||||
$this->response->addHTML(UserGroups::getHtmlForUserGroupsTable());
|
||||
}
|
||||
|
||||
$this->response->addHTML('</div>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Providers\ServerVariables\ServerVariablesProvider;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function header;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_numeric;
|
||||
use function mb_strtolower;
|
||||
use function pow;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Handles viewing and editing server variables
|
||||
*/
|
||||
class VariablesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = ['filter' => $_GET['filter'] ?? null];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$filterValue = ! empty($params['filter']) ? $params['filter'] : '';
|
||||
|
||||
$this->addScriptFiles(['server/variables.js']);
|
||||
|
||||
$variables = [];
|
||||
$serverVarsResult = $this->dbi->tryQuery('SHOW SESSION VARIABLES;');
|
||||
if ($serverVarsResult !== false) {
|
||||
$serverVarsSession = [];
|
||||
while ($arr = $this->dbi->fetchRow($serverVarsResult)) {
|
||||
$serverVarsSession[$arr[0]] = $arr[1];
|
||||
}
|
||||
$this->dbi->freeResult($serverVarsResult);
|
||||
|
||||
$serverVars = $this->dbi->fetchResult('SHOW GLOBAL VARIABLES;', 0, 1);
|
||||
|
||||
// list of static (i.e. non-editable) system variables
|
||||
$staticVariables = ServerVariablesProvider::getImplementation()->getStaticVariables();
|
||||
|
||||
foreach ($serverVars as $name => $value) {
|
||||
$hasSessionValue = isset($serverVarsSession[$name])
|
||||
&& $serverVarsSession[$name] !== $value;
|
||||
$docLink = Generator::linkToVarDocumentation(
|
||||
$name,
|
||||
$this->dbi->isMariaDB(),
|
||||
str_replace('_', ' ', $name)
|
||||
);
|
||||
|
||||
[$formattedValue, $isEscaped] = $this->formatVariable($name, $value);
|
||||
if ($hasSessionValue) {
|
||||
[$sessionFormattedValue] = $this->formatVariable(
|
||||
$name,
|
||||
$serverVarsSession[$name]
|
||||
);
|
||||
}
|
||||
|
||||
$variables[] = [
|
||||
'name' => $name,
|
||||
'is_editable' => ! in_array(strtolower($name), $staticVariables),
|
||||
'doc_link' => $docLink,
|
||||
'value' => $formattedValue,
|
||||
'is_escaped' => $isEscaped,
|
||||
'has_session_value' => $hasSessionValue,
|
||||
'session_value' => $sessionFormattedValue ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/variables/index', [
|
||||
'variables' => $variables,
|
||||
'filter_value' => $filterValue,
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'is_mariadb' => $this->dbi->isMariaDB(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the AJAX request for a single variable value
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function getValue(array $params): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send with correct charset
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
// Do not use double quotes inside the query to avoid a problem
|
||||
// when server is running in ANSI_QUOTES sql_mode
|
||||
$varValue = $this->dbi->fetchSingleRow(
|
||||
'SHOW GLOBAL VARIABLES WHERE Variable_name=\''
|
||||
. $this->dbi->escapeString($params['name']) . '\';',
|
||||
'NUM'
|
||||
);
|
||||
|
||||
$json = [
|
||||
'message' => $varValue[1],
|
||||
];
|
||||
|
||||
$variableType = ServerVariablesProvider::getImplementation()->getVariableType($params['name']);
|
||||
|
||||
if ($variableType === 'byte') {
|
||||
$json['message'] = implode(
|
||||
' ',
|
||||
Util::formatByteDown($varValue[1], 3, 3)
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the AJAX request for setting value for a single variable
|
||||
*
|
||||
* @param array $vars Request parameters
|
||||
*/
|
||||
public function setValue(array $vars): void
|
||||
{
|
||||
$params = [
|
||||
'varName' => $vars['name'],
|
||||
'varValue' => $_POST['varValue'] ?? null,
|
||||
];
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = (string) $params['varValue'];
|
||||
$variableName = (string) $params['varName'];
|
||||
$matches = [];
|
||||
$variableType = ServerVariablesProvider::getImplementation()->getVariableType($variableName);
|
||||
|
||||
if ($variableType === 'byte' && preg_match(
|
||||
'/^\s*(\d+(\.\d+)?)\s*(mb|kb|mib|kib|gb|gib)\s*$/i',
|
||||
$value,
|
||||
$matches
|
||||
)) {
|
||||
$exp = [
|
||||
'kb' => 1,
|
||||
'kib' => 1,
|
||||
'mb' => 2,
|
||||
'mib' => 2,
|
||||
'gb' => 3,
|
||||
'gib' => 3,
|
||||
];
|
||||
$value = (float) $matches[1] * pow(
|
||||
1024,
|
||||
$exp[mb_strtolower($matches[3])]
|
||||
);
|
||||
} else {
|
||||
$value = $this->dbi->escapeString($value);
|
||||
}
|
||||
|
||||
if (! is_numeric($value)) {
|
||||
$value = "'" . $value . "'";
|
||||
}
|
||||
|
||||
$json = [];
|
||||
if (! preg_match('/[^a-zA-Z0-9_]+/', $params['varName'])
|
||||
&& $this->dbi->query(
|
||||
'SET GLOBAL ' . $params['varName'] . ' = ' . $value
|
||||
)
|
||||
) {
|
||||
// Some values are rounded down etc.
|
||||
$varValue = $this->dbi->fetchSingleRow(
|
||||
'SHOW GLOBAL VARIABLES WHERE Variable_name="'
|
||||
. $this->dbi->escapeString($params['varName'])
|
||||
. '";',
|
||||
'NUM'
|
||||
);
|
||||
[$formattedValue, $isHtmlFormatted] = $this->formatVariable(
|
||||
$params['varName'],
|
||||
$varValue[1]
|
||||
);
|
||||
|
||||
if ($isHtmlFormatted === false) {
|
||||
$json['variable'] = htmlspecialchars($formattedValue);
|
||||
} else {
|
||||
$json['variable'] = $formattedValue;
|
||||
}
|
||||
} else {
|
||||
$this->response->setRequestStatus(false);
|
||||
$json['error'] = __('Setting variable failed');
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Variable
|
||||
*
|
||||
* @param string $name variable name
|
||||
* @param int|string $value variable value
|
||||
*
|
||||
* @return array formatted string and bool if string is HTML formatted
|
||||
*/
|
||||
private function formatVariable($name, $value): array
|
||||
{
|
||||
$isHtmlFormatted = false;
|
||||
$formattedValue = $value;
|
||||
|
||||
if (is_numeric($value)) {
|
||||
$variableType = ServerVariablesProvider::getImplementation()->getVariableType($name);
|
||||
|
||||
if ($variableType === 'byte') {
|
||||
$isHtmlFormatted = true;
|
||||
$formattedValue = trim(
|
||||
$this->template->render(
|
||||
'server/variables/format_variable',
|
||||
[
|
||||
'valueTitle' => Util::formatNumber($value, 0),
|
||||
'value' => implode(' ', Util::formatByteDown($value, 3, 3)),
|
||||
]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$formattedValue = Util::formatNumber($value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
$formattedValue,
|
||||
$isHtmlFormatted,
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue