diff --git a/assets/AppAsset.php b/assets/AppAsset.php index 87f2f9c..9087606 100644 --- a/assets/AppAsset.php +++ b/assets/AppAsset.php @@ -17,9 +17,12 @@ class AppAsset extends AssetBundle ]; public $depends = [ 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', + 'yii\bootstrap\BootstrapPluginAsset', 'rmrevin\yii\fontawesome\AssetBundle', - 'dmstr\web\AdminLteAsset', - 'shifrin\noty\NotyAsset', +// 'dmstr\web\AdminLteAsset', +// 'shifrin\noty\NotyAsset', + 'app\assets\MDThemeAsset', +// 'app\assets\JSCookieAsset', +// 'app\assets\ChartjsAsset', ]; } diff --git a/assets/ChartjsAsset.php b/assets/ChartjsAsset.php new file mode 100644 index 0000000..1d889c5 --- /dev/null +++ b/assets/ChartjsAsset.php @@ -0,0 +1,18 @@ + [ + '*.js', + ] + ]; +} diff --git a/assets/FontawesomeIconpickerAsset.php b/assets/FontawesomeIconpickerAsset.php new file mode 100644 index 0000000..b8c2bd5 --- /dev/null +++ b/assets/FontawesomeIconpickerAsset.php @@ -0,0 +1,19 @@ + [ + '*.js', + ] + ]; +} diff --git a/assets/MDThemeAsset.php b/assets/MDThemeAsset.php new file mode 100644 index 0000000..1b6968c --- /dev/null +++ b/assets/MDThemeAsset.php @@ -0,0 +1,26 @@ + [ + '*.js', + '*.css', + '*.js.*', + '*.css.*', + ] + ]; +} diff --git a/commands/PanelController.php b/commands/StartCoreServerController.php similarity index 84% rename from commands/PanelController.php rename to commands/StartCoreServerController.php index 7362cd0..e4c0c70 100644 --- a/commands/PanelController.php +++ b/commands/StartCoreServerController.php @@ -8,14 +8,13 @@ use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use React\EventLoop\Factory; use React\Socket\Server; -use Yii; use yii\console\Controller; -class PanelController extends Controller +class StartCoreServerController extends Controller { public function actionIndex($port = 8081) { - echo "Starting server on port $port..." . PHP_EOL; + echo "Starting Core WS Server on port $port..." . PHP_EOL; $loop = Factory::create(); diff --git a/components/ActionButtonColumn.php b/components/ActionButtonColumn.php index 1cebdc2..d6804c4 100644 --- a/components/ActionButtonColumn.php +++ b/components/ActionButtonColumn.php @@ -37,7 +37,7 @@ class ActionButtonColumn extends ActionColumn $this->buttons['view'] = function ($url, $model, $key) { return Html::a(FA::i('eye'), $url, array_merge([ 'title' => Yii::t('yii', 'View'), - 'class' => 'btn btn-ar btn-default btn-xs', + 'class' => 'btn btn-flat btn-xs btn-default', ], $this->buttonOptions)); }; } @@ -45,7 +45,7 @@ class ActionButtonColumn extends ActionColumn $this->buttons['update'] = function ($url, $model, $key) { return Html::a(FA::i('pencil'), $url, array_merge([ 'title' => Yii::t('yii', 'Update'), - 'class' => 'btn btn-ar btn-primary btn-xs', + 'class' => 'btn btn-flat btn-xs btn-default', ], $this->buttonOptions)); }; } @@ -53,7 +53,7 @@ class ActionButtonColumn extends ActionColumn $this->buttons['delete'] = function ($url, $model, $key) { return Html::a(FA::i('trash'), $url, array_merge([ 'title' => Yii::t('yii', 'Delete'), - 'class' => 'btn btn-ar btn-danger btn-xs', + 'class' => 'btn btn-flat btn-xs btn-default', 'data-confirm' => Yii::t('yii', 'Are you sure you want to delete this item?'), 'data-method' => 'post', ], $this->buttonOptions)); diff --git a/composer.json b/composer.json index 61f7b6d..d02fb54 100644 --- a/composer.json +++ b/composer.json @@ -11,20 +11,22 @@ "cboden/ratchet": "^0.3.4", "bower-asset/handlebars": "^4.0", "bower-asset/spectrum": "^1.8", + "bower-asset/snackbarjs": "dev-fixes", "rmrevin/yii2-fontawesome": "~2.12", - "dmstr/yii2-adminlte-asset": "2.*", "linslin/yii2-curl": "1.0.8", "shifrin/yii2-noty": "^1.0", "ratchet/pawl": "^0.2.2", "voskobovich/yii2-many-many-behavior": "^3.2", "kartik-v/yii2-widgets": "^3.4", "kartik-v/yii2-datecontrol": "^1.9", - "voskobovich/yii2-linker-behavior": "4.0.1-rc" + "voskobovich/yii2-linker-behavior": "4.0.1-rc", + "bower-asset/js-cookie": "^2.1", + "bower-asset/chart.js": ">=2.5", + "bower-asset/fontawesome-iconpicker": "^1.3" }, "require-dev": { "yiisoft/yii2-debug": "*", - "yiisoft/yii2-gii": "*", - "yiisoft/yii2-faker": "*" + "yiisoft/yii2-gii": "*" }, "config": { "process-timeout": 1800 @@ -33,6 +35,16 @@ "asset-installer-paths": { "npm-asset-library": "vendor/npm", "bower-asset-library": "vendor/bower" - } + }, + "asset-repositories": [ + { + "type": "bower-github", + "url": "https://github.com/CyanoFresh/snackbarjs", + "name": "bower-asset/snackbarjs" + } + ] + }, + "scripts": { + "post-install-cmd": "php init --env=Development --overwrite=n" } } diff --git a/composer.lock b/composer.lock index e2e19d7..1540710 100644 --- a/composer.lock +++ b/composer.lock @@ -4,49 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "87c8d87131a5bfba1bfd212e32223bc6", - "content-hash": "662fe9c5133c13341bcddbe8b1f55bfc", + "hash": "d72e15d1c94855a26467586b784ffcb8", + "content-hash": "50f7bcdee8e1349910fd57865528edf4", "packages": [ - { - "name": "almasaeed2010/adminlte", - "version": "v2.3.11", - "source": { - "type": "git", - "url": "https://github.com/almasaeed2010/AdminLTE.git", - "reference": "2be703222af2edcb87e562d3da2299e4352bff8a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/almasaeed2010/AdminLTE/zipball/2be703222af2edcb87e562d3da2299e4352bff8a", - "reference": "2be703222af2edcb87e562d3da2299e4352bff8a", - "shasum": "" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Abdullah Almsaeed", - "email": "abdullah@almsaeedstudio.com" - } - ], - "description": "AdminLTE - admin control panel and dashboard that's based on Bootstrap 3", - "homepage": "http://almsaeedstudio.com/", - "keywords": [ - "JS", - "admin", - "back-end", - "css", - "less", - "responsive", - "template", - "theme", - "web" - ], - "time": "2017-01-08 21:03:57" - }, { "name": "bower-asset/bootstrap", "version": "v3.3.7", @@ -96,6 +56,73 @@ "web" ] }, + { + "name": "bower-asset/chart.js", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/chartjs/Chart.js.git", + "reference": "c867d3f728d595eafc017f4d4136128824d1db25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chartjs/Chart.js/zipball/c867d3f728d595eafc017f4d4136128824d1db25", + "reference": "c867d3f728d595eafc017f4d4136128824d1db25", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "./dist/Chart.js", + "bower-asset-ignore": [ + ".github", + ".codeclimate.yml", + ".gitignore", + ".npmignore", + ".travis.yml", + "scripts" + ] + }, + "license": [ + "MIT" + ], + "description": "Simple HTML5 charts using the canvas element." + }, + { + "name": "bower-asset/fontawesome-iconpicker", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/itsjavi/fontawesome-iconpicker.git", + "reference": "6087c66a7395738f59cab3887221bb75cd0e08f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itsjavi/fontawesome-iconpicker/zipball/6087c66a7395738f59cab3887221bb75cd0e08f7", + "reference": "6087c66a7395738f59cab3887221bb75cd0e08f7", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap": ">=3.0.0,<4.0.0", + "bower-asset/jquery": ">=1.10" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "dist/css/fontawesome-iconpicker.css", + "dist/css/fontawesome-iconpicker.min.css", + "dist/js/fontawesome-iconpicker.js", + "dist/js/fontawesome-iconpicker.min.js" + ], + "bower-asset-ignore": [ + "\\.*", + "/extension", + "/src", + "index.html", + "/package.json", + "/Gruntfile.js" + ] + } + }, { "name": "bower-asset/handlebars", "version": "v4.0.5", @@ -151,16 +178,16 @@ }, { "name": "bower-asset/jquery.inputmask", - "version": "3.2.7", + "version": "3.3.4", "source": { "type": "git", - "url": "https://github.com/RobinHerbots/jquery.inputmask.git", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" + "url": "https://github.com/RobinHerbots/Inputmask.git", + "reference": "8a882bc471ba4077c4f8ecbe3d9d61c7558f3ef2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/8a882bc471ba4077c4f8ecbe3d9d61c7558f3ef2", + "reference": "8a882bc471ba4077c4f8ecbe3d9d61c7558f3ef2", "shasum": "" }, "require": { @@ -169,7 +196,14 @@ "type": "bower-asset-library", "extra": { "bower-asset-main": [ - "./dist/inputmask/inputmask.js" + "./dist/inputmask/inputmask.dependencyLib", + "./dist/inputmask/inputmask", + "./dist/inputmask/inputmask.extensions", + "./dist/inputmask/inputmask.date.extensions", + "./dist/inputmask/inputmask.numeric.extensions", + "./dist/inputmask/inputmask.phone.extensions", + "./dist/inputmask/inputmask.regex.extensions", + "./dist/inputmask/jquery.inputmask" ], "bower-asset-ignore": [ "**/*", @@ -195,6 +229,41 @@ "plugins" ] }, + { + "name": "bower-asset/js-cookie", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/js-cookie/js-cookie.git", + "reference": "68acf18560eb7a5d21db7197ae24d975971d1ae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/js-cookie/js-cookie/zipball/68acf18560eb7a5d21db7197ae24d975971d1ae0", + "reference": "68acf18560eb7a5d21db7197ae24d975971d1ae0", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "src/js.cookie.js" + ], + "bower-asset-ignore": [ + "travis.sh", + "test", + "Gruntfile.js", + "package.json", + ".gitignore", + ".jshintignore", + ".jshintrc", + ".tm_properties", + ".travis.yml" + ] + }, + "license": [ + "MIT" + ] + }, { "name": "bower-asset/noty", "version": "v2.4.0", @@ -244,6 +313,36 @@ ] } }, + { + "name": "bower-asset/snackbarjs", + "version": "dev-fixes", + "source": { + "type": "git", + "url": "https://github.com/CyanoFresh/snackbarjs.git", + "reference": "18df00bca47dea8421922b37db2ebd798cadd0fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CyanoFresh/snackbarjs/zipball/18df00bca47dea8421922b37db2ebd798cadd0fa", + "reference": "18df00bca47dea8421922b37db2ebd798cadd0fa", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=2.0.0,<3.0.0" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "dist/snackbar.min.js", + "dist/snackbar.min.css" + ], + "bower-asset-ignore": [ + ".jshintrc", + "**/*.txt" + ] + }, + "time": "2017-02-08 21:57:42" + }, { "name": "bower-asset/spectrum", "version": "1.8.0", @@ -422,109 +521,6 @@ ], "time": "2016-09-14 20:40:20" }, - { - "name": "cebe/yii2-gravatar", - "version": "1.1", - "target-dir": "cebe/gravatar", - "source": { - "type": "git", - "url": "https://github.com/cebe/yii2-gravatar.git", - "reference": "c9c01bd14c9bdee9e5ae1ef1aad23f80c182c057" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cebe/yii2-gravatar/zipball/c9c01bd14c9bdee9e5ae1ef1aad23f80c182c057", - "reference": "c9c01bd14c9bdee9e5ae1ef1aad23f80c182c057", - "shasum": "" - }, - "require": { - "yiisoft/yii2": "*" - }, - "type": "yii2-extension", - "autoload": { - "psr-0": { - "cebe\\gravatar\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" - } - ], - "description": "Gravatar Widget for Yii 2", - "keywords": [ - "gravatar", - "yii" - ], - "time": "2013-12-10 17:49:58" - }, - { - "name": "dmstr/yii2-adminlte-asset", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/dmstr/yii2-adminlte-asset.git", - "reference": "32fdb7378f3f4bcc5fb35db5e17a2dd11f9e2b57" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dmstr/yii2-adminlte-asset/zipball/32fdb7378f3f4bcc5fb35db5e17a2dd11f9e2b57", - "reference": "32fdb7378f3f4bcc5fb35db5e17a2dd11f9e2b57", - "shasum": "" - }, - "require": { - "almasaeed2010/adminlte": "~2.0", - "cebe/yii2-gravatar": "1.*", - "rmrevin/yii2-fontawesome": "~2.9", - "yiisoft/yii2": "2.*", - "yiisoft/yii2-bootstrap": "2.*" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "dmstr\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Tobias Munk", - "email": "tobias@diemeisterei.de" - }, - { - "name": "Evgeniy Tkachenko", - "email": "et.coder@gmail.com" - } - ], - "description": "AdminLTE backend theme asset bundle for Yii 2.0 Framework", - "keywords": [ - "AdminLTE", - "admin", - "asset", - "backend", - "css", - "extension", - "less", - "theme", - "yii2" - ], - "time": "2016-10-21 02:03:45" - }, { "name": "evenement/evenement", "version": "v2.0.0", @@ -2693,16 +2689,16 @@ }, { "name": "react/socket", - "version": "v0.4.5", + "version": "v0.4.6", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "32385d71f84c4a26ea577cb91f1220decb440dce" + "reference": "cf074e53c974df52388ebd09710a9018894745d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/32385d71f84c4a26ea577cb91f1220decb440dce", - "reference": "32385d71f84c4a26ea577cb91f1220decb440dce", + "url": "https://api.github.com/repos/reactphp/socket/zipball/cf074e53c974df52388ebd09710a9018894745d2", + "reference": "cf074e53c974df52388ebd09710a9018894745d2", "shasum": "" }, "require": { @@ -2714,6 +2710,7 @@ }, "require-dev": { "clue/block-react": "^1.1", + "phpunit/phpunit": "~4.8", "react/socket-client": "^0.5.1" }, "type": "library", @@ -2730,7 +2727,7 @@ "keywords": [ "Socket" ], - "time": "2017-01-08 11:36:16" + "time": "2017-01-26 09:23:38" }, { "name": "react/socket-client", @@ -2775,16 +2772,16 @@ }, { "name": "react/stream", - "version": "v0.4.5", + "version": "v0.4.6", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "23389503012e1ab721ad498a5a1f4b39f7a43c00" + "reference": "44dc7f51ea48624110136b535b9ba44fd7d0c1ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/23389503012e1ab721ad498a5a1f4b39f7a43c00", - "reference": "23389503012e1ab721ad498a5a1f4b39f7a43c00", + "url": "https://api.github.com/repos/reactphp/stream/zipball/44dc7f51ea48624110136b535b9ba44fd7d0c1ee", + "reference": "44dc7f51ea48624110136b535b9ba44fd7d0c1ee", "shasum": "" }, "require": { @@ -2815,7 +2812,7 @@ "pipe", "stream" ], - "time": "2016-11-13 17:06:02" + "time": "2017-01-25 14:44:14" }, { "name": "rmrevin/yii2-fontawesome", @@ -3015,7 +3012,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v3.2.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -3075,16 +3072,16 @@ }, { "name": "symfony/http-foundation", - "version": "v3.2.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "33eb76bf1d833c705433e5361a646c164696394b" + "reference": "e192b04de44aa1ed0e39d6793f7e06f5e0b672a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/33eb76bf1d833c705433e5361a646c164696394b", - "reference": "33eb76bf1d833c705433e5361a646c164696394b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e192b04de44aa1ed0e39d6793f7e06f5e0b672a0", + "reference": "e192b04de44aa1ed0e39d6793f7e06f5e0b672a0", "shasum": "" }, "require": { @@ -3124,7 +3121,7 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-01-08 20:47:33" + "time": "2017-02-02 13:47:35" }, { "name": "symfony/polyfill-mbstring", @@ -3187,16 +3184,16 @@ }, { "name": "symfony/routing", - "version": "v3.2.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "fda2c67d47ec801726ca888c95d701d31b27b444" + "reference": "af464432c177dbcdbb32295113b7627500331f2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/fda2c67d47ec801726ca888c95d701d31b27b444", - "reference": "fda2c67d47ec801726ca888c95d701d31b27b444", + "url": "https://api.github.com/repos/symfony/routing/zipball/af464432c177dbcdbb32295113b7627500331f2d", + "reference": "af464432c177dbcdbb32295113b7627500331f2d", "shasum": "" }, "require": { @@ -3258,7 +3255,7 @@ "uri", "url" ], - "time": "2017-01-02 20:32:22" + "time": "2017-01-28 02:37:08" }, { "name": "voskobovich/yii2-linker-behavior", @@ -3371,21 +3368,21 @@ }, { "name": "yiisoft/yii2", - "version": "2.0.10", + "version": "2.0.11.1", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "5bfcb7a6dfa9771e2248eb8c4448613330f343ff" + "reference": "5c9c894b1d20bc1a39814063adf8a1aeb7c86f77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/5bfcb7a6dfa9771e2248eb8c4448613330f343ff", - "reference": "5bfcb7a6dfa9771e2248eb8c4448613330f343ff", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/5c9c894b1d20bc1a39814063adf8a1aeb7c86f77", + "reference": "5c9c894b1d20bc1a39814063adf8a1aeb7c86f77", "shasum": "" }, "require": { "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", - "bower-asset/jquery.inputmask": "~3.2.2", + "bower-asset/jquery.inputmask": "~3.2.2 | ~3.3.3", "bower-asset/punycode": "1.3.*", "bower-asset/yii2-pjax": "~2.0.1", "cebe/markdown": "~1.0.0 | ~1.1.0", @@ -3453,6 +3450,12 @@ "name": "Dmitry Naumenko", "email": "d.naumenko.a@gmail.com", "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu", + "role": "Core framework development" } ], "description": "Yii PHP Framework Version 2", @@ -3461,7 +3464,7 @@ "framework", "yii2" ], - "time": "2016-10-20 12:02:50" + "time": "2017-02-02 18:53:16" }, { "name": "yiisoft/yii2-bootstrap", @@ -3642,54 +3645,6 @@ "bower-asset-main": "dist/typeahead.bundle.js" } }, - { - "name": "fzaninotto/faker", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", - "shasum": "" - }, - "require": { - "php": "^5.3.3|^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "type": "library", - "extra": { - "branch-alias": [] - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2016-04-29 12:21:54" - }, { "name": "phpspec/php-diff", "version": "v1.1.0", @@ -3775,53 +3730,6 @@ ], "time": "2016-11-24 09:42:29" }, - { - "name": "yiisoft/yii2-faker", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-faker.git", - "reference": "b88ca69ee226a3610b2c26c026c3203d7ac50f6c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/b88ca69ee226a3610b2c26c026c3203d7ac50f6c", - "reference": "b88ca69ee226a3610b2c26c026c3203d7ac50f6c", - "shasum": "" - }, - "require": { - "fzaninotto/faker": "*", - "yiisoft/yii2": "*" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\faker\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Mark Jebri", - "email": "mark.github@yandex.ru" - } - ], - "description": "Fixture generator. The Faker integration for the Yii framework.", - "keywords": [ - "Fixture", - "faker", - "yii2" - ], - "time": "2015-03-01 06:22:44" - }, { "name": "yiisoft/yii2-gii", "version": "2.0.5", @@ -3878,7 +3786,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "bower-asset/snackbarjs": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/config/main.php b/config/main.php index 44726b4..22c7646 100644 --- a/config/main.php +++ b/config/main.php @@ -40,11 +40,16 @@ return [ 'showScriptName' => false, 'rules' => [ '' => 'panel/index', - 'login' => 'auth/login', '' => '/index', + 'profile/' => 'profile/index', 'admin///' => 'admin//', 'admin//' => 'admin//view', 'admin/s' => 'admin//index', + [ + 'class' => 'yii\rest\UrlRule', + 'controller' => ['api/room', 'api/item'], + 'pluralize' => false, + ], ], ], 'view' => [ @@ -54,20 +59,15 @@ return [ 'force_charset' => 'UTF-8', ], 'formatter' => [ -// 'dateFormat' => 'dd.MM.yyyy', -// 'datetimeFormat' => 'php:d.m.Y H:i', 'defaultTimeZone' => 'Europe/Kiev', 'timeZone' => 'Europe/Kiev', ], 'assetManager' => [ + 'appendTimestamp' => true, 'bundles' => [ - 'dmstr\web\AdminLteAsset' => [ - 'skin' => 'skin-purple', + 'yii\bootstrap\BootstrapAsset' => [ + 'css' => [], ], -// 'yii\bootstrap\BootstrapAsset' => [ -// 'css' => [], -// 'js' => [], -// ], ], ], ], diff --git a/config/params.php b/config/params.php index 922ea74..021d80d 100644 --- a/config/params.php +++ b/config/params.php @@ -12,6 +12,15 @@ $params = [ 'connectionCheckTimeout' => 180, 'connectionCheckMaxIteration' => 2, ], + 'items' => [ + 'rgb' => [ + 'fade-time' => 3000, + 'color-time' => 3000, + 'red' => 0, + 'green' => 150, + 'blue' => 150, + ], + ], 'telegramBotApiKey' => '', 'telegramBotChatId' => '', ]; diff --git a/controllers/AuthController.php b/controllers/AuthController.php index 5dde7e7..2372cad 100644 --- a/controllers/AuthController.php +++ b/controllers/AuthController.php @@ -10,8 +10,6 @@ use app\models\LoginForm; class AuthController extends Controller { - public $layout = 'base'; - public function behaviors() { return [ @@ -41,14 +39,14 @@ class AuthController extends Controller return $this->goHome(); } - $this->layout = 'base'; - $model = new LoginForm(); if ($model->load(Yii::$app->request->post()) && $model->login()) { return $this->goBack(); } + $this->layout = 'base'; + return $this->render('login', [ 'model' => $model, ]); diff --git a/controllers/PanelController.php b/controllers/PanelController.php index 9a859e8..370730a 100644 --- a/controllers/PanelController.php +++ b/controllers/PanelController.php @@ -2,8 +2,10 @@ namespace app\controllers; +use app\models\Room; use Yii; use yii\filters\AccessControl; +use yii\helpers\Url; use yii\web\Controller; use yii\web\View; @@ -29,14 +31,21 @@ class PanelController extends Controller public function actionIndex() { + $this->layout = 'main-no-content'; + $this->view->registerJs(' var wsURL = "' . Yii::$app->params['wsURL'] . '/?type=user&id=' . Yii::$app->user->identity->id . '&auth_token=' . Yii::$app->user->identity->getAuthToken() . '"; + var itemValueChartUrl = "' . Url::to(['/api/item/chart-data', 'access-token' => Yii::$app->user->identity->api_key]) . '" ', View::POS_HEAD); - return $this->render('index'); + $roomModels = Room::find()->orderBy('sort_order')->all(); + + return $this->render('index', [ + 'roomModels' => $roomModels, + ]); } } diff --git a/controllers/ProfileController.php b/controllers/ProfileController.php new file mode 100644 index 0000000..306adbc --- /dev/null +++ b/controllers/ProfileController.php @@ -0,0 +1,57 @@ + [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], + ]; + } + + public function actionIndex($id = false) + { + $user = $id ? User::findOne($id) : Yii::$app->user->identity; + + return $this->render('index', [ + 'user' => $user, + ]); + } + + public function actionEdit() + { + $user = Yii::$app->user->identity; + $user->scenario = User::SCENARIO_UPDATE; + + if ($user->load(Yii::$app->request->post())) { + $user->setPassword($user->password); + $user->generateAuthKey(); + + if ($user->save()) { + return $this->redirect(['index']); + } + } + + return $this->render('edit', [ + 'user' => $user, + ]); + } +} diff --git a/controllers/SiteController.php b/controllers/SiteController.php index d42d7b3..8bbb0c3 100644 --- a/controllers/SiteController.php +++ b/controllers/SiteController.php @@ -13,6 +13,7 @@ class SiteController extends Controller 'error' => [ 'class' => 'app\components\ErrorAction', 'layout' => Yii::$app->user->isGuest ? 'base' : 'main', + 'view' => Yii::$app->user->isGuest ? '/error/guest-error' : '/error/error', ], ]; } diff --git a/giiTemplates/crud/my/views/index.php b/giiTemplates/crud/my/views/index.php index d897040..7ce6c0b 100644 --- a/giiTemplates/crud/my/views/index.php +++ b/giiTemplates/crud/my/views/index.php @@ -17,27 +17,23 @@ echo "indexWidgetType === 'grid' ? "yii\\grid\\GridView" : "yii\\widgets\\ListView" ?>; -enablePjax ? 'use yii\widgets\Pjax;' : '' ?> +indexWidgetType === 'grid' ? null : "use yii\\widgets\\ListView;" ?> +enablePjax ? 'use yii\widgets\Pjax;' . "\n" : '' ?> $this->title = generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- Html::a(generateString('Добавить') ?>, ['create'], ['class' => 'btn btn-success']) ?> -

-searchModelClass)): ?> -indexWidgetType === 'grid' ? "// " : "") ?>echo $this->render('_search', ['model' => $searchModel]); ?> - +
+
+ Html::a(generateString('Добавить') ?>, ['create'], ['class' => 'btn btn-default btn-flat']) ?> +
enablePjax ? ' ' . "\n" : '' ?> indexWidgetType === 'grid'): ?> - GridView::widget([ + \app\widgets\DataTable::widget([ 'dataProvider' => $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', searchModelClass) ? "'filterModel' => \$searchModel,\n 'columns' => [\n" : "'columns' => [\n"; ?> params['breadcrumbs'][] = $this->title;

- Html::a(, ['update', ], ['class' => 'btn btn-primary']) ?> - Html::a(, ['delete', ], [ + Html::a('', ['update', ], ['class' => 'btn btn-primary']) ?> + Html::a('', ['delete', ], [ 'class' => 'btn btn-danger', 'data' => [ 'confirm' => generateString('Are you sure you want to delete this item?') ?>, diff --git a/helpers/RGBHelper.php b/helpers/RGBHelper.php new file mode 100644 index 0000000..2ad6e00 --- /dev/null +++ b/helpers/RGBHelper.php @@ -0,0 +1,24 @@ +db->driverName === 'mysql') { + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; + } + + $this->createTable('item_widget', [ + 'id' => $this->primaryKey(), + 'active' => $this->boolean()->defaultValue(true), + 'type' => $this->smallInteger()->notNull(), + 'value_type' => $this->smallInteger(), + 'sort_order' => $this->integer()->defaultValue(0), + 'item_id' => $this->integer()->notNull(), + 'room_id' => $this->integer(), + 'name' => $this->string()->notNull(), + 'html_class' => $this->string(), + 'icon' => $this->string()->notNull(), + ], $tableOptions); + + $this->createIndex('idx-item_widget-item_id', 'item_widget', 'item_id'); + $this->addForeignKey('fk-item_widget-item_id', 'item_widget', 'item_id', 'item', 'id', 'CASCADE'); + + $this->createIndex('idx-item_widget-room_id', 'item_widget', 'room_id'); + $this->addForeignKey('fk-item_widget-room_id', 'item_widget', 'room_id', 'room', 'id', 'CASCADE'); + } + + public function safeDown() + { + $this->dropForeignKey('fk-item_widget-item_id', 'item_widget'); + $this->dropIndex('idx-item_widget-item_id', 'item_widget'); + + $this->dropForeignKey('fk-item_widget-room_id', 'item_widget'); + $this->dropIndex('idx-item_widget-room_id', 'item_widget'); + + $this->dropTable('item_widget'); + } +} diff --git a/migrations/m170303_150337_create_widgets_for_items.php b/migrations/m170303_150337_create_widgets_for_items.php new file mode 100644 index 0000000..2679cc6 --- /dev/null +++ b/migrations/m170303_150337_create_widgets_for_items.php @@ -0,0 +1,44 @@ +all() as $item) { + $itemWidget = new ItemWidget(); + $itemWidget->active = true; + $itemWidget->item_id = $item->id; + $itemWidget->room_id = $item->room_id; + $itemWidget->name = $item->name; + $itemWidget->html_class = $item->class; + $itemWidget->icon = 'fa-' . $item->icon; + + $itemWidget->type = $item->type; + + if ($item->type == 21 or $item->type == 22 or $item->type == 25 or $item->type == 26) { + $itemWidget->type = 20; + + if ($item->type == 21) { + $itemWidget->value_type = ItemWidget::VALUE_TYPE_CELSIUS; + } elseif ($item->type == 22) { + $itemWidget->value_type = ItemWidget::VALUE_TYPE_PERCENT; + } elseif ($item->type == 25) { + $itemWidget->value_type = ItemWidget::VALUE_TYPE_BOOLEAN; + } elseif ($item->type == 26) { + $itemWidget->value_type = ItemWidget::VALUE_TYPE_DOOR; + } + } + + $itemWidget->save(); + } + } + + public function safeDown() + { + return 0; + } +} diff --git a/migrations/m170303_164850_fix_items.php b/migrations/m170303_164850_fix_items.php new file mode 100644 index 0000000..cbe6f60 --- /dev/null +++ b/migrations/m170303_164850_fix_items.php @@ -0,0 +1,26 @@ +dropColumn('item', 'active'); + $this->dropColumn('item', 'room_id'); + $this->dropColumn('item', 'bg'); + $this->dropColumn('item', 'class'); + $this->dropColumn('item', 'icon'); + $this->dropColumn('item', 'sort_order'); + } + + public function safeDown() + { + $this->addColumn('item', 'active', $this->boolean()->defaultValue(true)); + $this->addColumn('item', 'room_id', $this->integer()->notNull()); + $this->addColumn('item', 'bg', $this->string()); + $this->addColumn('item', 'class', $this->string()); + $this->addColumn('item', 'icon', $this->string()->notNull()); + $this->addColumn('item', 'sort_order', $this->integer()->defaultValue(0)); + } +} diff --git a/migrations/m170312_221606_add_room_order_to_room.php b/migrations/m170312_221606_add_room_order_to_room.php new file mode 100644 index 0000000..76b1cf5 --- /dev/null +++ b/migrations/m170312_221606_add_room_order_to_room.php @@ -0,0 +1,18 @@ +addColumn('room', 'sort_order', $this->integer()->defaultValue(0)); + $this->dropColumn('room', 'bg'); + } + + public function safeDown() + { + $this->dropColumn('room', 'sort_order'); + $this->addColumn('room', 'bg', $this->string()); + } +} diff --git a/migrations/m170316_163700_add_user_fields.php b/migrations/m170316_163700_add_user_fields.php new file mode 100644 index 0000000..02457d8 --- /dev/null +++ b/migrations/m170316_163700_add_user_fields.php @@ -0,0 +1,26 @@ +addColumn('user', 'name', $this->string()->after('email')); + $this->addColumn('user', 'avatar', $this->string()->after('name')); + $this->addColumn('user', 'room_id', $this->integer()->after('email')); + + $this->createIndex('idx-user-room_id', 'user', 'room_id'); + $this->addForeignKey('fk-user-room_id', 'user', 'room_id', 'room', 'id'); + } + + public function safeDown() + { + $this->dropForeignKey('fk-user-room_id', 'user'); + $this->dropIndex('idx-user-room_id', 'user'); + + $this->dropColumn('user', 'name'); + $this->dropColumn('user', 'avatar'); + $this->dropColumn('user', 'room_id'); + } +} diff --git a/models/Item.php b/models/Item.php index 76308ff..cb88de8 100644 --- a/models/Item.php +++ b/models/Item.php @@ -11,25 +11,20 @@ use yii\helpers\ArrayHelper; * This is the model class for table "item". * * @property integer $id - * @property boolean $active * @property integer $type * @property integer $update_interval * @property boolean $enable_log * @property integer $save_history_interval - * @property integer $room_id * @property integer $board_id * @property integer $pin * @property string $url * @property string $name - * @property string $icon - * @property string $bg - * @property string $class - * @property integer $sort_order * @property string $default_value * * @property History[] $histories * @property Room $room * @property Board $board + * @property ItemWidget $widget */ class Item extends ActiveRecord { @@ -39,14 +34,15 @@ class Item extends ActiveRecord const TYPE_VARIABLE_BOOLEAN_DOOR = 26; const TYPE_VARIABLE_TEMPERATURE = 21; const TYPE_VARIABLE_HUMIDITY = 22; + const TYPE_VARIABLE_LIGHT = 23; const TYPE_RGB = 30; - const TYPE_LIGHT_LEVEL = 40; const VALUE_ON = 1; const VALUE_OFF = 0; - const MODE_RAINBOW = 'rainbow'; - const MODE_BREATH = 'breath'; + const RGB_MODE_STATIC = 'static'; + const RGB_MODE_WAVE = 'wave'; + const RGB_MODE_FADE = 'fade'; /** * Used for WS handler @@ -68,19 +64,12 @@ class Item extends ActiveRecord public function rules() { return [ - [['active', 'type', 'room_id', 'name', 'icon', 'bg', 'board_id'], 'required'], + [['type', 'name', 'board_id'], 'required'], [ - ['type', 'update_interval', 'save_history_interval', 'room_id', 'sort_order', 'board_id', 'pin'], + ['type', 'update_interval', 'save_history_interval', 'board_id', 'pin'], 'integer' ], - [['url', 'name', 'icon', 'bg', 'class', 'default_value'], 'string', 'max' => 255], - [ - ['room_id'], - 'exist', - 'skipOnError' => true, - 'targetClass' => Room::className(), - 'targetAttribute' => ['room_id' => 'id'] - ], + [['url', 'name', 'default_value'], 'string', 'max' => 255], [ ['board_id'], 'exist', @@ -88,9 +77,7 @@ class Item extends ActiveRecord 'targetClass' => Board::className(), 'targetAttribute' => ['board_id' => 'id'] ], - [['sort_order', 'update_interval', 'save_history_interval'], 'default', 'value' => 0], - [['active', 'enable_log'], 'default', 'value' => true], - [['active', 'enable_log'], 'boolean'], + [['update_interval', 'save_history_interval'], 'default', 'value' => 0], [['default_value'], 'default', 'value' => null], ]; } @@ -102,20 +89,14 @@ class Item extends ActiveRecord { return [ 'id' => Yii::t('app', 'ID'), - 'active' => Yii::t('app', 'Активно'), 'enable_log' => Yii::t('app', 'Логирование'), 'type' => Yii::t('app', 'Тип'), 'update_interval' => Yii::t('app', 'Интервал обновления'), 'save_history_interval' => Yii::t('app', 'Интервал сохранения в историю'), - 'room_id' => Yii::t('app', 'Комната'), 'board_id' => Yii::t('app', 'Плата'), 'url' => Yii::t('app', 'Url'), 'pin' => Yii::t('app', 'Pin'), 'name' => Yii::t('app', 'Название'), - 'icon' => Yii::t('app', 'Иконка'), - 'bg' => Yii::t('app', 'CSS Background'), - 'class' => Yii::t('app', 'CSS Класс'), - 'sort_order' => Yii::t('app', 'Порядок сортировки'), 'default_value' => Yii::t('app', 'Значение по умолчанию'), ]; } @@ -131,17 +112,17 @@ class Item extends ActiveRecord /** * @return \yii\db\ActiveQuery */ - public function getRoom() + public function getBoard() { - return $this->hasOne(Room::className(), ['id' => 'room_id'])->inverseOf('items'); + return $this->hasOne(Board::className(), ['id' => 'board_id'])->inverseOf('items'); } /** * @return \yii\db\ActiveQuery */ - public function getBoard() + public function getWidget() { - return $this->hasOne(Board::className(), ['id' => 'board_id'])->inverseOf('items'); + return $this->hasOne(ItemWidget::className(), ['item_id' => 'id'])->inverseOf('item'); } /** @@ -165,8 +146,8 @@ class Item extends ActiveRecord self::TYPE_VARIABLE_BOOLEAN_DOOR => 'Переменная boolean дверь', self::TYPE_VARIABLE_TEMPERATURE => 'Переменная температура', self::TYPE_VARIABLE_HUMIDITY => 'Переменная влажность', - self::TYPE_RGB => 'RGB LED', - self::TYPE_LIGHT_LEVEL => 'Уровень освещенности', + self::TYPE_VARIABLE_LIGHT => 'Переменная освещенность', + self::TYPE_RGB => 'RGB', ]; } @@ -180,16 +161,15 @@ class Item extends ActiveRecord /** * @param bool $prependId - * @param bool $appendRoomName * @return array */ - public static function getList($prependId = false, $appendRoomName = false) + public static function getList($prependId = false) { /** @var self[] $models */ $models = self::find()->all(); $result = []; - if (!$appendRoomName and !$prependId) { + if (!$prependId) { return ArrayHelper::map($models, 'id', 'name'); } @@ -202,10 +182,6 @@ class Item extends ActiveRecord $title .= $model->name; - if ($appendRoomName) { - $title .= ' - ' . $model->room->name; - } - $result[$model->id] = $title; } @@ -215,43 +191,28 @@ class Item extends ActiveRecord /** * @return array */ - public static function getModesArray() + public static function getRGBModesArray() { return [ - self::MODE_RAINBOW, - self::MODE_BREATH, + self::RGB_MODE_STATIC, + self::RGB_MODE_WAVE, + self::RGB_MODE_FADE, ]; } - /** - * Returns normalized default value - * @return mixed - */ - public function getDefaultValue() + public function getDefaultNAValue() { - if (!is_null($this->default_value)) { - return $this->default_value; - } - switch ($this->type) { - case Item::TYPE_SWITCH: - case Item::TYPE_VARIABLE_BOOLEAN: - case Item::TYPE_VARIABLE_BOOLEAN_DOOR: - return false; - - case Item::TYPE_VARIABLE_TEMPERATURE: - case Item::TYPE_VARIABLE_HUMIDITY: - case Item::TYPE_LIGHT_LEVEL: - return 0; - - case Item::TYPE_RGB: + case self::TYPE_RGB: return [ - 0, - 0, - 0, + 'mode' => 'static', + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'fade_time' => Yii::$app->params['items']['rgb']['fade-time'], ]; + default: + return 'N/A'; } - - return false; } } diff --git a/models/ItemSearch.php b/models/ItemSearch.php index 99ec892..6a4f44e 100644 --- a/models/ItemSearch.php +++ b/models/ItemSearch.php @@ -63,8 +63,6 @@ class ItemSearch extends Item 'type' => $this->type, 'update_interval' => $this->update_interval, 'save_history_interval' => $this->save_history_interval, - 'room_id' => $this->room_id, - 'sort_order' => $this->sort_order, ]); $query->andFilterWhere(['like', 'url', $this->url]) diff --git a/models/ItemWidget.php b/models/ItemWidget.php new file mode 100644 index 0000000..9b495ee --- /dev/null +++ b/models/ItemWidget.php @@ -0,0 +1,159 @@ + true], + [['type'], 'in', 'range' => self::getTypesArray()], + [['value_type'], 'in', 'range' => self::getValueTypesArray()], + [['name', 'item_id', 'icon', 'type'], 'required'], + [['name', 'html_class', 'icon'], 'string', 'max' => 255], + [['sort_order'], 'default', 'value' => 0], + [['item_id'], 'exist', 'skipOnError' => true, 'targetClass' => Item::className(), 'targetAttribute' => ['item_id' => 'id']], + [['room_id'], 'exist', 'skipOnError' => true, 'targetClass' => Room::className(), 'targetAttribute' => ['room_id' => 'id']], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'active' => Yii::t('app', 'Активно'), + 'type' => Yii::t('app', 'Тип'), + 'value_type' => Yii::t('app', 'Тип Значения'), + 'sort_order' => Yii::t('app', 'Порядок сортировки'), + 'item_id' => Yii::t('app', 'Элемент'), + 'room_id' => Yii::t('app', 'Комната'), + 'name' => Yii::t('app', 'Название'), + 'html_class' => Yii::t('app', 'HTML класс'), + 'icon' => Yii::t('app', 'Иконка'), + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getItem() + { + return $this->hasOne(Item::className(), ['id' => 'item_id'])->inverseOf('widget'); + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getRoom() + { + return $this->hasOne(Room::className(), ['id' => 'room_id'])->inverseOf('itemWidgets'); + } + + /** + * @inheritdoc + * @return ItemWidgetQuery the active query used by this AR class. + */ + public static function find() + { + return new ItemWidgetQuery(get_called_class()); + } + + /** + * @return string + */ + public function getName() + { + if ($this->name != null) { + return $this->name; + } + + return $this->item->name; + } + + /** + * @return array + */ + public static function getTypes() + { + return [ + self::TYPE_SWITCH => 'Переключатель', + self::TYPE_VARIABLE => 'Переменная', + self::TYPE_RGB => 'RGB', + ]; + } + + /** + * @return array + */ + public static function getTypesArray() + { + return array_keys(self::getTypes()); + } + + /** + * @return array + */ + public static function getValueTypes() + { + return [ + self::VALUE_TYPE_BOOLEAN => 'Boolean', + self::VALUE_TYPE_DOOR => 'Дверь', + self::VALUE_TYPE_CELSIUS => 'Цельсии', + self::VALUE_TYPE_PERCENT => 'Проценты', + ]; + } + + /** + * @return array + */ + public static function getValueTypesArray() + { + return array_keys(self::getValueTypes()); + } +} diff --git a/models/ItemWidgetQuery.php b/models/ItemWidgetQuery.php new file mode 100644 index 0000000..d9a33d0 --- /dev/null +++ b/models/ItemWidgetQuery.php @@ -0,0 +1,63 @@ +andWhere(['active' => true]); + } + + /** + * @return $this + */ + public function switches() + { + return $this->andWhere(['type' => ItemWidget::TYPE_SWITCH]); + } + + /** + * @return $this + */ + public function variables() + { + return $this->andWhere(['type' => ItemWidget::TYPE_VARIABLE]); + } + + /** + * @return $this + */ + public function rgb() + { + return $this->andWhere(['type' => ItemWidget::TYPE_RGB]); + } + + /** + * @inheritdoc + * @return ItemWidget[]|array + */ + public function all($db = null) + { + return parent::all($db); + } + + /** + * @inheritdoc + * @return ItemWidget|array|null + */ + public function one($db = null) + { + return parent::one($db); + } +} diff --git a/models/Room.php b/models/Room.php index ace37a8..d03e436 100644 --- a/models/Room.php +++ b/models/Room.php @@ -3,6 +3,7 @@ namespace app\models; use Yii; +use yii\db\ActiveRecord; use yii\helpers\ArrayHelper; /** @@ -10,11 +11,11 @@ use yii\helpers\ArrayHelper; * * @property integer $id * @property string $name - * @property string $bg + * @property integer $sort_order * * @property Item[] $items */ -class Room extends \yii\db\ActiveRecord +class Room extends ActiveRecord { /** * @inheritdoc @@ -31,7 +32,9 @@ class Room extends \yii\db\ActiveRecord { return [ [['name'], 'required'], - [['name', 'bg'], 'string', 'max' => 255], + [['name'], 'string', 'max' => 255], + [['sort_order'], 'integer'], + [['sort_order'], 'default', 'value' => 0], ]; } @@ -43,25 +46,16 @@ class Room extends \yii\db\ActiveRecord return [ 'id' => Yii::t('app', 'ID'), 'name' => Yii::t('app', 'Название'), - 'bg' => Yii::t('app', 'Фон'), + 'sort_order' => Yii::t('app', 'Порядок сортировки'), ]; } /** - * @return \yii\db\ActiveQuery + * @return \yii\db\ActiveQuery|ItemWidgetQuery */ - public function getItems() + public function getItemWidgets() { - return $this->hasMany(Item::className(), ['room_id' => 'id'])->inverseOf('room'); - } - - /** - * @inheritdoc - * @return RoomQuery the active query used by this AR class. - */ - public static function find() - { - return new RoomQuery(get_called_class()); + return $this->hasMany(ItemWidget::className(), ['room_id' => 'id'])->inverseOf('room'); } /** diff --git a/models/RoomQuery.php b/models/RoomQuery.php deleted file mode 100644 index 5a7bd87..0000000 --- a/models/RoomQuery.php +++ /dev/null @@ -1,34 +0,0 @@ -andWhere('[[status]]=1'); - }*/ - - /** - * @inheritdoc - * @return Room[]|array - */ - public function all($db = null) - { - return parent::all($db); - } - - /** - * @inheritdoc - * @return Room|array|null - */ - public function one($db = null) - { - return parent::one($db); - } -} diff --git a/models/RoomSearch.php b/models/RoomSearch.php index f002262..084b3ca 100644 --- a/models/RoomSearch.php +++ b/models/RoomSearch.php @@ -18,8 +18,8 @@ class RoomSearch extends Room public function rules() { return [ - [['id'], 'integer'], - [['name', 'bg'], 'safe'], + [['id', 'sort_order'], 'integer'], + [['name'], 'safe'], ]; } @@ -60,10 +60,10 @@ class RoomSearch extends Room // grid filtering conditions $query->andFilterWhere([ 'id' => $this->id, + 'sort_order' => $this->sort_order, ]); - $query->andFilterWhere(['like', 'name', $this->name]) - ->andFilterWhere(['like', 'bg', $this->bg]); + $query->andFilterWhere(['like', 'name', $this->name]); return $dataProvider; } diff --git a/models/User.php b/models/User.php index aac6a2e..adfc047 100644 --- a/models/User.php +++ b/models/User.php @@ -18,6 +18,9 @@ use yii\web\IdentityInterface; * @property string $auth_key * @property string $auth_token * @property string $api_key + * @property string $name + * @property string $avatar + * @property integer $room_id * @property integer $status * @property integer $group * @property integer $created_at @@ -26,13 +29,15 @@ use yii\web\IdentityInterface; * @property boolean $isAdmin * * @property string $password write-only password + * + * @property Room $room */ class User extends ActiveRecord implements IdentityInterface { const SCENARIO_CREATE = 'create'; const SCENARIO_UPDATE = 'update'; - const STATUS_DELETED = 0; + const STATUS_DISABLED = 0; const STATUS_ACTIVE = 10; const GROUP_ADMIN = 10; @@ -65,14 +70,15 @@ class User extends ActiveRecord implements IdentityInterface { return [ [['username', 'email'], 'required'], - [['api_key', 'auth_token'], 'string'], - [['group'], 'integer'], + [['api_key', 'auth_token', 'name', 'avatar'], 'string'], + [['group', 'room_id'], 'integer'], [['password'], 'required', 'on' => self::SCENARIO_CREATE], [['password'], 'safe', 'on' => self::SCENARIO_UPDATE], [['auth_token'], 'default', 'value' => null], [['status'], 'default', 'value' => self::STATUS_ACTIVE], [['status'], 'in', 'range' => self::getStatusesArray()], [['group'], 'in', 'range' => self::getGroupsArray()], + [['room_id'], 'exist', 'skipOnError' => true, 'targetClass' => Room::className(), 'targetAttribute' => ['room_id' => 'id']], ]; } @@ -88,6 +94,9 @@ class User extends ActiveRecord implements IdentityInterface 'email' => 'Email', 'status' => 'Статус', 'group' => 'Группа', + 'name' => 'Имя', + 'avatar' => 'Аватар', + 'room_id' => 'Комната', 'api_key' => 'API ключ', 'auth_token' => 'Auth токен', 'created_at' => 'Дата создания', @@ -102,7 +111,7 @@ class User extends ActiveRecord implements IdentityInterface { return [ self::STATUS_ACTIVE => 'Активен', - self::STATUS_DELETED => 'Удален', + self::STATUS_DISABLED => 'Не активен', ]; } @@ -260,12 +269,13 @@ class User extends ActiveRecord implements IdentityInterface } /** + * @param int $size * @return string */ - public function getAvatar() + public function getAvatar($size = 45) { $hash = md5($this->email); - return 'https://www.gravatar.com/avatar/' . $hash . '?s=45'; + return 'https://www.gravatar.com/avatar/' . $hash . '?s=' . $size; } /** @@ -276,6 +286,14 @@ class User extends ActiveRecord implements IdentityInterface return $this->hasMany(History::className(), ['user_id' => 'id'])->inverseOf('user'); } + /** + * @return \yii\db\ActiveQuery + */ + public function getRoom() + { + return $this->hasOne(Room::className(), ['id' => 'room_id']); + } + /** * @return bool */ diff --git a/modules/admin/controllers/ItemWidgetController.php b/modules/admin/controllers/ItemWidgetController.php new file mode 100644 index 0000000..9efc836 --- /dev/null +++ b/modules/admin/controllers/ItemWidgetController.php @@ -0,0 +1,125 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ]; + } + + /** + * Lists all ItemWidget models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new ItemWidgetSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single ItemWidget model. + * @param integer $id + * @return mixed + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new ItemWidget model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new ItemWidget(); + $model->active = true; + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } + + /** + * Updates an existing ItemWidget model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + + /** + * Deletes an existing ItemWidget model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the ItemWidget model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return ItemWidget the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = ItemWidget::findOne($id)) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/modules/admin/controllers/UserController.php b/modules/admin/controllers/UserController.php index b7c53ba..9139eca 100644 --- a/modules/admin/controllers/UserController.php +++ b/modules/admin/controllers/UserController.php @@ -4,7 +4,7 @@ namespace app\modules\admin\controllers; use Yii; use app\models\User; -use app\models\UserSearch; +use app\modules\admin\models\UserSearch; use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; diff --git a/modules/admin/models/ItemSearch.php b/modules/admin/models/ItemSearch.php index 9f6a7ec..7a2f101 100644 --- a/modules/admin/models/ItemSearch.php +++ b/modules/admin/models/ItemSearch.php @@ -18,7 +18,7 @@ class ItemSearch extends Item public function rules() { return [ - [['id', 'type', 'room_id', 'board_id', 'sort_order', 'pin'], 'integer'], + [['id', 'type', 'board_id', 'pin'], 'integer'], [['url', 'name'], 'safe'], ]; } @@ -61,9 +61,7 @@ class ItemSearch extends Item $query->andFilterWhere([ 'id' => $this->id, 'type' => $this->type, - 'room_id' => $this->room_id, 'board_id' => $this->board_id, - 'sort_order' => $this->sort_order, 'pin' => $this->pin, ]); diff --git a/modules/admin/models/ItemWidgetSearch.php b/modules/admin/models/ItemWidgetSearch.php new file mode 100644 index 0000000..28ed98c --- /dev/null +++ b/modules/admin/models/ItemWidgetSearch.php @@ -0,0 +1,75 @@ + $query, + ]); + + $this->load($params); + + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + $query->andFilterWhere([ + 'id' => $this->id, + 'active' => $this->active, + 'sort_order' => $this->sort_order, + 'item_id' => $this->item_id, + 'room_id' => $this->room_id, + ]); + + $query->andFilterWhere(['like', 'name', $this->name]) + ->andFilterWhere(['like', 'html_class', $this->html_class]) + ->andFilterWhere(['like', 'icon', $this->icon]); + + return $dataProvider; + } +} diff --git a/models/UserSearch.php b/modules/admin/models/UserSearch.php similarity index 84% rename from models/UserSearch.php rename to modules/admin/models/UserSearch.php index 11f2c45..93c4ca0 100644 --- a/models/UserSearch.php +++ b/modules/admin/models/UserSearch.php @@ -1,6 +1,6 @@ $this->group, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, + 'room_id' => $this->room_id, ]); - $query->andFilterWhere(['like', 'username', $this->username]) + $query + ->andFilterWhere(['like', 'username', $this->username]) + ->andFilterWhere(['like', 'name', $this->name]) ->andFilterWhere(['like', 'email', $this->email]); return $dataProvider; diff --git a/modules/admin/views/board/index.php b/modules/admin/views/board/index.php index 720ed10..2cd25c4 100644 --- a/modules/admin/views/board/index.php +++ b/modules/admin/views/board/index.php @@ -11,24 +11,21 @@ use yii\widgets\Pjax; $this->title = 'Устройства'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -

