diff --git a/src/controllers/HubController.php b/src/controllers/HubController.php index dbfcac08..75cb607a 100644 --- a/src/controllers/HubController.php +++ b/src/controllers/HubController.php @@ -21,11 +21,13 @@ use hipanel\filters\EasyAccessControl; use hipanel\helpers\ArrayHelper; use hipanel\models\Ref; +use hipanel\modules\server\forms\AssignSwitchesForm; use hipanel\modules\server\forms\HubSellForm; use hipanel\modules\server\models\HardwareSettings; use hiqdev\hiart\Collection; use Yii; use yii\base\Event; +use yii\web\NotFoundHttpException; class HubController extends CrudController { @@ -143,6 +145,41 @@ public function actions() } }, ], + 'assign-switches' => [ + 'class' => SmartUpdateAction::class, + 'success' => Yii::t('hipanel:server:hub', 'Switches have been edited'), + 'view' => 'assign-switches', + 'on beforeFetch' => function (Event $event) { + /** @var \hipanel\actions\SearchAction $action */ + $action = $event->sender; + $dataProvider = $action->getDataProvider(); + $dataProvider->query->withBindings()->select(['*']); + }, + 'collection' => [ + 'class' => Collection::class, + 'model' => new AssignSwitchesForm(), + 'scenario' => 'default', + ], + 'data' => function (Action $action, array $data) { + $result = []; + foreach ($data['models'] as $model) { + $result['models'][] = AssignSwitchesForm::fromOriginalModel($model); + } + if (!$result['models']) { + throw new NotFoundHttpException('There are no entries available for the selected operation.'); + } + $result['model'] = reset($result['models']); + + return $result; + }, + ], + 'validate-switches-form' => [ + 'class' => ValidateFormAction::class, + 'collection' => [ + 'class' => Collection::class, + 'model' => new AssignSwitchesForm(), + ], + ], 'validate-sell-form' => [ 'class' => ValidateFormAction::class, 'collection' => [ diff --git a/src/controllers/ServerController.php b/src/controllers/ServerController.php index b71e2d2c..4971461f 100644 --- a/src/controllers/ServerController.php +++ b/src/controllers/ServerController.php @@ -202,7 +202,7 @@ public function actions() ], 'assign-hubs' => [ 'class' => SmartUpdateAction::class, - 'success' => Yii::t('hipanel:server', 'Hubs were assigned'), + 'success' => Yii::t('hipanel:server', 'Hubs have been assigned'), 'view' => 'assignHubs', 'on beforeFetch' => function (Event $event) { /** @var \hipanel\actions\SearchAction $action */ @@ -218,7 +218,7 @@ public function actions() 'data' => function (Action $action, array $data) { $result = []; foreach ($data['models'] as $model) { - $result['models'][] = AssignHubsForm::fromServer($model); + $result['models'][] = AssignHubsForm::fromOriginalModel($model); } if (!$result['models']) { throw new NotFoundHttpException('There are no entries available for the selected operation. The type of selected records may not be suitable for the selected operation.'); diff --git a/src/forms/AssignHubsForm.php b/src/forms/AssignHubsForm.php index 6fdecf4e..8aaf38d3 100644 --- a/src/forms/AssignHubsForm.php +++ b/src/forms/AssignHubsForm.php @@ -2,7 +2,7 @@ namespace hipanel\modules\server\forms; -use hipanel\modules\server\models\Binding; +use hipanel\base\ModelTrait; use hipanel\modules\server\models\Server; use Yii; @@ -11,35 +11,19 @@ */ class AssignHubsForm extends Server { - use \hipanel\base\ModelTrait; + use ModelTrait; /** - * @inheritdoc + * @var array */ - public static function tableName() - { - return 'server'; - } + public $switchVariants = ['net', 'kvm', 'pdu', 'rack', 'pdu2', 'nic2', 'ipmi']; /** - * Create AttachHubsForm model from Server model - * - * @param Server $server - * @return AssignHubsForm + * @inheritdoc */ - public static function fromServer(Server $server): AssignHubsForm + public static function tableName() { - $attributes = array_merge($server->getAttributes(), []); - $model = new self(['scenario' => 'default']); - foreach ($server->bindings as $binding) { - if ($model->hasAttribute($binding->typeWithNo . '_id')) { - $attributes[$binding->typeWithNo . '_id'] = $binding->switch_id; - $attributes[$binding->typeWithNo . '_port'] = $binding->port; - } - } - $model->setAttributes($attributes); - - return $model; + return 'server'; } /** @@ -47,11 +31,7 @@ public static function fromServer(Server $server): AssignHubsForm */ public function rules() { - return array_merge(parent::rules(), [ - [['id'], 'required'], - [['net_id', 'kvm_id', 'pdu_id', 'rack_id', 'pdu2_id', 'nic2_id', 'ipmi_id'], 'integer'], - [['net_port', 'kvm_port', 'pdu_port', 'rack_port', 'pdu2_port', 'nic2_port', 'ipmi_port'], 'string'], - ], $this->generateUniqueValidators()); + return array_merge(parent::rules(), $this->defaultSwitchRules(), $this->generateUniqueValidators()); } public function attributeLabels() @@ -61,54 +41,5 @@ public function attributeLabels() 'nic2' => Yii::t('hipanel:server', 'Switch 2'), ]); } - - /** - * For compatibility with [[hiqdev\hiart\Collection]] - * - * @param $defaultScenario - * @param array $data - * @param array $options - * @return mixed - */ - public function batchQuery($defaultScenario, $data = [], array $options = []) - { - $map = [ - 'update' => 'assign-hubs', - ]; - $scenario = isset($map[$defaultScenario]) ? $map[$defaultScenario] : $defaultScenario; - - return (new Server)->batchQuery($scenario, $data, $options); - } - - private function generateUniqueValidators(): array - { - $rules = []; - - foreach (['net', 'kmv', 'pdu', 'rack', 'pdu2', 'nic2', 'ipmi'] as $variant) { - $rules[] = [ - [$variant . '_port'], - function ($attribute, $params, $validator) use ($variant) { - if ($this->{$attribute} && $this->{$variant . '_id'}) { - $query = Binding::find(); - $query->andWhere(['port' => $this->{$attribute}]); - $query->andWhere(['switch_id' => $this->{$variant . '_id'}]); - $query->andWhere(['ne', 'base_device_id', $this->id]); - /** @var Binding[] $bindings */ - $bindings = $query->all(); - if (!empty($bindings)) { - $binding = reset($bindings); - $this->addError($attribute, Yii::t('hipanel:server', '{switch}::{port} already taken by {device}', [ - 'switch' => $binding->switch_name, - 'port' => $binding->port, - 'device' => $binding->device_name, - ])); - } - } - }, - ]; - } - - return $rules; - } } diff --git a/src/forms/AssignSwitchesForm.php b/src/forms/AssignSwitchesForm.php new file mode 100644 index 00000000..ff4b020d --- /dev/null +++ b/src/forms/AssignSwitchesForm.php @@ -0,0 +1,30 @@ +defaultSwitchRules(), $this->generateUniqueValidators()); + } +} diff --git a/src/menus/HubDetailMenu.php b/src/menus/HubDetailMenu.php index 8371b515..fdb5ae9a 100644 --- a/src/menus/HubDetailMenu.php +++ b/src/menus/HubDetailMenu.php @@ -10,6 +10,8 @@ namespace hipanel\modules\server\menus; +use Yii; + class HubDetailMenu extends \hipanel\menus\AbstractDetailMenu { public $model; @@ -17,7 +19,16 @@ class HubDetailMenu extends \hipanel\menus\AbstractDetailMenu public function items() { $actions = HubActionsMenu::create(['model' => $this->model])->items(); - $items = array_merge($actions, []); + $items = array_merge($actions, [ + 'assign-switches' => [ + 'label' => Yii::t('hipanel:server', 'Switches'), + 'icon' => 'fa-plug', + 'url' => ['@hub/assign-switches', 'id' => $this->model->id], + 'linkOptions' => [ + 'data-pjax' => 0, + ], + ], + ]); unset($items['view']); return $items; diff --git a/src/menus/ServerActionsMenu.php b/src/menus/ServerActionsMenu.php index efa5e4ed..a435c5eb 100644 --- a/src/menus/ServerActionsMenu.php +++ b/src/menus/ServerActionsMenu.php @@ -52,7 +52,7 @@ public function items(): array ], 'assign-hubs' => [ 'label' => Yii::t('hipanel:server', 'Assign hubs'), - 'icon' => 'fa-exchange', + 'icon' => 'fa-plug', 'url' => ['@server/assign-hubs', 'id' => $this->model->id], 'visible' => Yii::$app->user->can('server.update'), 'linkOptions' => [ diff --git a/src/messages/ru/hipanel:server.php b/src/messages/ru/hipanel:server.php index 66f21725..597296b3 100644 --- a/src/messages/ru/hipanel:server.php +++ b/src/messages/ru/hipanel:server.php @@ -308,4 +308,5 @@ 'Mail settings have been changed successfully' => 'Настройки почты были успешно изменены', 'Number of mailboxes' => 'Количество почтовых ящиков', 'View parts' => 'Смотреть детали', + 'Hubs have been assigned' => 'Свитчи назначены', ]; diff --git a/src/models/AssignSwitchInterface.php b/src/models/AssignSwitchInterface.php new file mode 100644 index 00000000..ed2fbf87 --- /dev/null +++ b/src/models/AssignSwitchInterface.php @@ -0,0 +1,8 @@ +hasOne(HardwareSettings::class, ['id' => 'id']); } + + /** + * {@inheritdoc} + * @return HubQuery + */ + public static function find($options = []) + { + return new HubQuery(get_called_class(), [ + 'options' => $options, + ]); + } } diff --git a/src/models/Server.php b/src/models/Server.php index 0ce97a64..64cc50cb 100644 --- a/src/models/Server.php +++ b/src/models/Server.php @@ -10,11 +10,14 @@ namespace hipanel\modules\server\models; +use hipanel\base\Model; +use hipanel\base\ModelTrait; use hipanel\models\Ref; use hipanel\modules\finance\models\Sale; use hipanel\modules\hosting\models\Ip; use hipanel\modules\server\helpers\ServerHelper; use hipanel\modules\server\models\query\ServerQuery; +use hipanel\modules\server\models\traits\AssignSwitchTrait; use hipanel\validators\EidValidator; use hipanel\validators\RefValidator; use Yii; @@ -28,9 +31,9 @@ * * @property-read HardwareSale[] $hardwareSales */ -class Server extends \hipanel\base\Model +class Server extends Model implements AssignSwitchInterface { - use \hipanel\base\ModelTrait; + use ModelTrait, AssignSwitchTrait; const STATE_OK = 'ok'; const STATE_DISABLED = 'disabled'; diff --git a/src/models/query/HubQuery.php b/src/models/query/HubQuery.php new file mode 100644 index 00000000..ead95443 --- /dev/null +++ b/src/models/query/HubQuery.php @@ -0,0 +1,27 @@ +user->can('hub.read')) { + $this->joinWith('bindings'); + $this->andWhere(['with_bindings' => true]); + } + + return $this; + } +} diff --git a/src/models/traits/AssignSwitchTrait.php b/src/models/traits/AssignSwitchTrait.php new file mode 100644 index 00000000..6c752844 --- /dev/null +++ b/src/models/traits/AssignSwitchTrait.php @@ -0,0 +1,115 @@ +getAttributes(), []); + $model = new static(['scenario' => 'default']); + foreach ($originalModel->bindings as $binding) { + $attribute = $binding->typeWithNo . '_id'; + if ($model->hasAttribute($attribute)) { + $attributes[$binding->typeWithNo . '_id'] = $binding->switch_id; + $attributes[$binding->typeWithNo . '_port'] = $binding->port; + } + } + $model->setAttributes($attributes); + + return $model; + } + + public function defaultSwitchRules(): array + { + $variantIds = []; + $variantPorts = []; + foreach ($this->switchVariants as $variant) { + $variantIds[] = $variant . '_id'; + $variantPorts[] = $variant . '_port'; + } + return [ + [['id'], 'required'], + [$variantIds, 'integer'], + [$variantPorts, 'string'], + ]; + } + + /** + * For compatibility with [[hiqdev\hiart\Collection]] + * + * @param $defaultScenario + * @param array $data + * @param array $options + * + * @return mixed + */ + public function batchQuery($defaultScenario, $data = [], array $options = []) + { + $map = [ + 'update' => 'assign-hubs', + ]; + $scenario = isset($map[$defaultScenario]) ? $map[$defaultScenario] : $defaultScenario; + + return parent::batchQuery($scenario, $data, $options); + } + + /** + * Added to model's rules list of switch pairs + * + * @return array + * @throws InvalidConfigException + */ + protected function generateUniqueValidators(): array + { + if (empty($this->switchVariants)) { + throw new InvalidConfigException('Please specify `switchVariants` array to use AssignSwitchTrait::generateUniqueValidators()'); + } + $rules = []; + + foreach ($this->switchVariants as $variant) { + $rules[] = [ + [$variant . '_port'], + function ($attribute, $params, $validator) use ($variant) { + if ($this->{$attribute} && $this->{$variant . '_id'}) { + $query = Binding::find(); + $query->andWhere(['port' => $this->{$attribute}]); + $query->andWhere(['switch_id' => $this->{$variant . '_id'}]); + $query->andWhere(['ne', 'base_device_id', $this->id]); + /** @var Binding[] $bindings */ + $bindings = $query->all(); + if (!empty($bindings)) { + $binding = reset($bindings); + $this->addError($attribute, Yii::t('hipanel:server', '{switch}::{port} already taken by {device}', [ + 'switch' => $binding->switch_name, + 'port' => $binding->port, + 'device' => $binding->device_name, + ])); + } + } + }, + ]; + } + + return $rules; + } +} diff --git a/src/views/hub/assign-switches.php b/src/views/hub/assign-switches.php new file mode 100644 index 00000000..d83de9f0 --- /dev/null +++ b/src/views/hub/assign-switches.php @@ -0,0 +1,28 @@ +title = Yii::t('hipanel:server:hub', 'Edit switches'); +$this->params['breadcrumbs'][] = ['label' => Yii::t('hipanel:server', 'Switches'), 'url' => ['index']]; +if (count($models) === 1) { + $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; +} +$this->params['breadcrumbs'][] = $this->title; + +?> + + 'assign-switches-form', + 'enableClientValidation' => true, + 'validateOnBlur' => true, + 'enableAjaxValidation' => true, + 'validationUrl' => Url::toRoute(['validate-switches-form', 'scenario' => 'default']), +]) ?> + + $models, + 'switchVariants' => ['net', 'kvm', 'pdu', 'rack', 'console'], + 'form' => $form, +]) ?> diff --git a/src/views/hub/index.php b/src/views/hub/index.php index dc04bea2..07e5a18c 100644 --- a/src/views/hub/index.php +++ b/src/views/hub/index.php @@ -69,6 +69,7 @@ user->can('hub.update')) : ?> renderBulkButton('update', '  ' . Yii::t('hipanel', 'Update'))?> + renderBulkButton('assign-switches', '  ' . Yii::t('hipanel:server:hub', 'Switches')) ?> endContent('bulk-actions') ?> diff --git a/src/views/server/assignHubs.php b/src/views/server/assignHubs.php index 1b8f3478..59813459 100644 --- a/src/views/server/assignHubs.php +++ b/src/views/server/assignHubs.php @@ -1,6 +1,7 @@ params['breadcrumbs'][] = ['label' => reset($models)->name, 'url' => ['view', 'id' => reset($models)->id]]; } $this->params['breadcrumbs'][] = $this->title; -$variantMap = [ - 'pdu2' => 'pdu', - 'ipmi' => 'net', - 'nic2' => 'net', -]; ?> Url::toRoute(['validate-assign-hubs-form', 'scenario' => 'default']), ]) ?> - 'dynamicform_wrapper', // required: only alphanumeric characters plus "_" [A-Za-z0-9_] - 'widgetBody' => '.container-items', // required: css class selector - 'widgetItem' => '.item', // required: css class - 'limit' => 99, // the maximum times, an element can be cloned (default 999) - 'min' => 1, // 0 or 1 (default 1) - 'insertButton' => '.add-item', // css class - 'deleteButton' => '.remove-item', // css class - 'model' => reset($models), - 'formId' => 'assign-hubs-form', - 'formFields' => [ - 'id', - 'rack_id', - 'rack_port', - 'net_id', - 'net_port', - 'pdu_id', - 'pdu_port', - 'ipmi_id', - 'ipmi_port', - 'kvm_id', - 'kvm_port', - 'nic2_id', - 'nic2_port', - 'pdu2_port', - 'pdu2_port', - ], + $models, + 'switchVariants' => ['rack', 'net', 'pdu', 'ipmi', 'kvm', 'nic2', 'pdu2'], + 'form' => $form, ]) ?> -
- $model) : ?> -
- -
-
-

