Merge pull request #2189 from unraid/feat/unraid-api-tweaks

feat: add tweaks for the Unraid API
This commit is contained in:
tom mortensen
2025-05-19 23:57:18 -07:00
committed by GitHub
11 changed files with 204 additions and 299 deletions

View File

@@ -14,6 +14,10 @@ if (isset($_COOKIE[session_name()])) {
session_write_close();
}
// Include JS caching functions
require_once '/usr/local/emhttp/webGui/include/JSCache.php';
// Base whitelist of files
$arrWhitelist = [
'/webGui/styles/clear-sans-bold-italic.eot',
'/webGui/styles/clear-sans-bold-italic.woff',
@@ -39,8 +43,15 @@ $arrWhitelist = [
'/webGui/images/case-model.png',
'/webGui/images/green-on.png',
'/webGui/images/red-on.png',
'/webGui/images/yellow-on.png'
'/webGui/images/yellow-on.png',
'/webGui/images/UN-logotype-gradient.svg'
];
// Add JS files from the unraid-components directory using cache
$webComponentsDirectory = '/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/';
$jsFiles = getCachedJSFiles($webComponentsDirectory);
$arrWhitelist = array_merge($arrWhitelist, $jsFiles);
if (in_array(preg_replace(['/\?v=\d+$/','/\?\d+$/'],'',$_SERVER['REQUEST_URI']),$arrWhitelist)) {
// authorized
http_response_code(200);

View File

@@ -0,0 +1,8 @@
Menu="UNRAID-OS"
Title="Log Viewer (new)"
Icon="icon-log"
Tag="list"
---
<unraid-i18n-host>
<unraid-log-viewer></unraid-log-viewer>
</unraid-i18n-host>

View File

@@ -28,22 +28,22 @@ $entity = $notify['entity'] & 1 == 1;
$alerts = '/tmp/plugins/my_alerts.txt';
$wlan0 = file_exists('/sys/class/net/wlan0');
$safemode = _var($var,'safeMode')=='yes';
$safemode = _var($var, 'safeMode') == 'yes';
$banner = "$config/webGui/banner.png";
$notes = '/var/tmp/unRAIDServer.txt';
if (!file_exists($notes)) {
file_put_contents($notes, shell_exec("$docroot/plugins/dynamix.plugin.manager/scripts/plugin changes $docroot/plugins/unRAIDServer/unRAIDServer.plg"));
file_put_contents($notes, shell_exec("$docroot/plugins/dynamix.plugin.manager/scripts/plugin changes $docroot/plugins/unRAIDServer/unRAIDServer.plg"));
}
$taskPages = find_pages('Tasks');
$buttonPages = find_pages('Buttons');
$pages = []; // finds subpages
if (!empty($myPage['text'])) $pages[$myPage['name']] = $myPage;
if (_var($myPage,'Type')=='xmenu') $pages = array_merge($pages, find_pages($myPage['name']));
if (_var($myPage, 'Type') == 'xmenu') $pages = array_merge($pages, find_pages($myPage['name']));
// nchan related actions
$nchan = ['webGui/nchan/notify_poller','webGui/nchan/session_check'];
$nchan = ['webGui/nchan/notify_poller', 'webGui/nchan/session_check'];
if ($wlan0) $nchan[] = 'webGui/nchan/wlan0';
// build nchan scripts from found pages
$allPages = array_merge($taskPages, $buttonPages, $pages);
@@ -52,96 +52,94 @@ foreach ($allPages as $page) {
}
// act on nchan scripts
if (count($pages)) {
$running = file_exists($nchan_pid) ? file($nchan_pid,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) : [];
$running = file_exists($nchan_pid) ? file($nchan_pid, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
$start = array_diff($nchan, $running); // returns any new scripts to be started
$stop = array_diff($running, $nchan); // returns any old scripts to be stopped
$running = array_merge($start, $running); // update list of current running nchan scripts
// start nchan scripts which are new
foreach ($start as $row) {
$script = explode(':',$row)[0];
$script = explode(':', $row)[0];
exec("$docroot/$script &>/dev/null &");
}
// stop nchan scripts with the :stop option
foreach ($stop as $row) {
[$script,$opt] = my_explode(':',$row);
[$script, $opt] = my_explode(':', $row);
if ($opt == 'stop') {
exec("pkill -f $docroot/$script &>/dev/null &");
array_splice($running,array_search($row,$running),1);
array_splice($running, array_search($row, $running), 1);
}
}
if (count($running)) file_put_contents($nchan_pid,implode("\n",$running)."\n"); else @unlink($nchan_pid);
if (count($running)) file_put_contents($nchan_pid, implode("\n", $running) . "\n");
else @unlink($nchan_pid);
}
?>
<!DOCTYPE html>
<html <?=$display['rtl']?>lang="<?=strtok($locale, '_') ?: 'en'?>" class="<?= $themeHelper->getThemeHtmlClass() ?>">
<html <?= $display['rtl'] ?>lang="<?= strtok($locale, '_') ?: 'en' ?>" class="<?= $themeHelper->getThemeHtmlClass() ?>">
<head>
<title><?=_var($var, 'NAME')?>/<?=_var($myPage, 'name')?></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=1300">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="same-origin">
<link type="image/png" rel="shortcut icon" href="/webGui/images/<?=_var($var, 'mdColor', 'red-on')?>.png">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-fonts.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-cases.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/font-awesome.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/context.standalone.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.sweetalert.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.ui.css")?>">
<title><?= _var($var, 'NAME') ?>/<?= _var($myPage, 'name') ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=1300">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="same-origin">
<link type="image/png" rel="shortcut icon" href="/webGui/images/<?= _var($var, 'mdColor', 'red-on') ?>.png">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/default-fonts.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/default-cases.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/font-awesome.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/context.standalone.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/jquery.sweetalert.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/jquery.ui.css") ?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-color-palette.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-base.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-dynamix.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/themes/{$theme}.css")?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/default-color-palette.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/default-base.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/default-dynamix.css") ?>">
<link type="text/css" rel="stylesheet" href="<? autov("/webGui/styles/themes/{$theme}.css") ?>">
<style>
:root {
--customer-header-background-image: url(<?= file_exists($banner) ? autov($banner) : autov('/webGui/images/banner.png') ?>);
<?if ($header):?>
--customer-header-text-color: #<?=$header?>;
<?endif;?>
<?if ($backgnd):?>
--customer-header-background-color: #<?=$backgnd?>;
<?endif;?>
<?if ($display['font']):?>
--custom-font-size: <?=$display['font']?>%;
<?endif;?>
}
<style>
:root {
--customer-header-background-image: url(<?= file_exists($banner) ? autov($banner) : autov('/webGui/images/banner.png') ?>);
<? if ($header): ?>--customer-header-text-color: #<?= $header ?>;
<? endif; ?><? if ($backgnd): ?>--customer-header-background-color: #<?= $backgnd ?>;
<? endif; ?><? if ($display['font']): ?>--custom-font-size: <?= $display['font'] ?>%;
<? endif; ?>
}
<?php
// Generate sidebar icon CSS if using sidebar theme
if ($themeHelper->isSidebarTheme()) {
echo generate_sidebar_icon_css($taskPages, $buttonPages);
}
?>
</style>
<?php
// Generate sidebar icon CSS if using sidebar theme
if ($themeHelper->isSidebarTheme()) {
echo generate_sidebar_icon_css($taskPages, $buttonPages);
}
?>
</style>
<noscript>
<div class="upgrade_notice"><?=_("Your browser has JavaScript disabled")?></div>
</noscript>
<noscript>
<div class="upgrade_notice"><?= _("Your browser has JavaScript disabled") ?></div>
</noscript>
<script src="<?autov('/webGui/javascript/dynamix.js')?>"></script>
<script src="<?autov('/webGui/javascript/translate.'.($locale?:'en_US').'.js')?>"></script>
<script src="<? autov('/webGui/javascript/dynamix.js') ?>"></script>
<script src="<? autov('/webGui/javascript/translate.' . ($locale ?: 'en_US') . '.js') ?>"></script>
<? require_once "$docroot/webGui/include/DefaultPageLayout/HeadInlineJS.php"; ?>
<? require_once "$docroot/webGui/include/DefaultPageLayout/HeadInlineJS.php"; ?>
<?php
foreach ($buttonPages as $button) {
annotate($button['file']);
includePageStylesheets($button);
eval('?>' . parse_text($button['text']));
}
<?php
foreach ($buttonPages as $button) {
annotate($button['file']);
includePageStylesheets($button);
eval('?>'.parse_text($button['text']));
}
foreach ($pages as $page) {
annotate($page['file']);
includePageStylesheets($page);
}
?>
foreach ($pages as $page) {
annotate($page['file']);
includePageStylesheets($page);
}
?>
<?include "$docroot/plugins/dynamix.my.servers/include/myservers1.php"?>
<? include "$docroot/plugins/dynamix.my.servers/include/myservers1.php" ?>
<? require_once "$docroot/webGui/include/DefaultPageLayout/GUIModeSessionFix.php"; ?>
</head>
<body>
<? include "$docroot/webGui/include/DefaultPageLayout/Header.php"; ?>
<? include "$docroot/webGui/include/DefaultPageLayout/Navigation/Main.php"; ?>
@@ -149,5 +147,7 @@ foreach ($pages as $page) {
<? include "$docroot/webGui/include/DefaultPageLayout/Footer.php"; ?>
<? include "$docroot/webGui/include/DefaultPageLayout/MiscElements.php"; ?>
<? include "$docroot/webGui/include/DefaultPageLayout/BodyInlineJS.php"; ?>
<? include "$docroot/webGui/include/DefaultPageLayout/ToastSetup.php"; ?>
</body>
</html>
</html>

View File

@@ -71,32 +71,7 @@ defaultPage.on('message', function(msg,meta) {
$('#statusbar').html(status);
break;
case 2:
// notifications
var bell1 = 0, bell2 = 0, bell3 = 0;
$.each($.parseJSON(msg), function(i, notify){
switch (notify.importance) {
case 'alert' : bell1++; break;
case 'warning': bell2++; break;
case 'normal' : bell3++; break;
}
<?if ($notify['display']==0):?>
if (notify.show) {
$.jGrowl(notify.subject+'<br>'+notify.description,{
group: notify.importance,
header: notify.event+': '+notify.timestamp,
theme: notify.file,
beforeOpen: function(e,m,o){if ($('div.jGrowl-notification').hasClass(notify.file)) return(false);},
afterOpen: function(e,m,o){if (notify.link) $(e).css('cursor','pointer');},
click: function(e,m,o){if (notify.link) location.replace(notify.link);},
close: function(e,m,o){$.post('/webGui/include/Notify.php',{cmd:'hide',file:"<?=$notify['path'].'/unread/'?>"+notify.file,csrf_token:csrf_token}<?if ($notify['life']==0):?>,function(){$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});}<?endif;?>);}
});
}
<?endif;?>
});
$('#bell').removeClass('red-orb yellow-orb green-orb').prop('title',"<?=_('Alerts')?> ["+bell1+']\n'+"<?=_('Warnings')?> ["+bell2+']\n'+"<?=_('Notices')?> ["+bell3+']');
if (bell1) $('#bell').addClass('red-orb'); else
if (bell2) $('#bell').addClass('yellow-orb'); else
if (bell3) $('#bell').addClass('green-orb');
// notifications - moved to the Unraid API
break;
}
});