-

- 'btn btn-success']) ?> -

+
+
+ 'btn btn-default btn-flat']) ?> +
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ - [ - 'attribute' => 'id', - 'contentOptions' => ['style' => 'width: 5%'] - ], + 'id', 'name', [ 'attribute' => 'type', @@ -49,5 +46,6 @@ $this->params['breadcrumbs'][] = $this->title; ['class' => 'app\components\ActionButtonColumn'], ], ]); ?> +
diff --git a/modules/admin/views/event/index.php b/modules/admin/views/event/index.php index 51f514b..a11b811 100644 --- a/modules/admin/views/event/index.php +++ b/modules/admin/views/event/index.php @@ -10,19 +10,17 @@ use yii\widgets\Pjax; $this->title = 'События'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- 'btn btn-success']) ?> -

- render('_search', ['model' => $searchModel]); ?> +
+
+ 'btn btn-default btn-flat']) ?> +
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ [ diff --git a/modules/admin/views/history/index.php b/modules/admin/views/history/index.php index 32df452..acb45e9 100644 --- a/modules/admin/views/history/index.php +++ b/modules/admin/views/history/index.php @@ -7,21 +7,20 @@ use yii\helpers\Html; use yii\grid\GridView; use yii\widgets\Pjax; + $this->title = 'Histories'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- 'btn btn-success']) ?> -

