mirror of
https://github.com/unraid/webgui.git
synced 2026-01-04 08:29:51 -06:00
Merge branch 'vm-memory-slider' of https://github.com/bobbintb/webgui into vm-memory-slider
This commit is contained in:
12
.github/scripts/generate-pr-plugin.sh
vendored
12
.github/scripts/generate-pr-plugin.sh
vendored
@@ -53,14 +53,11 @@ cat > "$PLUGIN_NAME" << 'EOF'
|
||||
icon="wrench"
|
||||
support="&github;/pull/≺">
|
||||
|
||||
<!-- Put the change log within CDATA to handle circumstance if a character which needs to be escaped for xml somehow is added to the changelog -->
|
||||
<CHANGES>
|
||||
<![CDATA[
|
||||
##&version;
|
||||
- Test build for PR #≺ (commit &commit;)
|
||||
- This plugin installs modified files from the PR for testing
|
||||
- Original files are backed up and restored upon removal
|
||||
]]>
|
||||
</CHANGES>
|
||||
|
||||
<!-- FILE sections run in the listed order - Check if this is an update prior to installing -->
|
||||
@@ -271,7 +268,14 @@ Link='nav-user'
|
||||
<script>
|
||||
$(function() {
|
||||
// Check for updates (non-dismissible)
|
||||
caPluginUpdateCheck("webgui-pr-PR_PLACEHOLDER.plg", {noDismiss: true});
|
||||
caPluginUpdateCheck("webgui-pr-PR_PLACEHOLDER.plg", {noDismiss: true},function(result){
|
||||
try {
|
||||
let json = JSON.parse(result);
|
||||
if ( ! json.version ) {
|
||||
addBannerWarning("Note: webgui-pr-PR_PLACEHOLDER has either been merged or removed");
|
||||
}
|
||||
} catch(e) {}
|
||||
});
|
||||
|
||||
// Create banner with uninstall link (nondismissible)
|
||||
let bannerMessage = "Modified GUI installed via <b>webgui-pr-PR_PLACEHOLDER</b> plugin. " +
|
||||
|
||||
4
.github/workflows/pr-plugin-upload.yml
vendored
4
.github/workflows/pr-plugin-upload.yml
vendored
@@ -359,9 +359,9 @@ jobs:
|
||||
|
||||
### 🔄 To Remove:
|
||||
|
||||
Navigate to Plugins → Installed Plugins and remove `webgui-pr-${{ steps.metadata.outputs.version }}`, or run:
|
||||
Navigate to Plugins → Installed Plugins and remove `webgui-pr-${{ steps.metadata.outputs.pr_number }}`, or run:
|
||||
```bash
|
||||
plugin remove webgui-pr-${{ steps.metadata.outputs.version }}
|
||||
plugin remove webgui-pr-${{ steps.metadata.outputs.pr_number }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -1548,7 +1548,6 @@ provided the following conditions are met:
|
||||
* The Primary storage for a share is set to a pool.
|
||||
* The Secondary storage for a share is set to **none**.
|
||||
* The share exists on a single volume.
|
||||
* The share is **not** exported over NFS.
|
||||
|
||||
The advantage of *exclusive* shares is that transfers bypass the FUSE layer which may significantly
|
||||
increase I/O performance.
|
||||
|
||||
6
emhttp/plugins/dynamix.gui.search/gui_search.page
Normal file → Executable file
6
emhttp/plugins/dynamix.gui.search/gui_search.page
Normal file → Executable file
@@ -37,6 +37,9 @@ function guiSearchBoxSpan() {
|
||||
function setupGUIsearch() {
|
||||
window.addEventListener('keydown',function(e){
|
||||
if (!e.shiftKey && !e.altKey && (navigator.appVersion.indexOf('Mac')==-1 ? e.ctrlKey : e.metaKey) && e.keyCode==75) {
|
||||
// If a modal is visible, don't open the search box
|
||||
if ($('[role="modal"]').is(':visible')) return;
|
||||
|
||||
e.preventDefault();
|
||||
<?if ($themeHelper->isTopNavTheme()):?>
|
||||
if (guiSearchBoxSpan()) closeSearchBox(e); else gui_search();
|
||||
@@ -79,6 +82,9 @@ function closeSearchBox(e) {
|
||||
}
|
||||
|
||||
function guiSearch() {
|
||||
// If a modal is visible, don't navigate away from the page
|
||||
if ($('[role="modal"]').is(':visible')) return;
|
||||
|
||||
var searchInfo = $('#guiSearchBox').val().split('**');
|
||||
var separator = ('fragmentDirective' in document) ? '#:~:text=' : '#';
|
||||
var scrollText = (typeof searchInfo[1] != 'undefined') ? separator+searchInfo[1].replace(' ','%20').replace('-','%2d') : '';
|
||||
|
||||
6
emhttp/plugins/dynamix.plugin.manager/Plugins.page
Normal file → Executable file
6
emhttp/plugins/dynamix.plugin.manager/Plugins.page
Normal file → Executable file
@@ -145,9 +145,9 @@ function loadlist(id,check) {
|
||||
}
|
||||
$(function() {
|
||||
initlist();
|
||||
$('.tabs').append("<span id='checkall' class='status vhshift'><input type='button' value=\"_(Check For Updates)_\" onclick='openPlugin(\"checkall\",\"_(Plugin Update Check)_\",\":return\")' disabled></span>");
|
||||
$('.tabs').append("<span id='updateall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Update All Plugins)_\" onclick='updateList()'></span>");
|
||||
$('.tabs').append("<span id='removeall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Remove Selected Plugins)_\" onclick='removeList()'></span>");
|
||||
$('.tabs-container').append("<span id='checkall' class='status vhshift'><input type='button' value=\"_(Check For Updates)_\" onclick='openPlugin(\"checkall\",\"_(Plugin Update Check)_\",\":return\")' disabled></span>");
|
||||
$('.tabs-container').append("<span id='updateall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Update All Plugins)_\" onclick='updateList()'></span>");
|
||||
$('.tabs-container').append("<span id='removeall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Remove Selected Plugins)_\" onclick='removeList()'></span>");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
0
emhttp/plugins/dynamix.vm.manager/VMMachines.page
Normal file → Executable file
0
emhttp/plugins/dynamix.vm.manager/VMMachines.page
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
Menu="VMs:0"
|
||||
Menu="VMs:2"
|
||||
Title="VM Usage Statistics"
|
||||
Nchan="vm_usage"
|
||||
Cond="exec(\"grep -o '^USAGE=.Y' /boot/config/domain.cfg 2>/dev/null\") && is_file('/var/run/libvirt/libvirtd.pid')"
|
||||
|
||||
7
emhttp/plugins/dynamix.vm.manager/VMs.page
Normal file → Executable file
7
emhttp/plugins/dynamix.vm.manager/VMs.page
Normal file → Executable file
@@ -2,6 +2,7 @@ Menu="Tasks:70"
|
||||
Type="xmenu"
|
||||
Code="e918"
|
||||
Lock="true"
|
||||
Tabs="true"
|
||||
Cond="exec(\"grep -o '^SERVICE=.enable' /boot/config/domain.cfg 2>/dev/null\")"
|
||||
---
|
||||
<?PHP
|
||||
@@ -28,9 +29,3 @@ Cond="exec(\"grep -o '^SERVICE=.enable' /boot/config/domain.cfg 2>/dev/null\")"
|
||||
<? if ($noticeMessage): ?>
|
||||
<p class="notice"><?= $noticeMessage ?></p>
|
||||
<? endif; ?>
|
||||
|
||||
<?
|
||||
if (count($pages) == 2) {
|
||||
$tabbed = false;
|
||||
}
|
||||
?>
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2015-2023, Lime Technology
|
||||
/* Copyright 2015-2025, Lime Technology
|
||||
* Copyright 2015-2016, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -34,6 +34,7 @@ if (file_exists($cfgfile)) {
|
||||
}
|
||||
}
|
||||
if (isset($cfg_new)) {
|
||||
$tmp = "";
|
||||
foreach ($cfg_new as $key => $value) $tmp .= "$key=\"$value\"\n";
|
||||
file_put_contents($cfgfile, $tmp);
|
||||
}
|
||||
|
||||
@@ -459,6 +459,10 @@ function doAction(action, title, id) {
|
||||
clearTimeout(timers.calc);
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
swal({title:"_(Calculate Occupied Space)_",text:text,html:true,confirmButtonText:"_(Ok)_"});
|
||||
}).fail(function(xhr){
|
||||
clearTimeout(timers.calc);
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
swal({title:"Error", text:"Calculate failed: "+xhr.status+" "+xhr.statusText, type:"error"});
|
||||
});
|
||||
return;
|
||||
case 15: // search
|
||||
@@ -735,6 +739,10 @@ function doActions(action, title) {
|
||||
clearTimeout(timers.calc);
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
swal({title:"_(Calculate Occupied Space)_",text:text,html:true,confirmButtonText:"_(Ok)_"});
|
||||
}).fail(function(xhr){
|
||||
clearTimeout(timers.calc);
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
swal({title:"Error", text:"Calculate failed: "+xhr.status+" "+xhr.statusText, type:"error"});
|
||||
});
|
||||
return;
|
||||
case 15: // search
|
||||
|
||||
@@ -169,11 +169,16 @@ foreach ($memory_array as $device) {
|
||||
if ($base>=1) $memory_maximum += $size*pow(1024,$base);
|
||||
if (!$ecc && isset($device['Error Correction Type']) && $device['Error Correction Type']!='None') $ecc = "{$device['Error Correction Type']} ";
|
||||
}
|
||||
if ($memory_installed >= 1048576) {
|
||||
$memory_installed = round($memory_installed/1048576);
|
||||
$memory_maximum = round($memory_maximum/1048576);
|
||||
$unit = 'TiB';
|
||||
} else {
|
||||
if ($memory_installed >= 1024) {
|
||||
$memory_installed = round($memory_installed/1024);
|
||||
$memory_maximum = round($memory_maximum/1024);
|
||||
$unit = 'GiB';
|
||||
} else $unit = 'MiB';
|
||||
$unit = 'GiB';}
|
||||
else $unit = 'MiB'; }
|
||||
|
||||
// get system resources size
|
||||
exec("df --output=size /boot /var/log /var/lib/docker 2>/dev/null|awk '(NR>1){print $1*1024}'",$df);
|
||||
@@ -196,7 +201,7 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
|
||||
?>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
<script src="<?autov('/webGui/javascript/jquery.apexcharts.js')?>"></script>
|
||||
<script src="<?autov('/webGui/javascript/smoothie.js')?>"></script>
|
||||
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.docker.manager/javascript/docker.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/vmmanager.js')?>"></script>
|
||||
@@ -412,7 +417,7 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
|
||||
?>
|
||||
<tr id='cpu_chart'>
|
||||
<td>
|
||||
<div id='cpuchart'></div>
|
||||
<canvas id='cpuchart' style='width:100%; height:96px'></canvas>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -498,13 +503,19 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
|
||||
<div class='section'>
|
||||
<span class='flex flex-row items-center gap-4'>
|
||||
<h3 class='tile-header-main'>_(Interface)_</h3>
|
||||
<span class="head_gap flex flex-row items-center gap-4">
|
||||
<span class="head_gap flex flex-row items-center gap-4 network-selects">
|
||||
<i class='ups fa fa-angle-double-right'></i>
|
||||
<select name="port_select" onchange="portSelect(this.value)">
|
||||
<select name="port_select" class="network-select network-interface" onchange="portSelect(this.value)">
|
||||
<?foreach ($ports as $port):?>
|
||||
<?=mk_option("",$port,_($port))?>
|
||||
<?endforeach;?>
|
||||
</select>
|
||||
<select name="enter_view" class="network-select network-type" onchange="changeView(this.value)">
|
||||
<?=mk_option("", "0", _("General info"))?>
|
||||
<?=mk_option("", "1", _("Counters info"))?>
|
||||
<?=mk_option("", "2", _("Errors info"))?>
|
||||
<?=mk_option("", "3", _("Network traffic"))?>
|
||||
</select>
|
||||
</span><br>
|
||||
</span>
|
||||
<span class='flex flex-row flex-wrap items-center gap-4'>
|
||||
@@ -531,12 +542,7 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
|
||||
<tr>
|
||||
<td>
|
||||
<span class='w26'>
|
||||
<select name="enter_view" onchange="changeView(this.value)">
|
||||
<?=mk_option("", "0", _("General info"))?>
|
||||
<?=mk_option("", "1", _("Counters info"))?>
|
||||
<?=mk_option("", "2", _("Errors info"))?>
|
||||
<?=mk_option("", "3", _("Network traffic"))?>
|
||||
</select>
|
||||
|
||||
</span>
|
||||
<span class='w36'>
|
||||
<i class='view1'>_(Mode of operation)_</i>
|
||||
@@ -584,7 +590,7 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
|
||||
?>
|
||||
<tr class="view4">
|
||||
<td>
|
||||
<div id="netchart"></div>
|
||||
<canvas id="netchart" style='width:100%; height:120px'></canvas>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1427,8 +1433,8 @@ var rxd = [];
|
||||
var txd = [];
|
||||
var cputime = 0;
|
||||
var nettime = 0;
|
||||
var cpuline = cookie.cpuline||30;
|
||||
var netline = cookie.netline||30;
|
||||
var cpuline = parseInt(cookie.cpuline)||30;
|
||||
var netline = parseInt(cookie.netline)||30;
|
||||
var update2 = true;
|
||||
var box = null;
|
||||
var startup = true;
|
||||
@@ -1436,56 +1442,92 @@ var stopgap = '<thead class="stopgap"><tr><td class="stopgap"></td></tr></thead>
|
||||
var recall = null;
|
||||
var recover = null;
|
||||
|
||||
var options_cpu = {
|
||||
series:[{name:'load', data:cpu.slice()}],
|
||||
chart:{height:120, type:'line', fontFamily:'clear-sans', animations:{enabled:true, easing:'linear', dynamicAnimation:{speed:980}}, toolbar:{show:false}, zoom:{enabled:false},events:{updated:function(){if (cpuchart.customData.updateCount == 0) {cpuchart.customData.animationPending = false}cpuchart.customData.updateCount++;},animationEnd:function(){cpuchart.customData.animationPending = false;updateCPUChart();}}},
|
||||
dataLabels:{enabled:false},
|
||||
tooltip:{enabled:false},
|
||||
stroke:{curve:'smooth', width:1},
|
||||
colors:['#ff8c2f'],
|
||||
markers:{size:0},
|
||||
xaxis:{type:'datetime', range:cpuline-1, labels:{show:false}, axisTicks:{show:false}, axisBorder:{show:false}},
|
||||
yaxis:{max:100, min:0, tickAmount:4, labels:{formatter:function(v,i){return v.toFixed(0)+' %';}, style:{colors:'<?=$color?>'}}, axisBorder:{show:false}, axisTicks:{show:false}},
|
||||
grid:{show:true, borderColor:'<?=$grid?>'},
|
||||
legend:{show:false}
|
||||
};
|
||||
var options_net = {
|
||||
series:[{name:'receive', data:rxd.slice()},{name:'transmit', data:txd.slice()}],
|
||||
chart:{height:120, type:'line', fontFamily:'clear-sans', animations:{enabled:true, easing:'linear', dynamicAnimation:{speed:980}}, toolbar:{show:false}, zoom:{enabled:false},events:{updated:function(){if (netchart.customData.updateCount == 0) {netchart.customData.animationPending = false}netchart.customData.updateCount++;},animationEnd:function(){netchart.customData.animationPending = false;updateNetChart();}}},
|
||||
dataLabels:{enabled:false},
|
||||
tooltip:{enabled:false},
|
||||
stroke:{curve:'smooth', width:1},
|
||||
colors:['#e22828','#ff8c2f'],
|
||||
markers:{size:0},
|
||||
xaxis:{type:'datetime', range:netline-1, labels:{show:false}, axisTicks:{show:false}, axisBorder:{show:false}},
|
||||
yaxis:{tickAmount:4, labels:{formatter:function(v,i){return autoscale(v,'bps',1);}, style:{colors:'<?=$color?>'}}, axisBorder:{show:false}, axisTicks:{show:false}},
|
||||
grid:{show:true, borderColor:'<?=$grid?>'},
|
||||
legend:{show:false}
|
||||
};
|
||||
// Helper function to calculate millisPerPixel based on container width
|
||||
function getMillisPerPixel(timeInSeconds, containerId) {
|
||||
var container = document.getElementById(containerId);
|
||||
var width = (container && container.offsetWidth > 0) ? container.offsetWidth : 600; // fallback to 600 if not found or zero width
|
||||
return (timeInSeconds * 1000) / width;
|
||||
}
|
||||
|
||||
var cpuchart = new ApexCharts(document.querySelector('#cpuchart'), options_cpu);
|
||||
var netchart = new ApexCharts(document.querySelector('#netchart'), options_net);
|
||||
// SmoothieCharts initialization
|
||||
var cpuchart = new SmoothieChart({
|
||||
millisPerPixel: (cpuline * 1000) / 600, // Safe initial value
|
||||
minValue: -1,
|
||||
maxValue: 100,
|
||||
responsive: true,
|
||||
grid: {
|
||||
strokeStyle: '<?=$grid?>',
|
||||
fillStyle: 'transparent',
|
||||
lineWidth: 1,
|
||||
millisPerLine: 5000,
|
||||
verticalSections: 4
|
||||
},
|
||||
labels: {
|
||||
fillStyle: '<?=$color?>',
|
||||
fontSize: 11,
|
||||
precision: 0
|
||||
},
|
||||
timestampFormatter: function(date) { return ''; },
|
||||
minValueScale: 1.02,
|
||||
maxValueScale: 1.02,
|
||||
yMinFormatter: function(value) {
|
||||
return Math.max(0, Math.floor(value)) + ' %';
|
||||
},
|
||||
yMaxFormatter: function(value) {
|
||||
return Math.max(0, Math.min(100, Math.ceil(value))) + ' %';
|
||||
}
|
||||
});
|
||||
|
||||
// Add custom global variable to ncharts (won't affect ApexCharts functionality)
|
||||
var netchart = new SmoothieChart({
|
||||
millisPerPixel: (netline * 1000) / 600, // Safe initial value
|
||||
minValue: 0,
|
||||
responsive: true,
|
||||
grid: {
|
||||
strokeStyle: '<?=$grid?>',
|
||||
fillStyle: 'transparent',
|
||||
lineWidth: 1,
|
||||
millisPerLine: 5000,
|
||||
verticalSections: 4
|
||||
},
|
||||
labels: {
|
||||
fillStyle: '<?=$color?>',
|
||||
fontSize: 11,
|
||||
placement: 'left'
|
||||
},
|
||||
timestampFormatter: function(date) { return ''; },
|
||||
minValueScale: 1.02,
|
||||
maxValueScale: 1.02,
|
||||
yMaxFormatter: function(value) {
|
||||
if (value >= 1000) {
|
||||
return Math.floor(value / 1000) + ' Mbps';
|
||||
}
|
||||
return Math.floor(value) + ' kbps';
|
||||
},
|
||||
yMinFormatter: function(value) {
|
||||
return Math.floor(value) + ' kbps';
|
||||
}
|
||||
});
|
||||
|
||||
// Create TimeSeries for CPU and Network data
|
||||
var cpuTimeSeries = new TimeSeries();
|
||||
var rxTimeSeries = new TimeSeries();
|
||||
var txTimeSeries = new TimeSeries();
|
||||
|
||||
// Add TimeSeries to charts with colors
|
||||
cpuchart.addTimeSeries(cpuTimeSeries, { strokeStyle: '#ff8c2f', lineWidth: 1 });
|
||||
netchart.addTimeSeries(rxTimeSeries, { strokeStyle: '#e22828', lineWidth: 1 });
|
||||
netchart.addTimeSeries(txTimeSeries, { strokeStyle: '#ff8c2f', lineWidth: 1 });
|
||||
|
||||
// Custom data for chart state management
|
||||
netchart.customData = {
|
||||
isVisible: false,
|
||||
BrowserVisibility: true,
|
||||
animationPending: false,
|
||||
netAnimationInterval: null,
|
||||
newData: false,
|
||||
updateCount: 0,
|
||||
initialized: false
|
||||
isVisible: false
|
||||
};
|
||||
|
||||
cpuchart.customData = {
|
||||
isVisible: false,
|
||||
BrowserVisibility: true,
|
||||
animationPending: false,
|
||||
cpuAnimationInterval: null,
|
||||
coresVisible: false,
|
||||
newData: false,
|
||||
updateCount: 0,
|
||||
initialized: false
|
||||
cpuData: null,
|
||||
cpuLoad: 0
|
||||
};
|
||||
|
||||
$(function() {
|
||||
@@ -1495,9 +1537,9 @@ $(function() {
|
||||
entries.forEach(entry => {
|
||||
if (entry.target === netchartElement) {
|
||||
netchart.customData.isVisible = entry.isIntersecting;
|
||||
// Reset the update count as the chart doesn't always fire animationEnd in this case
|
||||
// Chart visibility changed
|
||||
if (netchart.customData.isVisible) {
|
||||
resetNetUpdateCount();
|
||||
// SmoothieCharts handles visibility automatically
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1520,9 +1562,9 @@ $(function() {
|
||||
entries.forEach(entry => {
|
||||
if (entry.target === cpuchartElement) {
|
||||
cpuchart.customData.isVisible = entry.isIntersecting;
|
||||
// Reset the update count as the chart doesn't always fire animationEnd in this case
|
||||
// Chart visibility changed
|
||||
if (cpuchart.customData.isVisible) {
|
||||
resetCPUUpdateCount();
|
||||
// SmoothieCharts handles visibility automatically
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1588,8 +1630,9 @@ let viewportResizeTimeout;
|
||||
window.addEventListener('resize', function(){
|
||||
clearTimeout(viewportResizeTimeout);
|
||||
viewportResizeTimeout = setTimeout(function(){
|
||||
resetNetUpdateCount();
|
||||
resetCPUUpdateCount();
|
||||
// Update millisPerPixel based on new container sizes
|
||||
cpuchart.options.millisPerPixel = getMillisPerPixel(cpuline, 'cpuchart');
|
||||
netchart.options.millisPerPixel = getMillisPerPixel(netline, 'netchart');
|
||||
}, 250); // Wait 250ms after resize stops before executing
|
||||
});
|
||||
|
||||
@@ -1627,80 +1670,45 @@ function sanitizeMultiCookie(cookieName, delimiter, removeDuplicates=false) {
|
||||
}
|
||||
|
||||
function initCharts(clear) {
|
||||
// initialize graphs entries
|
||||
var data = [];
|
||||
data.cpu = data.rxd = data.txd = "";
|
||||
|
||||
var now = new Date().getTime();
|
||||
if (!clear) {
|
||||
var c = data.cpu.split(';');
|
||||
var r = data.rxd.split(';');
|
||||
var t = data.txd.split(';');
|
||||
for (var i=0; i < cpuline; i++) {
|
||||
var x = now + i;
|
||||
var y = c[i]||0; cpu.push({x,y});
|
||||
}
|
||||
cputime = x + 1;
|
||||
} else {
|
||||
// clear network graph
|
||||
var r = ''; var t = '';
|
||||
rxd = []; txd = [];
|
||||
// SmoothieCharts manages its own data through TimeSeries
|
||||
if (clear) {
|
||||
// Clear the TimeSeries data if needed
|
||||
rxTimeSeries.clear();
|
||||
txTimeSeries.clear();
|
||||
}
|
||||
for (var i=0; i < netline; i++) {
|
||||
var x = now + i;
|
||||
var y = r[i]||0; rxd.push({x,y});
|
||||
var y = t[i]||0; txd.push({x,y});
|
||||
}
|
||||
nettime = x + 1;
|
||||
}
|
||||
|
||||
function resetCharts() {
|
||||
// prevent unlimited graph growing - limit to 300 (5 minutes) of data
|
||||
cpu = cpu.slice(-300);
|
||||
rxd = rxd.slice(-300);
|
||||
txd = txd.slice(-300);
|
||||
// SmoothieCharts automatically manages data retention
|
||||
// No manual cleanup needed
|
||||
}
|
||||
|
||||
function addChartCpu(load) {
|
||||
cputime++;
|
||||
var i = cpu.length - cpuline;
|
||||
if (i > 0) { // clear value outside graph
|
||||
i = i - 1;
|
||||
cpu[i].x = cputime - cpuline;
|
||||
cpu[i].y = 0;
|
||||
}
|
||||
cpu.push({x:cputime, y:load});
|
||||
// Add data point to SmoothieCharts TimeSeries
|
||||
cpuTimeSeries.append(new Date().getTime(), load);
|
||||
}
|
||||
|
||||
function addChartNet(rx, tx) {
|
||||
nettime++;
|
||||
var i = rxd.length - netline;
|
||||
if (i > 0) { // clear value outside graph
|
||||
i = i - 1;
|
||||
rxd[i].x = nettime - netline;
|
||||
rxd[i].y = 0;
|
||||
txd[i].x = nettime - netline;
|
||||
txd[i].y = 0;
|
||||
}
|
||||
rxd.push({x:nettime, y:rx});
|
||||
txd.push({x:nettime, y:tx});
|
||||
// Add data points to SmoothieCharts TimeSeries (convert bps to kbps)
|
||||
var now = new Date().getTime();
|
||||
rxTimeSeries.append(now, Math.floor(rx / 1000));
|
||||
txTimeSeries.append(now, Math.floor(tx / 1000));
|
||||
}
|
||||
|
||||
function resetCPUUpdateCount() {
|
||||
cpuchart.customData.updateCount = 0;
|
||||
cpuchart.customData.animationPending = false;
|
||||
}
|
||||
// Cache for last values to avoid unnecessary DOM updates
|
||||
var lastCpuValues = {
|
||||
load: -1,
|
||||
color: '',
|
||||
fontColor: '',
|
||||
coreValues: {}
|
||||
};
|
||||
|
||||
function resetNetUpdateCount() {
|
||||
netchart.customData.updateCount = 0;
|
||||
netchart.customData.animationPending = false;
|
||||
}
|
||||
function updateCPUBarCharts() {
|
||||
if (!isPageVisible()) {
|
||||
return;
|
||||
}
|
||||
// prevent an initial JS error if the first datapoint isn't available yet
|
||||
// (cpuchart.customData.newData is reset by the updateCPUChart function so can't be used)
|
||||
// Update CPU bar charts based on current data
|
||||
const customData = cpuchart.customData;
|
||||
if (!customData.cpuData?.cpus || typeof customData.cpuLoad === 'undefined') {
|
||||
return;
|
||||
@@ -1709,90 +1717,91 @@ function updateCPUBarCharts() {
|
||||
const cpuLoad = customData.cpuLoad;
|
||||
const critical = <?=$display['critical']?>;
|
||||
const warning = <?=$display['warning']?>;
|
||||
|
||||
// Cache DOM elements and calculations for overall CPU load
|
||||
const cpuLoadText = cpuLoad + '%';
|
||||
const cpuLoadColor = setColor(cpuLoad, critical, warning);
|
||||
const cpuLoadFontColor = fontColor(cpuLoad, critical, warning);
|
||||
|
||||
// Batch DOM updates for overall CPU load
|
||||
const $cpuElements = $('.cpu_, .cpu');
|
||||
const $cpuAliveElements = $('#cpu_, #cpu');
|
||||
|
||||
$cpuElements.text(cpuLoadText).css({'color': cpuLoadFontColor});
|
||||
$cpuAliveElements.alive(cpuLoadText, cpuLoadColor);
|
||||
|
||||
// Only update DOM if values have changed
|
||||
if (cpuLoad !== lastCpuValues.load) {
|
||||
const cpuLoadText = cpuLoad + '%';
|
||||
const cpuLoadColor = setColor(cpuLoad, critical, warning);
|
||||
const cpuLoadFontColor = fontColor(cpuLoad, critical, warning);
|
||||
|
||||
// Batch DOM updates for overall CPU load
|
||||
const $cpuElements = $('.cpu_, .cpu');
|
||||
const $cpuAliveElements = $('#cpu_, #cpu');
|
||||
|
||||
$cpuElements.text(cpuLoadText).css({'color': cpuLoadFontColor});
|
||||
|
||||
// Only call alive() if color actually changed
|
||||
//if (cpuLoadColor !== lastCpuValues.color) {
|
||||
$cpuAliveElements.alive(cpuLoadText, cpuLoadColor);
|
||||
lastCpuValues.color = cpuLoadColor;
|
||||
// }
|
||||
|
||||
lastCpuValues.load = cpuLoad;
|
||||
lastCpuValues.fontColor = cpuLoadFontColor;
|
||||
}
|
||||
|
||||
// Update individual CPU cores if they are visible
|
||||
if (customData.coresVisible) {
|
||||
const cpus = customData.cpuData.cpus;
|
||||
|
||||
|
||||
// Batch DOM updates for CPU cores
|
||||
const cpuCoreUpdates = [];
|
||||
const cpuAliveUpdates = [];
|
||||
|
||||
|
||||
cpus.forEach((cpuCore, index) => {
|
||||
const coreLoad = Math.round(cpuCore.percentTotal);
|
||||
const coreLoadText = coreLoad + '%';
|
||||
const coreColor = setColor(coreLoad, critical, warning);
|
||||
const coreFontColor = fontColor(coreLoad, critical, warning);
|
||||
|
||||
cpuCoreUpdates.push({
|
||||
selector: '.cpu' + index,
|
||||
text: coreLoadText,
|
||||
color: coreFontColor
|
||||
const coreLoad = Math.floor(cpuCore.percentTotal);
|
||||
|
||||
// Only process if value changed
|
||||
if (!lastCpuValues.coreValues[index] || lastCpuValues.coreValues[index].load !== coreLoad) {
|
||||
const coreLoadText = coreLoad + '%';
|
||||
const coreColor = setColor(coreLoad, critical, warning);
|
||||
const coreFontColor = fontColor(coreLoad, critical, warning);
|
||||
|
||||
cpuCoreUpdates.push({
|
||||
selector: '.cpu' + index,
|
||||
text: coreLoadText,
|
||||
color: coreFontColor
|
||||
});
|
||||
|
||||
// Only update alive() if color changed
|
||||
const lastCore = lastCpuValues.coreValues[index] || {};
|
||||
//if (coreColor !== lastCore.color) {
|
||||
cpuAliveUpdates.push({
|
||||
selector: '#cpu' + index,
|
||||
text: coreLoadText,
|
||||
color: coreColor
|
||||
});
|
||||
lastCore.color = coreColor;
|
||||
// }
|
||||
|
||||
// Update cache
|
||||
lastCpuValues.coreValues[index] = {
|
||||
load: coreLoad,
|
||||
color: coreColor,
|
||||
fontColor: coreFontColor
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Apply batch updates only for changed values
|
||||
if (cpuCoreUpdates.length > 0) {
|
||||
cpuCoreUpdates.forEach(update => {
|
||||
$(update.selector).text(update.text).css({'color': update.color});
|
||||
});
|
||||
|
||||
cpuAliveUpdates.push({
|
||||
selector: '#cpu' + index,
|
||||
text: coreLoadText,
|
||||
color: coreColor
|
||||
}
|
||||
|
||||
if (cpuAliveUpdates.length > 0) {
|
||||
cpuAliveUpdates.forEach(update => {
|
||||
$(update.selector).alive(update.text, update.color);
|
||||
});
|
||||
});
|
||||
|
||||
// Apply all CPU core updates in batches
|
||||
cpuCoreUpdates.forEach(update => {
|
||||
$(update.selector).text(update.text).css({'color': update.color});
|
||||
});
|
||||
|
||||
cpuAliveUpdates.forEach(update => {
|
||||
$(update.selector).alive(update.text, update.color);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateCPUChart() {
|
||||
if (!cpuchart.customData.newData || !cpuchart.customData.isVisible || !isPageVisible()) {
|
||||
return;
|
||||
}
|
||||
// Check if the animation is still running and a timeout hasn't been set
|
||||
if (!cpuchart.customData.animationPending) {
|
||||
// No animation running. Clear out the timeout and update the chart
|
||||
cpuchart.customData.animationPending = true;
|
||||
cpuchart.customData.newData = false;
|
||||
cpuchart.updateSeries([{data:cpu}]);
|
||||
}
|
||||
}
|
||||
|
||||
function updateNetChart() {
|
||||
if (!netchart.customData.newData || !netchart.customData.isVisible || !isPageVisible()) {
|
||||
return;
|
||||
}
|
||||
// Check if the animation is still running and a timeout hasn't been set
|
||||
if (!netchart.customData.animationPending) {
|
||||
// No animation running. Clear out the timeout and update the chart
|
||||
netchart.customData.animationPending = true;
|
||||
netchart.customData.newData = false;
|
||||
netchart.updateSeries([{data:rxd},{data:txd}]);
|
||||
}
|
||||
}
|
||||
|
||||
function isPageVisible() {
|
||||
// Check if charts are good to go
|
||||
if (netchart.customData.initialized && cpuchart.customData.initialized) {
|
||||
return !document.hidden;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// SmoothieCharts handles visibility automatically
|
||||
return !document.hidden;
|
||||
}
|
||||
|
||||
<?if ($wireguard):?>
|
||||
@@ -1905,21 +1914,23 @@ function changeView(item) {
|
||||
}
|
||||
|
||||
function changeCPUline(val) {
|
||||
cpuline = val;
|
||||
cpuline = parseInt(val);
|
||||
if (val==30) delete cookie.cpuline; else cookie.cpuline = val;
|
||||
saveCookie();
|
||||
// Reset the update count as the chart doesn't always fire animationEnd
|
||||
resetCPUUpdateCount();
|
||||
cpuchart.updateOptions({xaxis:{range:cpuline-1}});
|
||||
// Update chart range
|
||||
// Update SmoothieChart time window based on actual chart width
|
||||
cpuchart.options.millisPerPixel = getMillisPerPixel(cpuline, 'cpuchart');
|
||||
// No need to stop and restart, just update the option
|
||||
}
|
||||
|
||||
function changeNetline(val) {
|
||||
netline = val;
|
||||
netline = parseInt(val);
|
||||
if (val==30) delete cookie.netline; else cookie.netline = val;
|
||||
saveCookie();
|
||||
// Reset the update count as the chart doesn't always fire animationEnd
|
||||
resetNetUpdateCount();
|
||||
netchart.updateOptions({xaxis:{range:netline-1}});
|
||||
// Update chart range
|
||||
// Update SmoothieChart time window based on actual chart width
|
||||
netchart.options.millisPerPixel = getMillisPerPixel(netline, 'netchart');
|
||||
// No need to stop and restart, just update the option
|
||||
}
|
||||
|
||||
function smartMenu(table) {
|
||||
@@ -2585,20 +2596,41 @@ dashboard.on('message',function(msg,meta) {
|
||||
// rx & tx speeds
|
||||
for (let i=0,port; port=get.port[i]; i++) {
|
||||
if (port[0] == port_select) {
|
||||
$('#inbound').text(port[1]);
|
||||
$('#outbound').text(port[2]);
|
||||
// Cache for network values to avoid unnecessary DOM updates
|
||||
if (!window.lastNetValues) window.lastNetValues = { inbound: '', outbound: '' };
|
||||
|
||||
// Only update DOM if values changed
|
||||
if (port[1] !== window.lastNetValues.inbound) {
|
||||
$('#inbound').text(port[1]);
|
||||
window.lastNetValues.inbound = port[1];
|
||||
}
|
||||
if (port[2] !== window.lastNetValues.outbound) {
|
||||
$('#outbound').text(port[2]);
|
||||
window.lastNetValues.outbound = port[2];
|
||||
}
|
||||
|
||||
// update the netchart but only send to ApexCharts if the chart is visible
|
||||
addChartNet(port[3], port[4]);
|
||||
netchart.customData.newData = true;
|
||||
updateNetChart();
|
||||
|
||||
addChartNet(port[3], port[4]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
// port counters
|
||||
for (let k=0; k < get.mode.length; k++) $('#main'+k).html(get.mode[k]);
|
||||
for (let k=0; k < get.rxtx.length; k++) $('#port'+k).html(get.rxtx[k]);
|
||||
for (let k=0; k < get.stat.length; k++) $('#link'+k).html(get.stat[k]);
|
||||
// port counters - batch check for changes
|
||||
for (let k=0; k < get.mode.length; k++) {
|
||||
const $elem = $('#main'+k);
|
||||
const newVal = get.mode[k];
|
||||
if ($elem.html() !== newVal) $elem.html(newVal);
|
||||
}
|
||||
for (let k=0; k < get.rxtx.length; k++) {
|
||||
const $elem = $('#port'+k);
|
||||
const newVal = get.rxtx[k];
|
||||
if ($elem.html() !== newVal) $elem.html(newVal);
|
||||
}
|
||||
for (let k=0; k < get.stat.length; k++) {
|
||||
const $elem = $('#link'+k);
|
||||
const newVal = get.stat[k];
|
||||
if ($elem.html() !== newVal) $elem.html(newVal);
|
||||
}
|
||||
// current date and time
|
||||
$('#current_time').html(get.time[0]);
|
||||
$('#current_time_').html(get.time[0]);
|
||||
@@ -2653,8 +2685,15 @@ $(function() {
|
||||
|
||||
initCharts();
|
||||
|
||||
cpuchart.render();
|
||||
netchart.render();
|
||||
// Start SmoothieCharts streaming
|
||||
cpuchart.streamTo(document.getElementById('cpuchart'), 1000);
|
||||
netchart.streamTo(document.getElementById('netchart'), 1000);
|
||||
|
||||
// Set millisPerPixel after DOM is ready and charts are attached
|
||||
setTimeout(function() {
|
||||
cpuchart.options.millisPerPixel = getMillisPerPixel(cpuline, 'cpuchart');
|
||||
netchart.options.millisPerPixel = getMillisPerPixel(netline, 'netchart');
|
||||
}, 100);
|
||||
|
||||
addProperties();
|
||||
<?if ($group):?>
|
||||
@@ -2686,13 +2725,11 @@ $(function() {
|
||||
next: (result) => {
|
||||
if (result.data?.systemMetricsCpu) {
|
||||
cpuchart.customData.cpuData = result.data.systemMetricsCpu;
|
||||
cpuchart.customData.cpuLoad = Math.round(cpuchart.customData.cpuData.percentTotal);
|
||||
cpuchart.customData.cpuLoad = Math.floor(cpuchart.customData.cpuData.percentTotal);
|
||||
|
||||
//update cpu chart data
|
||||
addChartCpu(cpuchart.customData.cpuLoad);
|
||||
cpuchart.customData.newData = true;
|
||||
//update cpu chart data
|
||||
addChartCpu(cpuchart.customData.cpuLoad);
|
||||
updateCPUBarCharts();
|
||||
updateCPUChart();
|
||||
|
||||
}
|
||||
},
|
||||
@@ -2731,8 +2768,7 @@ $(function() {
|
||||
|
||||
// Inhibit chart updates until DOM quiets down
|
||||
setTimeout(function() {
|
||||
netchart.customData.initialized = true;
|
||||
cpuchart.customData.initialized = true;
|
||||
// Charts initialized
|
||||
},500);
|
||||
|
||||
// Cleanup GraphQL subscription on page unload
|
||||
@@ -2743,4 +2779,4 @@ $(function() {
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -876,7 +876,7 @@ _(Spin down delay)_:
|
||||
<?endif;?>
|
||||
<?if (diskType('Data') || (isPool($tag) && !isSubpool($tag))):?>
|
||||
_(File system status)_:
|
||||
: <?=_(_var($disk, 'fsStatus'))?>
|
||||
: <?=_(_var($disk, 'fsStatus'))?><?=(_var($disk,'fsStatus')==="Mounted")? (_var($disk,'fsEmpty')==="yes"? ", empty" : ", not empty"):""?>
|
||||
|
||||
<?if ($fsTypeImmutable):?>
|
||||
_(File system type)_:
|
||||
@@ -912,7 +912,6 @@ _(File system type)_:
|
||||
echo get_inline_fs_warnings($disk);
|
||||
?>
|
||||
<?endif;?>
|
||||
|
||||
<?if (diskType('Data') || isPool($tag)):?>
|
||||
<div markdown="1" id="profile">
|
||||
_(Allocation profile)_:
|
||||
|
||||
@@ -110,15 +110,16 @@ to the system log.*
|
||||
*Use* **Anonymize diagnostics** *when publishing the diagnostics file in the public forum. In private communication with Limetech it is recommended to uncheck this setting and capture all information unaltered.*
|
||||
:end
|
||||
|
||||
<br>
|
||||
|
||||
<label for="anonymize">
|
||||
<input type="checkbox" id="anonymize" checked>
|
||||
_(Anonymize diagnostics)_
|
||||
</label>
|
||||
|
||||
<span class="inline-block">
|
||||
<input id='download' type="button" value="_(Download)_" onclick="$(this).attr('disabled',true);diagnostics(zipfile())">
|
||||
<input type="button" value="_(Done)_" onclick="done()">
|
||||
</span>
|
||||
<div class="flex flex-col gap-4 items-start">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<label for="anonymize" class="inline-flex flex-row items-center gap-2">
|
||||
<input type="checkbox" id="anonymize" checked>
|
||||
<span>_(Anonymize diagnostics)_</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<input id='download' type="button" value="_(Download)_" onclick="$(this).attr('disabled',true);diagnostics(zipfile())">
|
||||
<input type="button" value="_(Done)_" onclick="done()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,30 +62,38 @@ effect of making it ***impossible*** to rebuild an existing failed drive - you h
|
||||
<input type="hidden" name="preserveArray" value="yes" disabled>
|
||||
<input type="hidden" name="preserveCache" value="yes" disabled>
|
||||
|
||||
_(Preserve current assignments)_:
|
||||
: <select id="s1" name="preset" multiple="multiple" style="display:none">
|
||||
<option value=''>_(All)_</option>
|
||||
<?=mk_option_check(0,'array',_('Array slots'))?>
|
||||
<?=mk_option_check(0,'cache',_('Pool slots'))?>
|
||||
</select>
|
||||
<div class="flex flex-col gap-4 items-start">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<span class="strong">_(Preserve current assignments)_:</span>
|
||||
<div class="inline-block">
|
||||
<select id="s1" name="preset" multiple="multiple" class="hidden">
|
||||
<option value=''>_(All)_</option>
|
||||
<?=mk_option_check(0,'array',_('Array slots'))?>
|
||||
<?=mk_option_check(0,'cache',_('Pool slots'))?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
: <span class="inline-block">
|
||||
<?if ($newarray):?>
|
||||
_(Array has been **Reset**)_ (_(please configure)_)
|
||||
<?elseif ($disabled):?>
|
||||
_(Array must be **Stopped** to change)_
|
||||
<?else:?>
|
||||
<label>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<?if ($newarray) {?>
|
||||
<span>
|
||||
_(Array has been **Reset**)_ (_(please configure)_)
|
||||
</span>
|
||||
<?} elseif ($disabled) {?>
|
||||
<span>
|
||||
_(Array must be **Stopped** to change)_
|
||||
</span>
|
||||
<?} else {?>
|
||||
<label class="flex flex-row items-center gap-2">
|
||||
<input type="checkbox" onClick="cmdInit.disabled=!this.checked">
|
||||
_(Yes, I want to do this)_
|
||||
<span>_(Yes, I want to do this)_</span>
|
||||
</label>
|
||||
<?endif;?>
|
||||
</span>
|
||||
<?}?>
|
||||
</div>
|
||||
|
||||
|
||||
: <span class="inline-block">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<input type="submit" class="lock" name="cmdInit" value="_(Apply)_" disabled>
|
||||
<input type="button" class="lock" value="_(Done)_" onclick="done()">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -91,36 +91,50 @@ Closing the window before completion will terminate the background process - so
|
||||
Note that this tool may negatively affect any docker containers if you allow your **appdata** share to be included.
|
||||
:end
|
||||
|
||||
<form method="POST" action="/update.htm" target="progressFrame">
|
||||
<span class="block">
|
||||
<select name="select" onchange="selection(this.value,true)">
|
||||
<?=mk_option(0,"0","_(Disks)_")?>
|
||||
<?=mk_option(0,"1","_(Shares)_")?>
|
||||
</select>
|
||||
</span>
|
||||
<form markdown="1" method="POST" action="/update.htm" target="progressFrame">
|
||||
<div class="flex flex-col gap-4 items-start">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<span class="strong">_(Target)_:</span>
|
||||
<div class="inline-block">
|
||||
<select name="select" onchange="selection(this.value,true)">
|
||||
<?=mk_option(0,"0","_(Disks)_")?>
|
||||
<?=mk_option(0,"1","_(Shares)_")?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="disks" class="block">
|
||||
<select id="s1" name="disks" style="display:none" multiple>
|
||||
<option value=''>_(All)_</option>
|
||||
<?foreach (array_filter($disks,'data_disks') as $disk):?>
|
||||
<?=mk_option(1,"/mnt/{$disk['name']}",_(my_disk($disk['name'])),3)?>
|
||||
<?endforeach;?>
|
||||
</select>
|
||||
</span>
|
||||
<div class="flex flex-col gap-4 items-start">
|
||||
<span class="strong">_(Items)_:</span>
|
||||
<div class="flex flex-col gap-2 items-start">
|
||||
<div id="disks">
|
||||
<select id="s1" name="disks" class="hidden" multiple>
|
||||
<option value=''>_(All)_</option>
|
||||
<?foreach (array_filter($disks,'data_disks') as $disk):?>
|
||||
<?=mk_option(1,"/mnt/{$disk['name']}",_(my_disk($disk['name'])),3)?>
|
||||
<?endforeach;?>
|
||||
</select>
|
||||
</div>
|
||||
<div id="shares" class="hidden">
|
||||
<select id="s2" name="shares" class="hidden" multiple>
|
||||
<option value=''>_(All)_</option>
|
||||
<?uksort($shares,'strnatcasecmp');?>
|
||||
<?foreach ($shares as $share):?>
|
||||
<?=mk_option(1,"/mnt/user/{$share['name']}",$share['name'])?>
|
||||
<?endforeach;?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="shares" class="block" style="display:none">
|
||||
<select id="s2" name="shares" style="display:none" multiple>
|
||||
<option value=''>_(All)_</option>
|
||||
<?uksort($shares,'strnatcasecmp');?>
|
||||
<?foreach ($shares as $share):?>
|
||||
<?=mk_option(1,"/mnt/user/{$share['name']}",$share['name'])?>
|
||||
<?endforeach;?>
|
||||
</select>
|
||||
</span>
|
||||
|
||||
<?if (_var($var,'fsState')=="Started"):?>
|
||||
<div><input type="button" value="_(Start)_" onclick="setNewPerms(this.form)"><input type="button" value="_(Done)_" class="lock" onclick="done()"></div>
|
||||
<?else:?>
|
||||
<div><input type="button" value="_(Start)_" disabled><input type="button" value="_(Done)_" class="lock" onclick="done()">_(Array must be **Started** to change permissions)_.</div>
|
||||
<?endif;?>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<?if (_var($var,'fsState')=="Started"):?>
|
||||
<input type="button" value="_(Start)_" onclick="setNewPerms(this.form)">
|
||||
<input type="button" value="_(Done)_" class="lock" onclick="done()">
|
||||
<?else:?>
|
||||
<input type="button" value="_(Start)_" disabled>
|
||||
<input type="button" value="_(Done)_" class="lock" onclick="done()">
|
||||
<span>_(Array must be **Started** to change permissions)_.</span>
|
||||
<?endif;?>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -31,7 +31,7 @@ _(Read settings from)_ <i class="fa fa-arrow-left fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name) echo mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name) echo mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name) echo mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
@@ -48,7 +48,7 @@ _(Write settings to)_ <i class="fa fa-arrow-right fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name) $rows[] = mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name) $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name) $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
if ($rows) echo "<option>("._('All').")</option>";
|
||||
foreach ($rows as $row) echo $row;
|
||||
|
||||
@@ -34,7 +34,7 @@ _(Read settings from)_ <i class="fa fa-arrow-left fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name) echo mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name) echo mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name) echo mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
@@ -51,7 +51,7 @@ _(Write settings to)_ <i class="fa fa-arrow-right fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name) $rows[] = mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name) $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name) $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
if ($rows) echo "<option>("._('All').")</option>";
|
||||
foreach ($rows as $row) echo $row;
|
||||
@@ -154,7 +154,7 @@ _(Read settings from)_ <i class="fa fa-arrow-left fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='secure') echo mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='secure') echo mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name && $sec[$list['name']]['security']=='secure') echo mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
@@ -171,7 +171,7 @@ _(Write settings to)_ <i class="fa fa-arrow-right fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='secure') $rows[] = mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='secure') $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name && $sec[$list['name']]['security']=='secure') $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
if ($rows) echo "<option>("._('All').")</option>";
|
||||
foreach ($rows as $row) echo $row;
|
||||
@@ -217,7 +217,7 @@ _(Read settings from)_ <i class="fa fa-arrow-left fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='private') echo mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='private') echo mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name && $sec[$list['name']]['security']=='private') echo mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
@@ -234,7 +234,7 @@ _(Write settings to)_ <i class="fa fa-arrow-right fa-fw"></i>
|
||||
if (isset($disks[$name])) {
|
||||
foreach (array_filter($disks,'clone_list') as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='private') $rows[] = mk_option("", $list['name'], _(my_disk($list['name']),3));
|
||||
} else {
|
||||
foreach ($shares as $list) if ($list['name']!=$name && $sec[$list['name']]['security']=='private') $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
foreach ($shares as $list) if (strpos($list['name'],"'") === false && $list['name']!=$name && $sec[$list['name']]['security']=='private') $rows[] = mk_option("", $list['name'], compress($list['name']));
|
||||
}
|
||||
if ($rows) echo "<option>("._('All').")</option>";
|
||||
foreach ($rows as $row) echo $row;
|
||||
@@ -267,7 +267,7 @@ $(function() {
|
||||
checkShareSettingsSMB(document.smb_edit);
|
||||
initDropdownSMB(false);
|
||||
<?if ($tabbed):?>
|
||||
$('#tab'+$('input[name$="tabs"]').length).bind({click:function(){initDropdownSMB(true);}});
|
||||
$('#tab'+$('.tabs-container button').length).bind({click:function(){initDropdownSMB(true);}});
|
||||
<?endif;?>
|
||||
<?if (count($users)==1):?>
|
||||
toggleButton('readusersmb',true);
|
||||
|
||||
@@ -327,7 +327,7 @@ function direction() {
|
||||
$myDisks = array_filter(array_diff(array_keys(array_filter($disks,'my_disks')), explode(',',$var['shareUserExclude'])), 'globalInclude');
|
||||
|
||||
$filteredShares = array_filter($shares, function($list) use ($name) {
|
||||
return $list['name'] != $name || !$name;
|
||||
return (strpos($list['name'],"'") === false) && ($list['name'] != $name || !$name) ;
|
||||
});
|
||||
?>
|
||||
:share_edit_global1_help:
|
||||
|
||||
@@ -28,6 +28,7 @@ $fileMax = (int) file_get_contents('/proc/sys/fs/file-max');
|
||||
$(function() {
|
||||
$('#s1').dropdownchecklist({emptyText:"_(All)_", width:<?=$width[0]?>, explicitClose:"..._(close)_"});
|
||||
$('#s2').dropdownchecklist({emptyText:"_(None)_", width:<?=$width[0]?>, explicitClose:"..._(close)_"});
|
||||
$('#s3').dropdownchecklist({emptyText:"_(None)_", width:<?=$width[0]?>, explicitClose:"..._(close)_"});
|
||||
presetShare(document.share_settings);
|
||||
});
|
||||
|
||||
@@ -47,6 +48,7 @@ async function prepareShare(form) {
|
||||
item = form.shareUserInclude.options[0];
|
||||
item.value = include;
|
||||
item.selected = true;
|
||||
|
||||
var exclude = '';
|
||||
for (var i=0,item; item=form.shareUserExclude.options[i]; i++) {
|
||||
if (item.selected) {
|
||||
@@ -59,6 +61,18 @@ async function prepareShare(form) {
|
||||
item.value = exclude;
|
||||
item.selected = true;
|
||||
|
||||
var emptying = '';
|
||||
for (var i=0,item; item=form.shareUserEmptying.options[i]; i++) {
|
||||
if (item.selected) {
|
||||
if (emptying.length) emptying += ',';
|
||||
emptying += item.value;
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
item = form.shareUserEmptying.options[0];
|
||||
item.value = emptying;
|
||||
item.selected = true;
|
||||
|
||||
/* Validate file count input against fileMax */
|
||||
try {
|
||||
const fileCountInput = form.querySelector('#file_count');
|
||||
@@ -87,8 +101,10 @@ function presetShare(form,shares) {
|
||||
var onOff = disabled ? 'disable':'enable';
|
||||
form.shareUserInclude.disabled = disabled;
|
||||
form.shareUserExclude.disabled = disabled;
|
||||
form.shareUserEmptying.disabled = disabled;
|
||||
$('#s1').dropdownchecklist(onOff);
|
||||
$('#s2').dropdownchecklist(onOff);
|
||||
$('#s3').dropdownchecklist(onOff);
|
||||
}
|
||||
</script>
|
||||
<form markdown="1" name="share_settings" method="POST" action="/update.htm" target="progressFrame" onsubmit="return prepareShare(this)">
|
||||
@@ -128,6 +144,15 @@ _(Excluded disk(s))_:
|
||||
|
||||
:shares_excluded_disks_help:
|
||||
|
||||
_(Emptying disk(s))_:
|
||||
: <select id="s3" name="shareUserEmptying" multiple="multiple" style="display:none">
|
||||
<?foreach ($disks as $disk):?>
|
||||
<?=mk_option_luks(_var($disk,'name'),_var($var,'shareUserEmptying'),strstr(_var($disk,'fsType'),':',true))?>
|
||||
<?endforeach;?>
|
||||
</select>
|
||||
|
||||
:shares_emptying_disks_help:
|
||||
|
||||
_(Permit exclusive shares)_:
|
||||
: <select name="shareUserExclusive" <?=$disabled?>>
|
||||
<?=mk_option($var['shareUserExclusive'], "no", _('No'))?>
|
||||
|
||||
@@ -41,158 +41,203 @@ SCRIPTNAME=$(basename "$0")
|
||||
LOG="/var/log/notify_${SCRIPTNAME%.*}"
|
||||
|
||||
# for quick test, setup environment to mimic notify script
|
||||
[[ -z "${EVENT}" ]] && EVENT='Unraid Status'
|
||||
[[ -z "${SUBJECT}" ]] && SUBJECT='Notification'
|
||||
[[ -z "${DESCRIPTION}" ]] && DESCRIPTION='No description'
|
||||
[[ -z "${IMPORTANCE}" ]] && IMPORTANCE='normal'
|
||||
[[ -z "${TIMESTAMP}" ]] && TIMESTAMP=$(date +%s)
|
||||
EVENT="${EVENT:-Unraid Status}"
|
||||
SUBJECT="${SUBJECT:-Notification}"
|
||||
DESCRIPTION="${DESCRIPTION:-No description}"
|
||||
IMPORTANCE="${IMPORTANCE:-normal}"
|
||||
CONTENT="${CONTENT:-}"
|
||||
LINK="${LINK:-}"
|
||||
HOSTNAME="${HOSTNAME:-$(hostname)}"
|
||||
TIMESTAMP="${TIMESTAMP:-$(date +%s)}"
|
||||
|
||||
# ensure link has a host
|
||||
if [[ -n "${LINK}" ]] && [[ ${LINK} != http* ]]; then
|
||||
source <(grep "NGINX_DEFAULTURL" /usr/local/emhttp/state/nginx.ini 2>/dev/null)
|
||||
LINK=${NGINX_DEFAULTURL}${LINK}
|
||||
if [[ -r /usr/local/emhttp/state/nginx.ini ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source <(grep "NGINX_DEFAULTURL" /usr/local/emhttp/state/nginx.ini || true)
|
||||
LINK="${NGINX_DEFAULTURL}${LINK}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Discord will not allow links with bare hostname, links must have both hostname and tld or no link at all
|
||||
if [[ -n "${LINK}" ]]; then
|
||||
HOST=$(echo "${LINK}" | cut -d'/' -f3)
|
||||
[[ ${HOST} != *.* ]] && LINK=
|
||||
HOST=$(echo "${LINK}" | cut -d'/' -f3)
|
||||
[[ ${HOST} != *.* ]] && LINK=
|
||||
fi
|
||||
|
||||
# note: there is no default for CONTENT
|
||||
|
||||
# send DESCRIPTION and/or CONTENT. Ignore the default DESCRIPTION.
|
||||
[[ "${DESCRIPTION}" == 'No description' ]] && DESCRIPTION=""
|
||||
FULL_DETAILS=""
|
||||
if [[ -n "${DESCRIPTION}" ]] && [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}\n\n${CONTENT}"
|
||||
FULL_DETAILS="${DESCRIPTION}"$'\n\n'"${CONTENT}"
|
||||
elif [[ -n "${DESCRIPTION}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}"
|
||||
elif [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${CONTENT}"
|
||||
fi
|
||||
# split into 1024 character segments
|
||||
[[ -n "${FULL_DETAILS}" ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"name": "Description",
|
||||
"value": "${FULL_DETAILS:0:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 1024 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:1024:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 2048 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:2048:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/timestamp.html
|
||||
# https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
|
||||
FORMATTED_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\" -d @"${TIMESTAMP}")
|
||||
# split into 1024 character segments
|
||||
DESC_FIELD=""; DESC_FIELD2=""; DESC_FIELD3=""
|
||||
if [[ "${FULL_DETAILS}" ]]; then
|
||||
DESC_FIELD="${FULL_DETAILS:0:1024}"
|
||||
if [[ ${#FULL_DETAILS} -gt 1024 ]]; then
|
||||
DESC_FIELD2="${FULL_DETAILS:1024:1024}"
|
||||
if [[ ${#FULL_DETAILS} -gt 2048 ]]; then
|
||||
DESC_FIELD3="${FULL_DETAILS:2048:1024}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Timestamp ISO 8601 (UTC) für Discord
|
||||
ISO_TS=$(date -u +%Y-%m-%dT%H:%M:%S.000Z -d @"${TIMESTAMP}")
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/thumbnail.html
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/color.html
|
||||
# vary data based on IMPORTANCE
|
||||
if [[ "${IMPORTANCE}" != "normal" ]] && [[ "${IMPORTANCE}" != "warning" ]] && [[ "${IMPORTANCE}" != "alert" ]]; then
|
||||
IMPORTANCE="normal"
|
||||
fi
|
||||
case "${IMPORTANCE}" in
|
||||
normal)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
warning)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-warning.png"
|
||||
COLOR="16747567"
|
||||
;;
|
||||
alert)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-alert.png"
|
||||
COLOR="14821416"
|
||||
[[ -n "${DISCORD_TAG_ID}" && "${DISCORD_TAG_ID}" == "none" ]] && DISCORD_TAG_ID=""
|
||||
if [[ -n "${DISCORD_TAG_ID}" ]]; then
|
||||
# add leading @ if needed
|
||||
[[ "${DISCORD_TAG_ID:0:1}" != "@" ]] && DISCORD_TAG_ID="@${DISCORD_TAG_ID}"
|
||||
# @mentions only work in the "content" area, not the "embed" area
|
||||
DISCORD_CONTENT_AREA="\"content\": \"<${DISCORD_TAG_ID}>\","
|
||||
fi
|
||||
normal)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
warning)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-warning.png"
|
||||
COLOR="16747567"
|
||||
;;
|
||||
alert)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-alert.png"
|
||||
COLOR="14821416"
|
||||
;;
|
||||
*)
|
||||
IMPORTANCE="normal"
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
esac
|
||||
|
||||
# @mentions only for alert
|
||||
CONTENT_AREA=""
|
||||
if [[ "${IMPORTANCE}" == "alert" ]]; then
|
||||
if [[ -n "${DISCORD_TAG_ID}" && "${DISCORD_TAG_ID}" != "none" ]]; then
|
||||
id="${DISCORD_TAG_ID}"
|
||||
# Strip surrounding angle brackets if present
|
||||
[[ "$id" == \<*\> ]] && id="${id:1:${#id}-2}"
|
||||
# If it already starts with @, @! or @& keep it; else prefix @ (user)
|
||||
[[ "$id" =~ ^@(|!|&)[0-9]+$ ]] || id="@${id}"
|
||||
CONTENT_AREA="<${id}>"
|
||||
fi
|
||||
fi
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/author.html
|
||||
# if SERVER_ICON is defined, use it
|
||||
[[ -n "${SERVER_ICON}" && "${SERVER_ICON:0:8}" == "https://" ]] && ICON_URL="\"icon_url\": \"${SERVER_ICON}\","
|
||||
ICON_URL=""
|
||||
if [[ -n "${SERVER_ICON}" && "${SERVER_ICON}" == "https://"* ]]; then
|
||||
ICON_URL="$SERVER_ICON"
|
||||
fi
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/url.html
|
||||
# if LINK is defined, use it
|
||||
[[ -n "${LINK}" ]] && LINK_URL="\"url\": \"${LINK}\","
|
||||
# shellcheck disable=SC2016
|
||||
jq_filter='
|
||||
# basic object
|
||||
{
|
||||
embeds: [
|
||||
{
|
||||
title: $event | tostring,
|
||||
description: $subject | tostring,
|
||||
timestamp: $ts | tostring,
|
||||
color: ($color | tonumber),
|
||||
author: {
|
||||
name: $hostname
|
||||
},
|
||||
thumbnail: { url: $thumb },
|
||||
fields: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
DATA=$(
|
||||
cat <<EOF
|
||||
{
|
||||
${DISCORD_CONTENT_AREA}
|
||||
"embeds": [
|
||||
{
|
||||
"title": "${EVENT:0:256}",
|
||||
"description": "${SUBJECT:0:2043}",
|
||||
${LINK_URL}
|
||||
"timestamp": ${FORMATTED_TIMESTAMP},
|
||||
"color": "${COLOR}",
|
||||
"author": {
|
||||
${ICON_URL}
|
||||
"name": "${HOSTNAME}"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "${THUMBNAIL}"
|
||||
},
|
||||
"fields": [
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Priority",
|
||||
"value": "${IMPORTANCE}",
|
||||
"inline": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
# Optional: content (Mentions)
|
||||
| if ($content_area | length) > 0 then . + {content: $content_area} else . end
|
||||
|
||||
# Optional: URL
|
||||
| if ($link | length) > 0 then .embeds[0].url = $link else . end
|
||||
|
||||
# Optional: icon_url
|
||||
| if ($icon | length) > 0 then .embeds[0].author.icon_url = $icon else . end
|
||||
|
||||
# Description
|
||||
| .embeds[0].fields += [ { name: "Description", value: $desc_field } ]
|
||||
| if ($desc_field2 | length) > 0 then .embeds[0].fields += [ { name: "Description (cont)", value: $desc_field2 } ] else . end
|
||||
| if ($desc_field3 | length) > 0 then .embeds[0].fields += [ { name: "Description (cont)", value: $desc_field3 } ] else . end
|
||||
|
||||
# Priority
|
||||
| .embeds[0].fields += [ { name: "Priority", value: $importance, inline: true } ]
|
||||
'
|
||||
|
||||
# create valid json
|
||||
args=(
|
||||
-n
|
||||
--arg event "${EVENT:0:256}"
|
||||
--arg subject "${SUBJECT:0:2043}"
|
||||
--arg ts "$ISO_TS"
|
||||
--arg color "$COLOR"
|
||||
--arg hostname "$HOSTNAME"
|
||||
--arg thumb "$THUMBNAIL"
|
||||
--arg link "$LINK"
|
||||
--arg icon "$ICON_URL"
|
||||
--arg importance "$IMPORTANCE"
|
||||
--arg desc_field "$DESC_FIELD"
|
||||
--arg desc_field2 "$DESC_FIELD2"
|
||||
--arg desc_field3 "$DESC_FIELD3"
|
||||
--arg content_area "${CONTENT_AREA}"
|
||||
"$jq_filter"
|
||||
)
|
||||
|
||||
# echo "${DATA}" >>"${LOG}"
|
||||
json=$(jq "${args[@]}" 2>&1)
|
||||
jq_status=$?
|
||||
if [[ "$jq_status" -ne 0 ]]; then
|
||||
echo "jq error $json" >>"$LOG"
|
||||
logger -t "$SCRIPTNAME" -- "Failed sending notification ($json)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# try several times in case we are being rate limited
|
||||
# this is not foolproof, messages can still be rejected
|
||||
args=(
|
||||
-s
|
||||
-X POST "$WEBH_URL"
|
||||
-H 'Content-Type: application/json'
|
||||
--data-binary "$json"
|
||||
)
|
||||
MAX=4
|
||||
for ((i = 1; i <= "${MAX}"; i++)); do
|
||||
RET=$(
|
||||
curl -s -X "POST" "$WEBH_URL" -H 'Content-Type: application/json' --data-ascii @- <<EOF
|
||||
${DATA}
|
||||
EOF
|
||||
)
|
||||
for ((i=1; i<=MAX; i++)); do
|
||||
ret=$(curl "${args[@]}")
|
||||
|
||||
# if nothing was returned, message was successfully sent. exit loop
|
||||
[[ -z "${RET}" ]] && break
|
||||
# log the attempt
|
||||
if [[ -z "$ret" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
{
|
||||
date
|
||||
echo "attempt ${i} of ${MAX} failed"
|
||||
echo "${RET}"
|
||||
} >>"${LOG}"
|
||||
echo "attempt $i of $MAX failed"
|
||||
echo "$ret"
|
||||
} >>"$LOG"
|
||||
|
||||
# if there was an error with the submission, log details and exit loop
|
||||
[[ "${RET}" != *"retry_after"* ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification" && break
|
||||
if [[ "$ret" != *"retry_after"* ]]; then
|
||||
echo "$json" >>"$LOG"
|
||||
logger -t "$SCRIPTNAME" -- "Failed sending notification"
|
||||
break
|
||||
fi
|
||||
|
||||
# if retries exhausted, log failure
|
||||
[[ "${i}" -eq "${MAX}" ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification - rate limited" && break
|
||||
if (( i == MAX )); then
|
||||
echo "$json" >>"$LOG"
|
||||
logger -t "$SCRIPTNAME" -- "Failed sending notification - rate limited"
|
||||
break
|
||||
fi
|
||||
|
||||
# we were rate limited, try again after a delay
|
||||
sleep 1
|
||||
|
||||
done
|
||||
]]>
|
||||
</Script>
|
||||
|
||||
@@ -344,6 +344,7 @@ $('body').on('click','a,.ca_href', function(e) {
|
||||
$.cookie('allowedDomains',JSON.stringify(domainsAllowed),{expires:3650}); // rewrite cookie to further extend expiration by 400 days
|
||||
if (domainsAllowed[dom.hostname]) return;
|
||||
e.preventDefault();
|
||||
$('.sweet-alert').removeClass('nchan'); // Prevent GUI issues if clicking external link from a changelog
|
||||
swal({
|
||||
title: "<?=_('External Link')?>",
|
||||
text: "<span title='"+href+"'><?=_('Clicking OK will take you to a 3rd party website not associated with Lime Technology')?><br><br><b>"+href+"<br><br><input id='Link_Always_Allow' type='checkbox'></input><?=_('Always Allow')?> "+dom.hostname+"</span>",
|
||||
@@ -504,9 +505,11 @@ $(document).on('mouseenter', 'a.info', function() {
|
||||
const tooltip = $(this).find('span');
|
||||
if (tooltip.length) {
|
||||
const aInfoPosition = $(this).offset();
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const scrollLeft = $(window).scrollLeft();
|
||||
const addtionalOffset = 16;
|
||||
const top = aInfoPosition.top + addtionalOffset;
|
||||
const left = aInfoPosition.left + addtionalOffset;
|
||||
const top = aInfoPosition.top - scrollTop + addtionalOffset;
|
||||
const left = aInfoPosition.left - scrollLeft + addtionalOffset;
|
||||
tooltip.css({ top, left });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -180,7 +180,6 @@ function my_error($code) {
|
||||
}
|
||||
|
||||
function mk_option($select, $value, $text, $extra="") {
|
||||
$value = htmlspecialchars($value);
|
||||
$text = htmlspecialchars($text);
|
||||
return "<option value='$value'".($value == $select ? " selected" : "").(strlen($extra) ? " $extra" : "").">$text</option>";
|
||||
}
|
||||
@@ -387,7 +386,7 @@ function cpu_list() {
|
||||
}
|
||||
|
||||
function my_explode($split, $text, $count=2) {
|
||||
return array_pad(explode($split, $text, $count), $count, '');
|
||||
return array_pad(explode($split, $text??"", $count), $count, '');
|
||||
}
|
||||
|
||||
function my_preg_split($split, $text, $count=2) {
|
||||
|
||||
@@ -24,6 +24,10 @@ case 'config':
|
||||
$filename = $plugin ? "$config/plugins/$name/$name.cfg" : "$config/$name.cfg";
|
||||
for ( $i=0;$i<2;$i++) {
|
||||
if (($need && !file_exists($filename)) || (file_exists($filename) && !@parse_ini_file($filename))) {
|
||||
if ( ! $need && $plugin && @filesize($filename) === 0) {
|
||||
$flag = 0;
|
||||
break;
|
||||
}
|
||||
$flag = 1;
|
||||
sleep(1);
|
||||
} else {
|
||||
@@ -33,6 +37,9 @@ case 'config':
|
||||
}
|
||||
if ($flag) break;
|
||||
}
|
||||
if ($flag) {
|
||||
my_logger("$filename corrupted or missing");
|
||||
}
|
||||
echo $flag;
|
||||
break;
|
||||
case 'notice':
|
||||
|
||||
@@ -21,7 +21,8 @@ _(New folder name)_:
|
||||
|
||||
: <span class="dfm_text"></span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This creates a folder at the current level)_</div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This creates a folder at the current level)_</div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateDeleteFolder">
|
||||
@@ -31,7 +32,8 @@ _(Folder name)_:
|
||||
|
||||
: <span class="dfm_text"></span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i><?=_("This deletes the folder and all its content")?></div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i><?=_("This deletes the folder and all its content")?></div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateRenameFolder">
|
||||
@@ -47,19 +49,14 @@ _(New folder name)_:
|
||||
|
||||
: <span class="dfm_text"></span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This renames the folder to the new name)_</div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This renames the folder to the new name)_</div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateCopyFolder">
|
||||
_(Source folder)_:
|
||||
: <span id="dfm_source"></span>
|
||||
|
||||
|
||||
: _(copy to)_ ...
|
||||
|
||||
_(Target folder)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
|
||||
: <span class="flex flex-col gap-4">
|
||||
<label for="dfm_sparse" class="inline-flex flex-wrap items-center gap-4">
|
||||
@@ -73,19 +70,22 @@ _(Target folder)_:
|
||||
<span class="dfm_text"></span>
|
||||
</span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i><?=_("This copies the folder and all its content to another folder")?></div>
|
||||
|
||||
: _(copy to)_ ...
|
||||
|
||||
<dt class="dfm_noticeLabel"> </dt>
|
||||
<dd class="dfm_notice">
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i><?=_("This copies the folder and all its content to another folder")?></div>
|
||||
</dd>
|
||||
|
||||
_(Target folder)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateMoveFolder">
|
||||
_(Source folder)_:
|
||||
: <span id="dfm_source"></span>
|
||||
|
||||
|
||||
: _(move to)_ ...
|
||||
|
||||
_(Target folder)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
|
||||
: <span class="flex flex-col gap-4">
|
||||
<label for="dfm_sparse" class="inline-flex flex-wrap items-center gap-4">
|
||||
@@ -99,7 +99,16 @@ _(Target folder)_:
|
||||
<span class="dfm_text"></span>
|
||||
</span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i><?=_("This moves the folder and all its content to another folder")?></div>
|
||||
|
||||
: _(move to)_ ...
|
||||
|
||||
<dt class="dfm_noticeLabel"> </dt>
|
||||
<dd class="dfm_notice">
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i><?=_("This moves the folder and all its content to another folder")?></div>
|
||||
</dd>
|
||||
|
||||
_(Target folder)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateDeleteFile">
|
||||
@@ -109,7 +118,8 @@ _(File name)_:
|
||||
|
||||
: <span class="dfm_text"></span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This deletes the selected file)_</div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This deletes the selected file)_</div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateRenameFile">
|
||||
@@ -125,19 +135,14 @@ _(New file name)_:
|
||||
|
||||
: <span class="dfm_text"></span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This renames the selected file)_</div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This renames the selected file)_</div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateCopyFile">
|
||||
_(Source file)_:
|
||||
: <span id="dfm_source"></span>
|
||||
|
||||
|
||||
: _(copy to)_ ...
|
||||
|
||||
_(Target file)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
|
||||
: <span class="flex flex-col gap-4">
|
||||
<label for="dfm_sparse" class="inline-flex flex-wrap items-center gap-4">
|
||||
@@ -151,19 +156,22 @@ _(Target file)_:
|
||||
<span class="dfm_text"></span>
|
||||
</span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This copies the selected file)_</div>
|
||||
|
||||
: _(copy to)_ ...
|
||||
|
||||
<dt class="dfm_noticeLabel"> </dt>
|
||||
<dd class="dfm_notice">
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This copies the selected file)_</div>
|
||||
</dd>
|
||||
|
||||
_(Target file)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateMoveFile">
|
||||
_(Source file)_:
|
||||
: <span id="dfm_source"></span>
|
||||
|
||||
|
||||
: _(move to)_ ...
|
||||
|
||||
_(Target file)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
|
||||
: <span class="flex flex-col gap-4">
|
||||
<label for="dfm_sparse" class="inline-flex flex-wrap items-center gap-4">
|
||||
@@ -177,7 +185,16 @@ _(Target file)_:
|
||||
<span class="dfm_text"></span>
|
||||
</span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This moves the selected file)_</div>
|
||||
|
||||
: _(move to)_ ...
|
||||
|
||||
<dt class="dfm_noticeLabel"> </dt>
|
||||
<dd class="dfm_notice">
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This moves the selected file)_</div>
|
||||
</dd>
|
||||
|
||||
_(Target file)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateDeleteObject">
|
||||
@@ -187,7 +204,8 @@ _(Source)_:
|
||||
|
||||
: <span class="dfm_text"></span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This deletes all selected sources)_</div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This deletes all selected sources)_</div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateRenameObject">
|
||||
@@ -200,19 +218,14 @@ _(Source)_:
|
||||
_(Target)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="">
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This renames the selected source)_</div>
|
||||
|
||||
: <div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This renames the selected source)_</div>
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateCopyObject">
|
||||
_(Source)_:
|
||||
: <select id="dfm_source"></select>
|
||||
|
||||
|
||||
: _(copy to)_ ...
|
||||
|
||||
_(Target)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
|
||||
: <span class="flex flex-col gap-4">
|
||||
<label for="dfm_sparse" class="inline-flex flex-wrap items-center gap-4">
|
||||
@@ -226,19 +239,22 @@ _(Target)_:
|
||||
<span class="dfm_text"></span>
|
||||
</span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This copies all the selected sources)_</div>
|
||||
|
||||
: _(copy to)_ ...
|
||||
|
||||
<dt class="dfm_noticeLabel"> </dt>
|
||||
<dd class="dfm_notice">
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This copies all the selected sources)_</div>
|
||||
</dd>
|
||||
|
||||
_(Target)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateMoveObject">
|
||||
_(Source)_:
|
||||
: <select id="dfm_source"></select>
|
||||
|
||||
|
||||
: _(move to)_ ...
|
||||
|
||||
_(Target)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
|
||||
: <span class="flex flex-col gap-4">
|
||||
<label for="dfm_sparse" class="inline-flex flex-wrap items-center gap-4">
|
||||
@@ -252,7 +268,16 @@ _(Target)_:
|
||||
<span class="dfm_text"></span>
|
||||
</span>
|
||||
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This moves all the selected sources)_</div>
|
||||
|
||||
: _(move to)_ ...
|
||||
|
||||
<dt class="dfm_noticeLabel"> </dt>
|
||||
<dd class="dfm_notice">
|
||||
<div class="dfm_info"><i class="fa fa-warning dfm"></i>_(This moves all the selected sources)_</div>
|
||||
</dd>
|
||||
|
||||
_(Target)_:
|
||||
: <input type="text" id="dfm_target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="dfm_templateChangeOwner">
|
||||
|
||||
@@ -83,7 +83,6 @@ function publish($endpoint, $message, $len=1, $abort=false, $abortTime=30) {
|
||||
$abortStart[$endpoint] = time();
|
||||
if ( (time() - $abortStart[$endpoint]) > $abortTime) {
|
||||
$script = removeNChanScript();
|
||||
my_logger("$script timed out after $abortTime seconds. Exiting.", 'publish');
|
||||
exit();
|
||||
}
|
||||
$reply = false; // if no subscribers, force return value to false
|
||||
|
||||
@@ -42,7 +42,7 @@ Europe/Athens|(UTC+02:00) Athens, Bucharest
|
||||
Asia/Beirut|(UTC+02:00) Beirut
|
||||
Africa/Cairo|(UTC+02:00) Cairo
|
||||
Africa/Johannesburg|(UTC+02:00) Harare, Pretoria
|
||||
Europe/Kiev|(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius
|
||||
Europe/Kyiv|(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius
|
||||
Europe/Istanbul|(UTC+02:00) Istanbul
|
||||
Asia/Jerusalem|(UTC+02:00) Jerusalem
|
||||
Asia/Baghdad|(UTC+03:00) Baghdad
|
||||
|
||||
@@ -541,8 +541,9 @@ case 'import':
|
||||
$vpn = (in_array($default4,$vpn) || in_array($default6,$vpn)) ? 8 : 0;
|
||||
if ($vpn==8) $import["Address:$n"] = '';
|
||||
$import["TYPE:$n"] = $vpn;
|
||||
ipfilter(_var($import,"AllowedIPs:$n"));
|
||||
if (_var($import,"TYPE:$n") == 0) $var['subnets1'] = "AllowedIPs="._var($import,"AllowedIPs:$n");
|
||||
$allowedIPs = _var($import, "AllowedIPs:$n");
|
||||
ipfilter($allowedIPs);
|
||||
if (_var($import,"TYPE:$n") == 0) $var['subnets1'] = "AllowedIPs=$allowedIPs";
|
||||
}
|
||||
foreach ($import as $key => $val) $sort[] = explode(':',$key)[1];
|
||||
array_multisort($sort, $import);
|
||||
|
||||
@@ -54,8 +54,8 @@ Q.find=(function(){var aP=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][
|
||||
/* Modified by Andrew Zawadzki - Changed variable code to be codeAZ due to conflict with const code in vue */
|
||||
shortcut={all_shortcuts:{},add:function(l,p,i){var e={type:"keydown",propagate:!1,disable_in_input:!1,target:document,keycode:!1};if(i)for(var t in e)void 0===i[t]&&(i[t]=e[t]);else i=e;var a=i.target;"string"==typeof i.target&&(a=document.getElementById(i.target));l=l.toLowerCase();function r(e){var t;if(e=e||window.event,!i.disable_in_input||(e.target?t=e.target:e.srcElement&&(t=e.srcElement),3==t.nodeType&&(t=t.parentNode),"INPUT"!=t.tagName&&"TEXTAREA"!=t.tagName)){e.keyCode?codeAZ=e.keyCode:e.which&&(codeAZ=e.which);var a=String.fromCharCode(codeAZ).toLowerCase();188==codeAZ&&(a=","),190==codeAZ&&(a=".");var r=l.split("+"),n=0,s={"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")","-":"_","=":"+",";":":","'":'"',",":"<",".":">","/":"?","\\":"|"},o={esc:27,escape:27,tab:9,space:32,return:13,enter:13,backspace:8,scrolllock:145,scroll_lock:145,scroll:145,capslock:20,caps_lock:20,caps:20,numlock:144,num_lock:144,num:144,pause:19,break:19,insert:45,home:36,delete:46,end:35,pageup:33,page_up:33,pu:33,pagedown:34,page_down:34,pd:34,left:37,up:38,right:39,down:40,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123},d={shift:{wanted:!1,pressed:!1},ctrl:{wanted:!1,pressed:!1},alt:{wanted:!1,pressed:!1},meta:{wanted:!1,pressed:!1}};e.ctrlKey&&(d.ctrl.pressed=!0),e.shiftKey&&(d.shift.pressed=!0),e.altKey&&(d.alt.pressed=!0),e.metaKey&&(d.meta.pressed=!0);for(var c=0;k=r[c],c<r.length;c++)"ctrl"==k||"control"==k?(n++,d.ctrl.wanted=!0):"shift"==k?(n++,d.shift.wanted=!0):"alt"==k?(n++,d.alt.wanted=!0):"meta"==k?(n++,d.meta.wanted=!0):1<k.length?o[k]==codeAZ&&n++:i.keycode?i.keycode==codeAZ&&n++:a==k?n++:s[a]&&e.shiftKey&&(a=s[a])==k&&n++;return n!=r.length||d.ctrl.pressed!=d.ctrl.wanted||d.shift.pressed!=d.shift.wanted||d.alt.pressed!=d.alt.wanted||d.meta.pressed!=d.meta.wanted||(p(e),i.propagate)?void 0:(e.cancelBubble=!0,e.returnValue=!1,e.stopPropagation&&(e.stopPropagation(),e.preventDefault()),!1)}}this.all_shortcuts[l]={callback:r,target:a,event:i.type},a.addEventListener?a.addEventListener(i.type,r,!1):a.attachEvent?a.attachEvent("on"+i.type,r):a["on"+i.type]=r},remove:function(e){e=e.toLowerCase();var t=this.all_shortcuts[e];if(delete this.all_shortcuts[e],t){var a=t.event,r=t.target,n=t.callback;r.detachEvent?r.detachEvent("on"+a,n):r.removeEventListener?r.removeEventListener(a,n,!1):r["on"+a]=!1}}};
|
||||
|
||||
/* readmore.js - v2.0.0, copyright Jed Foster NOT UPDATED */
|
||||
(function(c){function g(b,a){this.element=b;this.options=c.extend({},h,a);c(this.element).data("max-height",this.options.maxHeight);c(this.element).data("height-margin",this.options.heightMargin);delete this.options.maxHeight;if(this.options.embedCSS&&!k){var d=".readmore-js-toggle, .readmore-js-section { "+this.options.sectionCSS+" } .readmore-js-section { overflow: hidden; }",e=document.createElement("style");e.type="text/css";e.styleSheet?e.styleSheet.cssText=d:e.appendChild(document.createTextNode(d));document.getElementsByTagName("head")[0].appendChild(e);k=!0}this._defaults=h;this._name=f;this.init()}var f="readmore",h={speed:100,maxHeight:200,heightMargin:16,moreLink:'<a href="#">Read More</a>',lessLink:'<a href="#">Close</a>',embedCSS:!0,sectionCSS:"display: block; width: 100%;",startOpen:!1,expandedClass:"readmore-js-expanded",collapsedClass:"readmore-js-collapsed",beforeToggle:function(){},afterToggle:function(){}},k=!1;g.prototype={init:function(){var b=this;c(this.element).each(function(){var a=c(this),d=a.css("max-height").replace(/[^-\d\.]/g,"")>a.data("max-height")?a.css("max-height").replace(/[^-\d\.]/g,""):a.data("max-height"),e=a.data("height-margin");"none"!=a.css("max-height")&&a.css("max-height","none");b.setBoxHeight(a);if(a.outerHeight(!0)<=d+e)return!0;a.addClass("readmore-js-section "+b.options.collapsedClass).data("collapsedHeight",d);a.after(c(b.options.startOpen?b.options.lessLink:b.options.moreLink).on("click",function(c){b.toggleSlider(this,a,c)}).addClass("readmore-js-toggle"));b.options.startOpen||a.css({height:d})});c(window).on("resize",function(a){b.resizeBoxes()})},toggleSlider:function(b,a,d){d.preventDefault();var e=this;d=newLink=sectionClass="";var f=!1;d=c(a).data("collapsedHeight");c(a).height()<=d?(d=c(a).data("expandedHeight")+"px",newLink="lessLink",f=!0,sectionClass=e.options.expandedClass):(newLink="moreLink",sectionClass=e.options.collapsedClass);e.options.beforeToggle(b,a,f);c(a).animate({height:d},{duration:e.options.speed,complete:function(){e.options.afterToggle(b,a,f);c(b).replaceWith(c(e.options[newLink]).on("click",function(b){e.toggleSlider(this,a,b)}).addClass("readmore-js-toggle"));c(this).removeClass(e.options.collapsedClass+" "+e.options.expandedClass).addClass(sectionClass)}})},setBoxHeight:function(b){var a=b.clone().css({height:"auto",width:b.width(),overflow:"hidden"}).insertAfter(b),c=a.outerHeight(!0);a.remove();b.data("expandedHeight",c)},resizeBoxes:function(){var b=this;c(".readmore-js-section").each(function(){var a=c(this);b.setBoxHeight(a);(a.height()>a.data("expandedHeight")||a.hasClass(b.options.expandedClass)&&a.height()<a.data("expandedHeight"))&&a.css("height",a.data("expandedHeight"))})},destroy:function(){var b=this;c(this.element).each(function(){var a=c(this);a.removeClass("readmore-js-section "+b.options.collapsedClass+" "+b.options.expandedClass).css({"max-height":"",height:"auto"}).next(".readmore-js-toggle").remove();a.removeData()})}};c.fn[f]=function(b){var a=arguments;if(void 0===b||"object"===typeof b)return this.each(function(){if(c.data(this,"plugin_"+f)){var a=c.data(this,"plugin_"+f);a.destroy.apply(a)}c.data(this,"plugin_"+f,new g(this,b))});if("string"===typeof b&&"_"!==b[0]&&"init"!==b)return this.each(function(){var d=c.data(this,"plugin_"+f);d instanceof g&&"function"===typeof d[b]&&d[b].apply(d,Array.prototype.slice.call(a,1))})}})(jQuery);
|
||||
/* readmore.js - v2.3.0 PERFORMANCE OPTIMIZED for Unraid Docker page - No animations */
|
||||
(function(c){function g(b,a){this.element=b;this.options=c.extend({},h,a);c(this.element).data("max-height",this.options.maxHeight);c(this.element).data("height-margin",this.options.heightMargin);delete this.options.maxHeight;if(this.options.embedCSS&&!k){var d=".readmore-js-toggle, .readmore-js-section { "+this.options.sectionCSS+" } .readmore-js-section { overflow: hidden; }",e=document.createElement("style");e.type="text/css";e.styleSheet?e.styleSheet.cssText=d:e.appendChild(document.createTextNode(d));document.getElementsByTagName("head")[0].appendChild(e);k=!0}this._defaults=h;this._name=f;this.init()}var f="readmore",h={speed:0,maxHeight:200,heightMargin:16,moreLink:'<a href="#">Read More</a>',lessLink:'<a href="#">Close</a>',embedCSS:!0,sectionCSS:"display: block; width: 100%;",startOpen:!1,expandedClass:"readmore-js-expanded",collapsedClass:"readmore-js-collapsed",beforeToggle:function(){},afterToggle:function(){}},k=!1,resizeTimer=null,measuredHeights=new WeakMap();g.prototype={init:function(){var b=this;c(this.element).each(function(){var a=c(this);if(a.hasClass("readmore-js-section")){return}var d=a.css("max-height").replace(/[^-\d\.]/g,"")>a.data("max-height")?a.css("max-height").replace(/[^-\d\.]/g,""):a.data("max-height"),e=a.data("height-margin");"none"!=a.css("max-height")&&a.css("max-height","none");a.css({height:"auto",overflow:"visible"});var expandedHeight=a.outerHeight(!0);if(expandedHeight<=d+e){a.css({height:"",overflow:""});return!0}a.addClass("readmore-js-section "+b.options.collapsedClass).data("collapsedHeight",d).data("expandedHeight",expandedHeight);if(!b.options.startOpen){a.css({height:d+"px",overflow:"hidden"})}else{a.css({height:expandedHeight+"px"})}var toggle=c(b.options.startOpen?b.options.lessLink:b.options.moreLink).on("click",function(c){b.toggleSlider(this,a,c)}).addClass("readmore-js-toggle");a.after(toggle)});c(window).off("resize.readmore").on("resize.readmore",function(a){clearTimeout(resizeTimer);resizeTimer=setTimeout(function(){b.resizeBoxes()},250)})},toggleSlider:function(b,a,d){d.preventDefault();var e=this;var collapsed=a.hasClass(e.options.collapsedClass);var newHeight=collapsed?a.data("expandedHeight"):a.data("collapsedHeight");if(!newHeight||newHeight==0){a.css({height:"auto",overflow:"visible"});newHeight=collapsed?a.outerHeight(!0):a.data("collapsedHeight");if(collapsed)a.data("expandedHeight",newHeight)}var newLink=collapsed?e.options.lessLink:e.options.moreLink;var sectionClass=collapsed?e.options.expandedClass:e.options.collapsedClass;e.options.beforeToggle(b,a,collapsed);a.css({height:newHeight+"px",overflow:collapsed?"visible":"hidden"});a.removeClass(e.options.collapsedClass+" "+e.options.expandedClass).addClass(sectionClass);var newToggle=c(newLink).on("click",function(c){e.toggleSlider(this,a,c)}).addClass("readmore-js-toggle");c(b).replaceWith(newToggle);e.options.afterToggle(b,a,collapsed)},resizeBoxes:function(){var b=this;c(".readmore-js-section").each(function(){var a=c(this);a.css({height:"auto",overflow:"visible"});var expandedHeight=a.outerHeight(!0);a.data("expandedHeight",expandedHeight);if(a.hasClass(b.options.expandedClass)){a.css({height:expandedHeight+"px"})}else if(a.hasClass(b.options.collapsedClass)){a.css({height:a.data("collapsedHeight")+"px",overflow:"hidden"})}})},destroy:function(){var b=this;c(window).off("resize.readmore");c(this.element).each(function(){var a=c(this);a.removeClass("readmore-js-section "+b.options.collapsedClass+" "+b.options.expandedClass).css({"max-height":"",height:"",overflow:""}).next(".readmore-js-toggle").remove();a.removeData();measuredHeights.delete(a[0])})}};c.fn[f]=function(b){var a=arguments;if(void 0===b||"object"===typeof b)return this.each(function(){if(c.data(this,"plugin_"+f)){var a=c.data(this,"plugin_"+f);a.destroy.apply(a)}c.data(this,"plugin_"+f,new g(this,b))});if("string"===typeof b&&"_"!==b[0]&&"init"!==b)return this.each(function(){var d=c.data(this,"plugin_"+f);d instanceof g&&"function"===typeof d[b]&&d[b].apply(d,Array.prototype.slice.call(a,1))})}})(jQuery);
|
||||
/**
|
||||
* Simple, lightweight, usable local autocomplete library for modern browsers
|
||||
* Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P
|
||||
|
||||
1176
emhttp/plugins/dynamix/javascript/smoothie.js
Normal file
1176
emhttp/plugins/dynamix/javascript/smoothie.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,31 @@ span.head_bar {
|
||||
span.head_gap {
|
||||
/* padding-left: 14px; */
|
||||
}
|
||||
span.head_gap.network-selects {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
span.head_gap.network-selects > * {
|
||||
min-width: 0;
|
||||
}
|
||||
select.network-select {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
select.network-interface {
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
}
|
||||
select.network-type {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
span.head_time {
|
||||
padding-left: 40px;
|
||||
font-size: inherit !important;
|
||||
|
||||
@@ -631,7 +631,7 @@ div.title span img {
|
||||
background-color: var(--orange-800);
|
||||
}
|
||||
.nav-item.active:after {
|
||||
background-color: var(--background-color);
|
||||
background-color: var(--header-text-color);
|
||||
}
|
||||
.nav-user a {
|
||||
color: var(--inverse-text-color);
|
||||
|
||||
@@ -39,7 +39,7 @@ samba_running(){
|
||||
|
||||
samba_waitfor_shutdown(){
|
||||
local i
|
||||
for i in {1..5}; do
|
||||
for i in {1..10}; do
|
||||
if ! samba_running; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
23
sbin/mover
23
sbin/mover
@@ -83,6 +83,29 @@ start() {
|
||||
fi
|
||||
done
|
||||
|
||||
# maybe empty an array disk
|
||||
EMPTYING=$(grep '^shareUserEmptying=' /boot/config/share.cfg | cut -d'"' -f2)
|
||||
IFS=',' read -ra arr <<< "$EMPTYING"
|
||||
for DISK in "${arr[@]}"; do
|
||||
# we can only empty share directories
|
||||
for SHAREPATH in /mnt/$DISK/* ; do
|
||||
SHARE=$(basename "$SHAREPATH")
|
||||
if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then
|
||||
move "$SHAREPATH" "-e"
|
||||
fi
|
||||
done
|
||||
|
||||
# output list of files which could not be moved
|
||||
# use 'find' in case huge number of files left in /mnt/$DISK
|
||||
count=$(find /mnt/$DISK -mindepth 1 | wc -l)
|
||||
if [ "$count" -gt 0 ]; then
|
||||
find /mnt/$DISK -mindepth 1 -depth -printf 'move: %p Not moved\n' | head -n 100
|
||||
if [ "$count" -gt 100 ]; then
|
||||
echo "[output truncated to first 100 entries]"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
rm -f $PIDFILE
|
||||
echo "mover: finished"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user