View File

@@ -0,0 +1,26 @@
<?php
/**
* This file is used to fix the session for the Unraid web interface when booted in GUI mode.
* This can be deleted if GUI mode authentication is enabled.
*/
function is_localhost()
{
// Use the peer IP, not the Host header which can be spoofed
return $_SERVER['REMOTE_ADDR'] === '127.0.0.1' || $_SERVER['REMOTE_ADDR'] === '::1';
}
function is_good_session()
{
return isset($_SESSION) && isset($_SESSION['unraid_user']) && isset($_SESSION['unraid_login']);
}
if (is_localhost() && !is_good_session()) {
if (session_status() === PHP_SESSION_ACTIVE) {
session_destroy();
}
session_start();
$_SESSION['unraid_login'] = time();
$_SESSION['unraid_user'] = 'root';
session_write_close();
my_logger("Unraid GUI-boot: created root session for localhost request.");
}
?>

View File

@@ -477,32 +477,6 @@ function digits(number) {
return 'three';
}
function openNotifier() {
$.post('/webGui/include/Notify.php',{cmd:'get',csrf_token:csrf_token},function(msg) {
$.each($.parseJSON(msg), function(i, notify){
$.jGrowl(notify.subject+'<br>'+notify.description,{
group: notify.importance,
header: notify.event+': '+notify.timestamp,
theme: notify.file,
sticky: true,
beforeOpen: function(e,m,o){if ($('div.jGrowl-notification').hasClass(notify.file)) return(false);},
afterOpen: function(e,m,o){if (notify.link) $(e).css('cursor','pointer');},
click: function(e,m,o){if (notify.link) location.replace(notify.link);},
close: function(e,m,o){$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});}
});
});
});
}
function closeNotifier() {
$.post('/webGui/include/Notify.php',{cmd:'get',csrf_token:csrf_token},function(msg) {
$.each($.parseJSON(msg), function(i, notify){
$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});
});
$('div.jGrowl').find('div.jGrowl-close').trigger('click');
});
}
function viewHistory() {
location.replace('/Tools/NotificationsArchive');
}
@@ -536,17 +510,7 @@ $(function() {
}
$('#'+tab).attr('checked', true);
updateTime();
$.jGrowl.defaults.closeTemplate = '<i class="fa fa-close"></i>';
$.jGrowl.defaults.closerTemplate = '<?=$notify['position'][0]=='b' ? '<div class="bottom">':'<div class="top">'?>[ <?=_("close all notifications")?> ]</div>';
$.jGrowl.defaults.position = '<?=$notify['position']?>';
$.jGrowl.defaults.theme = '';
$.jGrowl.defaults.themeState = '';
$.jGrowl.defaults.pool = 10;
<?if ($notify['life'] > 0):?>
$.jGrowl.defaults.life = <?=$notify['life']*1000?>;
<?else:?>
$.jGrowl.defaults.sticky = true;
<?endif;?>
Shadowbox.setup('a.sb-enable', {modal:true});
// add any pre-existing reboot notices
$.post('/webGui/include/Report.php',{cmd:'notice'},function(notices){

View File

@@ -1,9 +1,5 @@
<div id="header" class="<?=$display['banner']?>">
<div class="logo">
<a href="https://unraid.net" target="_blank">
<?readfile("$docroot/webGui/images/UN-logotype-gradient.svg")?>
</a>
<unraid-i18n-host>
<unraid-header-os-version></unraid-header-os-version>
</unraid-i18n-host>

View File

@@ -0,0 +1 @@
<uui-toaster rich-colors close-button position="<?= ($notify['position'] === 'center') ? 'top-center' : $notify['position'] ?>"></uui-toaster>

View File

@@ -0,0 +1,86 @@
<?php
// Function to recursively find JS files in a directory
function findJsFiles($directory) {
if (!is_dir($directory)) {
return [];
}
$jsFiles = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
);
try {
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'js') {
$path = $file->getPathname();
$baseDir = '/usr/local/emhttp';
if (strpos($path, $baseDir) === 0) {
$path = substr($path, strlen($baseDir));
}
$jsFiles[] = $path;
}
}
} catch (Exception $e) {
my_logger("Error scanning for JS files: " . $e->getMessage());
return [];
}
return $jsFiles;
}
// Function to get JS files with caching
function getCachedJSFiles(string $directory, int $cacheLifetime = 300): array {
$cacheDir = sys_get_temp_dir();
$cacheFile = "$cacheDir/js_files_cache_" . md5($directory) . ".json";
$lockFile = "$cacheFile.lock";
// Check if cache exists and is still valid
$useCache = false;
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheLifetime)) {
$useCache = true;
}
if ($useCache) {
$data = @file_get_contents($cacheFile);
if ($data === false) {
my_logger("Warning: Unable to read JS cache at $cacheFile");
$useCache = false;
} else {
$jsFiles = json_decode($data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
my_logger("Warning: Corrupt JSON cache at $cacheFile");
$useCache = false;
} else {
return $jsFiles;
}
}
}
// Acquire lock to rebuild cache safely
$lockFp = @fopen($lockFile, 'w');
if ($lockFp && flock($lockFp, LOCK_EX | LOCK_NB)) {
try {
$jsFiles = findJsFiles($directory);
if (file_put_contents($cacheFile, json_encode($jsFiles)) === false) {
my_logger("Warning: Could not write JS cache to $cacheFile");
}
flock($lockFp, LOCK_UN);
fclose($lockFp);
@unlink($lockFile);
return $jsFiles;
} catch (Exception $e) {
my_logger("Error rebuilding JS cache: " . $e->getMessage());
flock($lockFp, LOCK_UN);
fclose($lockFp);
@unlink($lockFile);
return findJsFiles($directory);
}
}
// Fallback: unable to lock, generate without caching
if ($lockFp) {
fclose($lockFp);
}
return findJsFiles($directory);
}