- render('_search', ['model' => $searchModel]); ?> +
+
+ 'btn btn-default btn-flat']) ?> +
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ 'id', diff --git a/modules/admin/views/item-widget/_form.php b/modules/admin/views/item-widget/_form.php new file mode 100644 index 0000000..7e166ad --- /dev/null +++ b/modules/admin/views/item-widget/_form.php @@ -0,0 +1,128 @@ +registerJs(" +$('.fontawesome-iconpicker-input').iconpicker({container: 'body', inputSearch: true,}); +", \yii\web\View::POS_READY); +?> + +
+ + + +
+
+ field($model, 'item_id')->widget(Select2::className(), [ + 'data' => Item::getList(true), + 'pluginEvents' => [ + 'change' => "function(e) { + $.get({ + url: '" . \yii\helpers\Url::to([ + '/api/item/view', + 'access-token' => Yii::$app->user->identity->api_key, + ]) . "&id=' + $('#itemwidget-item_id').val(), + success: function (data) { + if (data) { + $('#itemwidget-name').val(data.name).change(); + + var widgetType = data.type; + var valueType; + + if (data.type == 20 || data.type == 21 || data.type == 22 || data.type == 25 || data.type == 26) { + widgetType = 20; + $('.field-itemwidget-value_type').fadeIn().removeClass('hidden'); + } + + if (data.type == 21) { + valueType = 30; + } + + if (data.type == 22) { + valueType = 40; + } + + if (data.type == 25) { + valueType = 10; + } + + if (data.type == 26) { + valueType = 20; + } + + $('#itemwidget-type').val(widgetType).change(); + $('#itemwidget-value_type').val(valueType).change(); + } + } + }); + }", + ], + 'options' => [ + 'placeholder' => 'Выберите элемент ...', + ], + ]) ?> + + field($model, 'name')->textInput(['maxlength' => true]) ?> + + field($model, 'type')->widget(Select2::className(), [ + 'data' => ItemWidget::getTypes(), + 'pluginEvents' => [ + 'change' => "function(e) { + if (parseInt($(this).val()) == 20) { + $('.field-itemwidget-value_type').fadeIn().removeClass('hidden'); + } else { + $('.field-itemwidget-value_type').fadeOut().addClass('hidden'); + } + }", + ], + 'options' => [ + 'placeholder' => 'Выберите тип ...', + ], + ]) ?> + + field($model, 'value_type', ['options' => ['class' => 'form-group hidden']])->widget(Select2::className(), [ + 'data' => ItemWidget::getValueTypes(), + 'options' => [ + 'placeholder' => 'Выберите тип значения ...', + ], + ]) ?> + + field($model, 'active')->checkbox() ?> +
+
+ field($model, 'icon')->textInput([ + 'maxlength' => true, + 'class' => 'fontawesome-iconpicker-input form-control' + ]) ?> + + field($model, 'room_id')->widget(Select2::className(), [ + 'data' => Room::getList(), + 'options' => [ + 'placeholder' => 'Выберите комнату ...', + ], + ]) ?> + + field($model, 'html_class')->textInput(['maxlength' => true]) ?> + + field($model, 'sort_order')->input('number') ?> +
+
+ +
+ isNewRecord ? 'Добавить' : 'Сохранить', ['class' => 'btn btn-primary']) ?> +
+ + + +
diff --git a/modules/admin/views/item-widget/create.php b/modules/admin/views/item-widget/create.php new file mode 100644 index 0000000..8df393a --- /dev/null +++ b/modules/admin/views/item-widget/create.php @@ -0,0 +1,18 @@ +title = 'Добавить Item Widget'; +$this->params['breadcrumbs'][] = ['label' => 'Item Widgets', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/modules/admin/views/item-widget/index.php b/modules/admin/views/item-widget/index.php new file mode 100644 index 0000000..682e59a --- /dev/null +++ b/modules/admin/views/item-widget/index.php @@ -0,0 +1,53 @@ +title = 'Виджеты'; +$this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; +?> + +
+
+ 'btn btn-default btn-flat']) ?> +
+ + + $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + 'id', + 'name', + 'icon', + [ + 'attribute' => 'room_id', + 'filter' => Room::getList(), + 'value' => function ($model) { + /** @var $model ItemWidget */ + return $model->room->name; + }, + ], + [ + 'attribute' => 'item_id', + 'filter' => Item::getList(), + 'value' => function ($model) { + /** @var $model ItemWidget */ + return $model->item->name; + }, + ], + 'active:boolean', + + ['class' => 'app\components\ActionButtonColumn'], + ], + ]); ?> + +
diff --git a/modules/admin/views/item-widget/update.php b/modules/admin/views/item-widget/update.php new file mode 100644 index 0000000..f96156c --- /dev/null +++ b/modules/admin/views/item-widget/update.php @@ -0,0 +1,19 @@ +title = 'Изменить Item Widget: ' . $model->name; +$this->params['breadcrumbs'][] = ['label' => 'Item Widgets', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = 'Редактирование'; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/modules/admin/views/item-widget/view.php b/modules/admin/views/item-widget/view.php new file mode 100644 index 0000000..98ccf39 --- /dev/null +++ b/modules/admin/views/item-widget/view.php @@ -0,0 +1,40 @@ +title = $model->name; +$this->params['breadcrumbs'][] = ['label' => 'Item Widgets', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

+ ', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?> + ', ['delete', 'id' => $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => 'Are you sure you want to delete this item?', + 'method' => 'post', + ], + ]) ?> +

