New Blade Components: x-device-link, x-port-link, x-graph-row, x-popup (#13197)

* working popover

* popup component

* cleanup

* finalize device-link component

* attributes WIP

* working graph component

* widgets WIP

* More dynamic configs

* Graph row component

* Build CSS so we can use a dark theme

* graph row set columns

* only one popup visible at a time.

* Just set graph row width statically

* responsive WIP

* rsponsive option for graph-row "working"

* remove @deviceLink and @portLink

* fix non-responsive graph row

* update js/css

* fix style

* bad type?

* types

* types

* types #3

* remove testing code

* full rebel, no closing tags for meta and link

* match previous formatting

* fix vlans display

* restore newline

* remove silly comment

* remove unused line

* style I guess
This commit is contained in:
Tony Murray
2021-09-10 08:07:08 -05:00
committed by GitHub
parent 0a76ca444b
commit c5b63bde86
45 changed files with 4864 additions and 13417 deletions
+1 -1
View File
@@ -99,7 +99,7 @@ class Smokeping
'device' => $device,
'graph' => [
'type' => 'smokeping_' . $direction,
'device' => $this->device->device_id,
'device' => $this->device,
$remote => $device->device_id,
],
];
+2 -4
View File
@@ -139,10 +139,8 @@ class Url
}
$content = '<div class=list-large>' . addslashes(htmlentities($port->device->displayName() . ' - ' . $label)) . '</div>';
if ($port['port_descr_descr']) {
$content .= addslashes(\LibreNMS\Util\Clean::html($port['port_descr_descr'], [])) . '<br />';
} elseif ($port['ifAlias']) {
$content .= addslashes(htmlentities($port->ifAlias)) . '<br />';
if ($description = $port->getDescription()) {
$content .= addslashes(htmlentities($description)) . '<br />';
}
$content .= "<div style=\'width: 850px\'>";
@@ -203,13 +203,10 @@ class DeviceController extends TableController
*/
private function getHostname($device)
{
$hostname = Url::deviceLink($device);
if ($this->isDetailed()) {
$hostname .= '<br />' . $device->name();
}
return $hostname;
return (string) view('device.list.hostname', [
'device' => $device,
'detailed' => $this->isDetailed(),
]);
}
/**
@@ -24,7 +24,6 @@
namespace App\Http\Controllers\Widgets;
use App\Models\Alert;
use Illuminate\Http\Request;
class AlertsController extends WidgetController
+8
View File
@@ -100,6 +100,14 @@ class Port extends DeviceRelatedModel
return Rewrite::shortenIfName(Rewrite::normalizeIfName($this->ifName ?: $this->ifDescr));
}
/**
* Get the description of this port either the parsed description or ifAlias
*/
public function getDescription(): string
{
return (string) ($this->port_descr_descr ?: $this->ifAlias);
}
/**
* Check if user can access this port.
*
-8
View File
@@ -63,17 +63,9 @@ class AppServiceProvider extends ServiceProvider
return auth()->check() && auth()->user()->isAdmin();
});
Blade::directive('deviceLink', function ($arguments) {
return "<?php echo \LibreNMS\Util\Url::deviceLink($arguments); ?>";
});
Blade::directive('deviceUrl', function ($arguments) {
return "<?php echo \LibreNMS\Util\Url::deviceUrl($arguments); ?>";
});
Blade::directive('portLink', function ($arguments) {
return "<?php echo \LibreNMS\Util\Url::portLink($arguments); ?>";
});
}
private function configureMorphAliases()
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\View\Components;
use App\Facades\DeviceCache;
use App\Models\Device;
use Illuminate\View\Component;
use LibreNMS\Util\Graph;
class DeviceLink extends Component
{
/**
* @var \App\Models\Device
*/
public $device;
/**
* @var string|null
*/
public $tab;
/**
* @var string|null
*/
public $section;
/**
* @var string
*/
public $status;
/**
* Create a new component instance.
*
* @param int|\App\Models\Device $device
*/
public function __construct($device, ?string $tab = null, ?string $section = null)
{
$this->device = $device instanceof Device ? $device : DeviceCache::get($device);
$this->tab = $tab;
$this->section = $section;
$this->status = $this->status();
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
if (! $this->device->canAccess(auth()->user())) {
return view('components.device-link-no-access');
}
return view('components.device-link', [
'graphs' => Graph::getOverviewGraphsForDevice($this->device),
]);
}
public function status(): string
{
if ($this->device->disabled) {
return 'disabled';
}
return $this->device->status ? 'up' : ($this->device->ignore ? 'disabled' : 'down');
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
namespace App\View\Components;
use App\Models\Device;
use App\Models\Port;
use Illuminate\View\Component;
class Graph extends Component
{
const DEFAULT_WIDE_WIDTH = 340;
const DEFAULT_WIDE_HEIGHT = 100;
const DEFAULT_NORMAL_WIDTH = 300;
const DEFAULT_NORMAL_HEIGHT = 150;
/**
* @var array
*/
public $vars;
/**
* @var int|null
*/
public $width;
/**
* @var int|null
*/
public $height;
/**
* @var string
*/
public $type;
/**
* @var int|string|null
*/
public $from;
/**
* @var int|string|null
*/
public $to;
/**
* @var string
*/
public $legend;
/**
* @var int
*/
public $absolute_size;
/**
* Create a new component instance.
*
* @param string $type
* @param array $vars
* @param int|string $from
* @param int|string $to
* @param string $legend
* @param string $aspect
* @param int|null $width
* @param int|null $height
* @param int $absolute_size
* @param \App\Models\Device|int|null $device
* @param \App\Models\Port|int|null $port
*/
public function __construct(string $type = '', array $vars = [], $from = '-1d', $to = null, string $legend = 'no', string $aspect = 'normal', ?int $width = null, ?int $height = null, int $absolute_size = 0, $device = null, $port = null)
{
$this->type = $type;
$this->vars = $vars;
$this->from = $from;
$this->to = $to;
$this->legend = $legend;
$this->absolute_size = $absolute_size;
$this->width = $width ?: ($aspect == 'wide' ? self::DEFAULT_WIDE_WIDTH : self::DEFAULT_NORMAL_WIDTH);
$this->height = $height ?: ($aspect == 'wide' ? self::DEFAULT_WIDE_HEIGHT : self::DEFAULT_NORMAL_HEIGHT);
// handle device and port ids/models for convenience could be set in $vars
if ($device instanceof Device) {
$this->vars['device'] = $device->device_id;
} elseif (is_numeric($device)) {
$this->vars['device'] = $device;
} elseif ($port instanceof Port) {
$this->vars['id'] = $port->port_id;
} elseif (is_numeric($port)) {
$this->vars['id'] = $port;
}
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.graph', [
'link' => url('graph.php') . '?' . http_build_query($this->vars + [
'type' => $this->type,
'legend' => $this->legend,
'absolute_size' => $this->absolute_size,
'width' => $this->width,
'height' => $this->height,
'from' => $this->from,
'to' => $this->to,
]),
]);
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class GraphRow extends Component
{
/**
* @var string
*/
public $type;
/**
* @var string
*/
public $loading;
/**
* @var null
*/
public $device;
/**
* @var null
*/
public $port;
/**
* @var array|string[][]
*/
public $graphs;
/**
* @var string|null
*/
public $title;
/**
* @var float|int
*/
public $rowWidth;
/**
* @var bool
*/
public $responsive;
/**
* @var string
*/
public $aspect;
/**
* Create a new component instance.
*
* @param string $type
* @param string|null $title
* @param string $loading
* @param string $aspect
* @param int $columns
* @param array $graphs
* @param \App\Models\Device|int|null $device
* @param \App\Models\Port|int|null $port
*/
public function __construct(string $type = '', string $title = null, string $loading = 'eager', string $aspect = 'normal', int $columns = 2, array $graphs = [['from' => '-1d'], ['from' => '-7d'], ['from' => '-30d'], ['from' => '-1y']], $device = null, $port = null)
{
$this->type = $type;
$this->aspect = $aspect;
$this->loading = $loading;
$this->device = $device;
$this->port = $port;
$this->graphs = $graphs;
$this->title = $title;
$this->responsive = $columns == 'responsive';
$this->rowWidth = $this->calculateRowWidth($columns);
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.graph-row');
}
private function calculateRowWidth(int $columns): ?int
{
if ($this->responsive) {
return null;
}
$max = max(array_column($this->graphs, 'width') + [0]);
if (! $max) {
$max = $this->aspect == 'wide' ? Graph::DEFAULT_WIDE_WIDTH : Graph::DEFAULT_NORMAL_WIDTH;
}
// width * columns, unless there is less graphs than columns
return $max * min($columns, count($this->graphs));
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Popup extends Component
{
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.popup');
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace App\View\Components;
use App\Models\Port;
use Illuminate\Support\Arr;
use Illuminate\View\Component;
use LibreNMS\Util\Rewrite;
use LibreNMS\Util\Url;
class PortLink extends Component
{
/**
* @var \App\Models\Port
*/
public $port;
/**
* @var string
*/
public $link;
/**
* @var array|string|string[]
*/
public $label;
/**
* @var string
*/
public $description;
/**
* @var array|array[]
*/
public $graphs;
/**
* @var string
*/
public $status;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct(Port $port, ?array $graphs = null)
{
$this->port = $port;
$this->link = Url::portUrl($port);
$this->label = Rewrite::normalizeIfName($port->getLabel());
$this->description = $port->getDescription();
$this->status = $this->status();
$this->graphs = $graphs === null ? [
['type' => 'port_bits', 'title' => trans('Traffic'), 'vars' => [['from' => '-1d'], ['from' => '-7d'], ['from' => '-30d'], ['from' => '-1y']]],
] : Arr::wrap($graphs);
if ($this->description == $this->label) {
$this->description = '';
}
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.port-link');
}
private function status(): string
{
if ($this->port->ifAdminStatus == 'down') {
return 'disabled';
}
return $this->port->ifAdminStatus == 'up' && $this->port->ifOperStatus != 'up'
? 'down'
: 'up';
}
public function fillDefaultVars(array $vars): array
{
return array_map(function ($graph_vars) {
return array_merge([
'from' => '-1d',
'type' => 'port_bits',
'legend' => 'yes',
'text' => '',
], Arr::wrap($graph_vars));
}, $vars);
}
}
+3 -3
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
(()=>{"use strict";var e,r={},t={};function o(e){var n=t[e];if(void 0!==n)return n.exports;var l=t[e]={id:e,loaded:!1,exports:{}};return r[e].call(l.exports,l,l.exports,o),l.loaded=!0,l.exports}o.m=r,e=[],o.O=(r,t,n,l)=>{if(!t){var i=1/0;for(d=0;d<e.length;d++){for(var[t,n,l]=e[d],a=!0,u=0;u<t.length;u++)(!1&l||i>=l)&&Object.keys(o.O).every((e=>o.O[e](t[u])))?t.splice(u--,1):(a=!1,l<i&&(i=l));a&&(e.splice(d--,1),r=n())}return r}l=l||0;for(var d=e.length;d>0&&e[d-1][2]>l;d--)e[d]=e[d-1];e[d]=[t,n,l]},o.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return o.d(r,{a:r}),r},o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e={929:0,170:0};o.O.j=r=>0===e[r];var r=(r,t)=>{var n,l,[i,a,u]=t,d=0;for(n in a)o.o(a,n)&&(o.m[n]=a[n]);if(u)var f=u(o);for(r&&r(t);d<i.length;d++)l=i[d],o.o(e,l)&&e[l]&&e[l][0](),e[i[d]]=0;return o.O(f)},t=self.webpackChunk=self.webpackChunk||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,r={},t={};function o(e){var n=t[e];if(void 0!==n)return n.exports;var i=t[e]={id:e,loaded:!1,exports:{}};return r[e].call(i.exports,i,i.exports,o),i.loaded=!0,i.exports}o.m=r,e=[],o.O=(r,t,n,i)=>{if(!t){var l=1/0;for(f=0;f<e.length;f++){for(var[t,n,i]=e[f],a=!0,u=0;u<t.length;u++)(!1&i||l>=i)&&Object.keys(o.O).every((e=>o.O[e](t[u])))?t.splice(u--,1):(a=!1,i<l&&(l=i));if(a){e.splice(f--,1);var d=n();void 0!==d&&(r=d)}}return r}i=i||0;for(var f=e.length;f>0&&e[f-1][2]>i;f--)e[f]=e[f-1];e[f]=[t,n,i]},o.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return o.d(r,{a:r}),r},o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e={929:0,213:0,170:0};o.O.j=r=>0===e[r];var r=(r,t)=>{var n,i,[l,a,u]=t,d=0;if(l.some((r=>0!==e[r]))){for(n in a)o.o(a,n)&&(o.m[n]=a[n]);if(u)var f=u(o)}for(r&&r(t);d<l.length;d++)i=l[d],o.o(e,i)&&e[i]&&e[i][0](),e[l[d]]=0;return o.O(f)},t=self.webpackChunk=self.webpackChunk||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})()})();
+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
*/
/*!
* vue-i18n v8.24.4
* vue-i18n v8.25.0
* (c) 2021 kazuya kawaguchi
* Released under the MIT License.
*/
+11 -10
View File
@@ -1,14 +1,15 @@
{
"/js/app.js": "/js/app.js?id=a04efc91bb73e91248e7",
"/js/manifest.js": "/js/manifest.js?id=1514baaea419f38abb7d",
"/css/app.css": "/css/app.css?id=996b9e3da0c3ab98067e",
"/js/vendor.js": "/js/vendor.js?id=ccca9695062f1f68aaa8",
"/js/lang/de.js": "/js/lang/de.js?id=8959e785f5790d6b6836",
"/js/lang/en.js": "/js/lang/en.js?id=19867ecb0d7e260236b0",
"/js/lang/fr.js": "/js/lang/fr.js?id=707149152bde5b5e2b8d",
"/js/lang/it.js": "/js/lang/it.js?id=4e3b200da489000822dd",
"/js/app.js": "/js/app.js?id=d5975c3162d3b388db35",
"/js/manifest.js": "/js/manifest.js?id=8d61162bb0caf92f60ff",
"/css/vendor.css": "/css/vendor.css?id=8d7b2ecb46047fe813e4",
"/css/app.css": "/css/app.css?id=b21e1df1f5102369eabc",
"/js/vendor.js": "/js/vendor.js?id=294cef17a3cf43045d61",
"/js/lang/de.js": "/js/lang/de.js?id=cc551d5a4aa98a6d7453",
"/js/lang/en.js": "/js/lang/en.js?id=fea09c4ff709961f8275",
"/js/lang/fr.js": "/js/lang/fr.js?id=aeac40fc3bd66cdfa69e",
"/js/lang/it.js": "/js/lang/it.js?id=ae198ed0b94351e4d950",
"/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c",
"/js/lang/uk.js": "/js/lang/uk.js?id=c19a5dcee4724579cb41",
"/js/lang/zh-CN.js": "/js/lang/zh-CN.js?id=acab91683e556299b62b",
"/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=4d48f47a17010848f963"
"/js/lang/zh-CN.js": "/js/lang/zh-CN.js?id=427acc5a5aed577c98e4",
"/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=f648dcc6f44e78951150"
}
+4230 -13299
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -12,6 +12,7 @@
"production": "mix --production"
},
"devDependencies": {
"autoprefixer": "^10.3.3",
"axios": "^0.21.1",
"bootstrap": "^4.5.3",
"cross-env": "^5.2.1",
@@ -19,10 +20,11 @@
"laravel-mix": "^6.0.6",
"lodash": "^4.17.21",
"popper.js": "^1.16.1",
"postcss": "^8.2.10",
"postcss": "^8.3.6",
"resolve-url-loader": "^3.1.2",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"tailwindcss": "^2.2.9",
"vue": "^2.6.12",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12"
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -0,0 +1,3 @@
{{ $slot->isNotEmpty() ? $slot : $device->displayName() }}
@@ -0,0 +1,26 @@
<x-popup>
<a class="tw-font-bold @if($status=='disabled') tw-text-gray-400 visited:tw-text-gray-400 @elseif($status=='down') tw-text-red-600 visited:tw-text-red-600 @else tw-text-blue-900 visited:tw-text-blue-900 @endif" href="{{ route('device', ['device' => $device->device_id, 'tab' => $tab, 'section' => $section]) }}">
{{ $slot->isNotEmpty() ? $slot : $device->displayName() }}
</a>
<x-slot name="title">
<span class="tw-text-nowrap tw-pr-1">
<span class="tw-text-xl tw-font-bold">{{ $device->displayName() }}</span>
{{ $device->hardware }}
</span>
<span class="tw-text-nowrap tw-pl-2 tw-pr-1">
@if($device->os){{ \LibreNMS\Config::getOsSetting($device->os, 'text') }}@endif
{{ $device->version }}
</span>
<span class="tw-text-nowrap tw-pl-2">
@if($device->feature)({{ $device->features }})@endif
@if($device->location)[{{ $device->location }}]@endif
</span>
</x-slot>
<x-slot name="body">
@foreach($graphs as $graph)
@isset($graph['text'], $graph['graph'])
<x-graph-row loading="lazy" :device="$device" :type="$graph['graph']" :title="$graph['text']" :graphs="[['from' => '-1d'], ['from' => '-7d']]"></x-graph-row>
@endisset
@endforeach
</x-slot>
</x-popup>
@@ -0,0 +1,10 @@
@isset($title)
<div class="tw-border-b tw-font-semibold">
{{ $title }}
</div>
@endisset
<div class="tw-flex tw-flex-wrap" @if(! $responsive) style="width: {{ $rowWidth }}px;" @endif {{ $attributes }}>
@foreach($graphs as $graph)
<x-graph :type="$type" :loading="$loading" :aspect="$aspect" :port="$port" :device="$device" :vars="$graph" {{ $attributes->class(['xl:tw-w-1/4 lg:tw-w-1/2 sm:tw-w-full' => $responsive]) }}></x-graph>
@endforeach
</div>
@@ -0,0 +1 @@
<img width="{{ $width }}" height="{{ $height }}" src="{{ $link }}" alt="{{ $type }}" {{ $attributes->merge(['class' => 'graph-image']) }}>
@@ -0,0 +1,54 @@
<div {{ $attributes->merge(['class' => 'tw-inline-block']) }} x-data="{
popupShow: false,
showTimeout: null,
hideTimeout: null,
ignoreNextShownEvent: false,
delay: 300,
show(timeout) {
clearTimeout(this.hideTimeout);
this.showTimeout = setTimeout(() => {
this.popupShow = true;
Popper.createPopper(this.$refs.targetRef, this.$refs.popupRef, {
padding: 8
});
// close other popups, except this one
this.ignoreNextShownEvent = true;
this.$dispatch('librenms-popup-shown', this.$el);
}, timeout);
},
hide(timeout) {
if (this.ignoreNextShownEvent) {
this.ignoreNextShownEvent = false;
return;
}
clearTimeout(this.showTimeout);
this.hideTimeout = setTimeout(() => this.popupShow = false, timeout)
}
}"
x-on:click.away="hide(0)"
x-on:librenms-popup-shown.window="() => hide(0)"
>
<div class="tw-inline-block" x-ref="targetRef" x-on:mouseenter='show(100)' x-on:mouseleave="hide(delay)">
{{ $slot }}
</div>
<div x-ref="popupRef"
x-on:mouseenter="clearTimeout(hideTimeout)"
x-on:mouseleave="hide(delay)"
x-bind:class="{'tw-hidden': !popupShow, 'tw-block': popupShow}"
class="tw-hidden tw-bg-white dark:tw-bg-gray-800 dark:tw-text-white tw-border-2 tw-border-gray-200 dark:tw-border-gray-600 tw-ml-3 tw-z-50 tw-font-normal tw-leading-normal tw-text-sm tw-text-left tw-no-underline tw-rounded-lg"
style="max-width:95vw;"
>
@isset($title)
<div class="tw-opacity-90 tw-p-3 tw-mb-0 tw-border-b-2 tw-border-solid tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-t-lg">
{{ $title }}
</div>
@endisset
@isset($body)
<div class="tw-p-3">
{{ $body }}
</div>
@endisset
</div>
</div>
@@ -0,0 +1,18 @@
<x-popup>
<a class="@if($status=='disabled') tw-text-gray-400 visited:tw-text-gray-400 @elseif($status=='down') tw-text-red-600 visited:tw-text-red-600 @else tw-text-blue-900 visited:tw-text-blue-900 @endif"
href="{{ $link }}"
{{ $attributes }}>
{{ $slot->isNotEmpty() ? $slot : $label }}
</a>
<x-slot name="title">
<div class="tw-text-xl tw-font-bold">{{ $port->device->displayName() }} - {{ $label }}</div>
<div>{{ $description }}</div>
</x-slot>
<x-slot name="body">
<div>
@foreach($graphs as $graph)
<x-graph-row loading="lazy" :port="$port" :type="$graph['type'] ?? 'port_bits'" :title="$graph['title'] ?? null" :graphs="$fillDefaultVars($graph['vars'] ?? [])"></x-graph-row>
@endforeach
</div>
</x-slot>
</x-popup>
+1 -1
View File
@@ -73,7 +73,7 @@
@foreach($ungrouped_devices as $device)
<tr id="row_{{ $device->device_id }}">
<td><img alt="{{ $device->os }}" src="{{ asset($device->icon) }}" width="32px" height="32px" title="{{ $device->os }}"></td>
<td>@deviceLink($device)<br />{{ $device->sysName }}</td>
<td><x-device-link :device="$device" /><br />{{ $device->sysName }}</td>
<td>{{ $device->hardware }}</td>
<td>{{ $device->os }} {{ $device->version }} @if($device->features) ({{ $device->features }}) @endif </td>
</tr>
+1 -1
View File
@@ -7,7 +7,7 @@
@if($device->isUnderMaintenance())
<span title="@lang('Scheduled Maintenance')" class="fa fa-wrench fa-fw fa-lg"></span>
@endif
<span style="font-size: 20px;">@deviceLink($device)</span><br/>
<span style="font-size: 20px;"><x-device-link :device="$device" /></span><br/>
<a href="{{ url('/devices/location=' . urlencode($device->location)) }}">{{ $device->location }}</a>
</div>
<div class="pull-right">
@@ -0,0 +1,5 @@
<x-device-link :device="$device"></x-device-link>
@if($detailed)
<br />
{{ $device->name() }}
@endif
+7 -31
View File
@@ -20,37 +20,13 @@
<div class="tab-content">
@foreach($data['smokeping_tabs'] as $direction)
<div class="tab-pane fade in @if($loop->first) active @endif" id="{{ $direction }}">
<div class="row">
<div class="col-md-12">
<h3>Average</h3>
</div>
</div>
<div class="row">
@foreach(\LibreNMS\Util\Html::graphRow(['type' => "device_smokeping_{$direction}_all_avg", 'device' => $device->device_id]) as $graph)
<div class='col-md-3'>{!! $graph !!}</div>
@endforeach
</div>
<div class="row">
<div class="col-md-12">
<h3>Aggregate</h3>
</div>
</div>
<div class="row">
@foreach(\LibreNMS\Util\Html::graphRow(['type' => "device_smokeping_{$direction}_all", 'device' => $device->device_id, 'legend' => 'no']) as $graph)
<div class='col-md-3'>{!! $graph !!}</div>
@endforeach
</div>
<x-graph-row :type="'device_smokeping_' . $direction . '_all_avg'" title="Average" :device="$device" columns="responsive"></x-graph-row>
</div>
<div class="row"><x-graph-row :type="'device_smokeping_' . $direction . '_all'" title="Aggregate" :device="$device" columns="responsive"></x-graph-row>
@foreach($data['smokeping']->otherGraphs($direction) as $info)
<div class="row">
<div class="col-md-12">
<h3>@deviceLink($info['device'])</h3>
</div>
</div>
<div class="row">
@foreach(\LibreNMS\Util\Html::graphRow($info['graph']) as $graph)
<div class='col-md-3'>{!! $graph !!}</div>
@endforeach
</div>
<x-graph-row :type="$info['graph']" :device="$info['device']" columns="responsive">
<x-slot name="title"><x-device-link device="$info['device']" /></x-slot>
</x-graph-row>
@endforeach
</div>
@endforeach
@@ -221,7 +197,7 @@
.panel.with-nav-tabs .nav-justified {
margin-bottom: -1px;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
inset: auto!important;
}
+7 -8
View File
@@ -18,14 +18,13 @@
<td>
@foreach($vlans as $port)
@if(!$vars)
@if($port->port)
@portLink($port->port, $port->port->getShortLabel())
@if($port->untagged)
(U)@endif
@if(!$loop->last),
@endif
@endif
<span class="tw-inline-flex">
@if($port->port)
<x-port-link :port="$port->port">{{ $port->port->getShortLabel() }}</x-port-link>
@if($port->untagged)<span>&nbsp;(U)</span>@endif
@if(!$loop->last)<span>,</span>@endif
@endif
</span>
@else
<div style="display: block; padding: 2px; margin: 2px; min-width: 139px; max-width:139px; min-height:85px; max-height:85px; text-align: center; float: left; background-color: {{ \LibreNMS\Config::get('list_colour.odd_alt2') }}">
+1 -1
View File
@@ -16,7 +16,7 @@
<tr>
<td>
@if ($vm->parentDevice)
@deviceLink($vm->parentDevice)
<x-device-link :device="$vm->parentDevice" />
@else
{{ $vm->vmwVmDisplayName }}
@endif
+35 -24
View File
@@ -4,8 +4,8 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ $pagetitle }}</title>
<base href="{{ LibreNMS\Config::get('base_url') }}" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<base href="{{ LibreNMS\Config::get('base_url') }}">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@if(!LibreNMS\Config::get('favicon', false))
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('images/apple-touch-icon.png') }}">
@@ -14,7 +14,7 @@
<link rel="mask-icon" href="{{ asset('images/safari-pinned-tab.svg') }}" color="#5bbad5">
<link rel="shortcut icon" href="{{ asset('images/favicon.ico') }}">
@else
<link rel="shortcut icon" href="{{ LibreNMS\Config::get('favicon') }}" />
<link rel="shortcut icon" href="{{ LibreNMS\Config::get('favicon') }}">
@endif
<link rel="manifest" href="{{ asset('images/manifest.json') }}" crossorigin="use-credentials">
@@ -22,33 +22,36 @@
<meta name="msapplication-config" content="{{ asset('images/browserconfig.xml') }}">
<meta name="theme-color" content="#ffffff">
<link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/bootstrap-datetimepicker.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/bootstrap-switch.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/toastr.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/jquery.bootgrid.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/tagmanager.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/mktree.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/vis.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/font-awesome.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/jquery.gridster.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/leaflet.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/MarkerCluster.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/MarkerCluster.Default.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/L.Control.Locate.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/leaflet.awesome-markers.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/select2.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/select2-bootstrap.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/query-builder.default.min.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20210421" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/' . LibreNMS\Config::get('applied_site_style', 'light') . '.css?ver=632417643') }}" rel="stylesheet" type="text/css" />
<link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/bootstrap-datetimepicker.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/bootstrap-switch.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/toastr.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/jquery.bootgrid.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/tagmanager.css') }}" rel="stylesheet">
<link href="{{ asset('css/mktree.css') }}" rel="stylesheet">
<link href="{{ asset('css/vis.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/font-awesome.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/jquery.gridster.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/leaflet.css') }}" rel="stylesheet">
<link href="{{ asset('css/MarkerCluster.css') }}" rel="stylesheet">
<link href="{{ asset('css/MarkerCluster.Default.css') }}" rel="stylesheet">
<link href="{{ asset('css/L.Control.Locate.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/leaflet.awesome-markers.css') }}" rel="stylesheet">
<link href="{{ asset('css/select2.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/select2-bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/query-builder.default.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20210421" rel="stylesheet">
<link href="{{ asset('css/' . LibreNMS\Config::get('applied_site_style', 'light') . '.css?ver=632417643') }}" rel="stylesheet">
@foreach(LibreNMS\Config::get('webui.custom_css', []) as $custom_css)
<link href="{{ $custom_css }}" rel="stylesheet" type="text/css" />
<link href="{{ $custom_css }}" rel="stylesheet">
@endforeach
@yield('css')
@stack('styles')
<script src="{{ asset('js/polyfill.min.js') }}"></script>
<script src="{{ asset('js/alpine.min.js') }}" defer></script>
<script src="{{ asset('js/popper.min.js') }}"></script>
<script src="{{ asset('js/jquery.min.js?ver=05072021') }}"></script>
<script src="{{ asset('js/bootstrap.min.js?ver=05072021') }}"></script>
<script src="{{ asset('js/bootstrap-hover-dropdown.min.js?ver=05072021') }}"></script>
@@ -86,6 +89,14 @@
<script type="text/javascript" src="{{ asset('js/overlib_mini.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/toastr.min.js?ver=05072021') }}"></script>
<script type="text/javascript" src="{{ asset('js/boot.js') }}"></script>
<script>
// Apply color scheme
if ('{{ LibreNMS\Config::get('applied_site_style', 'light') }}' === 'dark' || window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('tw-dark')
} else {
document.documentElement.classList.remove('tw-dark')
}
</script>
@yield('javascript')
</head>
<body>
+1 -1
View File
@@ -13,7 +13,7 @@
@endsection
@push('styles')
<link href="{{ asset(mix('/css/app.css')) }}" rel="stylesheet">
<link href="{{ asset(mix('/css/vendor.css')) }}" rel="stylesheet">
@endpush
@section('javascript')
+1 -1
View File
@@ -16,7 +16,7 @@
@endsection
@push('styles')
<link href="{{ asset(mix('/css/app.css')) }}" rel="stylesheet">
<link href="{{ asset(mix('/css/vendor.css')) }}" rel="stylesheet">
@endpush
@push('scripts')
+1 -1
View File
@@ -159,7 +159,7 @@
<strong class="green">@lang('Global Viewing Access')</strong>
@else
@forelse($devices as $device)
@deviceLink($device) <br />
<x-device-link :device="$device" /><br />
@empty
<strong class="red">@lang('No access!')</strong>
@endforelse
+3 -3
View File
@@ -11,9 +11,9 @@
<tbody>
@foreach($ports as $port)
<tr>
<td class="text-left">@deviceLink($port->device, $port->device->shortDisplayName())</td>
<td class="text-left">@portLink($port, $port->getShortLabel())</td>
<td class="text-left">@portLink($port, \LibreNMS\Util\Url::portErrorsThumbnail($port), 'port_errors')</td>
<td class="text-left"><x-device-link :device="$port->device">{{$port->device->shortDisplayName() }}</x-device-link></td>
<td class="text-left"><x-port-link :port="$port">{{ $port->getShortLabel() }}</x-port-link></td>
<td class="text-left"><x-port-link :port="$port"><x-graph :port="$port" type="port_bits" width="150" height="21"></x-graph></x-port-link></td>
</tr>
@endforeach
</tbody>
@@ -11,9 +11,9 @@
<tbody>
@foreach($ports as $port)
<tr>
<td class="text-left">@deviceLink($port->device, $port->device->shortDisplayName())</td>
<td class="text-left">@portLink($port, $port->getShortLabel())</td>
<td class="text-left">@portLink($port, \LibreNMS\Util\Url::portThumbnail($port))</td>
<td class="text-left"><x-device-link :device="$port->device">{{$port->device->shortDisplayName() }}</x-device-link></td>
<td class="text-left"><x-port-link :port="$port">{{ $port->getShortLabel() }}</x-port-link></td>
<td class="text-left"><x-port-link :port="$port"><x-graph :port="$port" type="port_bits" width="150" height="21"></x-graph></x-port-link></td>
</tr>
@endforeach
</tbody>
+23
View File
@@ -0,0 +1,23 @@
module.exports = {
prefix: 'tw-',
purge: [
'./storage/framework/views/*.php',
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
],
darkMode: 'class',
theme: {
extend: {},
screens: {
'sm': '576px',
'md': '768px',
'lg': '992px',
'xl': '1200px',
}
},
variants: {
extend: {},
},
plugins: [],
}
+4 -1
View File
@@ -14,6 +14,9 @@ const mix = require('laravel-mix');
mix.setPublicPath('html/')
.js('resources/js/app.js', 'js')
.vue()
.sass('resources/sass/app.scss', 'css')
.sass('resources/sass/app.scss', 'css/vendor.css')
.postCss('resources/css/app.css', 'css', [
require('tailwindcss'),
])
.extract()
.version('html/js/lang/*.js');