Some work

This commit is contained in:
Alex Solomaha
2016-09-02 21:13:00 +03:00
parent 8aff3ed686
commit 7ebad90e63
22 changed files with 346 additions and 69 deletions

View File

@@ -16,6 +16,7 @@ class WSClientAsset extends AssetBundle
'js/wsclient.js',
];
public $depends = [
'app\assets\AppAsset',
'yii\web\JqueryAsset',
];
}

View File

@@ -0,0 +1,29 @@
<?php
namespace app\components;
use app\models\User;
use Yii;
class WebSocketAuth
{
/**
* @return null|string
*/
public static function getAuthKey()
{
$user = User::findOne(Yii::$app->user->id);
if (!$user) {
return null;
}
$auth_key = $user->auth_key;
// Regenerate auth key
$user->generateAuthKey();
$user->save();
return $auth_key;
}
}

View File

@@ -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');

View File

@@ -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');
}

View File

@@ -0,0 +1,25 @@
<?php
use yii\db\Migration;
/**
* Handles adding type to table `board`.
*/
class m160901_181951_add_type_column_to_board_table extends Migration
{
/**
* @inheritdoc
*/
public function safeUp()
{
$this->addColumn('board', 'type', $this->smallInteger()->defaultValue(10)->after('id'));
}
/**
* @inheritdoc
*/
public function safeDown()
{
$this->dropColumn('board', 'type');
}
}

View File

@@ -0,0 +1,25 @@
<?php
use yii\db\Migration;
/**
* Handles adding secret to table `board`.
*/
class m160902_174332_add_secret_column_to_board_table extends Migration
{
/**
* @inheritdoc
*/
public function safeUp()
{
$this->addColumn('board', 'secret', $this->string()->after('name'));
}
/**
* @inheritdoc
*/
public function safeDown()
{
$this->dropColumn('board', 'secret');
}
}

View File

@@ -0,0 +1,25 @@
<?php
use yii\db\Migration;
/**
* Handles adding pin to table `item`.
*/
class m160902_180312_add_pin_column_to_item_table extends Migration
{
/**
* @inheritdoc
*/
public function safeUp()
{
$this->addColumn('item', 'pin', $this->integer()->after('board_id'));
}
/**
* @inheritdoc
*/
public function safeDown()
{
$this->dropColumn('item', 'pin');
}
}

View File

@@ -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];
}
}

View File

@@ -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;

View File

@@ -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']],

View File

@@ -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));
}

View File

@@ -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;
}

View File

@@ -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;
<?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'type')->dropDownList(Board::getTypesArray()) ?>
<?= $form->field($model, 'secret')->textInput() ?>
<?= $form->field($model, 'baseUrl')->textInput(['maxlength' => true]) ?>
<div class="form-group">

View File

@@ -28,6 +28,8 @@ $this->params['breadcrumbs'][] = $this->title;
'attributes' => [
'id',
'name',
'typeLabel',
'secret',
'baseUrl:ntext',
],
]) ?>

View File

@@ -29,6 +29,8 @@ use yii\widgets\ActiveForm;
ArrayHelper::map(Board::find()->all(), 'id', 'name')
) ?>
<?= $form->field($model, 'pin')->input('number') ?>
<?= $form->field($model, 'type')->dropDownList(Item::getTypesArray()) ?>
<?= $form->field($model, 'update_interval')->textInput() ?>

View File

@@ -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;
?>
<div class="item-create">

View File

@@ -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;
?>
<div class="item-index">

View File

@@ -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',

View File

@@ -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']],

View File

@@ -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);
?>
<div class="control-panel">

0
web/css/wsclient.css Normal file
View File

45
web/js/wsclient.js Normal file
View File

@@ -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();
});