+ + $model, + 'attributes' => [ + 'id', + 'active', + 'sort_order', + 'item_id', + 'room_id', + 'name', + 'html_class', + 'icon', + ], + ]) ?> + +
diff --git a/modules/admin/views/item/_form.php b/modules/admin/views/item/_form.php index e64841f..5486968 100644 --- a/modules/admin/views/item/_form.php +++ b/modules/admin/views/item/_form.php @@ -12,7 +12,6 @@ use yii\helpers\Html; use yii\widgets\ActiveForm; if ($model->isNewRecord) { - $model->active = true; $model->enable_log = true; } ?> @@ -23,8 +22,6 @@ if ($model->isNewRecord) {
-

Основные настройки

- field($model, 'name')->textInput(['maxlength' => true]) ?> field($model, 'type')->dropDownList(Item::getTypesArray(), [ @@ -50,38 +47,8 @@ if ($model->isNewRecord) { field($model, 'save_history_interval')->input('number') ?>
-
-

Сниппет

- - field($model, 'icon')->textInput(['maxlength' => true]) ?> - - field($model, 'bg')->dropDownList([ - 'light-blue' => 'Светло-синий', - 'aqua' => 'Бирюзовый', - 'green' => 'Зеленый', - 'yellow' => 'Желтый', - 'red' => 'Красный', - 'gray' => 'Серый', - 'navy' => 'Navy', - 'teal' => 'Teal', - 'purple' => 'Фиолетовый', - 'orange' => 'Оранжевый', - 'maroon' => 'Бордовый', - 'black' => 'Черный', - ]) ?> - - field($model, 'class')->textInput(['maxlength' => true]) ?> - - field($model, 'room_id')->dropDownList( - ArrayHelper::map(Room::find()->all(), 'id', 'name') - ) ?> - - field($model, 'sort_order')->input('number') ?> -
- field($model, 'active')->checkbox() ?> - field($model, 'enable_log')->checkbox() ?>
diff --git a/modules/admin/views/item/index.php b/modules/admin/views/item/index.php index 1fb3403..35e0aa9 100644 --- a/modules/admin/views/item/index.php +++ b/modules/admin/views/item/index.php @@ -13,40 +13,28 @@ use yii\widgets\Pjax; $this->title = 'Элементы'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- 'btn btn-success']) ?> +

+
+ 'btn btn-default btn-flat']) ?> Yii::$app->user->identity->api_key, ], [ - 'class' => 'btn btn-default ajax-call', + 'class' => 'btn btn-default btn-flat ajax-call', ]) ?> -

