diff --git a/CHANGELOG.md b/CHANGELOG.md index 161d05de49..1940923563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. This projec + [#128](https://github.com/luyadev/luya-module-admin/issues/128) A new indicator display the amount of time left until the user is logged out automatically. Also every keystroke inside any text field will reset the logout timer to null. No more timeouts while working! + [#126](https://github.com/luyadev/luya-module-admin/issues/126) Provide option to eager load api model relations. + [#20](https://github.com/luyadev/luya-module-admin/issues/20) New option `--sync-requests-count` for proxy command. ++ [#142](https://github.com/luyadev/luya-module-admin/issues/142) Proxy command can skip tables with `!` negation. + [#144](https://github.com/luyadev/luya-module-admin/pull/144) Proxy command ask for large table sync. ### Fixed diff --git a/src/commands/ProxyController.php b/src/commands/ProxyController.php index 62099bd51d..e503386d40 100644 --- a/src/commands/ProxyController.php +++ b/src/commands/ProxyController.php @@ -68,32 +68,33 @@ class ProxyController extends Command { use CacheableTrait; - + const CONFIG_VAR_URL = 'lcpProxyUrl'; - + const CONFIG_VAR_TOKEN = 'lcpProxyToken'; - + const CONFIG_VAR_IDENTIFIER = 'lcpProxyIdentifier'; - + /** * @inheritdoc */ public $defaultAction = 'sync'; - + /** * @var boolean Whether the isComplet sync check should be done after finish or not. If a table has a lot of traffic sometimes * there is a difference between the exchange of table informations (build) and transfer the data. In order to prevent * the exception message you can disable the strict compare mode. In order to ensure strict comparing enable $strict. */ public $strict = false; - + /** * @var string If a table option is passed only this table will be synchronised. If false by default all tables will be synced. You * can define multible tables ab seperating those with a comma `table1,table2,table`. In order to define only tables with start * with a given prefix you can use `app_*` using asterisks symbold to define wild card starts with string defintions. + * To exclude tables you can use a `!` before the tablename e.g. `!admin_*` or multible `!admin_*,!test_*` */ public $table; - + /** * @var string The production environment Domain where your LUYA application is running in production mode make so to use the right protocolo * examples: @@ -102,7 +103,7 @@ class ProxyController extends Command * */ public $url; - + /** * @var string The identifier you get from the Machines menu in your production env admin looks like this: lcp58e35acb4ca69 */ @@ -117,7 +118,7 @@ class ProxyController extends Command * @var integer Number of requests collected until they are written to the database. */ public $syncRequestsCount = 10; - + /** * @inheritdoc */ @@ -125,7 +126,7 @@ public function options($actionID) { return array_merge(parent::options($actionID), ['strict', 'table', 'url', 'idf', 'token', 'syncRequestsCount']); } - + /** * @inheritdoc */ @@ -133,7 +134,7 @@ public function optionAliases() { return array_merge(parent::optionAliases(), ['s' => 'strict', 't' => 'table', 'u' => 'url', 'i' => 'idf', 'tk' => 'token']); } - + /** * Sync Proxy Data. * @@ -143,7 +144,7 @@ public function actionSync() { if ($this->url === null) { $url = Config::get(self::CONFIG_VAR_URL); - + if (!$url) { $url = $this->prompt('Enter the Proxy PROD env URL (e.g. https://example.com):'); Config::set(self::CONFIG_VAR_URL, $url); @@ -151,10 +152,10 @@ public function actionSync() } else { $url = $this->url; } - + if ($this->idf === null) { $identifier = Config::get(self::CONFIG_VAR_IDENTIFIER); - + if (!$identifier) { $identifier = $this->prompt('Please enter the identifier:'); Config::set(self::CONFIG_VAR_IDENTIFIER, trim($identifier)); @@ -162,10 +163,10 @@ public function actionSync() } else { $identifier = $this->idf; } - + if ($this->token === null) { $token = Config::get(self::CONFIG_VAR_TOKEN); - + if (!$token) { $token = $this->prompt('Please enter the access token:'); Config::set(self::CONFIG_VAR_TOKEN, trim($token)); @@ -173,17 +174,17 @@ public function actionSync() } else { $token = $this->token; } - - + + $proxyUrl = Url::ensureHttp(rtrim(trim($url), '/')) . '/admin/api-admin-proxy'; $this->outputInfo('Connect to: ' . $proxyUrl); - + $curl = new Curl(); $curl->get($proxyUrl, ['identifier' => $identifier, 'token' => sha1($token)]); - + if (!$curl->error) { $this->flushHasCache(); - + $this->verbosePrint($curl->response); $response = Json::decode($curl->response); $build = new ClientBuild($this, [ @@ -199,18 +200,18 @@ public function actionSync() 'machineIdentifier' => $identifier, 'machineToken' => sha1($token), ]); - + $process = new ClientTransfer(['build' => $build]); if ($process->start()) { // as the admin_config table is synced to, we have to restore the current active config which has been used. Config::set(self::CONFIG_VAR_IDENTIFIER, $identifier); Config::set(self::CONFIG_VAR_TOKEN, $token); Config::set(self::CONFIG_VAR_URL, $url); - + return $this->outputSuccess('Sync process has been successfully finished.'); } } - + $this->clearConfig(); $this->output($curl->response); return $this->outputError($curl->error_message); @@ -222,7 +223,7 @@ private function clearConfig() Config::remove(self::CONFIG_VAR_URL); Config::remove(self::CONFIG_VAR_IDENTIFIER); } - + /** * Cleanup all stored Config Data. * diff --git a/src/proxy/ClientBuild.php b/src/proxy/ClientBuild.php index dff1dfd92c..f59a2160c3 100644 --- a/src/proxy/ClientBuild.php +++ b/src/proxy/ClientBuild.php @@ -71,41 +71,76 @@ public function init() } private $_buildConfig; - + public function setBuildConfig(array $config) { $this->_buildConfig = $config; - + foreach ($config['tables'] as $tableName => $tableConfig) { if (!empty($this->optionTable)) { - $skip = true; - - foreach ($this->optionTable as $useName) { - if ($useName == $tableName || StringHelper::startsWithWildcard($tableName, $useName)) { - $skip = false; - } - } - - if ($skip) { + + if ($this->isSkippableTable($tableName, $this->optionTable)) { continue; } } - + $schema = Yii::$app->db->getTableSchema($tableName); - + if ($schema !== null) { $this->_tables[$tableName] = new ClientTable($this, $tableConfig); } } } - + + /** + * Compare the tableName with the given filters. + * + * Example filters: + * + * "cms_*" include only cms_* tables + * "cms_*,admin_*" include only cms_* and admin_* tables + * "!cms_*" exclude all cms_* tables + * "!cms_*,!admin_*" exclude all cms_*and admin_* tables + * "cms_*,!admin_*" include all cms_* tables but exclude all admin_* tables + * + * Only first match is relevant: + * "cms_*,!admin_*,admin_*" include all cms_* tables but exclude all admin_* tables (last match has no effect) + * "cms_*,admin_*,!admin_*" include all cms_* and admin_* tables (last match has no effect) + * + * @param $tableName + * @param array $filters Array of tables which should skipped. + * @return bool True if table can be skipped. + * @since 1.2.1 + */ + private function isSkippableTable($tableName, array $filters) + { + $skip = true; + + foreach ($filters as $filter) { + + $exclude = false; + if (substr($filter, 0, 1) == "!") { + $exclude = true; + $skip = false; + + $filter = substr($filter, 1); + } + + if ($filter == $tableName || StringHelper::startsWithWildcard($tableName, $filter)) { + return $exclude; + } + } + + return $skip; + } + public function getStorageFilesCount() { return $this->_buildConfig['storageFilesCount']; } - + private $_tables = []; - + public function getTables() { return $this->_tables; diff --git a/tests/admin/proxy/ClientBuildTest.php b/tests/admin/proxy/ClientBuildTest.php new file mode 100644 index 0000000000..24be83c58c --- /dev/null +++ b/tests/admin/proxy/ClientBuildTest.php @@ -0,0 +1,62 @@ +app); + $build = new ClientBuild($ctrl, [ + 'buildConfig' => ['tables' => []], + ]); + + $tableFilters = [ + "cms_include_case1" => ["cms_*"], + "cms_include_case2" => ["!admin_*"], + + "cms_include_case3" => ["cms_*", "admin_*"], + "admin_include_case3" => ["cms_*", "admin_*"], + + "cms_include_case4" => ["cms_*", "admin_*"], + "admin_include_case4" => ["cms_*", "admin_*"], + + "cms_include_case5" => ["cms_*", "admin_*", "!cms_*"], + "admin_include_case5" => ["cms_*", "admin_*", "!cms_*"], + ]; + + foreach ($tableFilters as $tableName => $filters) { + $this->assertFalse($this->invokeMethod($build, 'isSkippableTable', [$tableName, $filters]), "$tableName should be skippable by filter " . implode(', ', $filters)); + } + } + + public function testIsNotSkippableTable() + { + $ctrl = new ProxyController('proxyctrl', $this->app); + $build = new ClientBuild($ctrl, [ + 'buildConfig' => ['tables' => []], + ]); + + $tableFilters = [ + "cms_exclude_case1" => ["!cms_*"], + "cms_exclude_case2" => ["admin_*"], + + "cms_exclude_case3" => ["!cms_*", "!admin_*"], + "admin_exclude_case3" => ["!cms_*", "!admin_*"], + + "cms_exclude_case4" => ["!cms_*", "!admin_*"], + "admin_exclude_case4" => ["!cms_*", "!admin_*"], + + "cms_exclude_case5" => ["!cms_*", "!admin_*", "cms_*"], + "admin_exclude_case5" => ["!cms_*", "!admin_*", "cms_*"], + ]; + + foreach ($tableFilters as $tableName => $filters) { + $this->assertTrue($this->invokeMethod($build, 'isSkippableTable', [$tableName, $filters]), "$tableName should not be skippable by filter " . implode(', ', $filters)); + } + } +}