Merge pull request #1261 from bergware/master

Add Scrub operation to ZFS pool
This commit is contained in:
tom mortensen
2023-02-14 13:30:00 -08:00
committed by GitHub
8 changed files with 252 additions and 65 deletions
+140 -12
View File
@@ -92,6 +92,32 @@ function prepareBTRFS(form) {
}
<?endif;?>
<?if (strpos($disk['fsType']??'',"zfs")!==false):?>
function presetZFS(form,hour) {
var mode = form.mode.value;
form.min.disabled = mode==0;
form.day.disabled = mode==0 || mode!=3;
form.dotm.disabled = mode==0 || mode!=4;
form.hour1.disabled = mode==0;
form.hour2.disabled = mode==0;
form.day.value = form.day.disabled ? '*' : (form.day.value=='*' ? 0 : form.day.value);
form.dotm.value = form.dotm.disabled ? '*' : (form.dotm.value=='*' ? 1 : form.dotm.value);
if (mode==1) {$(hour+'1').hide(); $(hour+'2').show();} else {$(hour+'2').hide(); $(hour+'1').show();}
}
function prepareZFS(form) {
var include = '';
var mode = form.mode.value;
form.hour.value = mode!=1 ? form.hour1.value : form.hour2.value;
form.min.value = mode!=1 ? form.min.value : 0;
form.hour1.disabled = true;
form.hour2.disabled = true;
if (form.usage.value != '') {
var job = $(form).find('input[name="#job"]');
job.val(job.val().replace('=50','='+form.usage.value));
}
}
<?endif;?>
function selectDiskFsWidth(num_devices,selected_width) {
$('#diskFsWidthZFS').empty();
if (num_devices == 0) {
@@ -264,7 +290,7 @@ function prepareFS(form,cookie,value) {
if ($(form).find('input[type="submit"]').val()=='Cancel') $.removeCookie(cookie); else $.cookie(cookie,value);
}
function btrfsBalance(path) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'balance',path:path},function(data) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'btrfs-balance',path:path},function(data) {
if (data.indexOf('running')>0) {
$('#btrfs-balance').text(data);
setTimeout(function(){btrfsBalance(path);},1000);
@@ -276,7 +302,7 @@ function btrfsBalance(path) {
return false;
}
function btrfsScrub(path) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'scrub',path:path},function(data) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'btrfs-scrub',path:path},function(data) {
if (data.indexOf('running')>0) {
$('#btrfs-scrub').text(data);
setTimeout(function(){btrfsScrub(path);},1000);
@@ -288,7 +314,7 @@ function btrfsScrub(path) {
return false;
}
function btrfsCheck(path) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'btrfs',path:path},function(data) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'btrfs-check',path:path},function(data) {
$('#btrfs-check').text(data);
if (data.slice(-1)!='\0') {
setTimeout(function(){btrfsCheck(path);},1000);
@@ -299,8 +325,20 @@ function btrfsCheck(path) {
});
return false;
}
function zfsScrub(path) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'zfs-scrub',path:path},function(data) {
if (data.indexOf('scrub in progress')>0) {
$('#zfs-scrub').text(data);
setTimeout(function(){zfsScrub(path);},1000);
} else {
$.removeCookie('zfs-scrub-<?=$tag?>');
refresh();
}
});
return false;
}
function rfsCheck(path) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'rfs',path:path},function(data) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'rfs-check',path:path},function(data) {
$('#rfs-check').text(data);
if (data.slice(-1)!='\0') {
setTimeout(function(){rfsCheck(path);},1000);
@@ -312,7 +350,7 @@ function rfsCheck(path) {
return false;
}
function xfsCheck(path) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'xfs',path:path},function(data) {
$.post('/webGui/include/FileSystemStatus.php',{cmd:'xfs-check',path:path},function(data) {
$('#xfs-check').text(data);
if (data.slice(-1)!='\0') {
setTimeout(function(){xfsCheck(path);},1000);
@@ -436,6 +474,7 @@ $(function() {
if ($.cookie('btrfs-balance-<?=$tag?>')) status = btrfsBalance($.cookie('btrfs-balance-<?=$tag?>'));
if ($.cookie('btrfs-scrub-<?=$tag?>')) status = btrfsScrub($.cookie('btrfs-scrub-<?=$tag?>'));
if ($.cookie('btrfs-check-<?=$tag?>')) status = btrfsCheck($.cookie('btrfs-check-<?=$tag?>'));
if ($.cookie('zfs-scrub-<?=$tag?>')) status = zfsScrub($.cookie('zfs-scrub-<?=$tag?>'));
if ($.cookie('rfs-check-<?=$tag?>')) status = rfsCheck($.cookie('rfs-check-<?=$tag?>'));
if ($.cookie('xfs-check-<?=$tag?>')) status = xfsCheck($.cookie('xfs-check-<?=$tag?>'));
if (status) {
@@ -443,18 +482,22 @@ $(function() {
var action = a.split(',');
for (var i=0,busy; busy=action[i]; i++) {
switch (busy) {
case 'balance':
case 'btrfs-balance':
$.cookie('btrfs-balance-<?=$tag?>','/mnt/<?=$tag?>');
btrfsBalance($.cookie('btrfs-balance-<?=$tag?>'));
break;
case 'scrub':
case 'btrfs-scrub':
$.cookie('btrfs-scrub-<?=$tag?>','/mnt/<?=$tag?>');
btrfsScrub($.cookie('btrfs-scrub-<?=$tag?>'));
break;
case 'check':
case 'btrfs-check':
$.cookie('btrfs-check-<?=$tag?>','/mnt/<?=$tag?>');
btrfsCheck($.cookie('btrfs-check-<?=$tag?>'));
break;
case 'zfs-scrub':
$.cookie('zfs-scrub-<?=$tag?>','<?=$tag?>');
zfsScrub($.cookie('zfs-scrub-<?=$tag?>'));
break;
}
}
});
@@ -904,11 +947,94 @@ _(btrfs check status)_:
<?endif;?>
<?if (strpos($disk['fsType']??'',"zfs")!==false):?>
<div class="title nocontrol"><span class="left"><i class="title fa fa-paint-brush"></i>_(Scrub Status)_</span></div>
<form markdown="1" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareFS(this,'zfs-scrub-<?=$tag?>','<?=$tag?>')">
<?if ($disk['fsStatus']=="Mounted"):?>
_(zpool status)_:
: <?echo "<pre>".shell_exec("/usr/sbin/zpool status -P $tag")."</pre>"?>
<?exec("$docroot/webGui/scripts/zfs_scrub status $tag", $scrub_status, $retval)?>
_(zfs scrub status)_:
: <?echo "<pre id='zfs-scrub'>".implode("\n", $scrub_status)."</pre>"?>
<?if ($retval != 0):?>
<input type="hidden" name="#command" value="/webGui/scripts/zfs_scrub">
<input type="hidden" name="#arg[1]" value="start">
<input type="hidden" name="#arg[2]" value="<?=$tag?>">
&nbsp;
: <input type="submit" value="_(Scrub)_">
:info_zfs_scrub_help:
<?else:?>
<input type="hidden" name="#command" value="/webGui/scripts/zfs_scrub">
<input type="hidden" name="#arg[1]" value="cancel">
<input type="hidden" name="#arg[2]" value="<?=$tag?>">
&nbsp;
: <input type="submit" value="_(Cancel)_">
:info_scrub_cancel_help:
<?endif;?>
<?else:?>
&nbsp;
: <input type="submit" value="_(Scrub)_" disabled><?=$tag==prefix($tag) ? "<b>"._('Scrub')."</b> "._('is only available when array is Started') : sprintf(_('See %s Settings'),ucfirst(prefix($tag)))?>
<?endif;?>
</form>
<hr>
<?$scrub = str_replace('-','_',"scrub_$tag")?>
<form markdown="1" name="scrub_schedule" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareZFS(this)">
<input type="hidden" name="#file" value="dynamix/dynamix.cfg">
<input type="hidden" name="#section" value="<?=$scrub?>">
<input type="hidden" name="#include" value="/webGui/include/update.zfs.php">
<input type="hidden" name="#job" value="scrub_<?=$tag?>;<?=$docroot?>/plugins/dynamix/scripts/zfs_scrub start <?=$tag?>">
<input type="hidden" name="hour" value="">
_(Scrub schedule)_:
: <select name="mode" onchange="presetZFS(this.form,'#scrub-hour')">
<?for ($m=0; $m<count($mode); $m++):?>
<?=mk_option($$scrub['mode']??'', strval($m), _($mode[$m]))?>
<?endfor;?>
</select>
_(Day of the week)_:
: <select name="day">
<?for ($d=0; $d<count($days); $d++):?>
<?=mk_option($$scrub['day']??'', strval($d), _($days[$d]),0)?>
<?endfor;?>
<?=mk_option($$scrub['day']??'', "*", "--------", _("disabled"))?>
</select>
_(Day of the month)_:
: <select name="dotm">
<?for ($d=1; $d<=31; $d++):?>
<?=mk_option($$scrub['dotm']??'', strval($d), sprintf("%02d", $d))?>
<?endfor;?>
<?=mk_option($$scrub['dotm']??'', "*", "--------", _("disabled"))?>
</select>
_(Time of the day)_:
: <span id="scrub-hour1" style="display:none"><select name="hour1" class="narrow">
<?for ($d=0; $d<=23; $d++):?>
<?=mk_option($$scrub['hour']??'', strval($d), sprintf("%02d", $d))?>
<?endfor;?>
</select>
<select name="min" class="narrow">
<?for ($d=0; $d<=55; $d+=5):?>
<?=mk_option($$scrub['min']??'', strval($d), sprintf("%02d", $d))?>
<?endfor;?>
</select>&nbsp;&nbsp;_(HH:MM)_</span>
: <span id="scrub-hour2" style="display:none"><select name="hour2">
<?=mk_option($$scrub['hour']??'', "*/1", _("Every hour"))?>
<?=mk_option($$scrub['hour']??'', "*/2", _("Every 2 hours"))?>
<?=mk_option($$scrub['hour']??'', "*/3", _("Every 3 hours"))?>
<?=mk_option($$scrub['hour']??'', "*/4", _("Every 4 hours"))?>
<?=mk_option($$scrub['hour']??'', "*/6", _("Every 6 hours"))?>
<?=mk_option($$scrub['hour']??'', "*/8", _("Every 8 hours"))?>
</select></span>
&nbsp;
: <input type="submit" name="#apply" value="_(Apply)_"><input type="button" value="_(Done)_" onclick="done()">
</form>
<?endif;?>
<?if (strpos($disk['fsType']??'',"reiserfs")!==false):?>
<div class="title nocontrol"><span class="left"><i class="title fa fa-shield"></i>_(Check Filesystem Status)_</span></div>
@@ -1090,11 +1216,13 @@ _(Name)_:
<input type='hidden' name='csrf_token' value='<?=$var['csrf_token']?>'>
</form>
<?if (strpos($disk['fsType']??'',"btrfs")!==false):?>
<script>
$(function(){
<?if (strpos($disk['fsType']??'',"btrfs")!==false):?>
presetBTRFS(document.balance_schedule,'#balance-hour');
presetBTRFS(document.scrub_schedule,'#scrub-hour');
<?elseif (strpos($disk['fsType']??'',"zfs")!==false):?>
presetZFS(document.scrub_schedule,'#scrub-hour');
<?endif;?>
});
</script>
<?endif;?>
@@ -257,7 +257,7 @@ function openTerminal(tag,name,more) {
name = name.replace(/[ #]/g,"_");
tty_window = makeWindow(name+(more=='.log'?more:''),Math.min(screen.availHeight,800),Math.min(screen.availWidth,1200));
var socket = ['ttyd','syslog'].includes(tag) ? '/webterminal/'+tag+'/' : '/logterminal/'+name+(more=='.log'?more:'')+'/';
$.get('/webGui/include/OpenTerminal.php',{tag:tag,name:name,more:more},function(){setTimeout(function(){tty_window.location=socket; tty_window.focus();},100);});
$.get('/webGui/include/OpenTerminal.php',{tag:tag,name:name,more:more},function(){setTimeout(function(){tty_window.location=socket; tty_window.focus();},200);});
}
function bannerAlert(text,cmd,plg,func,start) {
$.post('/webGui/include/StartCommand.php',{cmd:cmd,pid:1},function(pid) {
@@ -846,9 +846,9 @@ defaultPage.on('message', function(msg,meta) {
if (bell2) $('#bell').addClass('yellow-orb'); else
if (bell3) $('#bell').addClass('green-orb');
if (bell1) $('#dropdown-board li.dropdown-submenu:eq(0)').removeClass('disabled').find('.dropdown-menu'); else $('#dropdown-board li.dropdown-submenu:eq(0)').addClass('disabled').find('.dropdown-menu').hide();
if (bell2) $('#dropdown-board li.dropdown-submenu:eq(1)').removeClass('disabled').find('.dropdown-menu'); else $('#dropdown-board li.dropdown-submenu:eq(1)').addClass('disabled').find('.dropdown-menu').hide();
if (bell3) $('#dropdown-board li.dropdown-submenu:eq(2)').removeClass('disabled').find('.dropdown-menu'); else $('#dropdown-board li.dropdown-submenu:eq(2)').addClass('disabled').find('.dropdown-menu').hide();
if (bell1) $('#dropdown-board li.dropdown-submenu:eq(0)').removeClass('disabled'); else $('#dropdown-board li.dropdown-submenu:eq(0)').addClass('disabled').find('.dropdown-menu').hide();
if (bell2) $('#dropdown-board li.dropdown-submenu:eq(1)').removeClass('disabled'); else $('#dropdown-board li.dropdown-submenu:eq(1)').addClass('disabled').find('.dropdown-menu').hide();
if (bell3) $('#dropdown-board li.dropdown-submenu:eq(2)').removeClass('disabled'); else $('#dropdown-board li.dropdown-submenu:eq(2)').addClass('disabled').find('.dropdown-menu').hide();
break;
}
});
@@ -988,7 +988,7 @@ $(function() {
<?endif;?>
<?endif;?>
var opts = [];
context.init({above:false});
context.init({above:false,right:true});
opts.push({header:"<?=_('Notifications')?>"});
opts.push({text:"<?=_('Alerts')?>",icon:'fa-bell-o',subMenu:[{text:"<?=_('View')?>",icon:'fa-folder-open-o',action:function(e){e.preventDefault();openNotifier('alert');}},{text:"<?=_('History')?>",icon:'fa-file-text-o',action:function(e){e.preventDefault();viewHistory('alert');}},{text:"<?=_('Acknowledge')?>",icon:'fa-check-square-o',action:function(e){e.preventDefault();closeNotifier('alert');}}]});
opts.push({text:"<?=_('Warnings')?>",icon:'fa-star-o',subMenu:[{text:"<?=_('View')?>",icon:'fa-folder-open-o',action:function(e){e.preventDefault();openNotifier('warning');}},{text:"<?=_('History')?>",icon:'fa-file-text-o',action:function(e){e.preventDefault();viewHistory('warning');}},{text:"<?=_('Acknowledge')?>",icon:'fa-check-square-o',action:function(e){e.preventDefault();closeNotifier('warning');}}]});
+20 -11
View File
@@ -1,6 +1,6 @@
<?PHP
/* Copyright 2005-2022, Lime Technology
* Copyright 2012-2022, Bergware International.
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -16,23 +16,32 @@ $docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$cmd = $_POST['cmd'];
$path = $_POST['path'];
function btrfs($data) {return "btrfs-$data";}
function zfs($data) {return "zfs-$data";}
switch ($cmd) {
case 'status':
exec("ps -C btrfs -o cmd=|awk '/$path\$/{print $2}'",$action);
echo implode(',',$action);
exec("ps -C btrfs -o cmd=|awk '/$path\$/{print $2}'",$btrfs);
exec("/usr/sbin/zpool status|grep -om1 'scrub in progress'|grep -o scrub",$zfs);
echo implode(',',array_merge(array_map('btrfs',$btrfs),array_map('zfs',$zfs)));
break;
case 'balance':
case 'scrub':
case 'btrfs-balance':
case 'btrfs-scrub':
$cmd = explode('-',$cmd)[1];
echo shell_exec("/sbin/btrfs $cmd status $path");
break;
case 'zfs-scrub':
echo shell_exec("/usr/sbin/zpool status -P $path");
break;
default:
[$dev,$id] = explode(' ',$path);
$file = "/var/lib/$cmd/check.status.$id";
[$dev,$id] = array_pad(explode(' ',$path),2,'');
$dir = explode('-',$cmd)[0];
$file = "/var/lib/$dir/check.status.$id";
if (file_exists($file)) {
switch ($cmd) {
case 'btrfs': $pgrep = "pgrep -f '/sbin/btrfs check .*$dev'"; break;
case 'rfs': $pgrep = "pgrep -f '/sbin/reiserfsck $dev'"; break;
case 'xfs': $pgrep = "pgrep -f '/sbin/xfs_repair.*$dev'"; break;
case 'btrfs-check': $pgrep = "pgrep -f '/sbin/btrfs check .*$dev'"; break;
case 'rfs-check': $pgrep = "pgrep -f '/sbin/reiserfsck $dev'"; break;
case 'xfs-check': $pgrep = "pgrep -f '/sbin/xfs_repair.*$dev'"; break;
}
echo file_get_contents($file);
if (!exec($pgrep)) echo "\0";
+29
View File
@@ -0,0 +1,29 @@
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/webGui/include/Wrappers.php";
[$job, $cmd] = explode(';',$_POST['#job']);
$valid = "$docroot/plugins/dynamix/scripts/";
if ($_POST['mode']>0 && substr($cmd,0,strlen($valid))==$valid) {
$hour = isset($_POST['hour']) ? $_POST['hour'] : '*';
$min = isset($_POST['min']) ? $_POST['min'] : '*';
$dotm = isset($_POST['dotm']) ? $_POST['dotm'] : '*';
$day = isset($_POST['day']) ? $_POST['day'] : '*';
$cron = "# Generated zfs ".str_replace('_',' ',$job)." schedule:\n$min $hour $dotm * $day $cmd &> /dev/null\n\n";
} else {
$cron = "";
}
parse_cron_cfg('dynamix', $job, $cron);
?>
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -110,7 +110,7 @@ while (true) {
if ($var['fsState']!='Started') $process = -1;
elseif ($spot>0) $process = 1;
elseif (file_exists('/var/run/mover.pid')) $process = 2;
elseif (exec('pgrep -cf /sbin/btrfs')>0) $process = 3;
elseif (exec('ps -C btrfs -o cmd=|grep -cv show')>0) $process = 3;
else $process = 0;
publish('parity', implode(';',$data));
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# zfs_scrub start <pool>
# zfs_scrub status <pool>
# zfs_scrub cancel <pool>
case "$1" in
'start')
exec /usr/sbin/zpool scrub $2 2>/dev/null
;;
'status')
# first output whatever the status is to stdout
/usr/sbin/zpool status -P $2
# establish retval of this script: 0 running, 1 not running
/usr/sbin/zpool status -P $2 | grep -q 'scrub in progress'
;;
'cancel')
/usr/sbin/zpool scrub -s $2 2>/dev/null
;;
esac
+36 -34
View File
@@ -2,40 +2,42 @@
* ContextJS Styles
* For use WITHOUT Twitters Bootstrap CSS
*/
.nav-header{display:block;padding:3px 11px;font-size:1.1rem;font-weight:bold;line-height:20px;color:#333;background:linear-gradient(to bottom, #c1c1c1 0%, #c3c3c3 100%);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
.nav-header img{width:24px;height:24px;margin:-3px 4px 0 -5px;}
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:2px 0;margin:2px 0 30px 0;list-style:none;background-color:#FFF;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);font-size:1.3rem;border-right-width:2px;border-bottom-width:2px;border-radius:3px;box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;text-align:left;}
.dropdown-menu.pull-right{right:0;left:auto;}
.dropdown-menu .divider{width:100%;height:1px;margin:0;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
.dropdown-menu a{display:block;padding:2px 20px 2px 6px;clear:both;font-weight:normal;line-height:24px;color:#303030;white-space:nowrap;text-decoration:none;}
.dropdown-menu li > a:hover, .dropdown-menu li > a:focus, .dropdown-submenu:hover > a{color:#333;text-decoration:none;background-color:#0088cc;background-color:#0081c2;background-image:-moz-linear-gradient(top, #e1e1e1, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #e1e1e1, #0077b3);background-image:linear-gradient(to bottom, #e6e6e6, #e9e9e9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);}
.dropdown-menu .active > a, .dropdown-menu .active > a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;background-color:#0081c2;background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-repeat:repeat-x;outline:0;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);}
.dropdown-menu .disabled > a, .dropdown-menu .disabled > a:hover{color:#999999;}
.dropdown-menu .disabled > a:hover{text-decoration:none;cursor:default;background-color:transparent;}
.open{z-index:1000;}
.open > .dropdown-menu{display:block;}
.pull-right > .dropdown-menu{right:0;left:auto;}
.dropup .caret, .navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";}
.dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
.dropdown-submenu{position:relative;}
.dropdown-submenu > .dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;border-radius:0 6px 6px 6px;}
.dropdown-submenu > .dropdown-menu.drop-left{left:-100%;}
.dropdown-submenu:hover .dropdown-menu{display:block;}
.dropdown-submenu > a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#cccccc;border-style:solid;border-width:5px 0 5px 5px;content:" ";}
.dropdown-submenu:hover > a:after{border-left-color:#ffffff;}
.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px;}
.nav-header{display:block;padding:3px 11px;font-size:1.1rem;font-weight:bold;line-height:20px;color:#333;background:linear-gradient(to bottom,#c1c1c1 0%,#c3c3c3 100%);text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}
.nav-header img{width:24px;height:24px;margin:-3px 4px 0 -5px}
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:2px 0;margin:2px 0 30px 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);font-size:1.3rem;border-right-width:2px;border-bottom-width:2px;border-radius:3px;box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;text-align:left}
.dropdown-menu.pull-right{right:0;left:auto}
.dropdown-menu .divider{width:100%;height:1px;margin:0;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff}
.dropdown-menu a{display:block;padding:2px 20px 2px 6px;clear:both;font-weight:normal;line-height:24px;color:#303030;white-space:nowrap;text-decoration:none}
.dropdown-menu li > a:hover,.dropdown-menu li > a:focus,.dropdown-submenu:hover > a{color:#333;text-decoration:none;background-color:#0088cc;background-color:#0081c2;background-image:-moz-linear-gradient(top,#e1e1e1,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0088cc),to(#0077b3));background-image:-webkit-linear-gradient(top,#0088cc,#0077b3);background-image:-o-linear-gradient(top,#e1e1e1,#0077b3);background-image:linear-gradient(to bottom,#e6e6e6,#e9e9e9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}
.dropdown-menu .active > a,.dropdown-menu .active > a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;background-color:#0081c2;background-image:linear-gradient(to bottom,#0088cc,#0077b3);background-image:-moz-linear-gradient(top,#0088cc,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0088cc),to(#0077b3));background-image:-webkit-linear-gradient(top,#0088cc,#0077b3);background-image:-o-linear-gradient(top,#0088cc,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}
.dropdown-menu .disabled > a,.dropdown-menu .disabled > a:hover{color:#999999}
.dropdown-menu .disabled > a:hover{text-decoration:none;cursor:default;background-color:transparent}
.open{z-index:1000}
.open > .dropdown-menu{display:block}
.pull-right > .dropdown-menu{right:0;left:auto}
.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191"}
.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}
.dropdown-submenu{position:relative}
.dropdown-submenu > .dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;border-radius:0 6px 6px 6px}
.dropdown-submenu > .dropdown-menu.drop-left{left:-100%}
.dropdown-submenu:hover .dropdown-menu{display:block}
.dropdown-submenu > a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#cccccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}
.dropdown-submenu:hover > a:after{border-left-color:#ffffff}
.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}
/*
* Context Styles
*/
.dropdown-context .nav-header{cursor:default;}
.dropdown-context:before, .dropdown-context-up:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0, 0, 0, 0.2);content:'';}
.dropdown-context:after, .dropdown-context-up:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #ffffff;border-left:6px solid transparent;content:'';}
.dropdown-context-up:before, .dropdown-context-up:after{top:auto;bottom:-7px;z-index:9999;}
.dropdown-context-up:before{border-right:7px solid transparent;border-top:7px solid #ccc;border-bottom:none;border-left:7px solid transparent;}
.dropdown-context-up:after{border-right:6px solid transparent;border-top:6px solid #ffffff;border-left:6px solid transparent;border-bottom:none;}
.dropdown-context-sub:before, .dropdown-context-sub:after{display:none;}
.dropdown-context .dropdown-submenu:hover .dropdown-menu{display:none;}
.dropdown-context .dropdown-submenu:hover > .dropdown-menu{display:block;}
.compressed-context a{padding-left:7px;padding-top:0;padding-bottom:0;font-size:1.2rem;}
.compressed-context .divider{margin:-2px 0;}
.compressed-context .nav-header{padding:1px 13px;}
.dropdown-context .nav-header{cursor:default}
.dropdown-context:before,.dropdown-context-up:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}
.dropdown-context:after,.dropdown-context-up:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #ffffff;border-left:6px solid transparent;content:''}
.dropdown-context.right:before,.dropdown-context-up.right:before{left:auto;right:9px}
.dropdown-context.right:after,.dropdown-context-up.right:after{left:auto;right:10px}
.dropdown-context-up:before,.dropdown-context-up:after{top:auto;bottom:-7px;z-index:9999}
.dropdown-context-up:before{border-right:7px solid transparent;border-top:7px solid #ccc;border-bottom:none;border-left:7px solid transparent}
.dropdown-context-up:after{border-right:6px solid transparent;border-top:6px solid #ffffff;border-left:6px solid transparent;border-bottom:none}
.dropdown-context-sub:before,.dropdown-context-sub:after{display:none}
.dropdown-context .dropdown-submenu:hover .dropdown-menu{display:none}
.dropdown-context .dropdown-submenu:hover > .dropdown-menu{display:block}
.compressed-context a{padding-left:7px;padding-top:0;padding-bottom:0;font-size:1.2rem}
.compressed-context .divider{margin:-2px 0}
.compressed-context .nav-header{padding:1px 13px}