name ?>

-
-
-
- - -
- - - - - - - - - - - - -
getAttributeLabel($variant)) ?>
- field($model, "[$i]{$variant}_id")->widget(HubCombo::class, [ - 'name' => $variant, - 'hubType' => $variantMap[$variant] ?? $variant, - ])->label(false) ?> - - field($model, "[$i]{$variant}_port")->label(false) ?> -
-
- -
- -
-
-
-
- -
- -
-
- 'btn btn-success']) ?> -   - 'btn btn-default', 'onclick' => 'history.go(-1)']) ?> -
-
- - diff --git a/src/views/server/index.php b/src/views/server/index.php index 9ea45b03..e1865216 100644 --- a/src/views/server/index.php +++ b/src/views/server/index.php @@ -101,7 +101,7 @@ 'options' => ['class' => 'pull-right'], 'items' => array_filter([ [ - 'label' => ' ' . Yii::t('hipanel:server', 'Assign hubs'), + 'label' => ' ' . Yii::t('hipanel:server', 'Assign hubs'), 'url' => '#', 'linkOptions' => ['data-action' => 'assign-hubs'], 'visible' => Yii::$app->user->can('server.update'), diff --git a/src/widgets/AssignSwitchesPage.php b/src/widgets/AssignSwitchesPage.php new file mode 100644 index 00000000..d39d8d76 --- /dev/null +++ b/src/widgets/AssignSwitchesPage.php @@ -0,0 +1,50 @@ + 'pdu', + 'ipmi' => 'net', + 'nic2' => 'net', + ]; + + public function run() + { + return $this->render('AssignSwitchesPage', [ + 'switchVariants' => $this->switchVariants, + 'form' => $this->form, + 'models' => $this->models, + ]); + } + + public function getFormFields(): array + { + $fields = []; + foreach ($this->switchVariants as $name) { + $fields[] = $name . '_id'; + $fields[] = $name . '_port'; + } + + return array_merge(['id'], $fields); + } +} diff --git a/src/widgets/views/AssignSwitchesPage.php b/src/widgets/views/AssignSwitchesPage.php new file mode 100644 index 00000000..c2760a6b --- /dev/null +++ b/src/widgets/views/AssignSwitchesPage.php @@ -0,0 +1,74 @@ + + + 'dynamicform_wrapper', // required: only alphanumeric characters plus "_" [A-Za-z0-9_] + 'widgetBody' => '.container-items', // required: css class selector + 'widgetItem' => '.item', // required: css class + 'limit' => 99, // the maximum times, an element can be cloned (default 999) + 'min' => 1, // 0 or 1 (default 1) + 'insertButton' => '.add-item', // css class + 'deleteButton' => '.remove-item', // css class + 'model' => reset($models), + 'formId' => Inflector::camel2id(reset($models)->formName()) . '-form', + 'formFields' => $this->context->getFormFields(), +]) ?> +
+ $model) : ?> +
+ +
+
+

name ?>

+
+
+
+ + +
+ + + + + + + + + + + + +
getAttributeLabel($variant)) ?>
+ field($model, "[$i]{$variant}_id")->widget(HubCombo::class, [ + 'name' => $variant, + 'hubType' => $this->context->variantMap[$variant] ?? $variant, + ])->label(false) ?> + + field($model, "[$i]{$variant}_port")->label(false) ?> +
+
+ +
+ +
+
+
+
+ +
+ +
+
+ 'btn btn-success']) ?> +   + 'btn btn-default', 'onclick' => 'history.go(-1)']) ?> +
+
+ \ No newline at end of file