- render('_search', ['model' => $searchModel]); ?> +
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ - [ - 'attribute' => 'id', - 'contentOptions' => ['style' => 'width: 5%'] - ], + 'id', 'name', - [ - 'attribute' => 'room_id', - 'filter' => Room::getList(), - 'value' => function ($model) { - /** @var $model Item */ - return $model->room->name; - }, - ], [ 'attribute' => 'board_id', 'filter' => Board::getList(), @@ -68,5 +56,6 @@ $this->params['breadcrumbs'][] = $this->title; ['class' => 'app\components\ActionButtonColumn'], ], ]); ?> +
diff --git a/modules/admin/views/item/view.php b/modules/admin/views/item/view.php index 9f00531..59500ab 100644 --- a/modules/admin/views/item/view.php +++ b/modules/admin/views/item/view.php @@ -28,18 +28,13 @@ $this->params['breadcrumbs'][] = $this->title; 'attributes' => [ 'id', 'name', - 'icon', - 'bg', - 'class', ['attribute' => 'board_id', 'value' => $model->board->name], 'pin', 'type', 'default_value', 'update_interval', 'save_history_interval', - ['attribute' => 'room_id', 'value' => $model->room->name], 'url:url', - 'sort_order', ], ]) ?> diff --git a/modules/admin/views/room/_form.php b/modules/admin/views/room/_form.php index 607e9ee..ceadac7 100644 --- a/modules/admin/views/room/_form.php +++ b/modules/admin/views/room/_form.php @@ -14,12 +14,7 @@ use yii\widgets\ActiveForm; field($model, 'name')->textInput(['maxlength' => true]) ?> - field($model, 'bg')->dropDownList([ - 'success' => 'Зеленый', - 'danger' => 'Красный', - 'warning' => 'Оранжевый', - 'info' => 'Синий', - ]) ?> + field($model, 'sort_order')->input('number') ?>
isNewRecord ? 'Добавить' : 'Сохранить', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> diff --git a/modules/admin/views/room/index.php b/modules/admin/views/room/index.php index 9d3e63f..e947174 100644 --- a/modules/admin/views/room/index.php +++ b/modules/admin/views/room/index.php @@ -7,26 +7,25 @@ use yii\helpers\Html; use yii\grid\GridView; use yii\widgets\Pjax; + $this->title = 'Комнаты'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- 'btn btn-success']) ?> -

- render('_search', ['model' => $searchModel]); ?> +
+
+ 'btn btn-default btn-flat']) ?> +
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ 'id', 'name', - 'bg', + 'sort_order', ['class' => 'app\components\ActionButtonColumn'], ], diff --git a/modules/admin/views/room/view.php b/modules/admin/views/room/view.php index 9a6a0a1..2ea9600 100644 --- a/modules/admin/views/room/view.php +++ b/modules/admin/views/room/view.php @@ -28,7 +28,7 @@ $this->params['breadcrumbs'][] = $this->title; 'attributes' => [ 'id', 'name', - 'bg', + 'sort_order', ], ]) ?> diff --git a/modules/admin/views/setting/index.php b/modules/admin/views/setting/index.php index 1021351..91a32a7 100644 --- a/modules/admin/views/setting/index.php +++ b/modules/admin/views/setting/index.php @@ -11,10 +11,6 @@ $this->params['breadcrumbs'][] = $this->title; ?>
-

- title ?> -

-
diff --git a/modules/admin/views/task/_form.php b/modules/admin/views/task/_form.php index d6eea8f..b136cbd 100644 --- a/modules/admin/views/task/_form.php +++ b/modules/admin/views/task/_form.php @@ -36,7 +36,7 @@ if ($model->isNewRecord) {

Выполнить

field($model, 'item_id')->widget(Select2::className(), [ - 'data' => Item::getList(false, true), + 'data' => Item::getList(true), 'options' => [ 'placeholder' => 'Выберите элемент ...', ], diff --git a/modules/admin/views/task/index.php b/modules/admin/views/task/index.php index 5001c39..a9bb780 100644 --- a/modules/admin/views/task/index.php +++ b/modules/admin/views/task/index.php @@ -11,18 +11,17 @@ use yii\widgets\Pjax; $this->title = 'Задачи'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- 'btn btn-success']) ?> -

+
+
+ 'btn btn-default btn-flat']) ?> +
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ 'id', diff --git a/modules/admin/views/trigger/_form.php b/modules/admin/views/trigger/_form.php index 4b63270..73034b1 100644 --- a/modules/admin/views/trigger/_form.php +++ b/modules/admin/views/trigger/_form.php @@ -86,7 +86,7 @@ if ($model->isNewRecord) {
field($model, 'item_id')->widget(Select2::className(), [ - 'data' => Item::getList(false, true), + 'data' => Item::getList(true), 'options' => [ 'placeholder' => 'Выберите элемент ...', ], diff --git a/modules/admin/views/trigger/index.php b/modules/admin/views/trigger/index.php index ee2919f..aa6e06b 100644 --- a/modules/admin/views/trigger/index.php +++ b/modules/admin/views/trigger/index.php @@ -11,30 +11,26 @@ use yii\widgets\Pjax; $this->title = 'Триггеры'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

- 'btn btn-success']) ?> +

+
+ 'btn btn-default btn-flat']) ?> Yii::$app->user->identity->api_key ], [ - 'class' => 'btn btn-default ajax-call', + 'class' => 'btn btn-default btn-flat ajax-call', ]) ?> -

