Issues (213)

src/grid/ServerGridView.php (4 issues)

1
<?php
2
/**
3
 * Server module for HiPanel
4
 *
5
 * @link      https://github.com/hiqdev/hipanel-module-server
6
 * @package   hipanel-module-server
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2015-2019, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hipanel\modules\server\grid;
12
13
use hipanel\base\Model;
14
use hipanel\grid\BoxedGridView;
15
use hipanel\grid\MainColumn;
16
use hipanel\grid\RefColumn;
17
use hipanel\grid\XEditableColumn;
18
use hipanel\helpers\StringHelper;
19
use hipanel\helpers\Url;
20
use hipanel\modules\finance\models\Sale;
21
use hipanel\modules\hosting\controllers\AccountController;
22
use hipanel\modules\hosting\controllers\IpController;
23
use hipanel\modules\server\menus\ServerActionsMenu;
24
use hipanel\modules\server\models\Consumption;
25
use hipanel\modules\server\models\HardwareSale;
26
use hipanel\modules\server\models\Server;
27
use hipanel\modules\server\widgets\DiscountFormatter;
28
use hipanel\modules\server\widgets\Expires;
29
use hipanel\modules\server\widgets\OSFormatter;
30
use hipanel\modules\server\widgets\ResourceConsumptionTable;
31
use hipanel\modules\server\widgets\State;
32
use hipanel\widgets\ArraySpoiler;
33
use hipanel\widgets\gridLegend\ColorizeGrid;
34
use hipanel\widgets\gridLegend\GridLegend;
35
use hipanel\widgets\Label;
36
use hiqdev\yii2\menus\grid\MenuColumn;
37
use Tuck\Sort\Sort;
38
use Yii;
39
use yii\data\ArrayDataProvider;
40
use yii\helpers\ArrayHelper;
41
use yii\helpers\Html;
42
43
class ServerGridView extends BoxedGridView
44
{
45
    use ColorizeGrid;
46
47
    public $controllerUrl = '@server';
48
49
    /**
50
     * @var array
51
     */
52
    public $osImages;
53
54
    public function init()
