Files
webgui/emhttp/plugins/dynamix/ArrayOperation.page
Tom Mortensen e8a8291649 feat: exFAT support
chore: refactor file sysem check scripts
fix: fix some mode bits
2025-08-15 13:51:36 -07:00

863 lines
45 KiB
Plaintext

Menu="Main:5"
Title="Array Operation"
Tag="snowflake-o"
Nchan="device_list,disk_load,parity_list"
---
<?PHP
/* Copyright 2005-2025, Lime Technology
* Copyright 2012-2025, 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.
*/
?>
<?
$keyfile = file_exists(_var($var,'luksKeyfile'));
$spot = _var($var,'mdResyncPos',0)>0;
$poolsOnly = (_var($var,'SYS_ARRAY_SLOTS') == 0 ) ? true : false;
/* only one of $present, $missing, or $wrong will be true, or all will be false */
$forced = $present = $missing = $wrong = false;
foreach (luks_filter($disks) as $disk) {
$fsType = _var($disk,'fsType');
$luks = str_starts_with($fsType,'luks:');
$auto = $fsType == 'auto';
if ($luks || ($auto && str_starts_with(_var($var,'defaultFsType'),'luks:'))) $forced = true;
if ($luks || $auto) switch (_var($disk,'luksState',0)) {
case 1: $present = true; break;
case 2: $missing = true; break;
case 3: $wrong = true; break;
}
}
$encrypt = $forced || $present || $missing || $wrong;
if ($forced && ($present || $missing || $wrong)) $forced = false;
function check_encryption() {
global $forced, $missing, $wrong;
if ($forced)
$status = _('Enter new key');
elseif ($missing)
$status = _('Missing key');
elseif ($wrong)
$status = _('Wrong key');
else
return;
echo "<tr><td></td><td>",_('Encryption status').":</td><td><span class='red-text'>$status</span><span id='pass' class='hidden'><label class='inline-flex flex-row items-center gap-2 flex-shrink-0'><input name='luksReformat' type='checkbox' onchange='selectInput(this.form)'>permit reformat</label></span></td></tr>";
echo "<tr><td></td><td><label for='encryption-input'>",_('Encryption input').":</label></td><td>";
echo "<select id='encryption-input' name='input' size='1' onchange='selectInput(this.form)'>";
echo mk_option(1,'text',_('Passphrase'));
echo mk_option(1,'file',_('Keyfile'));
echo "</select></td></tr>";
echo "<tr id='text'><td></td><td><label for='key-passphrase'>",_('Passphrase'),":</label></td><td><div class='flex flex-row items-center gap-2'><input id='key-passphrase' type='password' name='text' maxlength='512' value='' onkeyup='selectInput(this.form)' placeholder=\""._('use printable characters only')."\"><label class='inline-flex flex-row items-center gap-2 flex-shrink-0'><input name='showPass' type='checkbox' onchange='selectInput(this.form)'>"._('show passphrase')."</label></div></td></tr>";
echo "<tr id='copy' class='hidden'><td></td><td><label for='key-retype-passphrase'>",_('Retype passphrase'),":</label></td><td><input id='key-retype-passphrase' type='password' name='copy' maxlength='512' value='' onkeyup='selectInput(this.form)'></td></tr>";
echo "<tr id='file' class='hidden'><td></td><td><label for='key-file-upload'>",_('Keyfile'),":</label></td><td><input id='key-file-upload' type='file' name='local' onchange='getFileContent(event,this.form)'></td></tr>";
}
function maintenance_mode() {
echo "<tr>";
echo "<td></td>";
echo "<td><label class='flex flex-row items-center gap-2'><input type='checkbox' name='startMode' value='Maintenance'>",_('Maintenance mode'),"</label></td>";
echo "<td><b>",_('Maintenance mode'),"</b> - ",_('if checked, Start array but do not mount disks'),"</td>";
echo "</tr>";
}
function status_indicator() {
global $var;
switch (_var($var,'mdColor')) {
case 'green-on': $orb = 'circle'; $color = 'green'; $help =_('Started, array protected'); break;
case 'green-blink': $orb = 'circle'; $color = 'grey'; $help = _('Stopped'); break;
case 'yellow-on': $orb = 'warning'; $color = 'yellow'; $help = _('Started, array unprotected'); break;
case 'yellow-blink': $orb = 'warning'; $color = 'grey'; $help = _('Stopped'); break;
}
echo "<a class='info'><i class='fa fa-$orb orb $color-orb'></i><span>$help</span></a>";
}
function missing_cache() {
global $disks;
$missing = false;
foreach (cache_filter($disks) as $disk) $missing |= (strpos(_var($disk,'status'),'_MISSING')!==false);
return $missing;
}
function resync($d) {
return in_array($d,['P','Q']) ? 'Parity-Sync' : 'Data-Rebuild';
}
function print_error($error) {
return sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0');
}
?>
<script>
var ctrl = '<span class="status"><a style="cursor:pointer" class="tooltip_diskio" title="_(Toggle reads/writes display)_" onclick="toggle_diskio();return false"><i class="toggle fa"></i></a></span>';
var recall = null;
var recover = null;
String.prototype.no_tilde = function(){return this.replace('<?=$_tilde_?>','<?=$_proxy_?>');}
String.prototype.master = function(){return this.split('<?=$_tilde_?>')[0];}
function toggle_state(device, name, action) {
var button = null;
if (name) {
var group = name.replace(/(\d+|\*)$/,'');
if (name.slice(-1) != '*') {
// single device
$('#dev-'+name.no_tilde()).removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
} else {
if (group == 'disk') {
// array devices
$('[id^="dev-parity"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
$('[id^="dev-disk"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
} else {
// pool devices
$('[id^="dev-'+group.master()+'"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
}
}
} else if (device != 'Clear') {
// all devices
$('[id^="dev-"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
button = '[id^=button-]';
}
devices.stop();
$.post('/webGui/include/ToggleState.php', {device:device,name:name,action:action}, function(){setTimeout(function(){devices.start();},1000);if (button) $(button).prop('disabled',false);});
}
function display_diskio() {
if ($.cookie('diskio') === undefined) {
$('span.diskio').show(); $('span.number').hide();
} else {
$('span.number').show(); $('span.diskio').hide();
}
}
function toggle_diskio(init) {
if (!init) {
if ($.cookie('diskio') === undefined) $.cookie('diskio','diskio',{expires:3650}); else $.removeCookie('diskio');
}
if ($.cookie('diskio') === undefined) {
$('i.toggle').removeClass('fa-list').addClass('fa-tachometer');
$('#clearstats').addClass('hidden');
} else {
$('i.toggle').removeClass('fa-tachometer').addClass('fa-list');
$('#clearstats').removeClass('hidden');
}
display_diskio();
}
function base64(str) {
return window.btoa(unescape(encodeURIComponent(str)));
}
function selectInput(form) {
<?if ($wrong && $keyfile):?>
form.input.value = 'file';
form.input.disabled = true;
<?endif;?>
if (form.input.value == 'text') {
form.file.value = '';
form.local.value = '';
<?if ($forced):?>
$('#text').show();
$('#copy').show();
$('#pass').hide();
$('input[name="confirmStart"]').prop('disabled',true);
<?elseif ($missing):?>
$('#text').show();
$('#copy').hide();
$('#pass').hide();
$('input[name="confirmStart"]').prop('disabled',true);
<?elseif ($wrong):?>
$('#text').show();
if ($('input[name="luksReformat"]').prop('checked')) $('#copy').show(); else $('#copy').hide();
$('#pass').show();
$('input[name="confirmStart"]').prop('disabled',true);
<?endif;?>
$('#file').hide();
$('input[name="text"],input[name="copy"]').attr('type',$('input[name="showPass"]').prop('checked')?'text':'password');
var item = $('input[name="confirmStart"]').length ? $('input[name="confirmStart"]') : $('#cmdStart');
item.prop('disabled',$('#copy').is(':visible') ? (form.text.value!=form.copy.value || form.text.value=='') : form.text.value=='');
} else {
form.text.value = '';
form.copy.value = '';
$('#text').hide();
$('#copy').hide();
$('#file').show();
<?if ($wrong):?>
$('#pass').show();
<?else:?>
$('#pass').hide();
<?endif;?>
var item = $('input[name="confirmStart"]').length ? $('input[name="confirmStart"]') : $('#cmdStart');
item.prop('disabled',!form.file.value);
}
}
function getFileContent(event, form) {
var input = event.target;
var reader = new FileReader();
reader.onload = function(){form.file.value=reader.result;selectInput(form);};
reader.readAsDataURL(input.files[0]);
}
function prepareInput(form, button, parityWarn) {
button.disabled = true;
$.post('/webGui/include/Report.php',{cmd:'state',pools:'<?=implode(',',$pools)?>'},function(state) {
if (state.length == 0) {
$(form).append('<input type="hidden" name="cmdStart" value="Start">');
if (form.input === undefined) {
parityWarn ? parityWarning(form,button) : form.submit();
return;
}
form.input.disabled = true;
form.local.disabled = true;
form.file.disabled = true;
form.text.disabled = true;
form.copy.disabled = true;
if (form.text.value) {
var valid = new RegExp('^[ -~]+$');
if (valid.test(form.text.value)) {
$(form).append('<input type="hidden" name="luksKey" value="'+base64(form.text.value)+'">');
form.submit();
} else {
form.input.disabled = false;
form.local.disabled = false;
form.file.disabled = false;
form.text.disabled = false;
form.copy.disabled = false;
swal({
title:"_(Printable Characters Only)_",
text:"_(Use **ASCII** characters from space ' ' to tilde '~')_<br>_(Otherwise use the **keyfile** method for UTF8 input)_",
html:true,
type:'error',
confirmButtonText:"_(Ok)_"
});
}
return;
}
var data = {};
data['#file'] = 'unused';
data['#include'] = 'webGui/include/KeyUpload.php';
data['file'] = form.file.value;
$.post('/update.php',data,function(){form.submit();});
} else {
swal({
title:"_(Wrong Pool State)_",
text:state,
type:'error',
html:true,
confirmButtonText:"_(Ok)_"
},function(){
button.disabled=false;
});
}
});
}
function parityWarning(form, button) {
if (form.md_invalidslot.checked) {
<?if (strpos(_var($disks['parity2'],'status'),'_NP')===false):?>
var text = "_(*Dual parity* valid requires **ALL** disks in their original slots)_";
<?else:?>
var text = "_(*Parity valid* requires **ALL** disks to have their original content)_";
<?endif;?>
} else {
var text = "_(*Parity* disk(s) content will be overwritten)_";
}
swal({
title:"_(Proceed to start)_",
text:text,
html:true,
type:'warning',
showCancelButton:true,
confirmButtonText:"_(Proceed)_",
cancelButtonText:"_(Cancel)_"
},function(confirmed){
confirmed ? form.submit() : button.disabled=false;
});
}
function tab0() {
$.removeCookie('one');
$.cookie('tab','tab0');
}
function stopArray(form) {
$(form).append('<input type="hidden" name="cmdStop" value="Stop">');
<?if ($confirm['stop']):?>
swal({
title:"_(Proceed)_?",
text:"_(This will stop the array)_",
type:'warning',
html:true,
showCancelButton:true,
confirmButtonText:"_(Proceed)_",
cancelButtonText:"_(Cancel)_"
},function(p){
if (p) form.submit(); else $('input[name="cmdStop"]').remove();
});
<?else:?>
form.submit();
<?endif;?>
}
function stopParity(form, text) {
$(form).append('<input type="hidden" name="cmdCheckCancel" value="">');
<?if ($confirm['stop']):?>
swal({
title:"_(Proceed)_?",
text:"_(This will stop the running operation)_: "+text,
type:'warning',
html:true,
showCancelButton:true,
confirmButtonText:"_(Proceed)_",
cancelButtonText:"_(Cancel)_"
},function(p){
if (p) form.submit(); else $('input[name="cmdCheckCancel"]').remove();
});
<?else:?>
form.submit();
<?endif;?>
}
function pauseParity(form) {
$.post('/webGui/include/ParityControl.php',{action:'pause'},function(){
$('#pauseButton').val("_(Resume)_").prop('disabled',true).prop('onclick',null).off('click').click(function(){resumeParity(form);});
$(form).append('<input type="hidden" name="cmdCheckPause" value="">');
form.submit();
});
}
function resumeParity(form) {
$.post('/webGui/include/ParityControl.php',{action:'resume'},function(){
$('#pauseButton').val("_(Pause)_").prop('disabled',true).prop('onclick',null).off('click').click(function(){pauseParity(form);});
$(form).append('<input type="hidden" name="cmdCheckResume" value="">');
form.submit();
});
}
function parityHistory() {
openChanges("parity_history", "_(Parity Operation History)_", "phistory");
}
function shutdown_now(form, cmd) {
$(form).append('<input type="hidden" name="cmd" value="'+cmd+'">');
<?if ($confirm['down']):?>
switch (cmd) {
case 'reboot': var text = "_(This will reboot the system)_"; break;
case 'shutdown': var text = "_(This will shutdown the system)_"; break;
}
swal({
title:"_(Proceed)_?",
text:text,
type:'warning',
html:true,
showCancelButton:true,
confirmButtonText:"_(Proceed)_",
cancelButtonText:"_(Cancel)_"
},function(p){
if (p) form.submit(); else $('input[name="cmd"]').remove();
});
<?else:?>
form.submit();
<?endif;?>
}
function toggleApply(checked) {
$('input[name="#apply"]').prop('disabled',!checked);
}
<?if ($tabbed):?>
$('.tabs').append(ctrl);
if ($.cookie('tab') == 'tab0') $('i.toggle').hide();
$('#tab'+$('input[name$="tabs"]').length).click(function(){tab0(); $('i.toggle').hide('slow');});
<?else:?>
$('div[class=title]:not(":last, .disable_diskio") .right').each(function(){$(this).append(ctrl);});
<?endif;?>
$('.tooltip_diskio').tooltipster({delay:100,trigger:'custom',triggerOpen:{mouseenter:true},triggerClose:{click:false,scroll:true,mouseleave:true}});
<?if (_var($var,'fsState') == 'Started'):?>
var mymonitor = new NchanSubscriber('/sub/mymonitor',{subscriber:'websocket', reconnectTimeout:5000});
mymonitor.on('message', function(state) {
switch (state) {
case '0': // normal operation
$('#stop-button').prop('disabled',false);
$('#stop-text').html("");
<?if (_var($var,'fsState') != "Stopped"):?>
$('#spinup-button').prop('disabled',false);
$('#spindown-button').prop('disabled',false);
<?endif;?>
<?if (_var($var,'shareUser') == 'e' && $pool_devices):?>
$('#mover-button').prop('disabled',false);
$('#mover-text').html("<b>_(Move)_</b> _(will immediately invoke the Mover)_.&nbsp;<a href=\"/Main/Settings/Scheduler\"<?if($tabbed):?> onclick=\"$.cookie('one','tab2')\"<?endif;?>>(_(Schedule)_)</a>");
<?endif;?>
break;
case '1': // parity running
$('#stop-button').prop('disabled',true);
$('#stop-text').html("<br><small>_(Disabled)_ -- _(Parity operation is running)_</small>");
<?if (_var($var,'fsState') != "Stopped" && _var($var,'mdResync',0) > 0):?>
$('#spinup-button').prop('disabled',true);
$('#spindown-button').prop('disabled',true);
<?endif;?>
<?if (_var($var,'shareUser') == 'e' && $pool_devices):?>
$('#mover-button').prop('disabled',true);
$('#mover-text').html("_(Disabled)_ -- _(Parity operation is running)_");
<?endif;?>
break;
case '2': // mover running
$('#stop-button').prop('disabled',true);
$('#stop-text').html("<br><small>_(Disabled)_ -- _(Mover is running)_</small>");
<?if (_var($var,'shareUser') == 'e' && $pool_devices):?>
$('#mover-button').prop('disabled',true);
$('#mover-text').html("_(Disabled)_ - _(Mover is running)_.");
<?endif;?>
break;
case '3': // btrfs running
$('#stop-button').prop('disabled',true);
$('#stop-text').html("<br><small>_(Disabled)_ -- _(BTRFS operation is running)_</small>");
<?if (_var($var,'shareUser') == 'e' && $pool_devices):?>
$('#mover-button').prop('disabled',true);
$('#mover-text').html("_(Disabled)_ -- _(BTRFS operation is running)_");
<?endif;?>
break;
}
});
mymonitor.start();
<?endif;?>
var arraymonitor = new NchanSubscriber('/sub/arraymonitor',{subscriber:'websocket', reconnectTimeout:5000});
arraymonitor.on('message', function(state) {
if (state == 1 && !timers.arraymonitor) timers.arraymonitor = setTimeout(refresh,1250);
});
var devices = new NchanSubscriber('/sub/devices<?=$spot?",parity":""?>',{subscriber:'websocket', reconnectTimeout:5000});
devices.on('message', function(msg, meta) {
switch (<?if($spot):?>meta.id.channel()<?else:?>0<?endif;?>) {
case 0:
// array + pool + ua + flash devices
var get = JSON.parse(msg);
for (var name in get) $('#'+name).html(get[name]);
if (recall !== null) recall.html('&nbsp;');
display_diskio();
// stop updating when array is stopped
if (get.stop == 1) {
$('thead tr').removeClass().addClass('offline');
<?if (_var($var,'fsState') == 'Started'):?>
setTimeout(refresh);
<?else:?>
if (!timers.stopped) timers.stopped = setTimeout(function(){devices.stop(); arraymonitor.start();},1500);
<?endif;?>
} else {
// make truncated descriptions fully visible when hovering over them
$('td.desc').hover(
function(){if ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {recall=$(this).next(); recover=recall.html(); recall.html('&nbsp;');}},
function(){if ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {recall.html(recover); recall=null;}}
);
}
break;
case 1:
// running parity status
var get = JSON.parse(msg);
$.each(get,function(k,v) {if ($('#line'+k).length>0) $('#line'+k).html(v);});
// button control
if ($('#pauseButton').length > 0 && $('#pauseButton').prop('disabled') == false) {
if ((get === "") && $('#cancelButton').val() == "_(Cancel)_") {
$('#cancelButton').val("_(Done)_").prop('onclick',null).off('click').click(function(){refresh();});
$('#pauseButton').prop('disabled',true);
$('#cancelText').html('');
$('#line4').html("_(completed)_");
} else {
var form = document.arrayOps;
if ($('#pauseButton').val() == "_(Pause)_" && get[1].search("_(paused)_") != -1) {
$('#pauseButton').val("_(Resume)_").prop('onclick',null).off('click').click(function(){resumeParity(form);});
} else if ($('#pauseButton').val() == "_(Resume)_" && get[1].search("_(paused)_") == -1) {
$('#pauseButton').val("_(Pause)_").prop('onclick',null).off('click').click(function(){pauseParity(form);});
}
}
}
break;
}
});
devices.start();
<?if (substr(_var($var,'fsState'),-3) == 'ing'):?>
var fsState = new NchanSubscriber('/sub/fsState',{subscriber:'websocket', reconnectTimeout:5000});
fsState.on('message', function(msg) {
switch (msg) {
case 'stop':
if (!timers.fsState) timers.fsState = setTimeout(refresh,1250);
break;
default:
if (msg) $('#fsState').html(msg);
break;
}
});
fsState.start();
<?elseif ($spot):?>
setTimeout(function(){$('#pauseButton').prop('disabled',false);$('#cancelButton').prop('disabled',false);},250);
<?else:?>
var paritymonitor = new NchanSubscriber('/sub/paritymonitor',{subscriber:'websocket', reconnectTimeout:5000});
paritymonitor.on('message', function(busy){if (busy == 1) refresh();});
setTimeout(function(){paritymonitor.start();},5000);
<?endif;?>
$(function(){
// Create ping listener for parity monitor and device info to monitor for listeners
var mainPingListener = new NchanSubscriber('/sub/mainPingListener',{subscriber:'websocket', reconnectTimeout:5000});
mainPingListener.on('message', function(msg){});
mainPingListener.start();
var form = document.arrayOps;
if (form.input !== undefined) selectInput(form);
toggle_diskio(true);
});
function formatWarning(val) {
if (val == true) {
swal({
title:"_(Format Unmountable disks)_",
text: "_(Create an empty file system on the disks shown as **Unmountable** discarding all data currently on the disks and update parity to reflect this)_. "+
"_(This is typically done when a new disk is added to the array to get it ready for files to be written to it)_.<br>"+
"<p><br>**_(WARNING)_**"+
"<p>_(A format is **NEVER** part of a data recovery or disk rebuild process and if done in such circumstances will normally lead to loss of all data on the disks being formatted)_.",
type: "warning",
html: true,
confirmButtonText:"_(Ok)_"
});
}
}
window.onunload = function(){
<?if (_var($var,'fsState') == 'Started'):?>
try {mymonitor.stop();} catch(e) {}
try {devices.stop();} catch(e) {}
<?endif;?>
<?if ($spot == 0):?>
try {paritymonitor.stop();} catch(e) {}
<?endif;?>
}
</script>
<form name="arrayOps" method="POST" action="/update.htm" target="progressFrame">
<input type="hidden" name="startState" value="<?=htmlspecialchars(_var($var,'mdState'))?>">
<input type="hidden" name="file" value="">
<table markdown="1" class="ArrayOperation-Table array_status">
<?switch (_var($var,'fsState')):
case "Started":?>
<tr><td><?status_indicator()?>**_(Started)_<?=((_var($var,'startMode')=='Maintenance')?' - _(Maintenance Mode)_':'')?>**</td>
<td><input type="button" id="stop-button" value="_(Stop)_" onclick="stopArray(this.form)"></td>
<td>**_(Stop)_** _(will take the array off-line)_.<span id="stop-text"></span></td></tr>
<? if (_var($var,'fsNumUnmountable',0) > 0):?>
<tr><td>**<?=_('Unmountable disk'.(_var($var,'fsNumUnmountable',0) == 1 ? '' : 's').' present')?>:**<br>
<? $cache = [];
foreach ($disks as $disk) if (substr(_var($disk,'fsStatus'),0,11) == 'Unmountable' || in_array(pool_name(_var($disk,'name')),$cache)) {
if (strlen(_var($disk,'id'))) echo "<span class='blue-text'>".my_disk(_var($disk,'name'))."</span> &bullet; ".my_id(_var($disk,'id'))." ("._var($disk,'device').")<br>";
if (in_array(_var($disk,'name'),$pools)) $cache[] = $disk['name'];
}
?> </td><td><input type="submit" id="btnFormat" name="cmdFormat" value="_(Format)_" disabled><input type="hidden" name="unmountable_mask" value="<?=_var($var,'fsUnmountableMask')?>"></td>
<td>**_(Format)_** _(will create a file system in all **Unmountable** disks)_.<br>
<a class="info none img nohand"><input type="checkbox" name="confirmFormat" value="OFF" onclick="formatWarning(this.checked),$('#btnFormat').prop('disabled',!arrayOps.confirmFormat.checked)">
<small>_(Yes, I want to do this)_</small></a>
</td></tr>
<? endif;
$action = preg_split('/\s+/',_var($var,'mdResyncAction'));
if (!$spot):
if ($action[0] == "recon"):
$resync = resync($action[1]);
?> <tr><td></td><td><input type="submit" name="cmdCheckSync" value="_(Sync)_"></td><td>**<?=_('Sync')?>** <?=_("will start **$resync**")?>.</td></tr>
<? elseif ($action[0]=="clear"):?>
<tr><td></td><td><input type="submit" name="cmdCheckClear" value="_(Clear)_"></td><td>**_(Clear)_** _(will start **Disk-Clear** of new data disk(s))_.</td></tr>
<? else:
if ($action[0] == "check" && count($action) > 1):?>
<tr><td>_(Parity is valid)_.</td><td><input type="submit" name="cmdCheck" value="_(Check)_"></td><td>**_(Check)_** _(will start **Parity-Check**)_.&nbsp;<a href="/Main/Settings/Scheduler"<?if ($tabbed):?> onclick="$.cookie('one','tab1')"<?endif;?>>(_(Schedule)_)</a>
<br><input type="checkbox" name="optionCorrect" value="correct" checked><small>_(Write corrections to parity)_</small></td></tr>
<? elseif ($action[0] == "check"):?>
<tr><td></td><td><input type="submit" name="cmdCheck" value="_(Check)_"></td><td>**_(Check)_** _(will start **Read-Check** of all array disks)_.</td></tr>
<? endif;?>
<? if (!$poolsOnly):?>
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td>
<? [$date, $duration, $speed, $status, $error, $action, $size] = last_parity_log();
if (_var($var,'sbSyncExit',0) != 0):?>
<td class="whitespace-normal"><?=sprintf(_('Last check incomplete on **%s**'),_(my_time(_var($var,'sbSynced2',0)).day_count(_var($var,'sbSynced2',0)),0))?><?if (_var($var,'sbSynced2')):?>
<br><i class="fa fa-fw fa-dot-circle-o"></i> _(Error code)_: <?=my_error(_var($var,'sbSyncExit'))?>
<br><i class="fa fa-fw fa-search"></i> <?=print_error(_var($var,'sbSyncErrs',0))?><?endif;?></td></tr>
<? elseif (_var($var,'sbSynced',0) == 0):
if ($status == 0):?>
<td class="whitespace-normal"><?=sprintf(_('Last checked on **%s**'),_(my_time($date).day_count($date),0))?>
<br><i class="fa fa-fw fa-clock-o"></i> _(Duration)_: <?=my_check($duration,$speed)?>
<br><i class="fa fa-fw fa-search"></i> <?=print_error($error)?></td></tr>
<? else:?>
<td class="whitespace-normal"><?=sprintf(_('Last check incomplete on **%s**'),_(my_time($date).day_count($date),0))?>
<br><i class="fa fa-fw fa-dot-circle-o"></i> _(Error code)_: <?=my_error($status)?>
<br><i class="fa fa-fw fa-search"></i> <?=print_error($error)?></td></tr>
<? endif;
elseif (_var($var,'sbSynced2',0)==0):
if ($status == 0):?>
<td class="whitespace-normal"><?=sprintf(_('Last checked on **%s**'),_(my_time(_var($var,'sbSynced',0)).day_count(_var($var,'sbSynced',0)),0))?>
<br><i class="fa fa-fw fa-clock-o"></i> _(Duration)_: <?=my_check($duration,$speed)?>
<br><i class="fa fa-fw fa-search"></i> <?=print_error($error)?></td></tr>
<? else:?>
<td class="whitespace-normal"><?=sprintf(_('Last check incomplete on **%s**'),_(my_time(_var($var,'sbSynced',0)).day_count(_var($var,'sbSynced',0)),0))?>
<br><i class="fa fa-fw fa-dot-circle-o"></i> _(Error code)_: <?=my_error($status)?>
<br><i class="fa fa-fw fa-search"></i> <?=print_error($error)?></td></tr>
<? endif;
else:?>
<td class="whitespace-normal"><?=sprintf(_('Last check completed on **%s**'),_(my_time(_var($var,'sbSynced2',0)).day_count(_var($var,'sbSynced2',0)),0))?>
<br><i class="fa fa-fw fa-clock-o"></i> _(Duration)_: <?=my_check($duration,$speed)?>
<br><i class="fa fa-fw fa-search"></i> <?=print_error(_var($var,'sbSyncErrs',0))?></td></tr>
<? endif;
endif; // end check for poolsOnly
endif;
else:
if ($action[0] == "recon"):
$resync = resync($action[1]);
?> <tr><td><?=_("$resync in progress")?>.</td><td>
<span class="buttons-spaced">
<input type="button" id="pauseButton"<?if (_var($var,'mdResync')):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?> disabled>
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'<?=$resync?>')" disabled>
</span></td>
<td id="cancelText"><?if (_var($var,'mdResync')):?>**<?=_('Pause')?>** <?=_("will pause $resync")?>.<?else:?>**<?=_('Resume')?>** <?=_("will resume $resync")?>.<?endif;?><br>**<?=_('Cancel')?>** <?=_("will stop $resync")?>.
<br>_(WARNING: canceling may leave the array unprotected)_!</td></tr>
<? elseif ($action[0] == "clear"):?>
<tr><td>_(Disk-Clear in progress)_.</td><td>
<span class="buttons-spaced">
<input type="button" id="pauseButton"<?if (_var($var,'mdResync')):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?> disabled>
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Disk-Clear')" disabled>
</span></td>
<td id="cancelText"><?if (_var($var,'mdResync')):?>**_(Pause)_** _(will pause Disk-Clear)_.<?else:?>**_(Resume)_** _(will resume Disk-Clear)_.<?endif;?><br>**_(Cancel)_** _(will stop Disk-Clear)_.</td></tr>
<? elseif ($action[0] == "check" && count($action) > 1):?>
<tr><td>_(Parity-Check in progress)_.</td><td>
<span class="buttons-spaced">
<input type="button" id="pauseButton"<?if (_var($var,'mdResync')):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?> disabled>
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Parity-Check')" disabled>
</span></td>
<td id="cancelText"><?if (_var($var,'mdResync')):?>**_(Pause)_** _(will pause Parity-Check)_.<?else:?>**_(Resume)_** _(will resume Parity-Check)_.<?endif;?><br>**_(Cancel)_** _(will stop Parity-Check)_.</td></tr>
<? elseif ($action[0] == "check"):?>
<tr><td>_(Read-Check in progress)_.</td><td>
<span class="buttons-spaced">
<input type="button" id="pauseButton"<?if (_var($var,'mdResync')):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?> disabled>
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Read-Check')" disabled>
</span></td>
<td id="cancelText"><?if (_var($var,'mdResync')):?>**_(Pause)_** _(will pause Read-Check)_.<?else:?>**_(Resume)_** _(will resume Read-Check)_.<?endif;?><br>**_(Cancel)_** _(will stop Read-Check)_.</td></tr>
<? endif;
$stamps = '/var/tmp/stamps.ini';
$synced = file_exists($stamps) ? explode(',',file_get_contents($stamps)) : [];
$sbSynced = array_shift($synced) ?: _var($var,'sbSynced');
$sbUpdate = _var($var,'mdResyncDt') ? $sbSynced : _var($var,'sbUpdated');?>
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td>
<td><?=sprintf(_('Current operation %s on **%s**'),(_var($var,'mdResyncDt')?_('started'):_('paused')),_(my_time($sbUpdate).day_count($sbUpdate),0))?></td></tr>
<tr><td>_(Total size)_:</td><td id="line0"></td><td></td></tr>
<tr><td>_(Elapsed time)_:</td><td id="line1"></td><td></td></tr>
<tr><td>_(Current position)_:</td><td id="line2"></td><td></td></tr>
<tr><td>_(Estimated speed)_:</td><td id="line3"></td><td></td></tr>
<tr><td>_(Estimated finish)_:</td><td id="line4"></td><td></td></tr>
<? if ($action[0] == "check"):?>
<? if (count($action) > 1):?>
<tr><td><?=_var($var,'mdResyncCorr') == 0 ? _('Sync errors detected') : _('Sync errors corrected')?>:</td><td id="line5"></td><td></td></tr>
<? else:?>
<tr><td><?=_var($var,'mdResyncCorr') == 0 ? _('Read errors detected') : _('Read errors corrected')?>:</td><td id="line5"></td><td></td></tr>
<? endif;
endif;
endif;
break;
case "Starting":?>
<tr><td><?status_indicator()?>**_(Starting)_...**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td><td></td></tr>
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td><td></td></tr>
<? break;
case "Formatting":?>
<tr><td><?status_indicator()?>**_(Started, formatting)_...**</td><td><input type="submit" name="cmdStop" value="_(Stop)_" disabled></td><td></td></tr>
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td><td></td></tr>
<? break;
case "Copying":?>
<tr><td><?status_indicator()?>**_(Starting, copying)_... <span id="fsState"></span>**</td><td><input type="submit" name="cmdNoCopy" value="_(Cancel)_"></td><td></td></tr>
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td>
<td><?=sprintf(_('Current operation %s on **%s**'),_('started'),_(my_time(_var($var,'sbUpdated',0)).day_count(_var($var,'sbSynced',0)),0))?></td></tr>
<? break;
case "Clearing":?>
<tr><td><?status_indicator()?>**_(Started, clearing)_... <span id="fsState"></span>**</td><td><input type="submit" name="cmdNoClear" value="_(Cancel)_"></td><td></td></tr>
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td>
<td><?=sprintf(_('Current operation %s on **%s**'),_('started'),_(my_time(_var($var,'sbUpdated',0)).day_count(_var($var,'sbSynced',0)),0))?></td></tr>
<? break;
case "Stopping":?>
<tr><td><?status_indicator()?>**_(Stopping)_...**</td><td><input type="submit" name="cmdStop" value="_(Stop)_" disabled></td><td></td></tr>
<? break;
case "Stopped":
if (_var($var,'configValid') == "error"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Invalid, missing or expired)_ <a href="/Tools/Registration">_(registration key)_</a>.</td></tr>
<? elseif (_var($var,'configValid') == "invalid"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Too many attached devices. Please consider upgrading your)_ <a href="/Tools/Registration">_(registration key)_</a>.</td></tr>
<? elseif (_var($var,'configValid') == "ineligible"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Ineligible to run this version of Unraid OS. Please consider extending your)_ <a href="/Tools/Registration">_(registration key)_</a>.</td></tr>
<? elseif (_var($var,'configValid') == "nokeyserver"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Cannot contact key-server. Please check your)_ <a href="/Settings/NetworkSettings">_(network settings)_</a>.</td></tr>
<? elseif (_var($var,'configValid') == "withdrawn"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(This Unraid OS release has been withdrawn and may no longer be used. Please)_ <a href="/Plugins">_(update)_</a> _(your server)_.</td></tr>
<? else:
switch (_var($var,'mdState')):
case "STARTED":
?> <tr><td><?status_indicator()?>**_(Stopped)_**. _(Configuration valid)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will bring the array on-line)_.</td></tr>
<? maintenance_mode();
check_encryption();
break;
case "STOPPED":
$action = explode(' ',_var($var,'mdResyncAction'));
if ($action[0] == "recon"):
$resync = resync($action[1]);
?> <tr><td><?status_indicator()?>**_(Stopped)_**. _(Configuration valid)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**<?=_('Start')?>** <?=_("will bring the array on-line and start **$resync**")?>.</td></tr>
<? elseif ($action[0] == "clear"):?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(New data disk(s) detected)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will bring the array on-line and start **Disk-Clear** of new data disk(s))_.</td></tr>
<? elseif (_var($var,'sbClean') != "yes" && $action[0] == "check" && count($action) > 1):?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Unclean shutdown detected)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will bring the array on-line and start **Parity-Check**)_.
<br><input type="checkbox" name="optionCorrect" value="correct" checked><small>_(Write corrections to parity)_</small></td></tr>
<? elseif (_var($var,'sbClean') != "yes" && $action[0] == "check"):?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Unclean shutdown detected)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will bring the array on-line)_.</td></tr>
<? elseif (missing_cache()):?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Missing pool disk)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)" disabled></td>
<td>**_(Start)_** _(will remove the missing pool disk and then bring the array on-line)_.
<br><input type="checkbox" name="confirmStart" value="OFF" onclick="$('#cmdStart').prop('disabled',!arrayOps.confirmStart.checked)"><small>_(Yes, I want to do this)_</small></td></tr>
<? else:?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Configuration valid)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will bring the array on-line)_.</td></tr>
<? endif;
maintenance_mode();
check_encryption();
break;
case "NEW_ARRAY":
if (strpos(_var($disks['parity'],'status'),"DISK_NP") === 0 && strpos(_var($disks['parity2'],'status'),"DISK_NP") === 0):?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Configuration valid)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will record all disk information and bring the array on-line)_.
<br>_(The array will be immediately available, but **unprotected** since *parity* has not been assigned)_.</td></tr>
<? else:?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Configuration valid)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this,true)"></td>
<td>**_(Start)_** _(will record all disk information, bring the array on-line, and start Parity-Sync)_.
<br>_(The array will be immediately available, but **unprotected** until Parity-Sync completes)_.
<br><input type="checkbox" name="md_invalidslot" value="99">_(Parity is already valid)_.</td></tr>
<? endif;
maintenance_mode();
check_encryption();
break;
case "DISABLE_DISK":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Missing disk)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)" disabled></td>
<td>**_(Start)_** _(will disable the missing disk and then bring the array on-line. Install a replacement disk as soon as possible)_.
<br><input type="checkbox" name="confirmStart" value="OFF" onclick="$('#cmdStart').prop('disabled',!arrayOps.confirmStart.checked)"><small>_(Yes, I want to do this)_</small></td></tr>
<? maintenance_mode();
check_encryption();
break;
case "RECON_DISK":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Replacement disk installed)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will start **Parity-Sync** and/or **Data-Rebuild**)_.</td></tr>
<? maintenance_mode();
check_encryption();
break;
case "SWAP_DSBL":
if (_var($var,'fsCopyPrcnt') == "100"):?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Upgrading disk/swapping parity)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form,this)"></td>
<td>**_(Start)_** _(will expand the file system of the data disk (if possible); then bring the array on-line and start Data-Rebuild)_.</td></tr>
<? maintenance_mode();
check_encryption();
else:?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Upgrading disk/swapping parity)_.</td><td><input type="submit" name="cmdCopy" value="_(Copy)_" disabled></td>
<td>**_(Copy)_** _(will copy the parity information to the new *parity* disk)_.
<br>_(Once copy completes, the array may be Started, to initiate Data-Rebuild of the disabled disk)_.
<br><input type="checkbox" name="confirmStart" value="OFF" onclick="arrayOps.cmdCopy.disabled=!arrayOps.confirmStart.checked"><small>_(Yes, I want to do this)_</small></td></tr>
<? endif;
break;
case "ERROR:INVALID_EXPANSION":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Invalid expansion)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(You may not add new disk(s) and also remove existing disk(s))_.</td></tr>
<? break;
case "ERROR:NEW_DISK_TOO_SMALL":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Replacement disk is too small)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(The replacement disk must be as big or bigger than the original)_.</td></tr>
<? break;
case "ERROR:PARITY_NOT_BIGGEST":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Disk in parity slot is not biggest)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(If this is a new array, move the largest disk into the *parity* slot)_.
<br>_(If you are adding a new disk or replacing a disabled disk, try Parity-Swap)_.</td></tr>
<? break;
case "ERROR:TOO_MANY_MISSING_DISKS":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Invalid configuration)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Too many wrong and/or missing disks)_!</td></tr>
<? break;
case "ERROR:NO_DATA_DISKS":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(No data disks)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(No array data disks have been assigned)_!</td></tr>
<? break;
case "ERROR:NO_DEVICES":?>
<tr><td><?status_indicator()?>**_(Stopped)_**. _(No devices)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(No array devices have been assigned)_!</td></tr>
<? break;
endswitch;
endif;
endswitch;?>
<tr><td></td><td class="line" colspan="2"></td></tr>
</table>
</form>
<?if (_var($var,'fsState') != "Stopped"):?>
<?if ($keyfile):?>
<form name="delete_keyfile" method="POST" action="/update.php" target="progressFrame">
<input type="hidden" name="#file" value="unused">
<input type="hidden" name="#include" value="webGui/include/KeyUpload.php">
<table markdown="1" class="ArrayOperation-Table array_status noshift">
<tr><td></td><td><input type="submit" name="#apply" value="_(Delete)_" disabled></td><td>**_(Delete)_** _(will delete the encryption keyfile)_.
<br><input type="checkbox" onchange="toggleApply(this.checked)"><small>_(Yes, I want to do this)_</small></td></tr>
<tr><td></td><td class="line" colspan="2"></td></tr>
</table>
</form>
<?endif;?><!-- markdown fix -->
<table markdown="1" class="ArrayOperation-Table array_status noshift">
<tr><td></td><td><span class="buttons-spaced"><input type="button" id="spinup-button" onclick="$('[id^=button-]').prop('disabled',true);toggle_state('up')" value="_(Spin Up)_"><input type="button" id="spindown-button" onclick="$('[id^=button-]').prop('disabled',true);toggle_state('down')" value="_(Spin Down)_"></span></td>
<td>**_(Spin Up)_** _(will immediately spin up all disks)_.<br>**_(Spin Down)_** _(will immediately spin down all disks)_.</td></tr>
<tr id="clearstats" class="hidden"><td></td><td><input type="button" value="_(Clear Stats)_" onclick="toggle_state('Clear')"></td><td>**_(Clear Stats)_** _(will immediately clear all disk statistics)_.</td></tr>
<tr><td></td><td class="line" colspan="2"></td></tr>
</table>
<?if (_var($var,'shareUser') == 'e' && $pool_devices):?>
<form name="mover_schedule" method="POST" action="/update.htm" target="progressFrame">
<table markdown="1" class="ArrayOperation-Table array_status noshift">
<tr><td></td><td><input type="submit" id="mover-button" name="cmdStartMover" value="_(Move)_"></td><td id="mover-text"></td></tr>
</table>
</form>
<?endif;?>
<?elseif ($wrong && $keyfile):?>
<form name="delete_keyfile" method="POST" action="/update.php" target="progressFrame">
<input type="hidden" name="#file" value="unused">
<input type="hidden" name="#include" value="webGui/include/KeyUpload.php">
<table markdown="1" class="ArrayOperation-Table array_status noshift">
<tr><td></td><td><input type="submit" name="#apply" value="_(Delete)_"></td><td>**_(Delete)_** _(will delete the encryption keyfile)_.</td></tr>
<tr><td></td><td class="line" colspan="2"></td></tr>
</table>
</form>
<?endif;?><!-- markdown fix -->
<form name="shutdownOps" method="POST" action="/webGui/include/Boot.php">
<table markdown="1" class="ArrayOperation-Table array_status noshift">
<tr><td></td><td><span class="buttons-spaced"><input type="button" name="reboot" value="_(Reboot)_" onclick="shutdown_now(this.form,'reboot')"><input type="button" name="shutdown" value="_(Shutdown)_" onclick="shutdown_now(this.form,'shutdown')"></span></td>
<td>**_(Reboot)_** _(will activate a *clean* system reset)_.<br>**_(Shutdown)_** _(will activate a *clean* system power down)_.<br><label class="flex flex-row items-center gap-2"><input type="checkbox" name="safemode">_(Reboot in safe mode)_</label></td></tr>
<tr><td></td><td class="line" colspan="2"></td></tr>
</table>
</form>
<!-- markdown fix --></p><?if (isset($display['sleep'])) eval('?>'.parse_file($display['sleep']))?>
:array_status_help:
<?if (_var($var,'fsState') == "Stopped"):?>
:array_devices_help:
<?if ($encrypt):?>
<div></div>
:encryption_help:
<?endif;?>
<?endif;?>