File diff suppressed because one or more lines are too long

View File

@@ -2,27 +2,6 @@
* This CSS is specifically for overwriting CSS libraries that dynamix.js uses
*/
:root {
--dynamix-jgrowl-bg-color: var(--gray-500);
--dynamix-jgrowl-border-color: var(--gray-600);
--dynamix-jgrowl-shadow-color: var(--gray-500);
--dynamix-jgrowl-text-color: var(--gray-600);
--dynamix-jgrowl-alert-bg-color: var(--red-300);
--dynamix-jgrowl-alert-border-color: var(--red-600);
--dynamix-jgrowl-alert-text-color: var(--red-600);
--dynamix-jgrowl-warning-bg-color: var(--yellow-200);
--dynamix-jgrowl-warning-border-color: var(--orange-300);
--dynamix-jgrowl-warning-text-color: var(--orange-300);
--dynamix-jgrowl-normal-bg-color: var(--green-100);
--dynamix-jgrowl-normal-border-color: var(--green-800);
--dynamix-jgrowl-normal-text-color: var(--green-800);
--dynamix-jgrowl-close-text-color: var(--gray-500);
--dynamix-jgrowl-close-bg-color: var(--black);
--dynamix-jgrowl-close-border: 2px solid var(--gray-300);
--dynamix-ui-dropdownchecklist-color: var(--gray-100);
--dynamix-ui-dropdownchecklist-color-alt1: var(--gray-700);
--dynamix-ui-dropdownchecklist-color-alt2: var(--gray-500);
@@ -89,132 +68,6 @@
--dynamix-awesomplete-mark-selected-bg-color: var(--green-900);
}
.jGrowl {
position: fixed;
font-size: 1.3rem;
z-index: 10001;
}
.jGrowl.top-left {
left: 10px;
top: 130px;
}
.jGrowl.top-right {
right: 10px;
top: 130px;
}
.jGrowl.bottom-left {
left: 10px;
bottom: 24px;
}
.jGrowl.bottom-right {
right: 10px;
bottom: 24px;
}
.jGrowl.center {
top: 130px;
left: 40%;
}
.jGrowl.center .jGrowl-closer,
.jGrowl.center .jGrowl-notification {
margin-left: auto;
margin-right: auto;
}
.jGrowl-notification {
opacity: 0.96;
min-height: 1.2rem;
width: 380px;
padding: 10px;
margin: 5px 0;
text-align: left;
border-radius: 6px;
&.alert {
color: var(--dynamix-jgrowl-alert-text-color);
background-color: var(--dynamix-jgrowl-alert-bg-color);
border: 1px solid var(--dynamix-jgrowl-alert-border-color);
box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
}
&.warning {
color: var(--dynamix-jgrowl-warning-text-color);
background-color: var(--dynamix-jgrowl-warning-bg-color);
border: 1px solid var(--dynamix-jgrowl-warning-border-color);
box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
}
&.normal {
color: var(--dynamix-jgrowl-normal-text-color);
background-color: var(--dynamix-jgrowl-normal-bg-color);
border: 1px solid var(--dynamix-jgrowl-normal-border-color);
box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
}
&.default {
color: var(--dynamix-jgrowl-text-color);
background-color: var(--dynamix-jgrowl-bg-color);
border: 1px solid var(--dynamix-jgrowl-border-color);
box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
}
}
.jGrowl-notification .jGrowl-header {
font-weight: bold;
}
.jGrowl-notification .jGrowl-close {
float: right;
text-align: right;
margin: 0;
padding: 0;
background: none;
color: inherit;
border: none;
cursor: pointer;
font-size: 1.8rem;
min-width: auto;
margin-left: 2rem;
line-height: 1;
}
.jGrowl-notification .jGrowl-close:hover {
color: inherit;
background: none;
}
.jGrowl-closer {
width: 400px;
color: var(--dynamix-jgrowl-close-text-color);
background-color: var(--dynamix-jgrowl-close-bg-color);
opacity: 0.96;
border: 2px solid var(--dynamix-jgrowl-close-border-color);
margin: 5px 0;
padding: 4px 0;
cursor: pointer;
font-weight: bold;
text-align: center;
border-radius: 6px;
}
.jGrowl-closer.top {
position: fixed;
top: 130px;
}
#jGrowl {
pointer-events: none !important;
}
.jGrowl-notification:first-of-type {
pointer-events: none !important;
}
.jGrowl-notification {
pointer-events: all !important;
}
.jGrowl-closer.top {
pointer-events: all !important;
}
.jGrowl-closer.bottom {
pointer-events: all !important;
}
@media print {
.jGrowl {
display: none;
}
}
#sb-info-inner,
#sb-loading-inner,
div.sb-message {
@@ -1507,18 +1360,6 @@ div.icon-zip {
* @see https://caniuse.com/?search=nesting
*/
.Theme--sidebar {
.jGrowl.top-left {
top: 90px;
}
.jGrowl.top-right {
top: 90px;
}
.jGrowl.center {
top: 90px;
}
.jGrowl-notification {
min-height: 4rem;
}
table.tablesorter thead tr th {
font-size: 1.3rem;
padding: 5px 20px 5px 6px;