55
    {
56
        parent::init();
57
58
        $this->view->registerCss('
59
        .tariff-chain {
60
            list-style: none;
61
            background-color: transparent;
62
        }
63
        .tariff-chain > li {
64
            display: inline-block;
65
        }
66
        .tariff-chain > li + li:before {
67
            font: normal normal normal 14px/1 FontAwesome;
68
            content: "\f178\00a0";
69
            padding: 0 5px;
70
            color: #ccc;
71
        }
72
        .inactiveLink {
73
           pointer-events: none;
74
           cursor: default;
75
        }
76
        ');
77
    }
78
79
    protected function formatTariff($model)
80
    {
81
        $user = Yii::$app->user;
82
        $models = [];
83
        $html = '';
84
        if ($user->can('sale.read') && isset($model->sales)) {
85
            foreach ($model->sales as $sale) {
86
                $models[] = $sale;
87
            }
88
        } elseif ($user->can('plan.read')) {
89
            if (!empty($model->parent_tariff)) {
90
                $title = $model->parent_tariff;
91
            } else {
92
                $title = $model->tariff;
93
            }
94
95
            $models[] = new Sale(['tariff' => $title, 'tariff_id' => $model->tariff_id]);
96
        } else {
97
            $models[] = new Sale([
98
                'tariff' => $model->tariff,
99
                'tariff_id' => $model->tariff_id,
100
            ]);
101
        }
102
103
        foreach ($models as $model) {
104
            if ($model->tariff) {
105
                $tariff = $model->tariff_id ? Html::a($model->tariff, [
106
                    '@plan/view',
107
                    'id' => $model->tariff_id,
108
                ]) : $model->tariff;
109
                $client = $model->seller ? '(' . Html::a($model->seller, [
110
                    '@client/view', 'id' => $model->seller_id,
111
                ]) . ')' : '';
112
113
                $html .= Html::tag('li', $tariff . '&nbsp;' . $client);
114
            }
115
        }
116
117
        return Html::tag('ul', $html, [
118
            'class' => 'tariff-chain ' . ($user->can('support') ?: 'inactiveLink'),
119
            'style' => 'margin: 0; padding: 0;',
120
        ]);
121
    }
122
123
    public function columns()
124
    {
125
        $canAdmin = Yii::$app->user->can('admin');
126
        $canSupport = Yii::$app->user->can('support');
127
128
        return array_merge(parent::columns(), [
129
            'server' => [
130
                'class' => MainColumn::class,
131
                'attribute' => 'name',
132
                'filterAttribute' => 'name_like',
133
                'note' => Yii::$app->user->can('server.set-label') ? 'label' : 'note',
134
                'noteOptions' => [
135
                    'url' => Yii::$app->user->can('server.set-label') ? Url::to('set-label') : (Yii::$app->user->can('server.set-note') ? Url::to('set-note') : ''),
136
                ],
137
                'badges' => function ($model) use ($canSupport) {
138
                    $badges = '';
139
                    if ($canSupport) {
140
                        if ($model->wizzarded) {
141
                            $badges .= Label::widget(['label' => 'W', 'tag' => 'sup', 'color' => 'success']);
142
                        }
143
                    }
144
                    $badges .= Label::widget(['label' => Yii::t('hipanel:server', $model->type_label), 'tag' => 'sup', 'color' => 'info']);
145
146
                    return $badges;
147
                },
148
            ],
149
            'dc' => [
150
                'attribute' => 'dc',
151
                'filter' => false,
152
            ],
153
            'state' => [
154
                'class' => RefColumn::class,
155
                'filterOptions' => ['class' => 'narrow-filter'],
156
                'i18nDictionary' => 'hipanel:server',
157
                'format' => 'raw',
158
                'gtype' => 'state,device',
159
                'visible' => $canSupport,
160
                'value' => function ($model) {
161
                    $html = State::widget(compact('model'));
162
                    if ($model->status_time) {
163
                        $html .= ' ' . Html::tag('nobr', Yii::t('hipanel:server', 'since {date}', ['date' => Yii::$app->formatter->asDate($model->status_time)]));
164
                    }
165
166
                    return $html;
167
                },
168
            ],
169
            'panel' => [
170
                'attribute' => 'panel',
171
                'format' => 'html',
172
                'contentOptions' => ['class' => 'text-uppercase'],
173
                'value' => function ($model) use ($canSupport) {
174
                    $value = $model->getPanel() ? Yii::t('hipanel:server:panel', $model->getPanel()) : Yii::t('hipanel:server:panel', 'No control panel');
175
                    if ($canSupport) {
176
                        $value .= $model->wizzarded ? Label::widget([
177
                            'label' => 'W', 'tag' => 'sup', 'color' => 'success',
178
                        ]) : '';
179
                    }
180
181
                    return $value;
182
                },
183
            ],
184
            'os' => [
185
                'attribute' => 'os',
186
                'format' => 'raw',
187
                'value' => function ($model) {
188
                    return OSFormatter::widget([
189
                        'osimages' => $this->osImages,
190
                        'imageName' => $model->osimage,
191
                    ]);
192
                },
193
            ],
194
            'os_and_panel' => [
195
                'attribute' => 'os',
196
                'format' => 'raw',
197
                'value' => function ($model) {
198
                    $html = OSFormatter::widget([
199
                        'osimages' => $this->osImages,
200
                        'imageName' => $model->osimage,
201
                    ]);
202
                    $html .= ' ' . ($model->panel ?: '');
203
204
                    return $html;
205
                },
206
            ],
207
            'discount' => [
208
                'attribute' => 'discount',
209
                'label' => Yii::t('hipanel:server', 'Discount'),
210
                'format' => 'raw',
211
                'headerOptions' => ['style' => 'width: 1em'],
212
                'value' => function ($model) {
213
                    return DiscountFormatter::widget([
214
                        'current' => $model->discounts['fee']['current'],
215
                        'next' => $model->discounts['fee']['next'],
216
                    ]);
217
                },
218
            ],
219
            'expires' => [
220
                'filter' => false,
221
                'format' => 'raw',
222
                'headerOptions' => ['style' => 'width: 1em'],
223
                'visible' => $canSupport,
224
                'value' => function ($model) {
225
                    return Expires::widget(compact('model'));
226
                },
227
            ],
228
            'tariff' => [
229
                'format' => 'raw',
230
                'filterAttribute' => 'tariff_like',
231
                'value' => function ($model) {
232
                    return $this->formatTariff($model);
233
                },
234
            ],
235
            'tariff_and_discount' => [
236
                'attribute' => 'tariff',
237
                'filterAttribute' => 'tariff_like',
238
                'format' => 'raw',
239
                'value' => function ($model) {
240
                    return $this->formatTariff($model) . ' ' . DiscountFormatter::widget([
241
                            'current' => $model->discounts['fee']['current'],
242
                            'next' => $model->discounts['fee']['next'],
243
                        ]);
244
                },
245
            ],
246
            'ip' => [
247
                'filter' => false,
248
            ],
249
            'mac' => [
250
                'filter' => false,
251
            ],
252
            'ips' => [
253
                'format' => 'raw',
254
                'attribute' => 'ips',
255
                'filter' => false,
256
                'contentOptions' => [
257
                    'class' => 'text-center',
258
                    'style' => 'width:1%; white-space:nowrap;',
259
                ],
260
                'value' => function ($model) {
261
                    return ArraySpoiler::widget([
262
                        'data' => ArrayHelper::getColumn($model->ips, 'ip'),
263
                        'delimiter' => '<br />',
264
                        'visibleCount' => 1,
265
                        'formatter' => function ($ip, $idx) use ($model) {
266
                            if ($idx === 0) {
267
                                return Html::a($ip, IpController::getSearchUrl(['server_in' => $model->name]), [
268
                                    'class' => 'text-bold',
269
                                    'target' => '_blank',
270
                                ]);
271
                            }
272
273
                            return $ip;
274
                        },
275
                        'button' => [
276
                            'label' => Yii::t('hipanel:server', '') . ' +' . (count($model->ips) - 1),
277
                            'tag' => 'button',
278
                            'type' => 'button',
279
                            'class' => 'btn btn-xs btn-flat',
280
                            'style' => 'font-size: 10px',
281
                            'popoverOptions' => [
282
                                'html' => true,
283
                                'placement' => 'bottom',
284
                                'title' => Yii::t('hipanel:server', 'IPs'),
285
                                'template' => '
286
                                    <div class="popover" role="tooltip">
287
                                        <div class="arrow"></div>
288
                                        <h3 class="popover-title"></h3>
289
                                        <div class="popover-content" style="min-width: 15rem; height: 15rem; overflow-x: scroll;"></div>
290
                                    </div>
291
                                ',
292
                            ],
293
                        ],
294
                    ]);
295
                },
296
            ],
297
            'sale_time' => [
298
                'attribute' => 'sale_time',
299
                'format' => 'datetime',
300
            ],
301
            'note' => [
302
                'class' => XEditableColumn::class,
303
                'pluginOptions' => [
304
                    'url' => Url::to('set-note'),
305
                ],
306
                'widgetOptions' => [
307
                    'linkOptions' => [
308
                        'data-type' => 'textarea',
309
                    ],
310
                ],
311
                'visible' => Yii::$app->user->can('server.set-note'),
312
            ],
313
            'label' => [
314
                'class' => XEditableColumn::class,
315
                'pluginOptions' => [
316
                    'url' => Url::to('set-label'),
317
                ],
318
                'widgetOptions' => [
319
                    'linkOptions' => [
320
                        'data-type' => 'textarea',
321
                    ],
322
                ],
323
                'visible' => Yii::$app->user->can('server.set-label'),
324
            ],
325
            'type' => [
326
                'format' => 'html',
327
                'filter' => false,
328
                'value' => function ($model) {
329
                    return Html::tag('span', $model->type_label, ['class' => 'label label-default']);
330
                },
331
            ],
332
            'detailed_type' => [
333
                'label' => Yii::t('hipanel', 'Type'),
334
                'format' => 'html',
335
                'filter' => false,
336
                'value' => function ($model) {
337
                    return Html::tag('span', $model->type_label, ['class' => 'label label-default']);
338
                },
339
                'contentOptions' => function ($model) {
340
                    return GridLegend::create($this->findOrFailGridLegend($model))->gridColumnOptions('actions');
341
                },
342
            ],
343
            'rack' => [
344
                'class' => BindingColumn::class,
345
            ],
346
            'net' => [
347
                'class' => BindingColumn::class,
348
            ],
349
            'kvm' => [
350
                'class' => BindingColumn::class,
351
            ],
352
            'pdu' => [
353
                'class' => BindingColumn::class,
354
            ],
355
            'ipmi' => [
356
                'class' => BindingColumn::class,
357
            ],
358
            'location' => [
359
                'class' => BindingColumn::class,
360
            ],
361
            'nums' => [
362
                'label' => '',
363
                'format' => 'raw',
364
                'value' => function ($model) {
365
                    $ips_num = $model->ips_num;
366
                    $ips = $ips_num ? Html::a("$ips_num ips", IpController::getSearchUrl(['server' => $model->name])) : 'no ips';
367
                    $act_acs_num = $model->acs_num - $model->del_acs_num;
368
                    $del_acs_num = $model->del_acs_num;
369
                    $acs_num = $act_acs_num . ($del_acs_num ? "+$del_acs_num" : '');
370
                    $acs = $acs_num ? Html::a("$acs_num acc", AccountController::getSearchUrl(['server' => $model->name])) : 'no acc';
371
372
                    return Html::tag('nobr', $ips) . ' ' . Html::tag('nobr', $acs);
373
                },
374
            ],
375
            'monthly_fee' => [
376
                'label' => Yii::t('hipanel:finance', 'Monthly fee'),
377
                'format' => 'html',
378
                'filter' => false,
379
                'value' => function ($model) {
380
                    return $this->getMonthlyFee($model);
381
                },
382
                'visible' => Yii::$app->user->can('consumption.read'),
383
            ],
384
            'traffic' => [
385
                'label' => Yii::t('hipanel:server', 'Traffic'),
386
                'format' => 'html',
387
                'filter' => false,
388
                'value' => function ($model) {
389
                    return isset($model->consumptions['overuse,server_traf_max']) ? $this->getFormattedConsumptionFor($model->consumptions['overuse,server_traf_max']) : null;
390
                },
391
                'visible' => Yii::$app->user->can('consumption.read'),
392
            ],
393
            'additional_services' => [
394
                'label' => Yii::t('hipanel:server', 'Additional services'),
395
                'format' => 'raw',
396
                'filter' => false,
397
                'contentOptions' => ['class' => 'no-padding'],
398
                'value' => function ($model) {
399
                    return $this->getAdditionalServices($model);
400
                },
401
                'visible' => Yii::$app->user->can('consumption.read'),
402
            ],
403
            'type_of_sale' => [
404
                'label' => Yii::t('hipanel:server', 'Type of sale'),
405
                'format' => 'raw',
406
                'filter' => false,
407
                'value' => function (Server $model) {
408
                    return $this->getTypeOfSale($model);
409
                },
410
                'visible' => Yii::$app->user->can('consumption.read'),
411
            ],
412
            'actions' => [
413
                'class' => MenuColumn::class,
414
                'menuClass' => ServerActionsMenu::class,
415
                'contentOptions' => [
416
                    'class' => 'text-center',
417
                    'style' => 'width:1%; white-space:nowrap;',
418
                ],
419
            ],
420
            'hwsummary' => [
421
                'filterAttribute' => 'hwsummary_like',
422
                'label' => Yii::t('hipanel:server', 'Hardware Summary'),
423
            ],
424
            'hwcomment' => [
425
                'filter' => false,
426
                'label' => Yii::t('hipanel:server', 'Hardware Comment'),
427
            ],
428
        ]);
429
    }
430
431
    private function getFormattedConsumptionFor(Consumption $consumption): string
432
    {
433
        $result = '';
434
        $widget = Yii::createObject(['class' => ResourceConsumptionTable::class, 'model' => $consumption]);
435
436
        if ($limit = $widget->getFormatted($consumption, $consumption->limit)) {
0 ignored issues
show
Bug Best Practice introduced by
The property limit does not exist on hipanel\modules\server\models\Consumption. Since you implemented __get, consider adding a @property annotation.
Loading history...
437
            $result .= sprintf('%s: %s<br />',
438
                Html::tag('b', Yii::t('hipanel:server', 'included')),
439
                $limit
440
            );
441
        }
442
        if ($price = $consumption->getFormattedPrice()) {
443
            $result .= sprintf('%s: %s',
444
                Html::tag('b', Yii::t('hipanel:server', 'price')),
445
                $price
446
            );
447
        }
448
449
        return $result;
450
    }
451
452
    private function getMonthlyFee($model): string
453
    {
454
        $unionConsumption = new Consumption();
455
        $prices = [];
456
        if ($model->consumptions) {
457
            array_walk($model->consumptions, function (Consumption $consumption) use (&$prices) {
458
                if ($consumption->type && $consumption->hasFormattedAttributes() && StringHelper::startsWith($consumption->type, 'monthly,')) {
459
                    if ($consumption->price) {
0 ignored issues
show
Bug Best Practice introduced by
The property price does not exist on hipanel\modules\server\models\Consumption. Since you implemented __get, consider adding a @property annotation.
Loading history...
460
                        $consumption->setAttribute('prices', [$consumption->currency => $consumption->price]);
0 ignored issues
show
Bug Best Practice introduced by
The property currency does not exist on hipanel\modules\server\models\Consumption. Since you implemented __get, consider adding a @property annotation.
Loading history...
461
                    }
462
                    foreach ($consumption->prices as $currency => $price) {
0 ignored issues
show
Bug Best Practice introduced by
The property prices does not exist on hipanel\modules\server\models\Consumption. Since you implemented __get, consider adding a @property annotation.
Loading history...
463
                        $prices[$currency] += $price;
464
                    }
465
                }
466
            });
467
        }
468
        $unionConsumption->setAttribute('prices', $prices);
469
470
        return $unionConsumption->getFormattedPrice();
471
    }
472
473
    private function getAdditionalServices($model): string
474
    {
475
        $additional = new class() extends Model {
476
            /**
477
             * @var string
478
             */
479
            public $typeLabel;
480
481
            /**
482
             * @var string
483
             */
484
            public $value;
485
        };
486
        $models = [];
487
        foreach (['overuse,support_time', 'overuse,backup_du', 'monthly,win_license'] as $type) {
488
            if (isset($model->consumptions[$type]) && $model->consumptions[$type]->hasFormattedAttributes()) {
489
                $consumption = $model->consumptions[$type];
490
                $models[] = new $additional([
491
                    'typeLabel' => Yii::t('hipanel.server.consumption.type', $consumption->typeLabel),
492
                    'value' => $this->getFormattedConsumptionFor($consumption),
493
                ]);
494
            }
495
        }
496
497
        return \yii\grid\GridView::widget([
498
            'layout' => '{items}',
499
            'showOnEmpty' => false,
500
            'emptyText' => '',
501
            'tableOptions' => ['class' => 'table table-striped table-condensed'],
502
            'headerRowOptions' => [
503
                'style' => 'display: none;',
504
            ],
505
            'dataProvider' => new ArrayDataProvider(['allModels' => $models, 'pagination' => false]),
506
            'columns' => [
507
                [
508
                    'attribute' => 'typeLabel',
509
                ],
510
                [
511
                    'attribute' => 'value',
512
                    'format' => 'html',
513
                ],
514
            ],
515
        ]);
516
    }
517
518
    private function getTypeOfSale(Server $model): string
519
    {
520
        $html = '';
521
        $badgeColors = [
522
            'leasing' => 'bg-orange',
523
            'rent' => 'bg-purple',
524
            'sold' => 'bg-olive',
525
        ];
526
527
        if (empty($model->hardwareSales)) {
528
            return $html;
529
        }
530
531
        $salesByType = [
532
            HardwareSale::USAGE_TYPE_LEASING => [],
533
            HardwareSale::USAGE_TYPE_COLO => [],
534
            HardwareSale::USAGE_TYPE_RENT => [],
535
        ];
536
        foreach ($model->hardwareSales as $sale) {
537
            $salesByType[$sale->usage_type][] = $sale;
538
        }
539
540
        foreach ($salesByType as $usageType => $sales) {
541
            $html .= ArraySpoiler::widget([
542
                'data' => Sort::by($sales, function (HardwareSale $sale) {
543
                    $order = ['CHASSIS', 'MOTHERBOARD', 'CPU', 'RAM', 'HDD', 'SSD'];
544
                    $type = substr($sale->part, 0, strpos($sale->part, ':'));
545
                    $key = array_search($type, $order, true);
546
                    if ($key !== false) {
547
                        return $key;
548
                    }
549
550
                    return INF;
551
                }),
552
                'delimiter' => '<br/>',
553
                'visibleCount' => 0,
554
                'button' => [
555
                    'label' => (function () use ($usageType, $sales) {
556
                        if ($usageType === HardwareSale::USAGE_TYPE_LEASING) {
557
                            /** @var \DateTime $maxLeasingDate */
558
                            $maxLeasingDate = array_reduce($sales, function (\DateTime $max, HardwareSale $item) {
559
                                $date = $item->saleTime();
560
561
                                return $date > $max ? $date : $max;
562
                            }, new \DateTime());
563
564
                            return Yii::t('hipanel:server', $usageType) . ' ' . \count($sales)
565
                                . '<br />' . $maxLeasingDate->format('d.m.Y');
566
                        }
567
568
                        return Yii::t('hipanel:server', $usageType) . ' ' . \count($sales);
569
                    })(),
570
                    'tag' => 'button',
571
                    'type' => 'button',
572
                    'class' => "btn btn-xs {$badgeColors[$usageType]}",
573
                    'popoverOptions' => [
574
                        'html' => true,
575
                        'placement' => 'bottom',
576
                        'title' => Yii::t('hipanel:server', 'Parts') . ' '
577
                            . Html::a(
578
                                Yii::t('hipanel:server', 'To new tab {icon}', ['icon' => '<i class="fa fa-external-link"></i>']),
579
                                Url::toSearch('part', ['dst_name_in' => $model->name]),
580
                                ['class' => 'pull-right', 'target' => '_blank']
581
                            ),
582
                        'template' => '
583
                            <div class="popover" role="tooltip">
584
                                <div class="arrow"></div>
585
                                <h3 class="popover-title"></h3>
586
                                <div class="popover-content" style="height: 25rem; overflow-x: scroll;"></div>
587
                            </div>
588
                        ',
589
                    ],
590
                ],
591
                'formatter' => function (HardwareSale $item) {
592
                    $additionalInfo = null;
593
                    $title = $item->part;
594
                    if (isset($item->serialno)) {
595
                        $title .= ': ' . $item->serialno;
596
                    }
597
598
                    if (isset($item->leasing_till)) {
599
                        $additionalInfo = Yii::t('hipanel:server', '{since} &mdash; {till}', [
600
                            'since' => Yii::$app->formatter->asDate($item->leasing_since, 'short'),
601
                            'till' => Yii::$app->formatter->asDate($item->leasing_till, 'short'),
602
                        ]);
603
                    }
604
605
                    if (isset($item->sale_time)) {
606
                        $additionalInfo = Yii::t('hipanel:server', 'since {date}', [
607
                            'date' => Yii::$app->formatter->asDate($item->sale_time, 'short'),
608
                        ]);
609
                    }
610
611
                    return Html::a(
612
                        $title,
613
                        ['@part/view', 'id' => $item['part_id']],
614
                        ['class' => 'text-nowrap', 'target' => '_blank']
615
                    ) . ($additionalInfo ? " ($additionalInfo)" : '');
616
                },
617
            ]);
618
        }
619
620
        return $html;
621
    }
622
}
623