From 7ebad90e634e0c4630111beb63bbc6ba41b6fcc6 Mon Sep 17 00:00:00 2001 From: Alex Solomaha Date: Fri, 2 Sep 2016 21:13:00 +0300 Subject: [PATCH] Some work --- assets/WSClientAsset.php | 1 + components/WebSocketAuth.php | 29 +++ config/params.php | 4 +- controllers/PanelController.php | 7 + ..._181951_add_type_column_to_board_table.php | 25 +++ ...74332_add_secret_column_to_board_table.php | 25 +++ ...02_180312_add_pin_column_to_item_table.php | 25 +++ models/Board.php | 31 ++- models/BoardSearch.php | 6 +- models/Item.php | 5 +- servers/Panel.php | 182 +++++++++++++----- servers/Test.php | 11 +- views/board/_form.php | 5 + views/board/view.php | 2 + views/item/_form.php | 2 + views/item/create.php | 2 +- views/item/index.php | 2 +- views/item/view.php | 1 + views/layouts/_left.php | 2 +- views/panel/index.php | 3 + web/css/wsclient.css | 0 web/js/wsclient.js | 45 +++++ 22 files changed, 346 insertions(+), 69 deletions(-) create mode 100644 components/WebSocketAuth.php create mode 100644 migrations/m160901_181951_add_type_column_to_board_table.php create mode 100644 migrations/m160902_174332_add_secret_column_to_board_table.php create mode 100644 migrations/m160902_180312_add_pin_column_to_item_table.php create mode 100644 web/css/wsclient.css create mode 100644 web/js/wsclient.js diff --git a/assets/WSClientAsset.php b/assets/WSClientAsset.php index b89917e..615f042 100644 --- a/assets/WSClientAsset.php +++ b/assets/WSClientAsset.php @@ -16,6 +16,7 @@ class WSClientAsset extends AssetBundle 'js/wsclient.js', ]; public $depends = [ + 'app\assets\AppAsset', 'yii\web\JqueryAsset', ]; } diff --git a/components/WebSocketAuth.php b/components/WebSocketAuth.php new file mode 100644 index 0000000..b9cc1af --- /dev/null +++ b/components/WebSocketAuth.php @@ -0,0 +1,29 @@ +user->id); + + if (!$user) { + return null; + } + + $auth_key = $user->auth_key; + + // Regenerate auth key + $user->generateAuthKey(); + $user->save(); + + return $auth_key; + } +} diff --git a/config/params.php b/config/params.php index a7e3d9a..31518e5 100644 --- a/config/params.php +++ b/config/params.php @@ -4,9 +4,7 @@ use yii\helpers\ArrayHelper; $params = [ 'adminEmail' => 'admin@example.com', - 'auth' => [ - 'tokenExpireSec' => 2, - ], + 'wsURL' => 'ws://192.168.1.101:8081', ]; return ArrayHelper::merge($params, require 'params-local.php'); diff --git a/controllers/PanelController.php b/controllers/PanelController.php index 989a520..def7409 100644 --- a/controllers/PanelController.php +++ b/controllers/PanelController.php @@ -2,8 +2,11 @@ namespace app\controllers; +use app\components\WebSocketAuth; +use Yii; use yii\filters\AccessControl; use yii\web\Controller; +use yii\web\View; class PanelController extends Controller { @@ -27,6 +30,10 @@ class PanelController extends Controller public function actionIndex() { + $this->view->registerJs(' + var wsURL = "' . Yii::$app->params['wsURL'] . '/?type=user&id=' . Yii::$app->user->identity->id . '&auth_key=' . WebSocketAuth::getAuthKey() . '"; + ', View::POS_HEAD); + return $this->render('index'); } diff --git a/migrations/m160901_181951_add_type_column_to_board_table.php b/migrations/m160901_181951_add_type_column_to_board_table.php new file mode 100644 index 0000000..60eaea6 --- /dev/null +++ b/migrations/m160901_181951_add_type_column_to_board_table.php @@ -0,0 +1,25 @@ +addColumn('board', 'type', $this->smallInteger()->defaultValue(10)->after('id')); + } + + /** + * @inheritdoc + */ + public function safeDown() + { + $this->dropColumn('board', 'type'); + } +} diff --git a/migrations/m160902_174332_add_secret_column_to_board_table.php b/migrations/m160902_174332_add_secret_column_to_board_table.php new file mode 100644 index 0000000..74913f7 --- /dev/null +++ b/migrations/m160902_174332_add_secret_column_to_board_table.php @@ -0,0 +1,25 @@ +addColumn('board', 'secret', $this->string()->after('name')); + } + + /** + * @inheritdoc + */ + public function safeDown() + { + $this->dropColumn('board', 'secret'); + } +} diff --git a/migrations/m160902_180312_add_pin_column_to_item_table.php b/migrations/m160902_180312_add_pin_column_to_item_table.php new file mode 100644 index 0000000..8f39ddb --- /dev/null +++ b/migrations/m160902_180312_add_pin_column_to_item_table.php @@ -0,0 +1,25 @@ +addColumn('item', 'pin', $this->integer()->after('board_id')); + } + + /** + * @inheritdoc + */ + public function safeDown() + { + $this->dropColumn('item', 'pin'); + } +} diff --git a/models/Board.php b/models/Board.php index 3cfd47e..3bb859e 100644 --- a/models/Board.php +++ b/models/Board.php @@ -9,11 +9,16 @@ use yii\db\ActiveRecord; * This is the model class for table "board". * * @property integer $id + * @property integer $type * @property string $name + * @property string $secret * @property string $baseUrl */ class Board extends ActiveRecord { + const TYPE_AREST = 10; + const TYPE_WEBSOCKET = 20; + /** * @inheritdoc */ @@ -28,9 +33,10 @@ class Board extends ActiveRecord public function rules() { return [ - [['name', 'baseUrl'], 'required'], + [['type', 'name'], 'required'], + [['type'], 'integer'], [['baseUrl'], 'string'], - [['name'], 'string', 'max' => 255], + [['name', 'secret'], 'string', 'max' => 255], ]; } @@ -41,7 +47,9 @@ class Board extends ActiveRecord { return [ 'id' => Yii::t('app', 'ID'), + 'type' => Yii::t('app', 'Тип'), 'name' => Yii::t('app', 'Название'), + 'secret' => Yii::t('app', 'Ключ'), 'baseUrl' => Yii::t('app', 'Base Url'), ]; } @@ -62,4 +70,23 @@ class Board extends ActiveRecord { return new BoardQuery(get_called_class()); } + + /** + * @return array + */ + public static function getTypesArray() + { + return [ + self::TYPE_AREST => 'aREST API', + self::TYPE_WEBSOCKET => 'WebSocket API', + ]; + } + + /** + * @return string + */ + public function getTypeLabel() + { + return self::getTypesArray()[$this->type]; + } } diff --git a/models/BoardSearch.php b/models/BoardSearch.php index 832320f..2b2e648 100644 --- a/models/BoardSearch.php +++ b/models/BoardSearch.php @@ -18,8 +18,8 @@ class BoardSearch extends Board public function rules() { return [ - [['id'], 'integer'], - [['name', 'baseUrl'], 'safe'], + [['id', 'type'], 'integer'], + [['name', 'baseUrl', 'secret'], 'safe'], ]; } @@ -60,9 +60,11 @@ class BoardSearch extends Board // grid filtering conditions $query->andFilterWhere([ 'id' => $this->id, + 'type' => $this->type, ]); $query->andFilterWhere(['like', 'name', $this->name]) + ->andFilterWhere(['like', 'secret', $this->secret]) ->andFilterWhere(['like', 'baseUrl', $this->baseUrl]); return $dataProvider; diff --git a/models/Item.php b/models/Item.php index 70e2030..78b216d 100644 --- a/models/Item.php +++ b/models/Item.php @@ -15,6 +15,7 @@ use yii\db\ActiveRecord; * @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 @@ -50,8 +51,8 @@ class Item extends ActiveRecord public function rules() { return [ - [['type', 'update_interval', 'save_history_interval', 'room_id', 'url', 'name', 'icon', 'bg', 'board_id'], 'required'], - [['type', 'update_interval', 'save_history_interval', 'room_id', 'sort_order', 'board_id'], 'integer'], + [['type', 'update_interval', 'save_history_interval', 'room_id', 'name', 'icon', 'bg', 'board_id'], 'required'], + [['type', 'update_interval', 'save_history_interval', 'room_id', 'sort_order', 'board_id', 'pin'], 'integer'], [['url', 'name', 'icon', 'bg', 'class'], 'string', 'max' => 255], [['room_id'], 'exist', 'skipOnError' => true, 'targetClass' => Room::className(), 'targetAttribute' => ['room_id' => 'id']], [['board_id'], 'exist', 'skipOnError' => true, 'targetClass' => Board::className(), 'targetAttribute' => ['board_id' => 'id']], diff --git a/servers/Panel.php b/servers/Panel.php index 3e1b353..326fc39 100644 --- a/servers/Panel.php +++ b/servers/Panel.php @@ -3,6 +3,7 @@ namespace app\servers; use app\components\ApiHelper; +use app\models\Board; use app\models\History; use app\models\Item; use app\models\User; @@ -31,7 +32,12 @@ class Panel implements MessageComponentInterface /** * @var ConnectionInterface[] */ - protected $clients; + protected $user_clients; + + /** + * @var ConnectionInterface[] + */ + protected $board_clients; /** * @var array @@ -48,7 +54,8 @@ class Panel implements MessageComponentInterface public function __construct($loop) { $this->loop = $loop; - $this->clients = []; + $this->user_clients = []; + $this->board_clients = []; $this->items = []; // Database driver hack @@ -81,53 +88,75 @@ class Panel implements MessageComponentInterface // Get query $query = $conn->WebSocket->request->getQuery(); - $id = $query->get('id'); - $time = $query->get('time'); - $token = $query->get('token'); + $type = $query->get('type'); - if ((time() - $time) >= Yii::$app->params['auth']['tokenExpireSec']) { - return $this->log("Auth failed (Token expired). ID: $id; Token: $token; IP: {$conn->remoteAddress}; Timestamp: $time"); + switch ($type) { + case 'user': + $userID = $query->get('id'); + $userAuthKey = $query->get('auth_key'); + + if (!$userID or !$userAuthKey) { + return false; + } + + $user = User::findOne([ + 'id' => $userID, + 'auth_key' => $userAuthKey, + ]); + + if (!$user) { + return false; + } + + // Close previous connection + if (isset($this->user_clients[$user->id])) { + $this->user_clients[$user->id]->close(); + } + + // Attach to clients + $conn->User = $user; + $this->user_clients[$user->id] = $conn; + + return $this->log("Connected user [{$user->id}] {$user->username}"); + case 'board': + $boardID = $query->get('id'); + $boardSecret = $query->get('secret'); + + if (!$boardID or !$boardSecret) { + return false; + } + + $board = Board::findOne([ + 'id' => $boardID, + 'type' => Board::TYPE_WEBSOCKET, + 'secret' => $boardSecret, + ]); + + if (!$board) { + return false; + } + + // Close previous connection + if (isset($this->board_clients[$board->id])) { + $this->board_clients[$board->id]->close(); + } + + // Attach to clients + $conn->Board = $board; + $this->board_clients[$board->id] = $conn; + + return $this->log("Connected board [{$board->id}]"); } - // Find user by auth info - /** @var User $user */ - $user = User::findOne([ - 'id' => $id, - 'auth_key' => $token, - ]); - - // Security checks - if (!$user) { - return $this->log("Auth failed. ID: $id; Token: $token; IP: {$conn->remoteAddress}; Timestamp: $time"); - } - - // Close previous connection - if (isset($this->clients[$user->id])) { - $this->clients[$user->id]->close(); - } - - // Attach to online users - $conn->User = $user; - $this->clients[$user->id] = $conn; - - return $this->log("Connected new user [$id] {$user->username}"); + return false; } public function onMessage(ConnectionInterface $from, $msg) { - if (!$from->User) { - return $from->send(Json::encode([ - 'type' => 'error', - 'message' => 'Необходима авторизаия', - ])); - } - - /** @var User $user */ - $user = $from->User; - $data = Json::decode($msg); - - if ($data['type'] === 'switch') { - return $this->handleSwitch($from, $user, $data); + if ($from->User) { + return $this->handleUserMessage($from, $msg); + } elseif ($from->Board) { + return $this->handleBoardMessage($from, $msg); } return false; @@ -136,11 +165,9 @@ class Panel implements MessageComponentInterface public function onClose(ConnectionInterface $conn) { if (isset($conn->User)) { - unset($this->clients[$conn->User->id]); - - // Regenerate auth key - $conn->User->generateAuthKey(); - $conn->User->save(); + unset($this->user_clients[$conn->User->id]); + } elseif (isset($conn->Board)) { + unset($this->board_clients[$conn->Board->id]); } } @@ -153,6 +180,61 @@ class Panel implements MessageComponentInterface $conn->close(); } + public function handleUserMessage($from, $msg) + { + $user = $from->User; + $data = Json::decode($msg); + + switch ($data['type']) { + case 'switch': + $item_id = $data['item_id']; + $item = Item::findOne($item_id); + + if (!$item) { + return false; + } + + $board = $item->board; + + if ($board->type === Board::TYPE_WEBSOCKET) { + if (isset($this->board_clients[$board->id])) { + $this->board_clients[$board->id]->send(Json::encode([ + 'type' => 'switch', + 'pin' => $item->pin, + ])); + } + } + + break; + } + + return false; + } + + public function handleBoardMessage($from, $msg) + { + $board = $from->Board; + $data = Json::decode($msg); + + switch ($data['type']) { + case 'value': + $pin = $data['pin']; + + $item = Item::findOne([ + 'board_id' => $board->id, + 'pin' => $pin, + ]); + + if (!$item) { + return false; + } + + break; + } + + return false; + } + /** * @param ConnectionInterface $from * @param User $user @@ -194,7 +276,7 @@ class Panel implements MessageComponentInterface { $encodedData = Json::encode($data); - foreach ($this->clients as $client) { + foreach ($this->user_clients as $client) { $client->send($encodedData); } } @@ -208,9 +290,9 @@ class Panel implements MessageComponentInterface */ private function sendTo($user_id, $data) { - if (isset($this->clients[$user_id])) { + if (isset($this->user_clients[$user_id])) { /** @var ConnectionInterface $client */ - $client = $this->clients[$user_id]; + $client = $this->user_clients[$user_id]; return $client->send(Json::encode($data)); } diff --git a/servers/Test.php b/servers/Test.php index 5318cd8..b153fef 100644 --- a/servers/Test.php +++ b/servers/Test.php @@ -69,7 +69,7 @@ class Test implements MessageComponentInterface $type = $query->get('type'); if (!$type) { - return $conn->send('Need more info!'); + return false; } if ($type == 'board') { @@ -79,25 +79,22 @@ class Test implements MessageComponentInterface $conn->id = $id; $this->boards[$id] = $conn; - $this->sendAll("Board connected. BoardID: $id"); $this->log("Board connected. BoardID: $id"); - $this->loop->addPeriodicTimer(5, function () use ($conn) { - $conn->send('switch'); + $this->loop->addPeriodicTimer(3, function () use ($conn) { + $conn->send(Json::encode(['type' => 'switch', 'pin' => 8])); }); return true; } elseif ($type == 'user') { $id = $query->get('id'); - $username = $query->get('username'); $conn->type = 'user'; $conn->id = $id; - $conn->username = $username; $this->clients[$id] = $conn; - $this->log("New user. Username: $username; ID: $id"); + $this->log("User connected. ID: $id"); return true; } diff --git a/views/board/_form.php b/views/board/_form.php index 51156f7..6e1a616 100644 --- a/views/board/_form.php +++ b/views/board/_form.php @@ -4,6 +4,7 @@ /* @var $model app\models\Board */ /* @var $form yii\widgets\ActiveForm */ +use app\models\Board; use yii\helpers\Html; use yii\widgets\ActiveForm; ?> @@ -14,6 +15,10 @@ use yii\widgets\ActiveForm; field($model, 'name')->textInput(['maxlength' => true]) ?> + field($model, 'type')->dropDownList(Board::getTypesArray()) ?> + + field($model, 'secret')->textInput() ?> + field($model, 'baseUrl')->textInput(['maxlength' => true]) ?>
diff --git a/views/board/view.php b/views/board/view.php index 03c3c0d..6a4a40c 100644 --- a/views/board/view.php +++ b/views/board/view.php @@ -28,6 +28,8 @@ $this->params['breadcrumbs'][] = $this->title; 'attributes' => [ 'id', 'name', + 'typeLabel', + 'secret', 'baseUrl:ntext', ], ]) ?> diff --git a/views/item/_form.php b/views/item/_form.php index b3350f4..979aaf8 100644 --- a/views/item/_form.php +++ b/views/item/_form.php @@ -29,6 +29,8 @@ use yii\widgets\ActiveForm; ArrayHelper::map(Board::find()->all(), 'id', 'name') ) ?> + field($model, 'pin')->input('number') ?> + field($model, 'type')->dropDownList(Item::getTypesArray()) ?> field($model, 'update_interval')->textInput() ?> diff --git a/views/item/create.php b/views/item/create.php index 6fbef98..52496ac 100644 --- a/views/item/create.php +++ b/views/item/create.php @@ -6,7 +6,7 @@ use yii\helpers\Html; $this->title = 'Добавить Item'; -$this->params['breadcrumbs'][] = ['label' => 'Items', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => 'Элементы', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; ?>
diff --git a/views/item/index.php b/views/item/index.php index 7ae7593..a15cb1d 100644 --- a/views/item/index.php +++ b/views/item/index.php @@ -8,7 +8,7 @@ use yii\helpers\Html; use yii\grid\GridView; use yii\widgets\Pjax; -$this->title = 'Items'; +$this->title = 'Элементы'; $this->params['breadcrumbs'][] = $this->title; ?>
diff --git a/views/item/view.php b/views/item/view.php index 3329acc..a48c6cf 100644 --- a/views/item/view.php +++ b/views/item/view.php @@ -32,6 +32,7 @@ $this->params['breadcrumbs'][] = $this->title; 'bg', 'class', ['attribute' => 'board_id', 'value' => $model->board->name], + 'pin', 'type', 'update_interval', 'save_history_interval', diff --git a/views/layouts/_left.php b/views/layouts/_left.php index 93ccd5a..e6cb965 100644 --- a/views/layouts/_left.php +++ b/views/layouts/_left.php @@ -22,7 +22,7 @@ ['label' => 'Панель Управления', 'icon' => 'fa fa-sliders', 'url' => ['/panel/index']], ['label' => 'Настройки', 'options' => ['class' => 'header']], - ['label' => 'Устройства', 'icon' => 'fa fa-cubes', 'url' => ['/item/index']], + ['label' => 'Элементы', 'icon' => 'fa fa-cubes', 'url' => ['/item/index']], ['label' => 'Комнаты', 'icon' => 'fa fa-folder-open', 'url' => ['/room/index']], ['label' => 'История', 'icon' => 'fa fa-bar-chart', 'url' => ['/history/index']], ['label' => 'Платы', 'icon' => 'fa fa-bolt', 'url' => ['/board/index']], diff --git a/views/panel/index.php b/views/panel/index.php index a4f6443..0427cd6 100644 --- a/views/panel/index.php +++ b/views/panel/index.php @@ -2,12 +2,15 @@ /* @var $this yii\web\View */ +use app\assets\WSClientAsset; use app\models\Item; use app\models\Room; +use yii\web\View; $this->title = 'Панель Управления'; $this->params['not-boxed'] = true; +WSClientAsset::register($this); ?>
diff --git a/web/css/wsclient.css b/web/css/wsclient.css new file mode 100644 index 0000000..e69de29 diff --git a/web/js/wsclient.js b/web/js/wsclient.js new file mode 100644 index 0000000..84ec67b --- /dev/null +++ b/web/js/wsclient.js @@ -0,0 +1,45 @@ +var wsURL; + +var WS; + +function log(msg) { + console.log(msg); +} + +function initWebSocket() { + WS = new WebSocket(wsURL); + + WS.onmessage = onMessage; +} + +function onMessage(e) { + try { + var data = JSON.parse(e.data); + + log(data); + + switch (data.type) { + case 'value': + updateValue(data); + break; + case 'error': + showErrorMessage(data.message); + break; + } + } catch (e) { + showErrorMessage('Ошибка обработки ответа от сервера'); + console.log("Error: " + e); + } +} + +function showErrorMessage(message) { + log(message); +} + +function updateValue(data) { + +} + +$(document).ready(function () { + initWebSocket(); +});