From 04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa Mon Sep 17 00:00:00 2001 From: Charles Date: Thu, 9 Jan 2020 10:55:03 +0100 Subject: phpmyadmin working --- .../Controllers/Server/BinlogController.php | 149 +++++ .../Controllers/Server/CollationsController.php | 100 ++++ .../Controllers/Server/DatabasesController.php | 424 ++++++++++++++ .../Controllers/Server/EnginesController.php | 69 +++ .../Controllers/Server/PluginsController.php | 77 +++ .../Controllers/Server/ReplicationController.php | 72 +++ .../classes/Controllers/Server/SqlController.php | 34 ++ .../Server/Status/AbstractController.php | 42 ++ .../Server/Status/AdvisorController.php | 60 ++ .../Server/Status/MonitorController.php | 146 +++++ .../Server/Status/ProcessesController.php | 240 ++++++++ .../Server/Status/QueriesController.php | 75 +++ .../Controllers/Server/Status/StatusController.php | 260 +++++++++ .../Server/Status/VariablesController.php | 639 +++++++++++++++++++++ .../Controllers/Server/VariablesController.php | 238 ++++++++ 15 files changed, 2625 insertions(+) create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/BinlogController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/CollationsController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/DatabasesController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/EnginesController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/PluginsController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/ReplicationController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/SqlController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AbstractController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AdvisorController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/MonitorController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/ProcessesController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/QueriesController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/StatusController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/VariablesController.php create mode 100644 srcs/phpmyadmin/libraries/classes/Controllers/Server/VariablesController.php (limited to 'srcs/phpmyadmin/libraries/classes/Controllers/Server') diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/BinlogController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/BinlogController.php new file mode 100644 index 0000000..859559c --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/BinlogController.php @@ -0,0 +1,149 @@ +binaryLogs = $this->dbi->fetchResult( + 'SHOW MASTER LOGS', + 'Log_name', + null, + DatabaseInterface::CONNECT_USER, + DatabaseInterface::QUERY_STORE + ); + } + + /** + * Index action + * + * @param array $params Request params + * + * @return string + */ + public function indexAction(array $params): string + { + global $cfg, $pmaThemeImage; + + include_once ROOT_PATH . 'libraries/server_common.inc.php'; + + $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; + } + + return $this->template->render('server/binlog/index', [ + 'url_params' => $urlParams, + 'binary_logs' => $this->binaryLogs, + 'log' => $params['log'], + 'sql_message' => Util::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' => $pmaThemeImage, + ]); + } + + /** + * @param string $log Binary log file name + * @param int $position Position to display + * @param int $maxRows Maximum number of rows + * + * @return string + */ + 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; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/CollationsController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/CollationsController.php new file mode 100644 index 0000000..2d806e8 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/CollationsController.php @@ -0,0 +1,100 @@ +charsets = $charsets ?? Charsets::getCharsets( + $this->dbi, + $cfg['Server']['DisableIS'] + ); + $this->collations = $collations ?? Charsets::getCollations( + $this->dbi, + $cfg['Server']['DisableIS'] + ); + } + + /** + * Index action + * + * @return string HTML + */ + public function indexAction(): string + { + include_once ROOT_PATH . 'libraries/server_common.inc.php'; + + $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, + ]; + } + + return $this->template->render('server/collations/index', [ + 'charsets' => $charsets, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/DatabasesController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/DatabasesController.php new file mode 100644 index 0000000..a601137 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/DatabasesController.php @@ -0,0 +1,424 @@ +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($replication_types ?? []); + + $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(); + + return $this->template->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' => $replication_info['master']['status'], + 'has_slave_replication' => $replication_info['slave']['status'], + 'is_drop_allowed' => $this->dbi->isSuperuser() || $cfg['AllowUserDropDatabase'], + 'default_tab_database' => $cfg['DefaultTabDatabase'], + 'pma_theme_image' => $pmaThemeImage, + 'text_dir' => $text_dir, + ]); + } + + /** + * Handles creating a new database + * + * @param array $params Request parameters + * + * @return array JSON + */ + public function createDatabaseAction(array $params): array + { + global $cfg, $db; + + // 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'])) { + list($databaseCharset) = explode('_', $params['db_collation']); + $charsets = Charsets::getCharsets( + $this->dbi, + $cfg['Server']['DisableIS'] + ); + $collations = Charsets::getCollations( + $this->dbi, + $cfg['Server']['DisableIS'] + ); + if (in_array($databaseCharset, array_keys($charsets)) + && in_array($params['db_collation'], array_keys($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($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']); + + $json = [ + 'message' => $message, + 'sql_query' => Util::getMessage(null, $sqlQuery, 'success'), + 'url_query' => Util::getScriptNameForOption( + $cfg['DefaultTabDatabase'], + 'database' + ) . Url::getCommon(['db' => $params['new_db']]), + ]; + } + + return $json; + } + + /** + * Handles dropping multiple databases + * + * @param array $params Request parameters + * + * @return array JSON + */ + public function dropDatabasesAction(array $params): array + { + global $submit_mult, $mult_btn, $selected; + + if (! isset($params['selected_dbs'])) { + $message = Message::error(__('No databases selected.')); + } else { + $action = 'server_databases.php'; + $err_url = $action . Url::getCommon(); + + $submit_mult = 'drop_db'; + $mult_btn = __('Yes'); + + include ROOT_PATH . 'libraries/mult_submits.inc.php'; + + if (empty($message)) { // no error message + $numberOfDatabases = count($selected); + $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()); + } + + return $json; + } + + /** + * Extracts parameters sort order and sort by + * + * @param string|null $sortBy sort by + * @param string|null $sortOrder sort order + * + * @return void + */ + private function setSortDetails(?string $sortBy, ?string $sortOrder): void + { + if (empty($sortBy)) { + $this->sortBy = 'SCHEMA_NAME'; + } else { + $sortByWhitelist = [ + '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, $sortByWhitelist)) { + $this->sortBy = $sortBy; + } + } + + $this->sortOrder = 'asc'; + if (isset($sortOrder) + && mb_strtolower($sortOrder) === 'desc' + ) { + $this->sortOrder = 'desc'; + } + } + + /** + * Returns database list + * + * @param array $replicationTypes replication types + * + * @return array + */ + private function getDatabases(array $replicationTypes): array + { + global $cfg, $replication_info; + + $databases = []; + $totalStatistics = $this->getStatisticsColumns(); + foreach ($this->databases as $database) { + $replication = [ + 'master' => [ + 'status' => $replication_info['master']['status'], + ], + 'slave' => [ + 'status' => $replication_info['slave']['status'], + ], + ]; + foreach ($replicationTypes as $type) { + if ($replication_info[$type]['status']) { + $key = array_search( + $database["SCHEMA_NAME"], + $replication_info[$type]['Ignore_DB'] + ); + if (strlen((string) $key) > 0) { + $replication[$type]['is_replicated'] = false; + } else { + $key = array_search( + $database["SCHEMA_NAME"], + $replication_info[$type]['Do_DB'] + ); + + if (strlen((string) $key) > 0 + || count($replication_info[$type]['Do_DB']) === 0 + ) { + // if ($key != null) did not work for index "0" + $replication[$type]['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; + } + } + + $databases[$database['SCHEMA_NAME']] = [ + 'name' => $database['SCHEMA_NAME'], + 'collation' => [], + 'statistics' => $statistics, + 'replication' => $replication, + 'is_system_schema' => $this->dbi->isSystemSchema( + $database['SCHEMA_NAME'], + true + ), + ]; + $collation = Charsets::findCollationByName( + $this->dbi, + $cfg['Server']['DisableIS'], + $database['DEFAULT_COLLATION_NAME'] + ); + if ($collation !== null) { + $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, + ], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/EnginesController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/EnginesController.php new file mode 100644 index 0000000..1170551 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/EnginesController.php @@ -0,0 +1,69 @@ +template->render('server/engines/index', [ + 'engines' => StorageEngine::getStorageEngines(), + ]); + } + + /** + * Displays details about a given Storage Engine + * + * @param array $params Request params + * + * @return string + */ + public function show(array $params): string + { + require ROOT_PATH . 'libraries/server_common.inc.php'; + + $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) : '', + ]; + } + + return $this->template->render('server/engines/show', [ + 'engine' => $engine, + 'page' => $page, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/PluginsController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/PluginsController.php new file mode 100644 index 0000000..dce48a6 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/PluginsController.php @@ -0,0 +1,77 @@ +plugins = $plugins; + } + + /** + * Index action + * + * @return string + */ + public function index(): string + { + include ROOT_PATH . 'libraries/server_common.inc.php'; + + $header = $this->response->getHeader(); + $scripts = $header->getScripts(); + $scripts->addFile('vendor/jquery/jquery.tablesorter.js'); + $scripts->addFile('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) + ); + } + return $this->template->render('server/plugins/index', [ + 'plugins' => $plugins, + 'clean_types' => $cleanTypes, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/ReplicationController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/ReplicationController.php new file mode 100644 index 0000000..dd48411 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/ReplicationController.php @@ -0,0 +1,72 @@ +getHtmlForErrorMessage(); + + if ($replication_info['master']['status']) { + $masterReplicationHtml = $replicationGui->getHtmlForMasterReplication(); + } + + if (isset($params['mr_configure'])) { + $masterConfigurationHtml = $replicationGui->getHtmlForMasterConfiguration(); + } else { + if (! isset($params['repl_clear_scr'])) { + $slaveConfigurationHtml = $replicationGui->getHtmlForSlaveConfiguration( + $replication_info['slave']['status'], + $server_slave_replication + ); + } + if (isset($params['sl_configure'])) { + $changeMasterHtml = $replicationGui->getHtmlForReplicationChangeMaster('slave_changemaster'); + } + } + + return $this->template->render('server/replication/index', [ + 'url_params' => $url_params, + 'is_super_user' => $this->dbi->isSuperuser(), + 'error_messages' => $errorMessages, + 'is_master' => $replication_info['master']['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 ?? '', + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/SqlController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/SqlController.php new file mode 100644 index 0000000..fd9ead6 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/SqlController.php @@ -0,0 +1,34 @@ +getHtml(); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AbstractController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AbstractController.php new file mode 100644 index 0000000..8d4b51d --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AbstractController.php @@ -0,0 +1,42 @@ +data = $data; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AdvisorController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AdvisorController.php new file mode 100644 index 0000000..5b93b0e --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/AdvisorController.php @@ -0,0 +1,60 @@ +advisor = $advisor; + } + + /** + * @return string + */ + public function index(): string + { + $data = ''; + if ($this->data->dataLoaded) { + $data = json_encode($this->advisor->run()); + } + + return $this->template->render('server/status/advisor/index', [ + 'data' => $data, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/MonitorController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/MonitorController.php new file mode 100644 index 0000000..1d2cadc --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/MonitorController.php @@ -0,0 +1,146 @@ +monitor = $monitor; + } + + /** + * @return string HTML + */ + public function index(): string + { + $form = [ + 'server_time' => microtime(true) * 1000, + 'server_os' => SysInfo::getOs(), + 'is_superuser' => $this->dbi->isSuperuser(), + 'server_db_isLocal' => $this->data->db_isLocal, + ]; + + $javascriptVariableNames = []; + foreach ($this->data->status as $name => $value) { + if (is_numeric($value)) { + $javascriptVariableNames[] = $name; + } + } + + return $this->template->render('server/status/monitor/index', [ + 'image_path' => $GLOBALS['pmaThemeImage'], + 'javascript_variable_names' => $javascriptVariableNames, + 'form' => $form, + ]); + } + + /** + * @param array $params Request parameters + * @return array JSON + */ + public function chartingData(array $params): array + { + $json = []; + $json['message'] = $this->monitor->getJsonForChartingData( + $params['requiredData'] ?? '' + ); + + return $json; + } + + /** + * @param array $params Request parameters + * @return array JSON + */ + public function logDataTypeSlow(array $params): array + { + $json = []; + $json['message'] = $this->monitor->getJsonForLogDataTypeSlow( + (int) $params['time_start'], + (int) $params['time_end'] + ); + + return $json; + } + + /** + * @param array $params Request parameters + * @return array JSON + */ + public function logDataTypeGeneral(array $params): array + { + $json = []; + $json['message'] = $this->monitor->getJsonForLogDataTypeGeneral( + (int) $params['time_start'], + (int) $params['time_end'], + (bool) $params['limitTypes'], + (bool) $params['removeVariables'] + ); + + return $json; + } + + /** + * @param array $params Request parameters + * @return array JSON + */ + public function loggingVars(array $params): array + { + $json = []; + $json['message'] = $this->monitor->getJsonForLoggingVars( + $params['varName'], + $params['varValue'] + ); + + return $json; + } + + /** + * @param array $params Request parameters + * @return array JSON + */ + public function queryAnalyzer(array $params): array + { + $json = []; + $json['message'] = $this->monitor->getJsonForQueryAnalyzer( + $params['database'] ?? '', + $params['query'] ?? '' + ); + + return $json; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/ProcessesController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/ProcessesController.php new file mode 100644 index 0000000..9817e1b --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/ProcessesController.php @@ -0,0 +1,240 @@ + 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); + + return $this->template->render('server/status/processes/index', [ + 'url_params' => $urlParams, + 'is_checked' => $isChecked, + 'server_process_list' => $serverProcessList, + ]); + } + + /** + * Only sends the process list table + * + * @param array $params Request parameters + * @return string + */ + public function refresh(array $params): string + { + return $this->getList($params); + } + + /** + * @param array $params Request parameters + * @return array + */ + public function kill(array $params): array + { + $kill = (int) $params['kill']; + $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); + + $json = []; + $json['message'] = $message; + + return $json; + } + + /** + * @param array $params Request parameters + * @return string + */ + 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) { + $columns[$columnKey]['has_full_query'] = true; + if ($showFullSql) { + $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) { + $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']) ? Util::formatSql( + $process['Info'], + ! $showFullSql + ) : '---', + ]; + } + + return $this->template->render('server/status/processes/list', [ + 'columns' => $columns, + 'rows' => $rows, + 'refresh_params' => $urlParams, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/QueriesController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/QueriesController.php new file mode 100644 index 0000000..76430d4 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/QueriesController.php @@ -0,0 +1,75 @@ +data->dataLoaded) { + $hourFactor = 3600 / $this->data->status['Uptime']; + $usedQueries = $this->data->used_queries; + $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; + } + } + + return $this->template->render('server/status/queries/index', [ + 'is_data_loaded' => $this->data->dataLoaded, + 'stats' => $stats ?? null, + 'queries' => $queries ?? [], + 'chart' => $chart ?? [], + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/StatusController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/StatusController.php new file mode 100644 index 0000000..53d78a3 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/StatusController.php @@ -0,0 +1,260 @@ +data->dataLoaded) { + $networkTraffic = implode( + ' ', + Util::formatByteDown( + $this->data->status['Bytes_received'] + $this->data->status['Bytes_sent'], + 3, + 1 + ) + ); + $uptime = Util::timespanFormat($this->data->status['Uptime']); + $startTime = Util::localisedDate($this->getStartTime()); + + $traffic = $this->getTrafficInfo(); + + $connections = $this->getConnectionsInfo(); + + // display replication information + if ($replication_info['master']['status'] + || $replication_info['slave']['status'] + ) { + $replication = $this->getReplicationInfo($replicationGui); + } + } + + return $this->template->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' => $replication_info['master']['status'], + 'is_slave' => $replication_info['slave']['status'], + 'replication' => $replication, + ]); + } + + /** + * @return int + */ + 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) . '%', + ], + ]; + } + + /** + * @param ReplicationGui $replicationGui ReplicationGui instance + * + * @return string + */ + private function getReplicationInfo(ReplicationGui $replicationGui): string + { + global $replication_info, $replication_types; + + $output = ''; + foreach ($replication_types as $type) { + if (isset($replication_info[$type]['status']) + && $replication_info[$type]['status'] + ) { + $output .= $replicationGui->getHtmlForReplicationStatusTable($type); + } + } + + return $output; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/VariablesController.php b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/VariablesController.php new file mode 100644 index 0000000..a17f15f --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Controllers/Server/Status/VariablesController.php @@ -0,0 +1,639 @@ +flush($params['flush']); + } + + if ($this->data->dataLoaded) { + $categories = []; + foreach ($this->data->sections as $sectionId => $sectionName) { + if (isset($this->data->sectionUsed[$sectionId])) { + $categories[$sectionId] = [ + 'id' => $sectionId, + 'name' => $sectionName, + 'is_selected' => false, + ]; + if (! empty($params['filterCategory']) + && $params['filterCategory'] === $sectionId + ) { + $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'] = Util::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])) { + foreach ($this->data->links[$name] as $linkName => $linkUrl) { + $variables[$name]['description_doc'][] = [ + 'name' => $linkName, + 'url' => $linkUrl, + ]; + } + } + } + } + + return $this->template->render('server/status/variables/index', [ + 'is_data_loaded' => $this->data->dataLoaded, + 'filter_text' => ! empty($params['filt