mirror of
https://github.com/unraid/webgui.git
synced 2026-01-14 05:30:07 -06:00
673 lines
39 KiB
Plaintext
673 lines
39 KiB
Plaintext
Menu="Main:5"
|
|
Title="Array Operation"
|
|
Tag="snowflake-o"
|
|
---
|
|
<?PHP
|
|
/* Copyright 2005-2020, Lime Technology
|
|
* Copyright 2012-2020, 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['luksKeyfile']);
|
|
$missing = file_exists('/var/tmp/missing.tmp');
|
|
$encrypt = false;
|
|
/* only one of $present, $missing, or $wrong will be true, or all will be false */
|
|
$forced = $present = $wrong = false;
|
|
foreach ($disks as $disk) {
|
|
if (strpos($disk['fsType'],'luks:')!==false || ($disk['fsType']=='auto' && (strpos($var['defaultFsType'],'luks:')!==false || $disk['luksState']==2 || $disk['luksState']==3))) {
|
|
$encrypt = true;
|
|
if ($disk['luksState']==0) $forced = true;
|
|
if ($disk['luksState']==1) $present = true;
|
|
if ($disk['luksState']==2) $missing = true;
|
|
if ($disk['luksState']==3) $wrong = true;
|
|
}
|
|
}
|
|
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 class='gap'>"._('Encryption status').":</td><td><span class='red-text'>$status</span><span id='pass'><input name='luksReformat' type='checkbox' onchange='selectInput(this.form)'>permit reformat</span></td></tr>";
|
|
echo "<tr><td></td><td class='gap'>"._('Encryption input').":</td><td>";
|
|
echo "<select 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 class='gap'>"._('Passphrase').":</td><td><input type='password' name='text' maxlength='512' value='' onkeyup='selectInput(this.form)' placeholder='"._('use printable characters only')."'><input name='showPass' type='checkbox' onchange='selectInput(this.form)'>"._('show passphrase')."</td></tr>";
|
|
echo "<tr id='copy'><td></td><td class='gap'>"._('Retype passphrase').":</td><td><input type='password' name='copy' maxlength='512' value='' onkeyup='selectInput(this.form)'></td></tr>";
|
|
echo "<tr id='file'><td></td><td class='gap'>"._('Keyfile').":</td><td><input type='file' name='local' onchange='getFileContent(event,this.form)'></td></tr>";
|
|
}
|
|
function maintenance_mode() {
|
|
echo "<tr>";
|
|
echo "<td></td>";
|
|
echo "<td><input type='checkbox' name='startMode' value='Maintenance'>_(Maintenance mode)_</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['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($disk['status'],'_MISSING')!==false);
|
|
return $missing;
|
|
}
|
|
?>
|
|
<style>
|
|
tr#copy,tr#file{display:none}
|
|
td.gap{padding-left:26px!important}
|
|
td.wrap{white-space:normal!important}
|
|
span#pass{display:none;margin-left:20px}
|
|
</style>
|
|
<script src="<?autov('/webGui/javascript/jquery.base64.js')?>"></script>
|
|
<script>
|
|
var ctrl = "<span class='status <?=$tabbed?'':'vhshift'?>'><a style='cursor:pointer' class='tooltip_diskio' title='_(Toggle reads/writes display)_' onclick='toggle_diskio();return false'><i class='toggle fa'></i></a></span>";
|
|
|
|
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) {
|
|
$(form).append('<input type="hidden" name="cmdStart" value="Start">');
|
|
if (form.input === undefined) {
|
|
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 <b>ASCII</b> characters from space " " to tilde "~")_<br>_(Otherwise use the <b>keyfile</b> 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();});
|
|
}
|
|
function parityWarning(form) {
|
|
if (form.md_invalidslot.checked) {
|
|
<?if (strpos($disks['parity2']['status'],'_NP')===false):?>
|
|
var text = '_(<i>Dual parity valid</i> requires <b>ALL</b> disks in their original slots)_';
|
|
<?else:?>
|
|
var text = '_(<i>Parity valid</i> requires <b>ALL</b> disks to have their original content)_';
|
|
<?endif;?>
|
|
} else {
|
|
var text = '_(<i>Parity</i> disk(s) content will be overwritten)_';
|
|
}
|
|
swal({title:'_(Proceed to start)_',text:text,html:true,type:'warning',showCancelButton:true,confirmButtonText:'_(Proceed)_',cancelButtonText:'_(Cancel)_'},function(){prepareInput(form);});
|
|
}
|
|
function tab0() {
|
|
$.removeCookie('one',{path:'/'});
|
|
$.cookie('tab','tab0',{path:'/'});
|
|
}
|
|
function parityStatus() {
|
|
$.post('/webGui/include/DeviceList.php',{path:'<?=$path?>',device:'parity'},function(data) {
|
|
if (data) {$.each(data.split(';'),function(k,v) {if ($('#line'+k).length>0) $('#line'+k).html(v);});}
|
|
timers.parityStatus = setTimeout(parityStatus,3000);
|
|
if (!data && $('#cancelButton').length>0 && $('#cancelButton').val()=='_(Cancel)_') {
|
|
$('#cancelButton').val('Done').prop('onclick',null).off('click').click(function(){refresh();});
|
|
$('#cancelText').html('');
|
|
$('#line4').html('_(completed)_');
|
|
}
|
|
});
|
|
}
|
|
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',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="cmdNoCheck" value="Cancel">');
|
|
<?if ($confirm['stop']):?>
|
|
swal({title:'_(Proceed)_?',text:'_(This will stop the running operation)_: '+text,type:'warning',showCancelButton:true,confirmButtonText:'_(Proceed)_',cancelButtonText:'_(Cancel)_'},function(p){if (p) form.submit(); else $('input[name="cmdNoCheck"]').remove();});
|
|
<?else:?>
|
|
form.submit();
|
|
<?endif;?>
|
|
}
|
|
function pauseParity(form) {
|
|
$(form).append('<input type="hidden" name="cmdNoCheck" value="Pause">');
|
|
$('#pauseButton').val('_(Resume)_').prop('onclick',null).off('click').click(function(){resumeParity(form);});
|
|
form.submit();
|
|
}
|
|
function resumeParity(form) {
|
|
$(form).append('<input type="hidden" name="cmdCheck" value="Resume">');
|
|
$('#pauseButton').val('_(Pause)_').prop('onclick',null).off('click').click(function(){pauseParity(form);});
|
|
form.submit();
|
|
}
|
|
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',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);
|
|
}
|
|
parityStatus();
|
|
<?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[id=title]:not(":last, .disable_diskio")').each(function(){$(this).append(ctrl);});
|
|
<?endif;?>
|
|
<?if (substr($var['fsState'],-3)=='ing'):?>
|
|
function reload_page() {
|
|
$.get('/webGui/include/ReloadPage.php',function(data) {
|
|
switch (data) {
|
|
case 'wait':
|
|
setTimeout(reload_page,10000);
|
|
break;
|
|
case 'stop':
|
|
setTimeout(refresh,0);
|
|
break;
|
|
default:
|
|
if (data) $('#fsState').html(data);
|
|
setTimeout(reload_page,3000);
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
reload_page();
|
|
<?endif;?>
|
|
$('.tooltip_diskio').tooltipster({delay:100,trigger:'custom',triggerOpen:{mouseenter:true},triggerClose:{click:false,scroll:true,mouseleave:true}});
|
|
toggle_diskio(true);
|
|
<?
|
|
$parity = $var['mdResync'] ? '<br><small>_(Disabled)_ -- _(Parity operation is running)_</small>' : '';
|
|
$mover = file_exists('/var/run/mover.pid') ? '<br><small>_(Disabled)_ -- _(Mover is running)_</small>' : '';
|
|
$btrfs = exec('pgrep -cf /sbin/btrfs') ? '<br><small>_(Disabled)_ -- _(BTRFS operation is running)_</small>' : '';
|
|
?>
|
|
$(function(){
|
|
var form = document.arrayOps;
|
|
if (form.input !== undefined) selectInput(form);
|
|
});
|
|
function formatWarning(val) {
|
|
if (val==true)
|
|
swal({
|
|
title: '_(Format Unmountable disks)_',
|
|
text: '_(Create an empty file system on the disks shown as <b>Unmountable</b> 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><b>_(WARNING)_</b>'
|
|
+ '<p>_(A format is <b>NEVER</b> 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",
|
|
confirmButtonText:'_(Ok)_',
|
|
html: true
|
|
});
|
|
}
|
|
</script>
|
|
<form name="arrayOps" method="POST" action="/update.htm" target="progressFrame">
|
|
<input type="hidden" name="startState" value="<?=htmlspecialchars($var['mdState'])?>">
|
|
<input type="hidden" name="file" value="">
|
|
<table markdown="1" class="array_status">
|
|
<?switch ($var['fsState']):
|
|
case "Started":?>
|
|
<tr><td><?status_indicator()?>**_(Started)_<?=(($var['startMode']=='Maintenance')?' - _(Maintenance Mode)_':'')?>**</td>
|
|
<td><input type="button" value="_(Stop)_" onclick="stopArray(this.form)"<?if ($parity||$mover||$btrfs):?> disabled<?endif;?>></td>
|
|
<td id="stop">**_(Stop)_** _(will take the array off-line)_.<?=$parity?:$mover?:$btrfs?:''?></td></tr>
|
|
<? if ($var['fsNumUnmountable']>0):?>
|
|
<tr><td>**<?=_('Unmountable disk'.($var['fsNumUnmountable']==1?'':'s').' present')?>:**<br>
|
|
<? $cache = [];
|
|
foreach ($disks as $disk) if (substr($disk['fsStatus'],0,11)=='Unmountable' || in_array(prefix($disk['name']),$cache)) {
|
|
if ($disk['id']) echo "<span class='blue-text'>".my_disk($disk['name'])."</span> • ".my_id($disk['id'])." (".$disk['device'].")<br>";
|
|
if (in_array($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['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;
|
|
if (!$var['mdResyncPos']):
|
|
if (strstr($var['mdResyncAction'],"recon")):?>
|
|
<tr><td></td><td><input type="submit" name="cmdCheck" value="_(Sync)_"></td><td>**_(Sync)_** _(will start **Parity-Sync** and/or **Data-Rebuild**)_.</td></tr>
|
|
<? elseif (strstr($var['mdResyncAction'],"clear")):?>
|
|
<tr><td></td><td><input type="submit" name="cmdCheck" value="_(Clear)_"></td><td>**_(Clear)_** _(will start **Clearing** new data disk(s))_.</td></tr>
|
|
<? else:
|
|
if ($var['mdResyncAction']=="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>
|
|
<? elseif (strstr($var['mdResyncAction'],"check")):?>
|
|
<tr><td>_(Parity is valid)_.</td><td><input type="submit" name="cmdCheck" value="_(Check)_"></td><td>**_(Check)_** _(will start **Parity-Check**)_. <a href="/Main/Scheduler"<?if ($tabbed):?> onclick="$.cookie('one','tab1',{path:'/'})"<?endif;?>>(_(Schedule)_)</a>
|
|
<br><input type="checkbox" name="optionCorrect" value="correct" checked><small>_(Write corrections to parity)_</small></td></tr>
|
|
<? endif;?>
|
|
<tr><td></td><td><input type="button" value="_(History)_" onclick="openBox('/webGui/include/ParityHistory.php','_(Parity/Read-Check History)_',600,900,false)"></td>
|
|
<? if ($var['sbSyncExit']!=0):?>
|
|
<td class="wrap"><?=sprintf(_('Last check incomplete on **%s**'),my_lang(my_time($var['sbSynced2']).my_lang(day_count($var['sbSynced2']),1)))?><?if ($var['sbSynced2']):?><br><?=sprintf(_('Finding **%s** error'.($var['sbSyncErrs']==1?'':'s')),$var['sbSyncErrs']?:'0')?><?endif;?>
|
|
<i class="fa fa-fw fa-dot-circle-o"></i>_(Error code)_: <?=my_error($var['sbSyncExit'])?></td></tr>
|
|
<? elseif ($var['sbSynced']==0):
|
|
[$date,$duration,$speed,$status,$error] = last_parity_log();?>
|
|
<? if ($status==0):?>
|
|
<td class="wrap"><?=sprintf(_('Last checked on **%s**'),my_lang(my_time($date).my_lang(day_count($date),1)))?><br><?=sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0')?>
|
|
<i class="fa fa-fw fa-clock-o"></i>_(Duration)_: <?=my_lang(my_check($duration,$speed),2)?></td></tr>
|
|
<? else:?>
|
|
<td class="wrap"><?=sprintf(_('Last check incomplete on **%s**'),my_lang(my_time($date).my_lang(day_count($date),1)))?><br><?=sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0')?>
|
|
<i class="fa fa-fw fa-dot-circle-o"></i>_(Error code)_: <?=my_error($status)?></td></tr>
|
|
<? endif;
|
|
elseif ($var['sbSynced2']==0):
|
|
[$date,$duration,$speed,$status,$error] = explode('|', read_parity_log($var['sbSynced']));
|
|
if ($status==0):?>
|
|
<td class="wrap"><?=sprintf(_('Last checked on **%s**'),my_lang(my_time($var['sbSynced']).my_lang(day_count($var['sbSynced']),1)))?><br><?=sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0')?>
|
|
<i class="fa fa-fw fa-clock-o"></i>_(Duration)_: <?=my_lang(my_check($duration,$speed),2)?></td></tr>
|
|
<? else:?>
|
|
<td class="wrap"><?=sprintf(_('Last check incomplete on **%s**'),my_lang(my_time($var['sbSynced']).my_lang(day_count($var['sbSynced']),1)))?><br><?=sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0')?>
|
|
<i class="fa fa-fw fa-dot-circle-o"></i>_(Error code)_: <?=my_error($status)?></td></tr>
|
|
<? endif;
|
|
else:
|
|
$duration = $var['sbSynced2']-$var['sbSynced'];
|
|
$speed = $duration?my_scale($var['mdResyncSize']*1024/$duration,$unit,1)." $unit/sec":'';?>
|
|
<td class="wrap"><?=sprintf(_('Last check completed on **%s**'),my_lang(my_time($var['sbSynced2']).my_lang(day_count($var['sbSynced2']),1)))?><br><?=sprintf(_('Finding **%s** error'.($var['sbSyncErrs']==1?'':'s')),$var['sbSyncErrs']?:'0')?>
|
|
<i class="fa fa-fw fa-clock-o"></i>_(Duration)_: <?=my_lang(my_check($duration,$speed),2)?></td></tr>
|
|
<? endif;
|
|
endif;
|
|
else:
|
|
if ($var['mdResyncAction']=="check"):?>
|
|
<tr><td>_(Read-Check in progress)_.</td><td>
|
|
<input type="button" id="pauseButton"<?if ($var['mdResync']):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?>>
|
|
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Read-Check')"></td>
|
|
<td id="cancelText"><?if ($var['mdResync']):?>**_(Pause)_** _(will pause the Read-Check)_.<?else:?>**_(Resume)_** _(will resume the paused Read-Check)_.<?endif;?><br>**_(Cancel)_** _(will stop the Read-Check)_.</td></tr>
|
|
<? elseif (strstr($var['mdResyncAction'],"check")):?>
|
|
<tr><td>_(Parity-Check in progress)_.</td><td>
|
|
<input type="button" id="pauseButton"<?if ($var['mdResync']):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?>>
|
|
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Parity-Check')"></td>
|
|
<td id="cancelText"><?if ($var['mdResync']):?>**_(Pause)_** _(will pause the Parity-Check)_.<?else:?>**_(Resume)_** _(will resume the paused Parity-Check)_.<?endif;?><br>**_(Cancel)_** _(will stop the Parity-Check)_.</td></tr>
|
|
<? elseif (strstr($var['mdResyncAction'],"recon")):?>
|
|
<tr><td>_(Parity-Sync/Data-Rebuild in progress)_.</td><td>
|
|
<input type="button" id="pauseButton"<?if ($var['mdResync']):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?>>
|
|
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Parity-Sync/Data-Rebuild')"></td>
|
|
<td id="cancelText"><?if ($var['mdResync']):?>**_(Pause)_** _(will pause the Parity-Sync/Data-Rebuild)_.<?else:?>**_(Resume)_** _(will resume the paused Parity-Sync/Data-Rebuild)_.<?endif;?><br>**_(Cancel)_** _(will stop Parity-Sync/Data-Rebuild)_.
|
|
<br>_(WARNING: canceling may leave the array unprotected)_!</td></tr>
|
|
<? elseif (strstr($var['mdResyncAction'],"clear")):?>
|
|
<tr><td>_(Clearing in progress)_.</td><td>
|
|
<input type="button" id="pauseButton"<?if ($var['mdResync']):?> value="_(Pause)_" onclick="pauseParity(this.form)"<?else:?> value="_(Resume)_" onclick="resumeParity(this.form)"<?endif;?>>
|
|
<input type="button" id="cancelButton" value="_(Cancel)_" onclick="stopParity(this.form,'Clearing')"></td>
|
|
<td id="cancelText"><?if ($var['mdResync']):?>**_(Pause)_** _(will pause the Clearing)_.<?else:?>**_(Resume)_** _(will resume the paused Clearing)_.<?endif;?><br>**_(Cancel)_** _(will stop Clearing)_.</td></tr>
|
|
<? endif;?>
|
|
<tr><td></td><td><input type="button" value="_(History)_" onclick="openBox('/webGui/include/ParityHistory.php','_(Parity/Read-Check History)_',600,900,false)"></td>
|
|
<td><?=sprintf(_('Current operation %s on **%s**'),($var['mdResync']?_('started'):_('paused')),my_lang(my_time($var['sbUpdated']).my_lang(day_count($var['sbSynced']),1)))?></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 (strstr($var['mdResyncAction'],"check ")):?>
|
|
<tr><td>_(Sync errors)_ <?if ($var['mdResyncCorr']==0):?>_(detected)_:<?else:?>_(corrected)_:<?endif;?></td><td id="line5"></td><td></td></tr>
|
|
<? endif;
|
|
endif;
|
|
break;
|
|
case "Starting":?>
|
|
<tr><td><?status_indicator()?>**_(Starting)_...**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></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>
|
|
<? break;
|
|
case "Copying":?>
|
|
<tr><td><?status_indicator()?><span id="fsState">**_(Copying)_, <?=$var['fsCopyPrcnt']?>% _(complete)_...**</span></td><td><input type="submit" name="cmdNoCopy" value="_(Cancel)_"></td><td></td></tr>
|
|
<? break;
|
|
case "Clearing":?>
|
|
<tr><td><?status_indicator()?><span id="fsState">**_(Clearing)_, <?=$var['fsClearPrcnt']?>% _(complete)_...**</span></td><td><input type="submit" name="cmdNoClear" value="_(Cancel)_"></td><td></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['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['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['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['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['mdState']):
|
|
case "STOPPED":
|
|
if (strstr($var['mdResyncAction'],"recon")):?>
|
|
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Configuration valid)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form)"></td>
|
|
<td>**_(Start)_** _(will bring the array on-line and start **Parity-Sync** and/or **Data-Rebuild**)_.</td></tr>
|
|
<? elseif ($var['mdResyncAction']=="clear"):?>
|
|
<tr><td><?status_indicator()?>**_(Stopped)_**. _(New data disk(s) detected)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form)"></td>
|
|
<td>**_(Start)_** _(will bring the array on-line and start **Clearing** new data disk(s))_.</td></tr>
|
|
<? elseif ($var['sbClean']!="yes" && $var['mdResyncAction']=="check"):?>
|
|
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Unclean shutdown detected)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form)"></td>
|
|
<td>**_(Start)_** _(will bring the array on-line)_.</td></tr>
|
|
<? elseif ($var['sbClean']!="yes" && strstr($var['mdResyncAction'],"check")):?>
|
|
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Unclean shutdown detected)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form)"></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 (missing_cache()):?>
|
|
<tr><td><?status_indicator()?>**_(Stopped)_**. _(Missing Cache disk)_.</td><td><input type="button" id="cmdStart" value="_(Start)_" onclick="prepareInput(this.form)" disabled></td>
|
|
<td>**_(Start)_** _(will remove the missing cache 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)"></td>
|
|
<td>**_(Start)_** _(will bring the array on-line)_.</td></tr>
|
|
<? endif;
|
|
maintenance_mode();
|
|
check_encryption();
|
|
break;
|
|
case "NEW_ARRAY":
|
|
if (strpos($disks['parity']['status'],"DISK_NP")===0 && strpos($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)"></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="parityWarning(this.form)"></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)" 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)"></td>
|
|
<td>**_(Start)_** _(will start **Parity-Sync** and/or **Data-Rebuild**)_.</td></tr>
|
|
<? maintenance_mode();
|
|
check_encryption();
|
|
break;
|
|
case "SWAP_DSBL":
|
|
if ($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)"></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;
|
|
endswitch;
|
|
endif;
|
|
endswitch;?>
|
|
<tr><td></td><td class="line" colspan="2"></td></tr>
|
|
</table>
|
|
</form>
|
|
<?if ($var['fsState']=="Started"):?>
|
|
<?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="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="array_status noshift">
|
|
<tr><td></td><td><input type="button" id="button-up" onclick="$('[id^=button-]').prop('disabled',true);toggle_state('up')" value="_(Spin Up)_"<?=$parity?' disabled':''?>><input type="button" id="button-down" onclick="$('[id^=button-]').prop('disabled',true);toggle_state('down')" value="_(Spin Down)_"<?=$parity?' disabled':''?>></td>
|
|
<td>**_(Spin Up)_** _(will immediately spin up all disks)_.<br>**_(Spin Down)_** _(will immediately spin down all disks)_.</td></tr>
|
|
<tr><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['shareUser']=='e' && $var['shareCacheEnabled']=="yes" && $pool_devices):?>
|
|
<form name="mover_schedule" method="POST" action="/update.htm" target="progressFrame">
|
|
<table markdown="1" class="array_status noshift">
|
|
<tr><td></td>
|
|
<?if ($mover):?>
|
|
<td><input type="submit" name="cmdStartMover" value="_(Move)_" disabled></td><td>_(Disabled)_ - _(Mover is running)_.</td>
|
|
<?else:?>
|
|
<td><input type="submit" name="cmdStartMover" value="_(Move)_"></td><td>**_(Move)_** _(will immediately invoke the Mover)_. <a href="/Main/Scheduler"<?if ($tabbed):?> onclick="$.cookie('one','tab2',{path:'/'})"<?endif;?>>(_(Schedule)_)</a></td>
|
|
<?endif;?>
|
|
</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="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="array_status noshift">
|
|
<tr><td></td><td><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')"></td>
|
|
<td>**_(Reboot)_** _(will activate a *clean* system reset)_.<br>**_(Shutdown)_** _(will activate a *clean* system power down)_.<br><input type="checkbox" name="safemode"><small>_(Reboot in safe mode)_</small></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']))?>
|
|
<?if ($btrfs):?>
|
|
<script>
|
|
function enable_stop() {
|
|
$.get('/webGui/include/ReloadPage.php',{btrfs:'btrfs'},function(data) {
|
|
switch (data) {
|
|
case 'disable':
|
|
setTimeout(enable_stop,5000);
|
|
break;
|
|
case 'enable':
|
|
var stop = $('td#stop');
|
|
stop.html(stop.html().replace("<?=$btrfs?>",""));
|
|
$('input[value="_(Stop)_"]').prop('disabled',false);
|
|
break;}
|
|
});
|
|
}
|
|
enable_stop();
|
|
</script>
|
|
<?endif;?>
|
|
|
|
:help6
|
|
> **Colored Status Indicator** the significance of the color indicator of the *Array* is as follows:
|
|
>
|
|
> <i class='fa fa-circle orb green-orb'></i>Array is Started and Parity is valid.
|
|
>
|
|
> <i class='fa fa-circle orb grey-orb'></i>Array is Stopped, Parity is valid.
|
|
>
|
|
> <i class='fa fa-warning orb yellow-orb'></i>Array is Started, but Parity is invalid.
|
|
>
|
|
> <i class='fa fa-warning orb grey-orb'></i>Array is Stopped, Parity is invalid.
|
|
>
|
|
:end
|
|
<?if ($var['fsState'] == "Stopped"):?>
|
|
:help7
|
|
> #### Assigning Devices
|
|
>
|
|
> An Unraid disk array consists of a single parity disk and a number of data disks. The data
|
|
> disks are exclusively used to store user data, and the parity disk provides the redundancy necessary
|
|
> to recover from any singe disk failure.
|
|
>
|
|
> Note that we are careful to use the term *disk* when referring to an array storage device. We
|
|
> use the term *hard drive* (or sometimes just *drive*) when referring to an actual hard disk drive (HDD)
|
|
> device. This is because in a RAID system it is possible to read/write an array disk whose corresponding
|
|
> hard drive is disabled or even missing! In addition, it is useful to be able to ask, "which device is
|
|
> assigned to be the parity disk?"; or, "which device corresponds to disk2?".
|
|
>
|
|
> We therefore need a way to assign hard drives to array disks. This is accomplished here on the
|
|
> Main page when the array is stopped. There is a drop-down box for each array disk which lists all the
|
|
> unassigned devices. To assign a device simply select it from the list. Each time a device
|
|
> assignment is made, the system updates a configuration file to record the assignment.
|
|
>
|
|
> #### Requirements
|
|
>
|
|
> Unlike traditional RAID systems which stripe data across all the array devices, an Unraid server
|
|
> stores files on individual hard drives. Consequently, all file write operations will involve both the
|
|
> data disk the file is being written to, and the parity disk. For these reasons,
|
|
>
|
|
> * the parity disk size must be as large or larger than any of the data disks,
|
|
>
|
|
> and
|
|
>
|
|
> * given a choice, the parity disk should be the fastest disk in your collection.
|
|
>
|
|
> #### Guidelines
|
|
>
|
|
> Here are the steps you should follow when designing your Unraid disk array:
|
|
>
|
|
> 1. Decide which hard drive you will use for parity, and which hard drives you will use for
|
|
> data disk1, disk2, etc., and label them in some fashion. Also, find the serial number of each hard
|
|
> drive and jot it down somewhere; you will need this information later.
|
|
>
|
|
> 2. Install your hard drive devices, boot Unraid OS and bring up the webGui. If this is a fresh system
|
|
> build, then the Main page will show no disks installed. This doesn't mean the system can't detect your
|
|
> hard drives; it just means that none have been assigned yet.
|
|
>
|
|
> 3. Remember the serial numbers you recored back in step 1? For parity and each data disk, select the
|
|
> proper hard drive based on its serial number from the drop down list.
|
|
>
|
|
> #### Hot Plug
|
|
>
|
|
> You may also *hot plug* hard drives into your server if your hardware supports it. For example,
|
|
> if you are using hard drive cages, you may simply plug them into your server while powered on and
|
|
> with array Stopped. Refresh the Main page to have new unassigned devices appear in the assignment
|
|
> dropdown lists.
|
|
>
|
|
> #### Next Steps
|
|
>
|
|
> Once you have assigned all of your hard drives, refer to the Array Status section below
|
|
> and Start the array.
|
|
:end
|
|
|
|
<?if ($encrypt):?>
|
|
<div></div>
|
|
:help8
|
|
> #### Encryption input
|
|
>
|
|
> With array Stopped, the user can specify a new encryption key. Note that once a device
|
|
> is formatted with a particular key it may only be opened using that same key. Changing the encryption key requires
|
|
> encrypted devices to be reformatted **resulting in permanent loss of all existing data on those devices.**
|
|
>
|
|
> #### Passphrase
|
|
>
|
|
> Enter a passphrase of up to 512 characters. It is highly advisable to only use the 95 printable characters from the
|
|
> first 128 characters of the [ASCII table](https://en.wikipedia.org/wiki/ASCII), as they will always have the same binary
|
|
> representation. Other characters may have different encoding depending on system configuration and your passphrase will
|
|
> not work with a different encoding. If you want a longer passphrase or to include binary data, upload a keyfile instead.
|
|
>
|
|
> Please refer to the [cryptsetup FAQ](https://gitlab.com/cryptsetup/cryptsetup/wikis/FrequentlyAskedQuestions#5-security-aspects)
|
|
> for what constitutes a *secure* passphrase.
|
|
>
|
|
> **Memorize** this passphrase. **IF LOST, ENCRYPTED CONTENT CANNOT BE RECOVERED!**
|
|
>
|
|
> #### Keyfile
|
|
>
|
|
> Select a local keyfile with a stored encryption key or a binary file. The maximum size of the keyfile is 8M (8388608 byte).
|
|
>
|
|
> **Backup** your local keyfile. **IF LOST, ENCRYPTED CONTENT CANNOT BE RECOVERED!**
|
|
:end
|
|
<?endif;?>
|
|
<?endif;?>
|