Files
webgui/emhttp/plugins/dynamix/ShareEdit.page

1189 lines
37 KiB
Plaintext

Menu="Share:1"
Title="Share Settings"
Tag="share-alt-square"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$width = [123,300];
if ($name == "") {
/* default values when adding new share. */
$share = [
"nameOrig" => "",
"name" => "",
"comment" => "",
"allocator" => "highwater",
"floor" => "",
"splitLevel" => "",
"include" => "",
"exclude" => "",
"useCache" => "no",
"cachePool" => "",
"cachePool2" => "",
"cow" => "auto"
];
} elseif (array_key_exists($name, $shares)) {
/* edit existing share. */
$share = $shares[$name];
} else {
/* handle share deleted case. */
echo "<p class='notice'>"._('Share')." '".htmlspecialchars($name)."' "._('has been deleted').".</p><input type='button' value=\""._('Done')."\" onclick='done()'>";
return;
}
/* If the configuration is pools only, then no array disks are available. */
$poolsOnly = ((int)$var['SYS_ARRAY_SLOTS'] <= 2) ? true : false;
/* Check for non existent pool device. */
if ($share['cachePool'] && !in_array($share['cachePool'],$pools)) $share['useCache'] = "no";
function globalInclude($name) {
global $var;
return substr($name,0,4)=='disk' && (!$var['shareUserInclude'] || in_array($name,explode(',',$var['shareUserInclude'])));
}
function sanitize(&$val) {
$data = explode('.',str_replace([' ',','],['','.'],$val));
$last = array_pop($data);
$val = count($data) ? implode($data).".$last" : $last;
}
/**
* Preset space calculation and formatting.
*
* @param float|string $val Value to calculate preset space for.
* @return string|null Formatted space string or null.
*/
function presetSpace($val) {
global $disks, $shares, $name, $pools, $display;
/* Return if the value is invalid or NaN */
if (!$val || strcasecmp($val, 'NaN') == 0) return null;
/* Sanitize the value */
sanitize($val);
/* Prepare the largest array disk */
$large = [];
foreach (data_filter($disks) as $disk) {
$large[] = _var($disk, 'fsSize');
}
/* Prepare the fsSize array for each pool */
foreach ($pools as $pool) {
$fsSize[$pool] = _var($disks[$pool], 'fsSize', 0);
}
/* Get the maximum value from the large array, filtering out non-numeric values */
if (!empty($large)) {
$fsSize[''] = max(array_filter($large, 'is_numeric'));
} else {
$fsSize[''] = $fsSize[$pool];
}
/* Get the cache pool size */
$pool = _var($shares[$name], 'cachePool');
$size = _var($fsSize, $pool, 0);
/* Calculate the size */
$size = $size > 0 ? round(100 * $val / $size, 1) : 0;
/* Units for size formatting */
$units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$base = $val > 0 ? floor(log($val, 1000)) : 0;
$formattedSize = round($val / pow(1000, $base), 1);
$unit = _var($units, $base);
/* Get number format settings */
[$dot, $comma] = str_split(_var($display, 'number', '.,'));
/* Return the formatted size */
return $formattedSize > 0 ? number_format($formattedSize, $formattedSize - floor($formattedSize) ? 1 : 0, $dot, $comma) . ' ' . $unit : '';
}
/**
* Function to get enabled disks based on include and exclude rules.
*
* @global array $disks Array of disk names to check.
* @global array $share Array containing include and exclude rules.
*
* @return array Array of keys for enabled disk names.
*/
function enabledDisks() {
global $disks, $share;
/* Prepare the resultant array */
$trueKeys = [];
/* Process each disk in the array */
foreach ($disks as $key => $disk) {
$include = true; /* Default to true */
/* Check the include field */
if (!empty($share['include'])) {
$includedDisks = explode(',', $share['include']);
if (!in_array($key, $includedDisks)) {
$include = false;
}
}
/* Check the exclude field */
if (!empty($share['exclude'])) {
$excludedDisks = explode(',', $share['exclude']);
if (in_array($key, $excludedDisks)) {
$include = false;
}
}
/* Add to trueKeys array if the disk should be included */
if ($include) {
$trueKeys[] = $key;
}
}
return $trueKeys;
}
function fsSize() {
global $disks, $pools, $share;
/* Initialize an array to hold filesystem sizes */
$fsSize = [];
/* Initialize an array to hold sizes of enabled disks */
$small = [];
/* Get a list of enabled disks */
$enabledDisks = enabledDisks();
/* Iterate over the filtered disk data */
foreach (data_filter($disks) as $key => $disk) {
/* Check if the disk is in the list of enabled disks */
if (in_array($key, $enabledDisks)) {
/* Add the filesystem size of the enabled disk to the array */
$small[] = _var($disk, 'fsSize');
}
}
/* Set the minimum filesystem size from the array of enabled disks */
if (!empty($small)) {
$fsSize[''] = min(array_filter($small));
} else {
$fsSize[''] = 0;
}
/* Iterate over the pool names and add their filesystem sizes */
foreach ($pools as $pool) {
/* Set the filesystem size for each pool, defaulting to 0 if not set */
$fsSize[$pool] = _var($disks[$pool], 'fsSize', 0);
}
/* Return the filesystem sizes as a JSON encoded string */
return json_encode($fsSize);
}
function fsFree() {
global $disks, $pools;
/* Initialize an array to hold free filesystem space */
$fsFree = [];
/* Initialize an array to hold free space of enabled disks */
$large = [];
/* Get a list of enabled disks */
$enabledDisks = enabledDisks();
/* Iterate over the filtered disk data */
foreach (data_filter($disks) as $key => $disk) {
/* Check if the disk is in the list of enabled disks */
if (in_array($key, $enabledDisks)) {
/* Add the free filesystem space of the enabled disk to the array */
$large[] = _var($disk, 'fsFree');
}
}
/* Iterate over the pool names and add their free filesystem space */
foreach ($pools as $pool) {
/* Set the free filesystem space for each pool, defaulting to 0 if not set */
$fsFree[$pool] = _var($disks[$pool], 'fsFree', 0);
}
/* Determine the free filesystem space based on the poolsOnly flag */
if (!empty($large)) {
/* Set the maximum free filesystem space from the array of enabled disks */
$fsFree[''] = max(array_filter($large));
} else {
/* Set the minimum free filesystem space from the pool array */
$fsFree[''] = min(array_filter($fsFree));
}
/* Return the free filesystem space as a JSON encoded string */
return json_encode($fsFree);
}
function fsType() {
global $disks,$pools;
$fsType = [];
foreach ($pools as $pool) {
$fsType[] = '"'.$pool.'":"'.str_replace('luks:','',_var($disks[$pool],'fsType')).'"';
}
return implode(',',$fsType);
}
function primary() {
global $share;
return $share['useCache']=='no' ? '' : $share['cachePool'];
}
function secondary() {
global $share;
$rc = in_array($share['useCache'],['no','only']) ? '0' : '1';
return $rc;
}
function direction() {
global $share;
return $share['useCache']=='prefer' ? '1' : '0';
}
/* global shares include/exclude. */
$myDisks = array_filter(array_diff(array_keys(array_filter($disks,'my_disks')), explode(',',$var['shareUserExclude'])), 'globalInclude');
?>
:share_edit_global1_help:
<?if ($name):?>
:share_edit_global2_help:
<?endif;?>
<br />
<div class="clone1">
<span class="clone">_(Read settings from)_</span><i class="fa fa-arrow-left fa-fw"></i>
<span class="wrap"><select name="readshare" class="clone" onchange="toggleButton('readshare',false)">
<option disabled selected>_(select)_...</option>
<?
foreach ($shares as $list) if ($list['name']!=$name || !$name) echo mk_option("", $list['name'], compress($list['name']));
?>
</select></span><input type="button" id="readshare" value="_(Read)_" class="clone" onclick="readShare()" disabled>
</div>
<?if ($name):?>
<div class="clone2">
<span class="clone">_(Write settings to)_</span><i class="fa fa-arrow-right fa-fw"></i>
<span class="wrap"><select id="s5" name="writeshare" multiple onchange="toggleButton('writeshare',this.id)">
<?
$rows = [];
foreach ($shares as $list) if ($list['name']!=$name) $rows[] = mk_option("", $list['name'], compress($list['name']));
if ($rows) echo "<option>("._('All').")</option>";
foreach ($rows as $row) echo $row;
?>
</select></span><input type="button" id="writeshare" value="_(Write)_" class="clone" onclick="writeShare()" disabled>
</div>
<?endif;?>
<form markdown="1" name="share_edit" method="POST" action="/update.htm" target="progressFrame" onsubmit="return prepareEdit()"<?=$name?" onchange=\"toggleButton('writeshare',true);$('#s5').dropdownchecklist('disable')\">":">"?>
<input type="hidden" name="shareNameOrig" value="<?=htmlspecialchars($share['nameOrig'])?>">
<input type="hidden" name="shareUseCache" value="<?=$share['useCache']?>">
<input type="hidden" name="shareCachePool2" value="<?=$share['cachePool2']?>">
<input type="hidden" name="shareAllocator" value="">
<input type="hidden" name="shareSplitLevel" value="">
<input type="hidden" name="shareInclude" value="">
<input type="hidden" name="shareExclude" value="">
<div markdown="1" class="shade-<?=$display['theme']?>">
_(Share name)_:
: <input type="text" id="shareName" name="shareName" maxlength="40" autocomplete="off" spellcheck="false" value="<?=htmlspecialchars($name)?>" oninput="checkName(this.value)" title="_(Hidden share names are not allowed)_" pattern="^[^\.].*"><span id="zfs-name" class="orange-text"><i class="fa fa-warning"></i> _(Share name contains invalid characters for ZFS use)_</span>
:share_edit_name_help:
_(Comments)_:
: <input type="text" name="shareComment" maxlength="256" autocomplete="off" spellcheck="false" value="<?=htmlspecialchars($share['comment'])?>">
:share_edit_comments_help:
<div markdown="1">
_(Minimum free space)_:
: <span class="input"><input type="text" name="shareFloor" maxlength="16" autocomplete="off" spellcheck="false" class="wide" value="<?=presetSpace($share['floor'])?>" placeholder="_(Free Space will be calculated)_">
:share_edit_free_space_help:
</div>
<?if ($name):?>
<div markdown="1" class="empty">
_(Share status)_:
: _(Share is empty)_
:share_edit_status_help:
</div>
<div markdown="1" class="full">
_(Share status)_:
: <span class="full1">&nbsp;</span><span class="full2">_(Share contains data)_</span>
:share_edit_delete_help:
</div>
<?if (_var($share,'exclusive')=="yes"):?>
_(Exclusive access)_:
: _(Yes)_
:share_edit_exclusive_access_help:
<?else:?>
_(Exclusive access)_:
: _(No)_
:share_edit_exclusive_access_help:
<?endif;?>
<?endif;?>
</div>
<div markdown="1" class="shade-<?=$display['theme']?>">
_(Primary storage (for new files and folders))_:
: <select id="primary" name="shareCachePool" onchange="form.shareFloor.value='';updateScreen(z(4),'slow')">
<?=mk_option(primary(), '', _('Array'), $poolsOnly ? 'disabled' : '')?>
<?foreach ($pools as $pool):?>
<?if ($disks[$pool]['devices']) echo mk_option(primary(),$pool,my_disk($pool),$disks[$pool]['shareEnabled']=='yes'?"":"disabled")?>
<?endforeach;?>
</select>
:share_edit_primary_storage_help:
<div markdown="1" id="cow-setting">
_(Enable Copy-on-write)_:
: <span class="input"><select name="shareCOW"<?if ($name):?> disabled<?endif;?>>
<?=mk_option($share['cow'], "no", _('No'))?>
<?=mk_option($share['cow'], "auto", _('Auto'))?>
</select></span><?if ($name):?><i class='fa fa-info i'></i>_(Set when adding new share only)_<?endif;?>
:share_edit_copy_on_write_help:
</div>
<div markdown="1" id="moreSettings1">
_(Allocation method)_:
: <select name="shareAllocator1">
<?=mk_option($share['allocator'], "highwater", _('High-water'))?>
<?=mk_option($share['allocator'], "fillup", _('Fill-up'))?>
<?=mk_option($share['allocator'], "mostfree", _('Most-free'))?>
</select>
:share_edit_allocation_method_help:
_(Split level)_:
: <select name="shareSplitLevel1">
<?=mk_option($share['splitLevel'], "", _('Automatically split any directory as required'))?>
<?=mk_option($share['splitLevel'], "1", _('Automatically split only the top level directory as required'))?>
<?=mk_option($share['splitLevel'], "2", _('Automatically split only the top two directory levels as required'))?>
<?=mk_option($share['splitLevel'], "3", _('Automatically split only the top three directory levels as required'))?>
<?=mk_option($share['splitLevel'], "4", _('Automatically split only the top four directory levels as required'))?>
<?=mk_option($share['splitLevel'], "5", _('Automatically split only the top five directory levels as required'))?>
<?=mk_option($share['splitLevel'], "0", _('Manual: do not automatically split directories'))?>
</select>
:share_edit_split_level_help:
_(Included disk(s))_:
: <select id="s1" name="shareInclude1" multiple>
<?foreach ($myDisks as $disk):?>
<?=mk_option_luks($disk, $share['include'], strstr(_var($disks[$disk],'fsType'),':',true))?>
<?endforeach;?>
</select>
:share_edit_included_disks_help:
_(Excluded disk(s))_:
: <select id="s2" name="shareExclude1" multiple>
<?foreach ($myDisks as $disk):?>
<?=mk_option_luks($disk, $share['exclude'], strstr(_var($disks[$disk],'fsType'),':',true))?>
<?endforeach;?>
</select>
:share_edit_excluded_disks_help:
</div>
</div>
<div markdown="1" class="shade-<?=$display['theme']?>">
_(Secondary storage)_:
: <select id="secondary" onchange="updateScreen(z(4),'slow')">
<?=mk_option(secondary(),'0',_('None'))?>
<?=mk_option(secondary(),'1',_('Array'), $poolsOnly ? 'disabled' : '')?>
<?foreach ($pools as $pool):?>
<?if ($disks[$pool]['devices']) echo mk_option(secondary(),$pool,my_disk($pool),$disks[$pool]['shareEnabled']=='yes'?"":"disabled")?>
<?endforeach;?>
</select>
:share_edit_secondary_storage_help:
<div markdown="1" id="moreSettings2">
_(Allocation method)_:
: <select name="shareAllocator2">
<?=mk_option($share['allocator'], "highwater", _('High-water'))?>
<?=mk_option($share['allocator'], "fillup", _('Fill-up'))?>
<?=mk_option($share['allocator'], "mostfree", _('Most-free'))?>
</select>
:share_edit_allocation_method_help:
_(Split level)_:
: <select name="shareSplitLevel2">
<?=mk_option($share['splitLevel'], "", _('Automatically split any directory as required'))?>
<?=mk_option($share['splitLevel'], "1", _('Automatically split only the top level directory as required'))?>
<?=mk_option($share['splitLevel'], "2", _('Automatically split only the top two directory levels as required'))?>
<?=mk_option($share['splitLevel'], "3", _('Automatically split only the top three directory levels as required'))?>
<?=mk_option($share['splitLevel'], "4", _('Automatically split only the top four directory levels as required'))?>
<?=mk_option($share['splitLevel'], "5", _('Automatically split only the top five directory levels as required'))?>
<?=mk_option($share['splitLevel'], "0", _('Manual: do not automatically split directories'))?>
</select>
:share_edit_split_level_help:
_(Included disk(s))_:
: <select id="s3" name="shareInclude2" multiple>
<?foreach ($myDisks as $disk):?>
<?=mk_option_luks($disk, $share['include'], strstr(_var($disks[$disk],'fsType'),':',true))?>
<?endforeach;?>
</select>
:share_edit_included_disks_help:
_(Excluded disk(s))_:
: <select id="s4" name="shareExclude2" multiple>
<?foreach ($myDisks as $disk):?>
<?=mk_option_luks($disk, $share['exclude'], strstr(_var($disks[$disk],'fsType'),':',true))?>
<?endforeach;?>
</select>
:share_edit_excluded_disks_help:
</div>
</div>
<div markdown="1" class="shade-<?=$display['theme']?>">
<div markdown="1" id="moverDirection1">
_(Mover action)_:
: <span class="input"><select id="direction" onchange="updateScreen(z(3),'slow')">
<?=mk_option(direction(),'0','')?>
<?=mk_option(direction(),'1','')?>
</select></span><span id="moverAction1"></span>
:share_edit_mover_action_help:
</div>
<div markdown="1" id="moverDirection2">
_(Mover action)_:
: <span class="input"><select disabled>
<?=mk_option('','',"_(Not used)_")?>
</select></span><span id="moverAction2"></span>
:share_edit_mover_action_help:
</div>
</div>
<?if (!$name):?>
&nbsp;
: <input type="submit" name="cmdEditShare" value="_(Add Share)_" onclick="this.value='Add Share'"><input type="button" value="_(Done)_" onclick="done()">
<?else:?>
<div markdown="1" class="empty">
_(Delete)_<input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, document.getElementById('cmdEditShare'));">
: <input type="submit" id="cmdEditShare" name="cmdEditShare" value="<?= _('Apply') ?>" onclick="return handleDeleteClick(this)" disabled><input type="button" value="<?= _('Done') ?>" onclick="done()">
</div>
<div markdown="1" class="full">
&nbsp;
: <input type="submit" name="cmdEditShare" value="_(Apply)_" onclick="this.value='Apply'" disabled><input type="button" value="_(Done)_" onclick="done()">
</div>
<?endif;?>
</form>
<script>
var form = document.share_edit;
function initDropdown(forceInit) {
if (forceInit) {
destroyDropdownIfExists('#s1');
destroyDropdownIfExists('#s2');
destroyDropdownIfExists('#s3');
destroyDropdownIfExists('#s4');
<?if ($name):?>
destroyDropdownIfExists('#s5');
<?endif;?>
}
initializeDropdown('#s1', "_(All)_", <?=$width[1]?>);
initializeDropdown('#s2', "_(None)_", <?=$width[1]?>);
initializeDropdown('#s3', "_(All)_", <?=$width[1]?>);
initializeDropdown('#s4', "_(None)_", <?=$width[1]?>);
<?if ($name):?>
initializeDropdown("#s5", "_(select)_...", <?=$width[0]?>, true);
<?endif;?>
}
function initializeDropdown(selector, emptyText, width, firstItemChecksAll = false) {
try {
$(selector).dropdownchecklist({
emptyText: emptyText,
width: width,
explicitClose: "..._(close)_",
firstItemChecksAll: firstItemChecksAll
});
} catch (e) {
console.error(`Error initializing ${selector}: ` + e.message);
}
}
function destroyDropdownIfExists(selector) {
try {
$(selector).dropdownchecklist('destroy');
} catch (e) {
if (e.message.includes('prior to initialization')) {
console.log(`${selector} not initialized, skipping destroy.`);
} else {
console.error(`Error destroying ${selector}: ` + e.message);
}
}
}
function z(i) {
switch (i) {
case 0:
return $('#primary').prop('selectedIndex');
case 1:
return $('#secondary').prop('selectedIndex');
case 2:
return $('#direction').prop('selectedIndex');
case 3:
return z(2) == 0 ? 'yes' : 'prefer';
case 4:
return z(0) == 0 ? 'no' : (z(1) == 0 ? 'only' : z(3));
}
}
function updateScreen(cache, slow) {
const secondaryDropdown = document.getElementById('secondary');
const secondaryValue = secondaryDropdown.value;
const primaryDropdown = document.getElementById('primary');
const primaryValue = primaryDropdown.value;
const poolsOnly = <?= $poolsOnly ? 'true' : 'false' ?>;
switch (cache) {
case 'no':
$('#primary option:eq(' + z(0) + ')').prop('selected', true);
$('#primary option:eq(0)').prop('disabled', poolsOnly);
secondaryDropdown.selectedIndex = 0;
$('#moverDirection1').hide();
if (poolsOnly) {
$('#moverDirection2').hide();
$('#moreSettings1').hide();
} else {
$('#moverDirection2').show();
$('#moreSettings1').show(slow);
}
$('#moreSettings2').hide(slow);
$('#cow-setting').hide(slow);
form.shareAllocator.disabled = false;
form.shareSplitLevel.disabled = false;
enableDropdownIfExists('#s1');
enableDropdownIfExists('#s2');
$('#moverAction2').html("<i class='fa fa-info i'></i>_(Mover takes no action)_");
break;
case 'yes':
$('#primary option:eq(' + z(0) + ')').prop('selected', true);
$('#secondary option:eq(' + z(1) + ')').prop('selected', true);
/* Disable the secondary option that matches the primary selection and always disable option 1 */
for (let i = 0; i < secondaryDropdown.options.length; i++) {
if (secondaryDropdown.options[i].value === primaryValue || (i === 1 && poolsOnly)) {
secondaryDropdown.options[i].disabled = true;
} else {
secondaryDropdown.options[i].disabled = false;
}
}
/* If the primary value equals the current secondary value, change secondary selection to index 0 or 1 based on poolsOnly */
if (secondaryValue === primaryValue) {
secondaryDropdown.selectedIndex = poolsOnly ? 0 : 1;
if (poolsOnly) {
$('#moverAction2').html("<i class='fa fa-info i'></i>_(Mover takes no action)_");
$('#moverDirection1').hide();
$('#moverDirection2').show();
} else {
$('#moverDirection1 option:eq(0)').text($('#primary option:eq(' + z(0) + ')').text() + ' → ' + $('#secondary option:eq(' + z(1) + ')').text());
$('#moverDirection1 option:eq(1)').text($('#secondary option:eq(' + z(1) + ')').text() + ' → ' + $('#primary option:eq(' + z(0) + ')').text());
$('#moverDirection1').val('0').show();
$('#moverDirection2').hide();
$('#moverAction1').html("<i class='fa fa-info i'></i>_(Mover transfers files from Primary storage to Secondary storage)_");
}
} else if (secondaryValue !== "0") {
$('#moverDirection1 option:eq(0)').text($('#primary option:eq(' + z(0) + ')').text() + ' → ' + $('#secondary option:eq(' + z(1) + ')').text());
$('#moverDirection1 option:eq(1)').text($('#secondary option:eq(' + z(1) + ')').text() + ' → ' + $('#primary option:eq(' + z(0) + ')').text());
$('#moverDirection1').val('0').show();
$('#moverDirection2').hide();
$('#moverAction1').html("<i class='fa fa-info i'></i>_(Mover transfers files from Primary storage to Secondary storage)_");
}
$('#moreSettings1').hide(slow);
$('#cow-setting').hide(slow);
form.shareAllocator.disabled = false;
form.shareSplitLevel.disabled = false;
enableDropdownIfExists('#s3');
enableDropdownIfExists('#s4');
break;
case 'only':
$('#primary option:eq(' + z(0) + ')').prop('selected', true);
$('#primary option:eq(0)').prop('disabled', poolsOnly);
$('#secondary option:eq(' + z(1) + ')').prop('selected', true);
/* Enable all secondary options except option 1 if poolsOnly is true */
for (let i = 0; i < secondaryDropdown.options.length; i++) {
if (secondaryDropdown.options[i].value === primaryValue || (i === 1 && poolsOnly)) {
secondaryDropdown.options[i].disabled = true;
}
}
$('#moverDirection1').hide();
$('#moverDirection2').show();
$('#moreSettings1').hide(slow);
$('#moreSettings2').hide(slow);
updateCOW(form.shareCachePool.value, slow);
form.shareAllocator.disabled = true;
form.shareSplitLevel.disabled = true;
disableDropdownIfExists('#s1');
disableDropdownIfExists('#s2');
$('#moverAction2').html("<i class='fa fa-info i'></i>_(Mover takes no action)_");
break;
case 'prefer':
$('#primary option:eq(' + z(0) + ')').prop('selected', true);
$('#secondary option:eq(' + z(1) + ')').prop('selected', true);
$('#secondary option:eq(1)').prop('disabled', poolsOnly);
$('#moverDirection1 option:eq(0)').text($('#primary option:eq(' + z(0) + ')').text() + ' → ' + $('#secondary option:eq(' + z(1) + ')').text());
$('#moverDirection1 option:eq(1)').text($('#secondary option:eq(' + z(1) + ')').text() + ' → ' + $('#primary option:eq(' + z(0) + ')').text());
if (!poolsOnly) {
$('#moverDirection1').val('1').show();
} else {
secondaryDropdown.selectedIndex = 0;
$('#moverDirection1').val('1').hide();
}
$('#moverDirection2').hide();
$('#moreSettings1').hide(slow);
$('#cow-setting').hide(slow);
form.shareAllocator.disabled = false;
form.shareSplitLevel.disabled = false;
enableDropdownIfExists('#s3');
enableDropdownIfExists('#s4');
$('#moverAction1').html("<i class='fa fa-info i'></i>_(Mover transfers files from Secondary storage to Primary storage)_");
break;
}
/* Check secondary dropdown index and show/hide moreSettings2 */
if (secondaryDropdown.selectedIndex === 1) {
$('#moreSettings2').show('slow');
} else {
$('#moreSettings2').hide(slow);
}
}
/* Function to update Copy-on-Write (COW) setting */
function updateCOW(pool, slow) {
const fsType = {<?=fsType()?>};
if (fsType[pool] === 'btrfs') {
$('#cow-setting').show(slow);
} else {
$('#cow-setting').hide(slow);
}
}
function enableDropdownIfExists(selector) {
try {
$(selector).dropdownchecklist('enable');
} catch (e) {
console.log(`${selector} not initialized, cannot enable.`);
}
}
function disableDropdownIfExists(selector) {
try {
$(selector).dropdownchecklist('disable');
} catch (e) {
console.log(`${selector} not initialized, cannot disable.`);
}
}
/* Unite selected options into a comma-separated string */
function unite(field) {
const list = [];
for (let i = 0; i < field.options.length; i++) {
const item = field.options[i];
if (item.selected) {
list.push(item.value);
}
}
return list.join(',');
}
/* Set the share floor by trying to set the floor at 10% of a pool, or 10% of the largest disk in the array. */
function setFloor(val) {
const fsSize = JSON.parse('<?= fsSize() ?>');
const fsFree = JSON.parse('<?= fsFree() ?>');
/* Retrieve size and free space based on selected primary value */
const primaryValue = $('#primary').val();
const full = fsSize[primaryValue];
const free = fsFree[primaryValue];
/* This is the disk with the largest free space or current pool. */
const arrayFree = fsFree[''];
/* Calculate 10% of available size as default */
let size = parseInt(full * 0.1);
/* Parse the input string to get numeric bytes */
const parsedVal = parseDiskSize(val);
/* Check if parsedVal is a valid number and less than free */
if (parsedVal && parsedVal < free) {
size = parsedVal;
$.removeCookie('autosize-' + $('#shareName').val());
} else {
/* If parsedVal is not set or invalid */
if (!parsedVal && size < free) {
$.cookie('autosize-' + $('#shareName').val(), '1', { expires: 365 });
} else {
/* If parsedVal is greater than or equal to free, set size to 90% of free */
size = free * 0.9;
$.removeCookie('autosize-' + $('#shareName').val());
}
}
/* Is the secondary device array. */
const primarySelectElement = document.getElementById('primary');
const primarySelectedOption = primarySelectElement.options[primarySelectElement.selectedIndex];
const primaryText = primarySelectedOption.text;
/* Is the secondary device array. */
const secondarySelectElement = document.getElementById('secondary');
const secondarySelectedOption = secondarySelectElement.options[secondarySelectElement.selectedIndex];
const secondaryText = secondarySelectedOption.text;
/* See if either primary or secondary is an array device. */
if (primaryText === "Array" || secondaryText === "Array") {
/* Check that after all calculations to set the size it is still less than the largest array free. */
if (size > arrayFree) {
size = arrayFree * 0.9;
}
}
/* Return the possibly adjusted size as a string */
return size.toString();
}
/* Converts human readable size strings to numeric bytes */
function parseDiskSize(sizeStr) {
const units = {
B: 1 / 1000,
KB: 1,
MB: 1000,
GB: 1000 * 1000,
TB: 1000 * 1000 * 1000,
PB: 1000 * 1000 * 1000 * 1000,
EB: 1000 * 1000 * 1000 * 1000 * 1000,
ZB: 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
YB: 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000
};
/* Check if the input is numeric only (assumed to be kilobytes). */
if (/^\d+$/.test(sizeStr)) {
return parseInt(sizeStr, 10);
}
/* Extract the numeric part and the unit. */
const result = sizeStr.match(/(\d+(\.\d+)?)\s*(B|KB|MB|GB|TB|PB|EB|ZB|YB)?/i);
if (!result) {
return null;
}
/* The numeric part. */
const value = parseFloat(result[1]);
/* The unit part, default to KB. */
const unit = (result[3] || "KB").toUpperCase();
/* Calculate total kilobytes. */
if (unit in units) {
return Math.round(value * units[unit]);
} else {
return null;
}
}
/* Compose input fields. */
function prepareEdit() {
/* Declare variables at the function scope */
var share, reserved, pools;
/* Test share name validity. */
share = form.shareName.value.trim();
if (share.length == 0) {
swal({
title: "Missing share name",
text: "Enter a name for the share",
type: 'error',
html: true,
confirmButtonText: "Ok"
});
return false;
}
reserved = [<?=implode(',',array_map('escapestring',explode(',',$var['reservedNames'])))?>];
if (reserved.includes(share)) {
swal({
title: "Invalid share name",
text: "Do not use reserved names",
type: 'error',
html: true,
confirmButtonText: "Ok"
});
return false;
}
pools = [<?=implode(',',array_map('escapestring',$pools))?>];
if (pools.includes(share)) {
swal({
title: "Invalid share name",
text: "Do not use pool names",
type: 'error',
html: true,
confirmButtonText: "Ok"
});
return false;
}
/* Clean up the share name. */
share = safeName(share);
/* Update settings. */
form.shareName.value = share;
form.shareUseCache.value = z(4);
form.shareFloor.value = setFloor(form.shareFloor.value);
/* shareCachePool2 is not used when none or array is secondary. */
secondaryValue = $('#secondary').val();
if (secondaryValue == "0" || secondaryValue == "1") {
form.shareCachePool2.value = "";
} else {
form.shareCachePool2.value = form.secondary.value;
}
switch (form.shareUseCache.value) {
case 'no':
form.shareAllocator.value = form.shareAllocator1.value;
form.shareSplitLevel.value = form.shareSplitLevel1.value;
form.shareInclude.value = unite(form.shareInclude1);
form.shareExclude.value = unite(form.shareExclude1);
break;
case 'yes':
case 'prefer':
form.shareAllocator.value = form.shareAllocator2.value;
form.shareSplitLevel.value = form.shareSplitLevel2.value;
form.shareInclude.value = unite(form.shareInclude2);
form.shareExclude.value = unite(form.shareExclude2);
break;
}
return true;
}
function readShare() {
/* Declare variables at the function scope */
var name, data, disk, include, exclude, i, j;
name = $('select[name="readshare"]').val();
initDropdown(true);
$.get('/webGui/include/ShareData.php', { name: name }, function(json) {
data = $.parseJSON(json);
form.shareAllocator.value = data.allocator;
form.shareFloor.value = data.floor;
form.shareSplitLevel.value = data.splitLevel;
form.shareInclude.value = data.include;
form.shareExclude.value = data.exclude;
form.shareUseCache.value = data.useCache;
form.shareCOW.value = data.cow;
for (i = 0; (disk = data.include.split(',')[i]); i++) {
for (j = 0; (include = form.shareInclude.options[j]); j++) {
if (include.value == disk) include.selected = true;
}
}
for (i = 0; (disk = data.exclude.split(',')[i]); i++) {
for (j = 0; (exclude = form.shareExclude.options[j]); j++) {
if (exclude.value == disk) exclude.selected = true;
}
}
initDropdown(false);
});
$(form).find('select').trigger('change');
}
function writeShare(data, n, i) {
/* Declare variables at the function scope */
var newData, i;
if (data) {
if (n < i) {
$.post('/update.htm', data[n], function() {
setTimeout(function() {
writeShare(data, ++n, i);
}, 3000);
});
} else {
toggleButton('writeshare', false);
$('div.spinner.fixed').hide();
}
} else {
newData = [];
i = 0;
$('select#s5 option').map(function() {
if ($(this).prop('selected') == true && $(this).val() != '(All)') {
newData[i] = {};
newData[i]['shareName'] = $(this).val();
newData[i]['shareNameOrig'] = $(this).val();
newData[i]['shareAllocator'] = '<?=addslashes(htmlspecialchars($share['allocator']))?>';
newData[i]['shareFloor'] = '<?=addslashes(htmlspecialchars($share['floor']))?>';
newData[i]['shareSplitLevel'] = '<?=addslashes(htmlspecialchars($share['splitLevel']))?>';
newData[i]['shareInclude'] = '<?=addslashes(htmlspecialchars($share['include']))?>';
newData[i]['shareExclude'] = '<?=addslashes(htmlspecialchars($share['exclude']))?>';
newData[i]['shareUseCache'] = '<?=addslashes(htmlspecialchars($share['useCache']))?>';
newData[i]['cmdEditShare'] = 'Apply';
i++;
}
});
toggleButton('writeshare', true);
$('div.spinner.fixed').show('slow');
writeShare(newData, 0, i);
}
}
document.addEventListener('DOMContentLoaded', function() {
/* Declare variables at the function scope */
let primaryDefaultValue, primaryDropdown, i;
let secondaryDefaultValue, secondaryDropdown;
let poolsOnly = <?= $poolsOnly ? 'true' : 'false' ?>;
let useCache = "<?= $share['useCache'] ?>";
/* Handle the primary dropdown */
primaryDefaultValue = document.forms['share_edit'].elements['shareCachePool'].value;
primaryDropdown = document.getElementById('primary');
/* Select the correct pool or the first one if no pool is defined. */
if (primaryDefaultValue === "") {
/* Set the default to the first selection - "Array" or first pool. */
if (poolsOnly) {
primaryDropdown.selectedIndex = 1;
} else {
primaryDropdown.selectedIndex = 0;
}
} else {
/* Loop through the options and set the selected value */
for (i = 0; i < primaryDropdown.options.length; i++) {
if (primaryDropdown.options[i].value == primaryDefaultValue) {
primaryDropdown.selectedIndex = i;
break;
}
}
}
/* Handle the secondary dropdown */
secondaryDefaultValue = "<?= $share['cachePool2'] ?>";
secondaryDropdown = document.getElementById('secondary');
if (!secondaryDropdown) {
/* Exit if the element is not found. */
return;
}
/* Function to disable matching pool option in secondary dropdown. */
function disableMatchingOption() {
let primaryValue = primaryDropdown.value;
let firstEnabledOption = null;
let matchFound = false;
for (let i = 1; i < secondaryDropdown.options.length; i++) {
if (i === 1 && poolsOnly) {
secondaryDropdown.options[i].disabled = true;
continue;
}
if (secondaryDropdown.options[i].value === primaryValue) {
secondaryDropdown.options[i].disabled = true;
matchFound = true;
} else {
secondaryDropdown.options[i].disabled = false;
if (firstEnabledOption === null) {
firstEnabledOption = secondaryDropdown.options[i];
}
}
}
/* Disable all secondary options if primary is array (index 0). */
if (primaryDropdown.selectedIndex === 0) {
for (let i = 1; i < secondaryDropdown.options.length; i++) {
secondaryDropdown.options[i].disabled = true;
}
secondaryDropdown.selectedIndex = 0;
/* Skip further processing as we already set the selection. */
return;
}
/* Enable and select secondary selection 1 if useCache is "yes" and poolsOnly is false */
if (useCache === "yes" && !poolsOnly) {
secondaryDropdown.options[1].disabled = false;
/* Skip further processing as we already set the selection. */
return;
}
/* Select the first enabled option in the secondary dropdown */
if (matchFound || primaryValue === secondaryDefaultValue) {
if (secodaryDropdown.selectedIndex !== 1) {
secondaryDropdown.selectedIndex = 0;
}
} else if (firstEnabledOption !== null) {
firstEnabledOption.selected = true;
} else {
/* If no enabled option found, reset the selection */
secondaryDropdown.selectedIndex = -1;
}
}
/* Call the function initially */
disableMatchingOption();
/* Select the appropriate pool from the dropdown. */
let matchFound = false;
/* Loop through the options and set the selected value */
for (let i = 0; i < secondaryDropdown.options.length; i++) {
if (secondaryDropdown.options[i].value == secondaryDefaultValue) {
secondaryDropdown.selectedIndex = i;
matchFound = true;
break;
}
}
/* If no match is found or secondary default matches primary, default to the first selection */
if ((!matchFound || primaryDefaultValue === secondaryDefaultValue) && (secondaryDefaultValue != "")) {
secondaryDropdown.selectedIndex = 0;
}
/* Add event listener to primary dropdown to handle change event */
primaryDropdown.addEventListener('change', function() {
disableMatchingOption();
});
/* Trigger the onchange event if needed */
secondaryDropdown.dispatchEvent(new Event('change'));
});
/* Clean up the share name by removing invalid characters. */
function safeName(name) {
/* Declare variables at the function scope */
const validChars = /^[A-Za-z0-9-_.: ]*$/;
let isValidName, sanitizedString, i;
/* Check if the name contains only valid characters */
isValidName = validChars.test(name);
/* If valid, return the name as it is */
if (isValidName) {
return name;
}
/* If not valid, sanitize the name by removing invalid characters */
sanitizedString = '';
for (i = 0; i < name.length; i++) {
if (validChars.test(name[i])) {
sanitizedString += name[i];
}
}
/* Return the sanitized string */
return sanitizedString;
}
/* Remove characters not allowed in share name. */
function checkName(name) {
/* Declare variables at the function scope */
var isValidName;
isValidName = /^[A-Za-z0-9-_.: ]*$/.test(name);
if (isValidName) {
$('#zfs-name').hide();
} else {
$('#zfs-name').show();
}
}
/* Remove hidden '.DS_Store' files from share before deleting the share. */
function handleDeleteClick(button) {
const deleteLabel = "<?= _('Delete') ?>";
if (button.value === deleteLabel) {
$.post('/webGui/include/ShareList.php', { delete: "<?=$name?>" }, function(response) {
if (response === 'success') {
return true;
} else {
return false;
}
});
}
}
$(function() {
<?if ($name):?>
$.post('/webGui/include/ShareList.php', { scan: "<?=$name?>" }, function(e) {
if (e == 1) {
$('.empty').show();
$('.full').hide();
} else {
$('.full1').hide();
$('.full2').show();
}
});
<?endif;?>
initDropdown(true);
<?if ($tabbed):?>
$('#tab1').bind({
click: function() {
initDropdown(true);
}
});
<?endif;?>
updateScreen(form.shareUseCache.value);
if ($.cookie('autosize-' + $('#shareName').val())) $('#autosize').show();
checkName($('#shareName').val());
});
</script>