+
- $dataProvider, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'filterModel' => $searchModel, 'columns' => [ - [ - 'attribute' => 'id', - 'contentOptions' => ['style' => 'width: 5%'] - ], + 'id', 'name', [ 'filter' => Trigger::getTypes(), diff --git a/modules/admin/views/user/_form.php b/modules/admin/views/user/_form.php index 7c416a9..0ca0691 100644 --- a/modules/admin/views/user/_form.php +++ b/modules/admin/views/user/_form.php @@ -4,7 +4,9 @@ /* @var $model app\models\User */ /* @var $form yii\widgets\ActiveForm */ +use app\models\Room; use app\models\User; +use kartik\widgets\Select2; use yii\helpers\Html; use yii\widgets\ActiveForm; @@ -26,10 +28,21 @@ if ($model->isNewRecord) { field($model, 'email')->input('email') ?> + field($model, 'name')->textInput() ?> + + field($model, 'avatar')->textInput() ?> + field($model, 'status')->dropDownList(User::getStatuses()) ?> field($model, 'group')->dropDownList(User::getGroups()) ?> + field($model, 'room_id')->widget(Select2::className(), [ + 'data' => Room::getList(), + 'options' => [ + 'placeholder' => 'Выберите комнату ...', + ], + ]) ?> + field($model, 'api_key')->textInput()->hint($randomApiKey) ?>
diff --git a/modules/admin/views/user/create.php b/modules/admin/views/user/create.php index ca45d64..ad39017 100644 --- a/modules/admin/views/user/create.php +++ b/modules/admin/views/user/create.php @@ -11,8 +11,6 @@ $this->params['breadcrumbs'][] = $this->title; ?>
-

title ?>

- render('_form', [ 'model' => $model, ]) ?> diff --git a/modules/admin/views/user/index.php b/modules/admin/views/user/index.php index 75cb812..cbf32e9 100644 --- a/modules/admin/views/user/index.php +++ b/modules/admin/views/user/index.php @@ -1,32 +1,41 @@ title = 'Пользователи'; $this->params['breadcrumbs'][] = $this->title; +$this->params['in-card'] = false; ?> -
-

'btn btn-success']) ?>

+
+
+ 'btn btn-default btn-flat']) ?> +
- + $dataProvider, 'filterModel' => $searchModel, - 'summaryOptions' => ['class' => 'alert alert-info'], - 'layout' => '{summary}
{items}
{pager}', 'columns' => [ - [ - 'attribute' => 'id', - 'contentOptions' => ['style' => 'width: 5%'] - ], + 'id', 'username', 'email', + 'name', + [ + 'attribute' => 'room_id', + 'filter' => Room::getList(), + 'value' => function ($model) { + /** @var $model User */ + return $model->room ? $model->room->name : null; + }, + ], [ 'filter' => User::getStatuses(), 'attribute' => 'status', @@ -47,4 +56,5 @@ $this->params['breadcrumbs'][] = $this->title; ['class' => 'app\components\ActionButtonColumn'], ], ]); ?> +
diff --git a/modules/admin/views/user/update.php b/modules/admin/views/user/update.php index 73a017c..179099a 100644 --- a/modules/admin/views/user/update.php +++ b/modules/admin/views/user/update.php @@ -10,8 +10,6 @@ $this->params['breadcrumbs'][] = 'Редактирование'; ?>
-

title ?>

- render('_form', [ 'model' => $model, ]) ?> diff --git a/modules/admin/views/user/view.php b/modules/admin/views/user/view.php index f3ddcc5..2c47286 100644 --- a/modules/admin/views/user/view.php +++ b/modules/admin/views/user/view.php @@ -12,8 +12,7 @@ $this->params['breadcrumbs'][] = $this->title; ?>
-

- title ?> +

$model->id], ['class' => 'btn btn-primary']) ?> $model->id], [ 'class' => 'btn btn-danger', @@ -22,13 +21,14 @@ $this->params['breadcrumbs'][] = $this->title; 'method' => 'post', ], ]) ?> -

+

