mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Don't check file permissions on every request, handle failures (#9264)
* Don't check file permissions on every request, handle failures Improve error page visually * only print minimal mkdir * invert file_exists check, whoops * docblock * revert accidental changes * rename variable * Change database errors to use the new layout * Add support url to the default layout * Replaced \n for && in fix for user perms * fix web output
This commit is contained in:
@@ -41,9 +41,10 @@ class DatabaseConnectException extends \Exception
|
||||
'message' => 'Error connecting to database: ' . $this->getMessage(),
|
||||
]);
|
||||
} else {
|
||||
$message = "<h2 style='color:darkred'>Error connecting to database.</h2>";
|
||||
$message .= $this->getMessage();
|
||||
return response($message);
|
||||
return response()->view('errors.generic', [
|
||||
'title' => 'Error connecting to database.',
|
||||
'content' => $this->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -132,6 +132,13 @@ class ValidationResult
|
||||
return $this->fix;
|
||||
}
|
||||
|
||||
/**
|
||||
* The commands (generally) to fix the issue.
|
||||
* If there are multiple, use an array.
|
||||
*
|
||||
* @param string|array $fix
|
||||
* @return ValidationResult $this
|
||||
*/
|
||||
public function setFix($fix)
|
||||
{
|
||||
$this->fix = $fix;
|
||||
@@ -146,7 +153,10 @@ class ValidationResult
|
||||
c_echo(str_pad('[' . $this->getStatusText($this->status) . ']', 12) . $this->message . PHP_EOL);
|
||||
|
||||
if (isset($this->fix)) {
|
||||
c_echo("\t[%BFIX%n] %B$this->fix%n\n");
|
||||
c_echo("\t[%BFIX%n]: \n");
|
||||
foreach ((array)$this->fix as $fix) {
|
||||
c_echo("\t%B$fix%n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->list)) {
|
||||
|
@@ -67,9 +67,11 @@ class User extends BaseValidation
|
||||
$rrd_dir = Config::get('rrd_dir', "$dir/rrd");
|
||||
|
||||
// generic fix
|
||||
$fix = "sudo chown -R $lnms_username:$lnms_groupname $dir\n" .
|
||||
"sudo setfacl -d -m g::rwx $rrd_dir $log_dir $dir/bootstrap/cache/ $dir/storage/\n" .
|
||||
"sudo chmod -R ug=rwX $rrd_dir $log_dir $dir/bootstrap/cache/ $dir/storage/\n";
|
||||
$fix = [
|
||||
"sudo chown -R $lnms_username:$lnms_groupname $dir",
|
||||
"sudo setfacl -d -m g::rwx $rrd_dir $log_dir $dir/bootstrap/cache/ $dir/storage/",
|
||||
"sudo chmod -R ug=rwX $rrd_dir $log_dir $dir/bootstrap/cache/ $dir/storage/",
|
||||
];
|
||||
|
||||
$find_result = rtrim(`find $dir \! -user $lnms_username -o \! -group $lnms_groupname 2> /dev/null`);
|
||||
if (!empty($find_result)) {
|
||||
|
189
app/Checks.php
189
app/Checks.php
@@ -30,8 +30,8 @@ use App\Models\Notification;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Dotenv\Dotenv;
|
||||
use LibreNMS\Config;
|
||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||
use Toastr;
|
||||
|
||||
class Checks
|
||||
@@ -39,97 +39,13 @@ class Checks
|
||||
public static function preBoot()
|
||||
{
|
||||
// check php extensions
|
||||
$missing = self::missingPhpExtensions();
|
||||
|
||||
if (!empty($missing)) {
|
||||
if ($missing = self::missingPhpExtensions()) {
|
||||
self::printMessage(
|
||||
"Missing PHP extensions. Please install and enable them on your LibreNMS server.",
|
||||
$missing,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// check file/folder permissions
|
||||
$check_folders = [
|
||||
self::basePath('bootstrap/cache'),
|
||||
self::basePath('storage'),
|
||||
self::basePath('storage/framework/sessions'),
|
||||
self::basePath('storage/framework/views'),
|
||||
self::basePath('storage/framework/cache'),
|
||||
self::basePath('logs'),
|
||||
];
|
||||
|
||||
$check_files = [
|
||||
self::basePath('logs/librenms.log'), // This file is important because Laravel needs to be able to write to it
|
||||
];
|
||||
|
||||
// check that each is writable
|
||||
$check_folders = array_filter($check_folders, function ($path) {
|
||||
return !is_writable($path);
|
||||
});
|
||||
|
||||
$check_files = array_filter($check_files, function ($path) {
|
||||
return file_exists($path) xor is_writable($path);
|
||||
});
|
||||
|
||||
if (!empty($check_folders) || !empty($check_files)) {
|
||||
// only operate on parent directories, not files
|
||||
$check = array_unique(array_merge($check_folders, array_map('dirname', $check_files)));
|
||||
|
||||
// load .env, it isn't loaded
|
||||
$dotenv = new Dotenv(__DIR__ . '/../');
|
||||
$dotenv->load();
|
||||
|
||||
$user = env('LIBRENMS_USER', 'librenms');
|
||||
$group = env('LIBRENMS_GROUP', $user);
|
||||
|
||||
// build chown message
|
||||
$dirs = implode(' ', $check);
|
||||
$chown_commands = [
|
||||
"chown -R $user:$group $dirs",
|
||||
"setfacl -R -m g::rwx $dirs",
|
||||
"setfacl -d -m g::rwx $dirs",
|
||||
];
|
||||
|
||||
$current_groups = explode(' ', trim(exec('groups')));
|
||||
if (!in_array($group, $current_groups)) {
|
||||
$current_user = trim(exec('whoami'));
|
||||
$chown_commands[] = "usermod -a -G $group $current_user";
|
||||
}
|
||||
|
||||
|
||||
//check for missing directories
|
||||
$missing = array_filter($check, function ($file) {
|
||||
return !file_exists($file);
|
||||
});
|
||||
|
||||
if (!empty($missing)) {
|
||||
array_unshift($chown_commands, 'mkdir -p ' . implode(' ', $missing));
|
||||
}
|
||||
|
||||
$short_dirs = implode(', ', array_map(function ($dir) {
|
||||
return str_replace(self::basePath(), '', $dir);
|
||||
}, $check));
|
||||
|
||||
self::printMessage(
|
||||
"Error: $short_dirs not writable! Run these commands as root on your LibreNMS server to fix:",
|
||||
$chown_commands
|
||||
);
|
||||
|
||||
// build SELinux output
|
||||
$selinux_commands = [];
|
||||
foreach ($check as $dir) {
|
||||
$selinux_commands[] = "semanage fcontext -a -t httpd_sys_content_t '$dir(/.*)?'";
|
||||
$selinux_commands[] = "semanage fcontext -a -t httpd_sys_rw_content_t '$dir(/.*)?'";
|
||||
$selinux_commands[] = "restorecon -RFvv $dir";
|
||||
}
|
||||
|
||||
self::printMessage(
|
||||
"If using SELinux you may also need:",
|
||||
$selinux_commands,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,12 +123,6 @@ class Checks
|
||||
}
|
||||
}
|
||||
|
||||
private static function basePath($path = '')
|
||||
{
|
||||
$base_dir = realpath(__DIR__ . '/..');
|
||||
return "$base_dir/$path";
|
||||
}
|
||||
|
||||
private static function missingPhpExtensions()
|
||||
{
|
||||
// allow mysqli, but prefer mysqlnd
|
||||
@@ -226,4 +136,99 @@ class Checks
|
||||
return !extension_loaded($module);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check exception for errors related to not being able to write to the filesystem
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @return bool|SymfonyResponse
|
||||
*/
|
||||
public static function filePermissionsException($e)
|
||||
{
|
||||
if ($e instanceof \ErrorException) {
|
||||
// cannot write to storage directory
|
||||
if (starts_with($e->getMessage(), 'file_put_contents(') && str_contains($e->getMessage(), '/storage/')) {
|
||||
return self::filePermissionsResponse();
|
||||
}
|
||||
}
|
||||
|
||||
if ($e instanceof \Exception) {
|
||||
// cannot write to bootstrap directory
|
||||
if ($e->getMessage() == 'The bootstrap/cache directory must be present and writable.') {
|
||||
return self::filePermissionsResponse();
|
||||
}
|
||||
}
|
||||
|
||||
if ($e instanceof \UnexpectedValueException) {
|
||||
// monolog cannot init log file
|
||||
if (str_contains($e->getFile(), 'Monolog/Handler/StreamHandler.php')) {
|
||||
return self::filePermissionsResponse();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a semi generic list of commands for the user to run to fix file permissions
|
||||
* and render it to a nice html response
|
||||
*
|
||||
* @return SymfonyResponse
|
||||
*/
|
||||
private static function filePermissionsResponse()
|
||||
{
|
||||
$user = config('librenms.user');
|
||||
$group = config('librenms.group');
|
||||
$install_dir = base_path();
|
||||
$commands = [];
|
||||
$dirs = [
|
||||
base_path('bootstrap/cache'),
|
||||
base_path('storage'),
|
||||
Config::get('log_dir', base_path('logs')),
|
||||
Config::get('rrd_dir', base_path('rrd')),
|
||||
];
|
||||
|
||||
// check if folders are missing
|
||||
$mkdirs = [
|
||||
base_path('bootstrap/cache'),
|
||||
base_path('storage/framework/sessions'),
|
||||
base_path('storage/framework/views'),
|
||||
base_path('storage/framework/cache'),
|
||||
Config::get('log_dir', base_path('logs')),
|
||||
Config::get('rrd_dir', base_path('rrd')),
|
||||
];
|
||||
|
||||
$mk_dirs = array_filter($mkdirs, function ($file) {
|
||||
return !file_exists($file);
|
||||
});
|
||||
|
||||
if (!empty($mk_dirs)) {
|
||||
$commands[] = 'sudo mkdir -p ' . implode(' ', $mk_dirs);
|
||||
}
|
||||
|
||||
// always print chwon/setfacl/chmod commands
|
||||
$commands[] = "sudo chown -R $user:$group $install_dir";
|
||||
$commands[] = 'sudo setfacl -d -m g::rwx ' . implode(' ', $dirs);
|
||||
$commands[] = 'sudo chmod -R ug=rwX ' . implode(' ', $dirs);
|
||||
|
||||
// check if webserver is in the librenms group
|
||||
$current_groups = explode(' ', trim(exec('groups')));
|
||||
if (!in_array($group, $current_groups)) {
|
||||
$current_user = trim(exec('whoami'));
|
||||
$commands[] = "usermod -a -G $group $current_user";
|
||||
}
|
||||
|
||||
// selinux:
|
||||
$commands[] = '<h4>If using SELinux you may also need:</h4>';
|
||||
foreach ($dirs as $dir) {
|
||||
$commands[] = "semanage fcontext -a -t httpd_sys_rw_content_t '$dir(/.*)?'";
|
||||
}
|
||||
$commands[] = "restorecon -RFv $install_dir";
|
||||
|
||||
// use pre-compiled template because we probably can't compile it.
|
||||
$template = file_get_contents(base_path('resources/views/errors/static/file_permissions.html'));
|
||||
$content = str_replace('!!!!CONTENT!!!!', '<p>' . implode('</p><p>', $commands) . '</p>', $template);
|
||||
|
||||
return SymfonyResponse::create($content);
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Checks;
|
||||
use Exception;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\QueryException;
|
||||
@@ -36,19 +37,14 @@ class Handler extends ExceptionHandler
|
||||
|
||||
protected function convertExceptionToResponse(Exception $e)
|
||||
{
|
||||
if ($e instanceof QueryException) {
|
||||
// connect exception, convert to our standard connection exception
|
||||
if (config('app.debug')) {
|
||||
// get message form PDO exception, it doesn't contain the query
|
||||
$message = $e->getMessage();
|
||||
} else {
|
||||
$message = $e->getPrevious()->getMessage();
|
||||
}
|
||||
// handle database exceptions
|
||||
if ($db_response = $this->dbExceptionToResponse($e)) {
|
||||
return $db_response;
|
||||
}
|
||||
|
||||
if (in_array($e->getCode(), [1044, 1045, 2002])) {
|
||||
throw new DatabaseConnectException($message, $e->getCode(), $e);
|
||||
}
|
||||
return response('Unhandled MySQL Error [' . $e->getCode() . "] $message");
|
||||
// check for exceptions relating to not being able to write to the filesystem
|
||||
if ($fs_response = Checks::filePermissionsException($e)) {
|
||||
return $fs_response;
|
||||
}
|
||||
|
||||
// show helpful response if debugging, otherwise print generic error so we don't leak information
|
||||
@@ -74,4 +70,28 @@ class Handler extends ExceptionHandler
|
||||
|
||||
return redirect()->guest(route('login'));
|
||||
}
|
||||
|
||||
protected function dbExceptionToResponse(Exception $e)
|
||||
{
|
||||
if ($e instanceof QueryException) {
|
||||
// connect exception, convert to our standard connection exception
|
||||
if (config('app.debug')) {
|
||||
// get message form PDO exception, it doesn't contain the query
|
||||
$message = $e->getMessage();
|
||||
} else {
|
||||
$message = $e->getPrevious()->getMessage();
|
||||
}
|
||||
|
||||
if (in_array($e->getCode(), [1044, 1045, 2002])) {
|
||||
// this Exception has it's own render function
|
||||
throw new DatabaseConnectException($message, $e->getCode(), $e);
|
||||
}
|
||||
return response()->view('errors.generic', [
|
||||
'title' => 'Unhandled MySQL Error [' . $e->getCode() . ']',
|
||||
'content' => $message
|
||||
]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -88,7 +88,11 @@ foreach ($validator->getAllResults() as $group => $results) {
|
||||
if ($result->hasFix() || $result->hasList()) {
|
||||
echo '<div class="panel-body">';
|
||||
if ($result->hasFix()) {
|
||||
echo 'Fix: <code>' . linkify($result->getFix()) . '</code>';
|
||||
echo 'Fix: <code>';
|
||||
foreach ((array)$result->getFix() as $fix) {
|
||||
echo '<br />' . linkify($fix) . PHP_EOL;
|
||||
}
|
||||
echo '</code>';
|
||||
if ($result->hasList()) {
|
||||
echo '<br /><br />';
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('javascript')
|
||||
<script src="{{ asset('js/jquery.qrcode.min.js') }}"></script>
|
||||
<script src="js/jquery.qrcode.min.js"></script>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
16
resources/views/errors/file_permissions.blade.php
Normal file
16
resources/views/errors/file_permissions.blade.php
Normal file
@@ -0,0 +1,16 @@
|
||||
@extends('layouts.error')
|
||||
|
||||
@section('title')
|
||||
@lang('Whoops, the web server could not write required files to the filesystem.')
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<h3>@lang('Running the following commands will fix the issue most of the time:')</h3>
|
||||
|
||||
@foreach($commands as $command)
|
||||
<p>{{ $command }}</p>
|
||||
@endforeach
|
||||
<hr class="separator"/>
|
||||
|
||||
<p>@lang("If that doesn't fix the issue. You can find how to get help at") <a href="https://docs.librenms.org/Support">https://docs.librenms.org/Support</a>.</p>
|
||||
@endsection
|
File diff suppressed because one or more lines are too long
78
resources/views/errors/static/file_permissions.html
Normal file
78
resources/views/errors/static/file_permissions.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<style> body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; }
|
||||
|
||||
a { cursor: pointer; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
|
||||
|
||||
code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
|
||||
|
||||
table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
|
||||
table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
|
||||
table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; }
|
||||
table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }
|
||||
|
||||
.hidden-xs-down { display: none; }
|
||||
.block { display: block; }
|
||||
.break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }
|
||||
.text-muted { color: #999; }
|
||||
|
||||
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
|
||||
.container::after { content: ""; display: table; clear: both; }
|
||||
|
||||
.exception-summary { background: #863836; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; }
|
||||
|
||||
.exception-message-wrapper { display: flex; align-items: center; min-height: 70px; }
|
||||
.exception-message { flex-grow: 1; padding: 30px 0; }
|
||||
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
|
||||
.exception-message.long { font-size: 18px; }
|
||||
.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
|
||||
.exception-message a:hover { border-bottom-color: #ffffff; }
|
||||
|
||||
.exception-illustration { flex-basis: 64px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
|
||||
|
||||
.trace + .trace { margin-top: 30px; }
|
||||
.trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
|
||||
|
||||
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
|
||||
|
||||
.trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; }
|
||||
.trace-class { color: #B0413E; }
|
||||
.trace-type { padding: 0 2px; }
|
||||
.trace-method { color: #B0413E; font-weight: bold; }
|
||||
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
|
||||
|
||||
hr.separator { border: 0; margin: 1.8em 0; height: 1px; background: #333 linear-gradient(to right, #ccc, #333, #ccc); }
|
||||
|
||||
@media (min-width: 575px) {
|
||||
.hidden-xs-down { display: initial; }
|
||||
}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="exception-summary">
|
||||
<div class="container">
|
||||
<div class="exception-message-wrapper">
|
||||
<h1 class="break-long-words exception-message">Whoops, the web server could not write required files to the filesystem.</h1>
|
||||
<div class="exception-illustration hidden-xs-down">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M35.758 22.094l6.148 6.148-2.42 2.459-6.147-6.148zm11.245-11.226l6.149 6.148-2.44 2.46-6.148-6.148zm-.396 22.094l6.188 6.148-2.44 2.46-6.346-6.149zm11.245-11.225L64 27.885l-2.42 2.459-6.148-6.148zm-24.335-4.562l6.188-6.148 2.44 2.44-6.149 6.148zm10.868 10.868l6.149-6.148 2.459 2.42-6.148 6.148zm10.849 10.869l6.148-6.188 2.46 2.44-6.347 6.147zM22.272 28.46l6.148-6.148 2.46 2.42-6.149 6.147zm10.849 10.848l6.187-6.148 2.42 2.42-6.148 6.148zm10.868 10.869l6.148-6.188 2.46 2.44-6.189 6.346z" fill="#fff"></path><path d="M13.288 44.544l6.188 6.148-2.42 2.46-6.188-6.188zM24.533 33.32l6.148 6.148-2.42 2.44-6.148-6.149zm-.337 22.093l6.148 6.149L27.904 64l-6.148-6.148zM35.4 44.187l6.148 6.148-2.42 2.44-6.147-6.148zM2.44 33.676l6.149 6.148-2.44 2.42L0 36.095zM13.685 22.45l6.148 6.148-2.44 2.44-6.148-6.149zM24.89 11.225l6.148 6.148-2.38 2.46-6.148-6.148zM36.095 0l6.188 6.148-2.44 2.44-6.346-6.149zM22.67 6.346L28.817.198l2.44 2.42-6.149 6.148zM11.424 17.592l6.148-6.149 2.459 2.44-6.148 6.148zM.198 28.837l6.148-6.148 2.44 2.38-6.148 6.187zm10.829 10.828l6.148-6.148 2.46 2.44-6.149 6.148zm10.868 10.869l6.148-6.149 2.44 2.42-6.148 6.148zm10.829 10.868l6.188-6.148 2.44 2.38-6.149 6.188z" fill="#db202e"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h3>Running the following commands will fix the issue most of the time:</h3>
|
||||
|
||||
!!!!CONTENT!!!!
|
||||
|
||||
<hr class="separator"/>
|
||||
|
||||
<p>If that doesn't fix the issue. You can find how to get help at <a href="https://docs.librenms.org/Support">https://docs.librenms.org/Support</a>.</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
75
resources/views/layouts/error.blade.php
Normal file
75
resources/views/layouts/error.blade.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<style> body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; }
|
||||
|
||||
a { cursor: pointer; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
|
||||
|
||||
code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
|
||||
|
||||
table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
|
||||
table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
|
||||
table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; }
|
||||
table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }
|
||||
|
||||
.hidden-xs-down { display: none; }
|
||||
.block { display: block; }
|
||||
.break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }
|
||||
.text-muted { color: #999; }
|
||||
|
||||
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
|
||||
.container::after { content: ""; display: table; clear: both; }
|
||||
|
||||
.exception-summary { background: #863836; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; }
|
||||
|
||||
.exception-message-wrapper { display: flex; align-items: center; min-height: 70px; }
|
||||
.exception-message { flex-grow: 1; padding: 30px 0; }
|
||||
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
|
||||
.exception-message.long { font-size: 18px; }
|
||||
.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
|
||||
.exception-message a:hover { border-bottom-color: #ffffff; }
|
||||
|
||||
.exception-illustration { flex-basis: 64px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
|
||||
|
||||
.trace + .trace { margin-top: 30px; }
|
||||
.trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
|
||||
|
||||
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
|
||||
|
||||
.trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; }
|
||||
.trace-class { color: #B0413E; }
|
||||
.trace-type { padding: 0 2px; }
|
||||
.trace-method { color: #B0413E; font-weight: bold; }
|
||||
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
|
||||
|
||||
hr.separator { border: 0; margin: 1.8em 0; height: 1px; background: #333 linear-gradient(to right, #ccc, #333, #ccc); }
|
||||
|
||||
@media (min-width: 575px) {
|
||||
.hidden-xs-down { display: initial; }
|
||||
}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="exception-summary">
|
||||
<div class="container">
|
||||
<div class="exception-message-wrapper">
|
||||
<h1 class="break-long-words exception-message">@yield('title')</h1>
|
||||
<div class="exception-illustration hidden-xs-down">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M35.758 22.094l6.148 6.148-2.42 2.459-6.147-6.148zm11.245-11.226l6.149 6.148-2.44 2.46-6.148-6.148zm-.396 22.094l6.188 6.148-2.44 2.46-6.346-6.149zm11.245-11.225L64 27.885l-2.42 2.459-6.148-6.148zm-24.335-4.562l6.188-6.148 2.44 2.44-6.149 6.148zm10.868 10.868l6.149-6.148 2.459 2.42-6.148 6.148zm10.849 10.869l6.148-6.188 2.46 2.44-6.347 6.147zM22.272 28.46l6.148-6.148 2.46 2.42-6.149 6.147zm10.849 10.848l6.187-6.148 2.42 2.42-6.148 6.148zm10.868 10.869l6.148-6.188 2.46 2.44-6.189 6.346z" fill="#fff"></path><path d="M13.288 44.544l6.188 6.148-2.42 2.46-6.188-6.188zM24.533 33.32l6.148 6.148-2.42 2.44-6.148-6.149zm-.337 22.093l6.148 6.149L27.904 64l-6.148-6.148zM35.4 44.187l6.148 6.148-2.42 2.44-6.147-6.148zM2.44 33.676l6.149 6.148-2.44 2.42L0 36.095zM13.685 22.45l6.148 6.148-2.44 2.44-6.148-6.149zM24.89 11.225l6.148 6.148-2.38 2.46-6.148-6.148zM36.095 0l6.188 6.148-2.44 2.44-6.346-6.149zM22.67 6.346L28.817.198l2.44 2.42-6.149 6.148zM11.424 17.592l6.148-6.149 2.459 2.44-6.148 6.148zM.198 28.837l6.148-6.148 2.44 2.38-6.148 6.187zm10.829 10.828l6.148-6.148 2.46 2.44-6.149 6.148zm10.868 10.869l6.148-6.149 2.44 2.42-6.148 6.148zm10.829 10.868l6.188-6.148 2.44 2.38-6.149 6.188z" fill="#db202e"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
@yield('content')
|
||||
|
||||
<hr class="separator"/>
|
||||
|
||||
<p>@lang("If you need additional help, you can find how to get help at") <a href="https://docs.librenms.org/Support">https://docs.librenms.org/Support</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -8,12 +8,12 @@
|
||||
<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') }}">
|
||||
<link rel="icon" type="image/png" href="{{ asset('images/favicon-32x32.png') }}" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="{{ asset('images/favicon-16x16.png') }}" sizes="16x16">
|
||||
<link rel="manifest" href="{{ asset('images/manifest.json') }}">
|
||||
<link rel="mask-icon" href="{{ asset('images/safari-pinned-tab.svg') }}" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="{{ asset('images/favicon.ico') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="images/manifest.json">
|
||||
<link rel="mask-icon" href="images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="images/favicon.ico">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="msapplication-config" content="images/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
@@ -21,53 +21,53 @@
|
||||
<link rel="shortcut icon" href="{{ LibreNMS\Config::get('favicon') }}" />
|
||||
@endif
|
||||
|
||||
<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-ui.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/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=20180512" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('css/' . LibreNMS\Config::get('site_style', 'light') . '.css?ver=632417642') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="css/bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/bootstrap-datetimepicker.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/bootstrap-switch.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/toastr.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/jquery-ui.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/jquery.bootgrid.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/tagmanager.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/mktree.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/vis.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/font-awesome.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/jquery.gridster.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/leaflet.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/MarkerCluster.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/MarkerCluster.Default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/leaflet.awesome-markers.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/select2.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/select2-bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/query-builder.default.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ LibreNMS\Config::get('stylesheet', 'css/styles.css') }}?ver=20180512" rel="stylesheet" type="text/css" />
|
||||
<link href="css/{{ LibreNMS\Config::get('site_style', 'light') }}.css?ver=632417642" rel="stylesheet" type="text/css" />
|
||||
@foreach(LibreNMS\Config::get('webui.custom_css', []) as $custom_css)
|
||||
<link href="{{ asset($custom_css) }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ $custom_css }}" rel="stylesheet" type="text/css" />
|
||||
@endforeach
|
||||
@yield('css')
|
||||
|
||||
<script src="{{ asset('js/jquery.min.js') }}"></script>
|
||||
<script src="{{ asset('js/bootstrap.min.js') }}"></script>
|
||||
<script src="{{ asset('js/bootstrap-hover-dropdown.min.js') }}"></script>
|
||||
<script src="{{ asset('js/bootstrap-switch.min.js') }}"></script>
|
||||
<script src="{{ asset('js/hogan-2.0.0.js') }}"></script>
|
||||
<script src="{{ asset('js/jquery.cycle2.min.js') }}"></script>
|
||||
<script src="{{ asset('js/moment.min.js') }}"></script>
|
||||
<script src="{{ asset('js/bootstrap-datetimepicker.min.js') }}"></script>
|
||||
<script src="{{ asset('js/typeahead.bundle.min.js') }}"></script>
|
||||
<script src="{{ asset('js/jquery-ui.min.js') }}"></script>
|
||||
<script src="{{ asset('js/tagmanager.js') }}"></script>
|
||||
<script src="{{ asset('js/mktree.js') }}"></script>
|
||||
<script src="{{ asset('js/jquery.bootgrid.min.js') }}"></script>
|
||||
<script src="{{ asset('js/handlebars.min.js') }}"></script>
|
||||
<script src="{{ asset('js/pace.min.js') }}"></script>
|
||||
<script src="{{ asset('js/qrcode.min.js') }}"></script>
|
||||
<script src="js/jquery.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/bootstrap-hover-dropdown.min.js"></script>
|
||||
<script src="js/bootstrap-switch.min.js"></script>
|
||||
<script src="js/hogan-2.0.0.js"></script>
|
||||
<script src="js/jquery.cycle2.min.js"></script>
|
||||
<script src="js/moment.min.js"></script>
|
||||
<script src="js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script src="js/typeahead.bundle.min.js"></script>
|
||||
<script src="js/jquery-ui.min.js"></script>
|
||||
<script src="js/tagmanager.js"></script>
|
||||
<script src="js/mktree.js"></script>
|
||||
<script src="js/jquery.bootgrid.min.js"></script>
|
||||
<script src="js/handlebars.min.js"></script>
|
||||
<script src="js/pace.min.js"></script>
|
||||
<script src="js/qrcode.min.js"></script>
|
||||
@if(LibreNMS\Config::get('enable_lazy_load', true))
|
||||
<script src="{{ asset('js/jquery.lazyload.min.js') }}"></script>
|
||||
<script src="{{ asset('js/lazyload.js') }}"></script>
|
||||
<script src="js/jquery.lazyload.min.js"></script>
|
||||
<script src="js/lazyload.js"></script>
|
||||
@endif
|
||||
<script src="{{ asset('js/select2.min.js') }}"></script>
|
||||
<script src="{{ asset('js/librenms.js?ver=20180512') }}"></script>
|
||||
<script src="js/select2.min.js"></script>
|
||||
<script src="js/librenms.js?ver=20180512"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
<!-- Begin
|
||||
@@ -79,9 +79,9 @@
|
||||
}
|
||||
// End -->
|
||||
</script>
|
||||
<script type="text/javascript" src="{{ asset('js/overlib_mini.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ asset('js/toastr.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ asset('js/boot.js') }}"></script>
|
||||
<script type="text/javascript" src="js/overlib_mini.js"></script>
|
||||
<script type="text/javascript" src="js/toastr.min.js"></script>
|
||||
<script type="text/javascript" src="js/boot.js"></script>
|
||||
@yield('javascript')
|
||||
</head>
|
||||
<body>
|
||||
|
@@ -95,7 +95,7 @@ if (str_contains(`tail config.php`, '?>')) {
|
||||
|
||||
// Composer checks
|
||||
if (!file_exists('vendor/autoload.php')) {
|
||||
print_fail('Composer has not been run, dependencies are missing', 'composer install --no-dev');
|
||||
print_fail('Composer has not been run, dependencies are missing', './scripts/composer_wrapper.php install --no-dev');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user