$model, 'attributes' => [ 'id', 'username', + 'name', 'email:email', [ 'attribute' => 'status', @@ -38,6 +38,10 @@ $this->params['breadcrumbs'][] = $this->title; 'attribute' => 'group', 'value' => $model->getGroupLabel(), ], + [ + 'attribute' => 'room_id', + 'value' => $model->room ? $model->room->name : null, + ], 'api_key', 'created_at:datetime', 'updated_at:datetime', diff --git a/modules/api/components/WebSocketAPIBridge.php b/modules/api/components/WebSocketAPIBridge.php index d8a8522..98c013f 100644 --- a/modules/api/components/WebSocketAPIBridge.php +++ b/modules/api/components/WebSocketAPIBridge.php @@ -93,44 +93,6 @@ class WebSocketAPIBridge ]); } - /** - * @param int $itemID - * @param int $red - * @param int $green - * @param int $blue - * @param boolean $fade - * @return bool - * @internal param array $rgbData - */ - public function rgb($itemID, $red, $green, $blue, $fade) - { - return $this->send([ - 'type' => 'rgb', - 'item_id' => $itemID, - 'red' => $red, - 'green' => $green, - 'blue' => $blue, - 'fade' => $fade, - ]); - } - - /** - * @param int $itemID - * @param string $mode - * @param bool $start - * @return bool - * @internal param array $rgbData - */ - public function rgbMode($itemID, $mode, $start) - { - return $this->send([ - 'type' => 'rgbMode', - 'item_id' => $itemID, - 'mode' => $mode, - 'start' => $start, - ]); - } - /** * @param int $triggerID * @return bool diff --git a/modules/api/controllers/ItemController.php b/modules/api/controllers/ItemController.php index 983ca86..36864b9 100644 --- a/modules/api/controllers/ItemController.php +++ b/modules/api/controllers/ItemController.php @@ -3,18 +3,22 @@ namespace app\modules\api\controllers; use app\models\Board; +use app\models\History; use app\models\Item; use app\modules\api\components\WebSocketAPIBridge; use Yii; use yii\base\InvalidParamException; use yii\base\NotSupportedException; -use yii\rest\Controller; +use yii\rest\ActiveController; use yii\web\BadRequestHttpException; use yii\web\NotFoundHttpException; use yii\web\ServerErrorHttpException; +use yii\web\UnprocessableEntityHttpException; -class ItemController extends Controller +class ItemController extends ActiveController { + public $modelClass = 'app\models\Item'; + /** * @inheritdoc */ @@ -24,19 +28,10 @@ class ItemController extends Controller 'turn-on' => ['POST'], 'turn-off' => ['POST'], 'rgb' => ['POST'], - 'rgb-mode' => ['POST'], 'value' => ['GET'], ]; } - /** - * @throws NotSupportedException - */ - public function actionIndex() - { - throw new NotSupportedException(); - } - /** * @param $item_id * @return array @@ -138,21 +133,75 @@ class ItemController extends Controller /** * @param int $item_id + * @param string $mode + * @param int $fade_time * @param int $red * @param int $green * @param int $blue - * @param bool $fade + * @param int $color_time * @return array * @throws BadRequestHttpException * @throws NotSupportedException * @throws ServerErrorHttpException + * @throws UnprocessableEntityHttpException + * @internal param bool $fade */ - public function actionRgb($item_id, $red = 0, $green = 0, $blue = 0, $fade = false) - { + public function actionRgb( + $item_id, + $mode, + $fade_time = null, + $red = null, + $green = null, + $blue = null, + $color_time = null + ) { $item = $this->findItem($item_id); if ($item->type !== Item::TYPE_RGB) { - throw new BadRequestHttpException(); + throw new BadRequestHttpException('It is not a RGB Item'); + } + + if (!in_array($mode, Item::getRGBModesArray())) { + throw new BadRequestHttpException('Invalid mode'); + } + + if (!isset($fade_time) or $fade_time === null) { + $fade_time = Yii::$app->params['items']['rgb']['fade-time']; + } elseif ($fade_time < 0) { + $fade_time = 0; + } + + $sendParameters = [ + 'type' => 'rgb', + 'item_id' => $item_id, + 'mode' => $mode, + 'fade_time' => $fade_time, + ]; + + if ($mode === Item::RGB_MODE_STATIC or $mode === Item::RGB_MODE_FADE) { + if (!isset($red) or $red === null) { + throw new UnprocessableEntityHttpException('Missing red parameter'); + } + + if (!isset($green) or $green === null) { + throw new UnprocessableEntityHttpException('Missing green parameter'); + } + + if (!isset($blue) or $blue === null) { + throw new UnprocessableEntityHttpException('Missing blue parameter'); + } + + $sendParameters['red'] = $red; + $sendParameters['green'] = $green; + $sendParameters['blue'] = $blue; + } + + if ($mode === Item::RGB_MODE_WAVE or $mode === Item::RGB_MODE_FADE) { + if (!isset($color_time) or $color_time === null) { + throw new UnprocessableEntityHttpException('Missing color_time parameter'); + } + + $sendParameters['color_time'] = $color_time; } $board = $item->board; @@ -165,7 +214,7 @@ class ItemController extends Controller $api = new WebSocketAPIBridge(Yii::$app->user->identity); return [ - 'success' => $api->rgb($item_id, $red, $green, $blue, $fade), + 'success' => $api->send($sendParameters), ]; default: throw new ServerErrorHttpException(); @@ -189,7 +238,7 @@ class ItemController extends Controller throw new BadRequestHttpException('This item is not the RGB one'); } - if (!in_array($mode, Item::getModesArray())) { + if (!in_array($mode, Item::getRGBModesArray())) { throw new InvalidParamException('Unknown mode'); } @@ -210,6 +259,47 @@ class ItemController extends Controller } } + /** + * @param int $item_id + * @return array + * @throws BadRequestHttpException + */ + public function actionChartData($item_id) + { + $item = $this->findItem($item_id); + + if (!in_array($item->type, [ + Item::TYPE_VARIABLE, + Item::TYPE_VARIABLE_HUMIDITY, + Item::TYPE_VARIABLE_LIGHT, + Item::TYPE_VARIABLE_TEMPERATURE + ]) + ) { + throw new BadRequestHttpException('This item is not the variable one'); + } + + $historyModels = History::find() + ->where([ + 'type' => History::TYPE_ITEM_VALUE, + 'item_id' => $item->id, + ]) + ->andWhere(['>=', 'commited_at', time() - 21600]) + ->orderBy('commited_at DESC') + ->all(); + + $data = []; + + foreach ($historyModels as $historyModel) { + $data[$historyModel->commited_at] = $historyModel->value; + } + + return [ + 'success' => true, + 'data' => $data, + 'item' => $item, + ]; + } + /** * @param int $id * @return Item diff --git a/modules/api/controllers/RoomController.php b/modules/api/controllers/RoomController.php new file mode 100644 index 0000000..05df537 --- /dev/null +++ b/modules/api/controllers/RoomController.php @@ -0,0 +1,10 @@ + TimerInterface * @var TimerInterface[] */ - protected $connectionCheckTimers; + protected $pingTimers; /** * `item_id` => iteration count * @var array */ - protected $connectionCheckIteration; + protected $pingCount; /** * @var TimerInterface[] @@ -85,11 +90,11 @@ class CoreServer implements MessageComponentInterface { // Init variables $this->loop = $loop; - $this->user_clients = []; - $this->board_clients = []; - $this->item_values = []; - $this->connectionCheckTimers = []; - $this->connectionCheckIteration = []; + $this->userConnections = []; + $this->boardConnections = []; + $this->itemValues = []; + $this->pingTimers = []; + $this->pingCount = []; $this->triggerTimers = []; // Database driver hack: Prevent MySQL for disconnecting by timeout @@ -98,166 +103,40 @@ class CoreServer implements MessageComponentInterface Yii::$app->db->createCommand('SHOW TABLES;')->execute(); }); - $this->updateItems(); - + // Do the startup tasks +// $this->updateItems(); $this->scheduleTriggers(); $this->log('Server started'); } + /** + * @inheritdoc + */ public function onOpen(ConnectionInterface $conn) { + /** @var Connection $conn */ + + /** @var QueryString $query */ $query = $conn->WebSocket->request->getQuery(); $type = $query->get('type'); switch ($type) { case 'user': - $this->log('Connection type is User. Authenticating...'); - - $userID = $query->get('id'); - $userAuthToken = $query->get('auth_token'); - - if (!$userID or !$userAuthToken) { - $this->log("Invalid credentials: '$userID', '$userAuthToken'"); - - throw new InvalidParamException("Required fields does not exist"); - } - - $user = User::findOne([ - 'id' => $userID, - ]); - - if (!$user) { - $this->log("User was not found with id '$userID'"); - - throw new NotFoundHttpException("User was not found with id '$userID'"); - } - - if ($user->getAuthToken() != $userAuthToken) { - $this->log("User [$user->id] wrong auth token ($userAuthToken and {$user->getAuthToken()})"); - - throw new AuthenticationException("Wrong credentials"); - } - - // API request - $api = false; - - if ($conn->remoteAddress == '127.0.0.1' and $conn->WebSocket->request->getHeader('Origin') == 'origin') { - $api = true; - } - - // Close previous connection if it is not an API connection - if (isset($this->user_clients[$user->id]) and !$api) { - $this->user_clients[$user->id]->close(); - } - - // Regenerate auth key - $user->reGenerateAuthToken(); - - // Attach to users - $conn->User = $user; - $conn->api = $api; - $this->user_clients[$user->id] = $conn; - - // Prepare Items for User - $items = Item::find() - ->active() - ->select(['id', 'type', 'room_id', 'board_id', 'default_value']) - ->asArray() - ->all(); - - for ($i = 0; $i < count($items); $i++) { - $items[$i]['value'] = $this->getItemSavedValue($items[$i]['id'], $items[$i]['default_value']); - } - - $conn->send(Json::encode([ - 'type' => 'init', - 'items' => $items, - ])); - - $this->logUserConnection($user, true); - - return $this->log("Connected user [{$user->id}] {$user->username}"); + $this->handleUserConnection($conn, $query); + break; case 'board': - $this->log('Connection type is Board. Authenticating...'); - - $boardID = $query->get('id'); - $boardSecret = $query->get('secret'); - - if (!$boardID or !$boardSecret) { - $this->log("Wrong login data: '$boardID' and '$boardSecret'"); - - throw new UnauthorizedHttpException('Wrong credentials'); - } - - $board = Board::findOne([ - 'id' => $boardID, - 'type' => Board::TYPE_WEBSOCKET, - 'secret' => $boardSecret, - ]); - - if (!$board) { - $this->log("Board [$boardID] not found!"); - - throw new NotFoundHttpException("Board with given ID does not exists"); - } - - if (!$board->remote_connection and !IPHelper::isLocal($conn->remoteAddress)) { - $this->log("Remote connection blocked for board [$boardID]; IP: {$conn->remoteAddress}"); - - throw new ForbiddenHttpException("Remote connection is not allowed for this Board"); - } - - // Attach to boards - $conn->Board = $board; - $this->board_clients[$board->id] = $conn; - - // Reset previous timer and start a new one - $this->startConnectionCheckTimer($boardID, true); - - // Set default values to board's items - foreach ($board->items as $item) { - if ($item->default_value and !is_null($item->default_value)) { - switch ($item->type) { - case Item::TYPE_SWITCH: - $this->sendToBoard($board->id, [ - 'type' => $item->default_value == 1 ? 'turnON' : 'turnOFF', - 'pin' => $item->pin, - ]); - - break; - case Item::TYPE_RGB: - $rgbData = $this->valueToRgbData($item->default_value); - - $red = $rgbData[0]; - $green = $rgbData[1]; - $blue = $rgbData[2]; - $fade = (bool)$rgbData[3]; - - $this->sendToBoard($board->id, [ - 'type' => 'rgb', - 'red' => $red * 4, - 'green' => $green * 4, - 'blue' => $blue * 4, - 'fade' => $fade, - ]); - - break; - } - } - } - - $this->triggerBoardConnection($board, true); - - $this->logBoardConnection($board->id, true); - - return $this->log("Connected board [{$board->id}]"); + $this->handleBoardConnection($conn, $query); + break; + default: + throw new BadRequestHttpException('Unknown device type'); } - - return $this->log('Connection has unknown type. Disconnect'); } + /** + * @inheritdoc + */ public function onMessage(ConnectionInterface $from, $msg) { if (isset($from->User)) { @@ -273,21 +152,27 @@ class CoreServer implements MessageComponentInterface return $this->log("Message: '$msg' from unknown client"); } + /** + * @inheritdoc + */ public function onClose(ConnectionInterface $conn) { if (isset($conn->User)) { $this->logUserConnection($conn->User, false); + if (isset($this->userConnections[$conn->User->id][$conn->resourceId]) + and $this->userConnections[$conn->User->id][$conn->resourceId] instanceof ConnectionInterface) { + unset($this->userConnections[$conn->User->id][$conn->resourceId]); + } + $this->log("User [{$conn->User->id}] disconnected"); } elseif (isset($conn->Board)) { $boardId = $conn->Board->id; - $this->log("Disconnecting Board [{$boardId}]..."); - // Cancel connection check timer - $this->stopConnectionCheckTimer($boardId); + $this->stopPingTimer($boardId); - unset($this->board_clients[$boardId]); + unset($this->boardConnections[$boardId]); $this->triggerBoardConnection($conn->Board, false); @@ -297,6 +182,9 @@ class CoreServer implements MessageComponentInterface } } + /** + * @inheritdoc + */ public function onError(ConnectionInterface $conn, \Exception $e) { $this->log("Error: {$e->getMessage()} in file {$e->getFile()} at line {$e->getLine()}"); @@ -305,6 +193,161 @@ class CoreServer implements MessageComponentInterface $conn->close(); } + /** + * @param Connection $conn + * @param QueryString $query + * @throws UnauthorizedHttpException + */ + protected function handleUserConnection($conn, $query) + { + $userID = $query->get('id'); + $userAuthToken = $query->get('auth_token'); + + $user = User::findOne([ + 'id' => $userID, + 'auth_token' => $userAuthToken, + ]); + + if (!$user) { + $this->log("Wrong credentials: '$userID', '$userAuthToken''"); + + throw new UnauthorizedHttpException("Wrong credentials"); + } + + // Check if it is an API request + $api = $conn->remoteAddress === '127.0.0.1' and $conn->WebSocket->request->getHeader('Origin') == 'origin'; + + // Close previous connection if it is not an API connection +// if (!$api and isset($this->userConnections[$user->id]) and $this->userConnections[$user->id] instanceof ConnectionInterface) { +// $this->userConnections[$user->id]->close(); +// } + + // Regenerate auth token + $user->reGenerateAuthToken(); + + // Attach to users + $conn->User = $user; + $conn->api = $api; + + $this->userConnections[$user->id][$conn->resourceId] = $conn; + + // Prepare Items for User + $itemModels = Item::find()->all(); + + $items = []; + + foreach ($itemModels as $itemModel) { + if (!$itemModel->widget) { + continue; + } + + $itemData = []; + $itemData['id'] = $itemModel->id; + $itemData['type'] = $itemModel->widget->type; + $itemData['value_type'] = $itemModel->widget->value_type; + $itemData['room_id'] = $itemModel->widget->room_id; + $itemData['board_id'] = $itemModel->board_id; + $itemData['name'] = $itemModel->widget->getName(); + $itemData['icon'] = $itemModel->widget->icon; + $itemData['html_class'] = $itemModel->widget->html_class; + $itemData['sort_order'] = $itemModel->widget->sort_order; + + $itemData['value'] = $this->getItemSavedValue($itemModel->id, $itemModel->getDefaultNAValue()); + + $items[$itemModel->id] = $itemData; + } + + $conn->send(Json::encode([ + 'type' => 'init', + 'items' => $items, + ])); + + $this->logUserConnection($user, true); + + $this->log("Connected user [{$user->id}] {$user->username}"); + } + + /** + * @param ConnectionInterface $conn + * @param QueryString $query + * @throws ForbiddenHttpException + * @throws NotFoundHttpException + * @throws UnauthorizedHttpException + */ + protected function handleBoardConnection(ConnectionInterface $conn, $query) + { + $boardID = $query->get('id'); + $boardSecret = $query->get('secret'); + + if (!$boardID or !$boardSecret) { + $this->log("Wrong board login data: '$boardID' and '$boardSecret'"); + + throw new UnauthorizedHttpException('Wrong credentials'); + } + + $board = Board::findOne([ + 'id' => $boardID, + 'type' => Board::TYPE_WEBSOCKET, + 'secret' => $boardSecret, + ]); + + if (!$board) { + $this->log("Board [$boardID] not found!"); + + throw new NotFoundHttpException("Board with given ID does not exists"); + } + + if (!$board->remote_connection and !IPHelper::isLocal($conn->remoteAddress)) { + $this->log("Remote connection blocked for board [$boardID]; IP: {$conn->remoteAddress}"); + + throw new ForbiddenHttpException("Remote connection is not allowed for this Board"); + } + + // Attach to boards + $conn->Board = $board; + $this->boardConnections[$board->id] = $conn; + + // Reset previous timer and start a new one + $this->startPingTimer($boardID, true); + + // Set default values to board's items +// foreach ($board->items as $item) { +// if ($item->default_value and !is_null($item->default_value)) { +// switch ($item->type) { +// case Item::TYPE_SWITCH: +// $this->sendToBoard($board->id, [ +// 'type' => $item->default_value == 1 ? 'turnON' : 'turnOFF', +// 'pin' => $item->pin, +// ]); +// +// break; +// case Item::TYPE_RGB: +// $rgbData = $this->valueToRgbData($item->default_value); +// +// $red = $rgbData[0]; +// $green = $rgbData[1]; +// $blue = $rgbData[2]; +// $fade = (bool)$rgbData[3]; +// +// $this->sendToBoard($board->id, [ +// 'type' => 'rgb', +// 'red' => $red * 4, +// 'green' => $green * 4, +// 'blue' => $blue * 4, +// 'fade' => $fade, +// ]); +// +// break; +// } +// } +// } + + $this->triggerBoardConnection($board, true); + $this->logBoardConnection($board->id, true); + + $this->log("Connected board [{$board->id}]"); + } + /** * @param $from * @param $msg @@ -320,14 +363,12 @@ class CoreServer implements MessageComponentInterface } switch ($data['type']) { - case 'turnON': + case 'turn_on': return $this->handleTurnOn($from, $user, $data); - case 'turnOFF': + case 'turn_off': return $this->handleTurnOff($from, $user, $data); case 'rgb': return $this->handleRgb($from, $user, $data); - case 'rgbMode': - return $this->handleRgbMode($from, $user, $data); case 'schedule-triggers': return $this->scheduleTriggers(); case 'update-items': @@ -343,6 +384,7 @@ class CoreServer implements MessageComponentInterface * @param $from * @param $msg * @return bool + * @throws NotFoundHttpException */ public function handleBoardMessage($from, $msg) { @@ -350,8 +392,8 @@ class CoreServer implements MessageComponentInterface $board = $from->Board; $data = Json::decode($msg); - // Board responds: restart connection check timer - $this->startConnectionCheckTimer($board->id); + // Restart ping timer + $this->startPingTimer($board->id); switch ($data['type']) { case 'value': @@ -372,54 +414,19 @@ class CoreServer implements MessageComponentInterface $value = $this->saveItemValue($item->id, $value, $item->type); - $this->sendUsers([ - 'type' => 'value', - 'item_id' => $item->id, - 'item_type' => $item->type, - 'value' => $value, - ]); + if ($item->widget) { + $this->sendUsers([ + 'type' => 'value', + 'item_id' => $item->id, + 'item_type' => $item->widget->type, + 'value_type' => $item->widget->value_type, + 'value' => $value, + ]); + } // Save to history $this->logItemValue($item, $value); - break; - case 'rgbMode': - /** - * Message structure: - * {"start":true,"type":"rgbMode","mode":"rainbow","pin":1} - */ - $value = $data['mode']; - $pin = (integer)$data['pin']; - $start = (bool)$data['start']; - - $item = Item::findOne([ - 'board_id' => $board->id, - 'pin' => $pin, - ]); - - if (!$item) { - return $this->log('Trying to use unknown item'); - } - - if ($start) { - $value = $this->saveItemValue($item->id, $value, $item->type, false); - } else { - $value = $this->saveItemValue($item->id, $item->getDefaultValue(), $item->type); - } - - // TODO: trigger - - $this->sendUsers([ - 'type' => 'value', - 'item_id' => $item->id, - 'item_type' => $item->type, - 'value' => $value, - 'start' => $start, - ]); - - // Save to history - $this->logItemValue($item, $start ? 'start:' : '' . $value); - break; case 'values': unset($data['type']); @@ -439,17 +446,74 @@ class CoreServer implements MessageComponentInterface $value = $this->saveItemValue($item->id, $value, $item->type); - $this->sendUsers([ - 'type' => 'value', - 'item_id' => $item->id, - 'item_type' => $item->type, - 'value' => $value, - ]); + if ($item->widget) { + $this->sendUsers([ + 'type' => 'value', + 'item_id' => $item->id, + 'item_type' => $item->widget->type, + 'value_type' => $item->widget->value_type, + 'value' => $value, + ]); + } // Save to history $this->logItemValue($item, $value); } + break; + case 'rgb': + $itemId = (integer)$data['item_id']; + $mode = $data['mode']; + $fadeTime = (int)$data['fade_time']; + + $item = Item::findOne([ + 'id' => $itemId, + 'board_id' => $board->id, + ]); + + if (!$item) { + $this->log("Board [{$board->id}] tried to use unknown item"); + throw new NotFoundHttpException('Item does not exist'); + } + + $commonParameters = [ + 'type' => 'rgb', + 'item_id' => $item->id, + 'mode' => $mode, + 'fade_time' => $fadeTime, + ]; + + $modeParameters = []; + + if ($mode === Item::RGB_MODE_STATIC or $mode === Item::RGB_MODE_FADE) { + // Fill saved values if not provided + $red = isset($data['red']) ? $data['red'] : Yii::$app->params['items']['rgb']['red']; + $green = isset($data['green']) ? $data['green'] : Yii::$app->params['items']['rgb']['green']; + $blue = isset($data['blue']) ? $data['blue'] : Yii::$app->params['items']['rgb']['blue']; + + // Convert color from 1023 to 255 + $red = RGBHelper::from10to8($red); + $green = RGBHelper::from10to8($green); + $blue = RGBHelper::from10to8($blue); + + $modeParameters['red'] = $red; + $modeParameters['green'] = $green; + $modeParameters['blue'] = $blue; + } + + if ($mode === Item::RGB_MODE_WAVE or $mode === Item::RGB_MODE_FADE) { + $colorTime = isset($data['color_time']) ? $data['color_time'] : Yii::$app->params['items']['rgb']['color-time']; + + $modeParameters['color_time'] = $colorTime; + } + + $parameters = ArrayHelper::merge($commonParameters, $modeParameters); + + $this->sendUsers($parameters); + + $this->logItemValue($item, serialize($parameters)); + $this->saveItemValue($item->id, $parameters, $item->type); + break; case 'pong': $this->log("Pong from board [$board->id]"); @@ -598,108 +662,66 @@ class CoreServer implements MessageComponentInterface ]); } - $red = $data['red']; - $green = $data['green']; - $blue = $data['blue']; - - if ($red > 255) { - $red = 255; - } - - if ($green > 255) { - $green = 255; - } - - if ($blue > 255) { - $blue = 255; - } - - $board = $item->board; - - switch ($board->type) { - case Board::TYPE_AREST: - throw new NotSupportedException(); - - case Board::TYPE_WEBSOCKET: - if (!$this->isBoardConnected($board->id)) { - return $from->send(Json::encode([ - 'type' => 'error', - 'message' => 'Устройство не подключено', - ])); - } - - $fade = isset($data['fade']) ? (bool)$data['fade'] : false; - - $this->sendToBoard($board->id, [ - 'type' => 'rgb', - 'red' => $red * 4, - 'green' => $green * 4, - 'blue' => $blue * 4, - 'fade' => $fade, - ]); - - break; - } - -// $rgbArray = [ -// $red, -// $green, -// $blue -// ]; - -// $this->item_values[$item->id]['value'] = $rgbArray; - - $history = new History(); - $history->type = History::TYPE_USER_ACTION; - $history->user_id = $user->id; - $history->item_id = $item->id; - $history->commited_at = time(); - $history->value = $red . ',' . $green . ',' . $blue; - - if (!$history->save()) { - $this->log("Cannot log: "); - var_dump($history->errors); - } - - return true; - } - - /** - * @param ConnectionInterface $from - * @param User $user - * @param array $data - * @return bool|mixed - * @throws NotSupportedException - */ - protected function handleRgbMode($from, $user, $data) - { - $item_id = (int)$data['item_id']; - $item = Item::findOne($item_id); - - if (!$item) { - return $from->send(Json::encode([ - 'type' => 'error', - 'message' => 'Такое устройство не существует', - ])); - } - - if ($item->type !== Item::TYPE_RGB) { - return $from->send(Json::encode([ - 'type' => 'error', - 'message' => 'Данный тип устройства не является RGB', - ])); - } - $mode = $data['mode']; - $start = (bool)$data['start']; + $fadeTime = isset($data['fade_time']) ? $data['fade_time'] : Yii::$app->params['items']['rgb']['fade-time']; - if (!in_array($mode, Item::getModesArray())) { - return $from->send(Json::encode([ - 'type' => 'error', - 'message' => 'Неизвестный режим', - ])); + if (!in_array($mode, Item::getRGBModesArray())) { + throw new InvalidParamException('Unknown RGB mode'); } + if ($fadeTime < 0) { + $fadeTime = 0; + } + + $commonParameters = [ + 'type' => 'rgb', + 'item_id' => $item->id, + 'mode' => $mode, + 'fade_time' => $fadeTime, + ]; + + $modeParameters = []; + + if ($mode === Item::RGB_MODE_STATIC or $mode === Item::RGB_MODE_FADE) { + // Fill saved values if not provided + $red = isset($data['red']) ? $data['red'] : Yii::$app->params['items']['rgb']['red']; + $green = isset($data['green']) ? $data['green'] : Yii::$app->params['items']['rgb']['green']; + $blue = isset($data['blue']) ? $data['blue'] : Yii::$app->params['items']['rgb']['blue']; + + // Convert color from 255 to 1023 + if ($red > 255) { + $red = 255; + } + + if ($green > 255) { + $green = 255; + } + + if ($blue > 255) { + $blue = 255; + } + + $red = RGBHelper::from8to10($red); + $green = RGBHelper::from8to10($green); + $blue = RGBHelper::from8to10($blue); + + $modeParameters['red'] = $red; + $modeParameters['green'] = $green; + $modeParameters['blue'] = $blue; + } + + if ($mode === Item::RGB_MODE_WAVE or $mode === Item::RGB_MODE_FADE) { + $colorTime = isset($data['color_time']) ? $data['color_time'] : Yii::$app->params['items']['rgb']['color-time']; + + if ($colorTime < 0) { + $colorTime = 0; + } + + $modeParameters['color_time'] = $colorTime; + } + + $parameters = ArrayHelper::merge($commonParameters, $modeParameters); + $board = $item->board; switch ($board->type) { @@ -714,11 +736,7 @@ class CoreServer implements MessageComponentInterface ])); } - $this->sendToBoard($board->id, [ - 'type' => 'rgbMode', - 'mode' => $mode, - 'action' => $start ? 'start' : 'stop', - ]); + $this->sendToBoard($board->id, $parameters); break; } @@ -728,10 +746,10 @@ class CoreServer implements MessageComponentInterface $history->user_id = $user->id; $history->item_id = $item->id; $history->commited_at = time(); - $history->value = $mode . ', ' . $start ? 'start' : 'stop'; + $history->value = serialize($parameters); if (!$history->save()) { - $this->log("Cannot log: "); + $this->log("Cannot log:"); var_dump($history->errors); } @@ -790,8 +808,15 @@ class CoreServer implements MessageComponentInterface { $msg = Json::encode($data); - foreach ($this->user_clients as $client) { - $client->send($msg); + foreach ($this->userConnections as $userConnections) { + if (is_array($userConnections)) { + /** @var Connection[] $userConnections */ + foreach ($userConnections as $connection) { + $connection->send($msg); + } + } else { + $userConnections->send($msg); + } } } @@ -800,24 +825,21 @@ class CoreServer implements MessageComponentInterface * * @param integer $board_id * @param array $data - * @return bool|ConnectionInterface */ protected function sendToBoard($board_id, $data) { - if (isset($this->board_clients[$board_id])) { + if (isset($this->boardConnections[$board_id])) { /** @var ConnectionInterface $client */ - $client = $this->board_clients[$board_id]; + $client = $this->boardConnections[$board_id]; $msg = Json::encode($data); $this->log("Sending to board [$board_id]: $msg"); - return $client->send($msg); + $client->send($msg); + } else { + $this->log("Cannot send to board [$board_id]: not connected"); } - - $this->log("Cannot send to board [$board_id]: not connected"); - - return false; } /** @@ -850,7 +872,7 @@ class CoreServer implements MessageComponentInterface */ protected function isBoardConnected($boardID) { - return isset($this->board_clients[$boardID]); + return isset($this->boardConnections[$boardID]); } /** @@ -945,10 +967,6 @@ class CoreServer implements MessageComponentInterface return; } - if ($item->type === Item::TYPE_RGB) { - $value = implode(',', $value); - } - $model = new History(); $model->type = History::TYPE_ITEM_VALUE; $model->item_id = $item->id; @@ -971,7 +989,7 @@ class CoreServer implements MessageComponentInterface $this->log("Connection check for Board [$boardID]..."); // Check if it is already disconnected - if (!isset($this->board_clients[$boardID])) { + if (!isset($this->boardConnections[$boardID])) { $this->logBoardConnection($boardID, false); $this->log("Board [$boardID] has already been disconnected"); @@ -979,18 +997,18 @@ class CoreServer implements MessageComponentInterface return; } - if (isset($this->connectionCheckIteration[$boardID])) { - if ($this->connectionCheckIteration[$boardID] >= Yii::$app->params['server']['connectionCheckMaxIteration']) { + if (isset($this->pingCount[$boardID])) { + if ($this->pingCount[$boardID] >= Yii::$app->params['server']['connectionCheckMaxIteration']) { $this->log("Maximum ignored ping commands reached. Disconnecting..."); - $this->board_clients[$boardID]->close(); + $this->boardConnections[$boardID]->close(); return; } - $this->connectionCheckIteration[$boardID]++; + $this->pingCount[$boardID]++; } else { - $this->connectionCheckIteration[$boardID] = 1; + $this->pingCount[$boardID] = 1; } $this->sendToBoard($boardID, [ @@ -1342,11 +1360,11 @@ class CoreServer implements MessageComponentInterface $this->log("Loading items..."); /** @var Item[] $items */ - $items = Item::find()->active()->all(); + $items = Item::find()->all(); foreach ($items as $item) { if (!$this->hasItemSavedValue($item->id)) { - $this->saveItemValue($item->id, $item->getDefaultValue(), $item->type, false); + $this->saveItemValue($item->id, $item->getDefaultNAValue(), $item->type, false); } } @@ -1354,30 +1372,35 @@ class CoreServer implements MessageComponentInterface } /** + * Checks if item value is stored and if so returns it. + * If value is missing - returns default from parameter + * * @param int $item_id * @param mixed $defaultValue * @return mixed */ - protected function getItemSavedValue($item_id, $defaultValue = false) + protected function getItemSavedValue($item_id, $defaultValue = null) { if ($this->hasItemSavedValue($item_id)) { - return $this->item_values[$item_id]; + return $this->itemValues[$item_id]; } return $defaultValue; } /** + * Checks if item value is stored. + * * @param int $item_id * @return bool */ protected function hasItemSavedValue($item_id) { - return isset($this->item_values[$item_id]); + return isset($this->itemValues[$item_id]) and $this->itemValues[$item_id] !== null; } /** - * Saves to value array and returns it. Normalization is on by default + * Saves to value array and returns it. Normalization is enabled by default * * @param int $item_id * @param mixed $value @@ -1391,7 +1414,7 @@ class CoreServer implements MessageComponentInterface $value = $this->normalizeItemValue($value, $item_type); } - $this->item_values[$item_id] = $value; + $this->itemValues[$item_id] = $value; return $value; } @@ -1412,9 +1435,6 @@ class CoreServer implements MessageComponentInterface case Item::TYPE_VARIABLE_TEMPERATURE: case Item::TYPE_VARIABLE_HUMIDITY: return (int)$value; - - case Item::TYPE_RGB: - return $this->valueToRgb($value); } return $value; @@ -1428,14 +1448,14 @@ class CoreServer implements MessageComponentInterface * @param int $boardID * @param bool $stopPrevious */ - protected function startConnectionCheckTimer($boardID, $stopPrevious = true) + protected function startPingTimer($boardID, $stopPrevious = true) { if ($stopPrevious) { - $this->stopConnectionCheckTimer($boardID); + $this->stopPingTimer($boardID); } // Start connection checks - $this->connectionCheckTimers[$boardID] = $this->loop->addPeriodicTimer( + $this->pingTimers[$boardID] = $this->loop->addPeriodicTimer( Yii::$app->params['server']['connectionCheckTimeout'], function () use ($boardID) { $this->doConnectionCheckTimer($boardID); @@ -1449,18 +1469,18 @@ class CoreServer implements MessageComponentInterface * @param int $boardID * @param bool $resetCount */ - protected function stopConnectionCheckTimer($boardID, $resetCount = true) + protected function stopPingTimer($boardID, $resetCount = true) { - if (isset($this->connectionCheckTimers[$boardID])) { - if ($this->connectionCheckTimers[$boardID] instanceof TimerInterface) { - $this->connectionCheckTimers[$boardID]->cancel(); + if (isset($this->pingTimers[$boardID])) { + if ($this->pingTimers[$boardID] instanceof TimerInterface) { + $this->pingTimers[$boardID]->cancel(); } - unset($this->connectionCheckTimers[$boardID]); + unset($this->pingTimers[$boardID]); } - if ($resetCount and isset($this->connectionCheckIteration[$boardID])) { - unset($this->connectionCheckIteration[$boardID]); + if ($resetCount and isset($this->pingCount[$boardID])) { + unset($this->pingCount[$boardID]); } } } diff --git a/views/auth/login.php b/views/auth/login.php index c2f6810..42ab6a6 100644 --- a/views/auth/login.php +++ b/views/auth/login.php @@ -8,7 +8,7 @@ use yii\helpers\Html; use yii\bootstrap\ActiveForm; $this->title = 'Авторизация'; -$this->params['body-class'] = 'login-page'; +$this->params['bodyClass'] = 'login-body'; $fieldOptions1 = [ 'options' => ['class' => 'form-group has-feedback'], @@ -21,37 +21,37 @@ $fieldOptions2 = [ ]; ?> -