mirror of
https://github.com/unraid/webgui.git
synced 2026-01-01 23:20:35 -06:00
1456 lines
47 KiB
Plaintext
Executable File
1456 lines
47 KiB
Plaintext
Executable File
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.
|
|
*/
|
|
|
|
if (!empty($name) && !array_key_exists($name, $shares)) {
|
|
echo "<script>done()</script>";
|
|
exit;
|
|
}
|
|
$width = [123,300];
|
|
|
|
if ($name == "") {
|
|
/* default values when adding new share. */
|
|
$share = [
|
|
"nameOrig" => "",
|
|
"name" => "",
|
|
"comment" => "",
|
|
"allocator" => "highwater",
|
|
"floor" => "",
|
|
"splitLevel" => "",
|
|
"include" => "",
|
|
"exclude" => "",
|
|
"useCache" => "",
|
|
"cachePool" => "",
|
|
"cachePool2" => "",
|
|
"cow" => "auto"
|
|
];
|
|
} else {
|
|
/* edit existing share. */
|
|
$share = $shares[$name];
|
|
}
|
|
|
|
/* If the configuration is pools only, then no array disks are available. */
|
|
$poolsOnly = ((int)$var['SYS_ARRAY_SLOTS'] <= 2) ? true : false;
|
|
|
|
/* Remove subpools from the array of pools. */
|
|
$pools = array_filter($pools, function($pool) {
|
|
return !isSubpool($pool);
|
|
});
|
|
|
|
/* Check for cachePool2 only which is a situation where the primary device is the cachePool2 device. */
|
|
if ((! $share['cachePool']) && ($share['cachePool2'])) {
|
|
$share['cachePool'] = $share['cachePool2'];
|
|
$share['cachePool2'] = "";
|
|
}
|
|
|
|
/* Correct a situation in previous Unraid versions where an array only share has a useCache defined. */
|
|
if ((!$poolsOnly) && ($share['useCache'] == "no")) {
|
|
$share['cachePool'] = "";
|
|
}
|
|
|
|
/* Check for non existent pool device. */
|
|
if (($share['cachePool'] && !in_array($share['cachePool'], $pools))) {
|
|
$share['useCache'] = $share['cachePool2'] ? "yes" : ($poolsOnly ? "no" : "yes");
|
|
$poolDefined = false;
|
|
} else {
|
|
$share['useCache'] = $share['useCache'] ?: ($poolsOnly ? "only" : "no");
|
|
$poolDefined = true;
|
|
}
|
|
|
|
/* Check for pool 2 (or array) being defined. */
|
|
$poolDefined2 = true;
|
|
if ((($share['useCache'] == "yes") || ($share['useCache'] == "prefer")) && ($poolsOnly) && (!$share['cachePool2'])) {
|
|
$share['useCache'] = "only";
|
|
} else if ($share['cachePool2']) {
|
|
$poolDefined2 = in_array($share['cachePool2'], $pools);
|
|
}
|
|
|
|
$cachePoolCapitalized = compress(my_disk($share['cachePool'],$display['raw']));
|
|
$cachePoolCapitalized2 = $share['cachePool2'] ? compress(my_disk($share['cachePool2'],$display['raw'])) : _("Array");
|
|
|
|
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 */
|
|
$fsSize = [];
|
|
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)) {
|
|
/* Filter non-numeric values */
|
|
$numericLarge = array_filter($large, 'is_numeric');
|
|
|
|
/* Set the maximum value */
|
|
$fsSize[''] = !empty($numericLarge) ? max($numericLarge) : 0;
|
|
} else if (!empty($fsSize)) {
|
|
/* Filter non-numeric values */
|
|
$numericFsSize = array_filter($fsSize, 'is_numeric');
|
|
|
|
/* Set the minimum value */
|
|
$fsSize[''] = !empty($numericFsSize) ? min($numericFsSize) : 0;
|
|
} else {
|
|
$fsSize[''] = 0;
|
|
}
|
|
|
|
/* 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)) {
|
|
/* Filter out non-numeric values and zeros */
|
|
$numericSmall = array_filter($small, function($value) {
|
|
return is_numeric($value) && (float)$value > 0;
|
|
});
|
|
|
|
/* Set the minimum value */
|
|
$fsSize[''] = !empty($numericSmall) ? min($numericSmall) : 0;
|
|
} 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 */
|
|
if (!empty($large)) {
|
|
/* Filter non-numeric values */
|
|
$numericLarge = array_filter($large, 'is_numeric');
|
|
|
|
/* Set the maximum free filesystem space from the array of enabled disks */
|
|
$fsFree[''] = !empty($numericLarge) ? max($numericLarge) : 0;
|
|
} elseif (!empty($fsFree)) {
|
|
/* Filter non-numeric values */
|
|
$numericFsFree = array_filter($fsFree, 'is_numeric');
|
|
|
|
/* Set the minimum free filesystem space from the pool array */
|
|
$fsFree[''] = !empty($numericFsFree) ? min($numericFsFree) : 0;
|
|
} else {
|
|
/* The minimum free space cannot be determined from the array or pools, so it is set to zero */
|
|
$fsFree[''] = 0;
|
|
}
|
|
|
|
/* 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');
|
|
|
|
$filteredShares = array_filter($shares, function($list) use ($name) {
|
|
return (strpos($list['name'],"'") === false) && ($list['name'] != $name || !$name) ;
|
|
});
|
|
?>
|
|
:share_edit_global1_help:
|
|
<?if ($name):?>
|
|
:share_edit_global2_help:
|
|
<?endif;?>
|
|
|
|
<div markdown="1" class="relative">
|
|
<?if (($share['hasCfg']??false)!='similar'):?>
|
|
<?if (!empty($filteredShares)):?>
|
|
<div markdown="1" class="clone-settings shade">
|
|
_(Read settings from)_ <i class="fa fa-arrow-left fa-fw"></i>
|
|
: <span class="flex flex-row items-center gap-4">
|
|
<select name="readshare" onchange="toggleButton('readshare',false)">
|
|
<option disabled selected>_(select)_...</option>
|
|
<? foreach ($filteredShares as $list):?>
|
|
<?=mk_option("", $list['name'], compress($list['name']))?>
|
|
<?endforeach;?>
|
|
</select>
|
|
<span class="buttons-spaced">
|
|
<input type="button" id="readshare" value="_(Read)_" class="clone" onclick="readShare()" disabled>
|
|
</span>
|
|
</span>
|
|
|
|
<?if ($name):?>
|
|
_(Write settings to)_ <i class="fa fa-arrow-right fa-fw"></i>
|
|
: <span class="flex flex-row items-center gap-4">
|
|
<select id="s5" name="writeshare" multiple onchange="toggleButton('writeshare',this.id)">
|
|
<?if (!empty($filteredShares)):?>
|
|
<option>(<?=_('All')?>)</option>
|
|
<?endif;?>
|
|
<? foreach ($filteredShares as $list):?>
|
|
<?=mk_option("", $list['name'], compress($list['name']))?>
|
|
<?endforeach;?>
|
|
</select>
|
|
<span class="buttons-spaced">
|
|
<input type="button" id="writeshare" value="_(Write)_" class="clone" onclick="writeShare()" disabled>
|
|
</span>
|
|
</span>
|
|
<?endif;?>
|
|
</div>
|
|
<?endif;?>
|
|
<?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">
|
|
_(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 restricted character(s))_</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>
|
|
|
|
<div markdown="1" class="shade">
|
|
_(Minimum free space)_:
|
|
: <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:
|
|
|
|
<?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"> </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">
|
|
_(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;?>
|
|
<?if ($share['cachePool']):?>
|
|
<option id="cachePoolOption" value="<?= $share['cachePool'] ?>" disabled><?= $cachePoolCapitalized ?></option>
|
|
<?endif;?>
|
|
</select><span id="cachePoolMessage" style="display: none; color: red;"></span>
|
|
|
|
: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" id="secondaryStorage" class="shade">
|
|
_(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;?>
|
|
<?if ($share['cachePool2']):?>
|
|
<option id="cachePoolOption2" value="<?= $share['cachePool2'] ?>" disabled><?= $cachePoolCapitalized2 ?></option>
|
|
<?endif;?>
|
|
</select><span id="cachePoolMessage2" style="display: none; color: red;"></span>
|
|
|
|
: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" id="moverAction" class="shade">
|
|
<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):?>
|
|
|
|
: <span class="inline-block">
|
|
<input type="submit" name="cmdEditShare" value="_(Add Share)_" onclick="this.value='Add Share'">
|
|
<input type="button" value="_(Done)_" onclick="done()">
|
|
</span>
|
|
<?else:?>
|
|
<div markdown="1">
|
|
<label id="deleteLabel" title="" class="inline-flex flex-row items-center justify-end gap-2">
|
|
_(Delete)_
|
|
<input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, document.getElementById('cmdEditShare'));" title="" disabled>
|
|
</label>
|
|
: <span class="inline-flex flex-row items-center gap-2">
|
|
<input type="submit" id="cmdEditShare" name="cmdEditShare" value="_(Apply)_" onclick="if (this.value=='_(Delete)_') this.value='Delete'; else this.value='Apply'; return handleDeleteClick(this)" disabled>
|
|
<input type="button" value="_(Done)_" onclick="done()">
|
|
<?if (($share['hasCfg']??false)=='similar'):?>
|
|
<span class="orange-text"><i class="fa fa-warning"></i> _(Case-insensitive Share name is not unique)_</span>
|
|
<?endif;?>
|
|
</span>
|
|
</div>
|
|
<?endif;?>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
let form = document.share_edit;
|
|
|
|
/* Global flag to skip prepareEdit. */
|
|
let skipPrepareEdit = false;
|
|
|
|
/* Primary variables to check for valid selection of missing pool. */
|
|
let changeMadePrimary = false;
|
|
let checkRequiredPrimary = false;
|
|
|
|
/* Secondary variables to check for valid selection of missing pool. */
|
|
let changeMadeSecondary = false;
|
|
let checkRequiredSecondary = false;
|
|
|
|
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 {
|
|
if ($(selector).length) {
|
|
$(selector).dropdownchecklist({
|
|
emptyText: emptyText,
|
|
width: width,
|
|
explicitClose: "..._(close)_",
|
|
firstItemChecksAll: firstItemChecksAll
|
|
});
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
function destroyDropdownIfExists(selector) {
|
|
try {
|
|
if ($(selector).length) {
|
|
$(selector).dropdownchecklist('destroy');
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
function enableDropdownIfExists(selector) {
|
|
try {
|
|
if ($(selector).length) {
|
|
$(selector).dropdownchecklist('enable');
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
function disableDropdownIfExists(selector) {
|
|
try {
|
|
if ($(selector).length) {
|
|
$(selector).dropdownchecklist('disable');
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
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 primaryDropdown = document.getElementById('primary');
|
|
const primaryValue = primaryDropdown.value;
|
|
const poolsOnly = <?= $poolsOnly ? 'true' : 'false' ?>;
|
|
let secondaryValue = secondaryDropdown.value;
|
|
|
|
/* Mover action text. */
|
|
const moverNoActionText = "<?= addslashes(_('Mover takes no action')) ?>";
|
|
const moverAction1Text = "<?= addslashes(_('Mover transfers files from Primary storage to Secondary storage')) ?>";
|
|
const moverAction2Text = "<?= addslashes(_('Mover transfers files from Secondary storage to Primary storage')) ?>";
|
|
|
|
switch (cache) {
|
|
case 'no':
|
|
$('#primary option:eq(' + z(0) + ')').prop('selected', true);
|
|
$('#primary option:eq(0)').prop('disabled', poolsOnly);
|
|
$('#moverDirection1').hide();
|
|
|
|
for (let i = 0; i < secondaryDropdown.options.length; i++) {
|
|
secondaryDropdown.options[i].disabled = true;
|
|
}
|
|
secondaryDropdown.selectedIndex = 0;
|
|
|
|
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>" + moverNoActionText);
|
|
break;
|
|
case 'yes':
|
|
$('#primary option:eq(' + z(0) + ')').prop('selected', true);
|
|
$('#secondary option:eq(' + z(1) + ')').prop('selected', true);
|
|
|
|
for (let i = 0; i < secondaryDropdown.options.length; i++) {
|
|
if (secondaryDropdown.options[i].value === primaryValue || (i === 1 && poolsOnly) || secondaryDropdown.options[i].id === 'cachePoolOption2') {
|
|
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;
|
|
secondaryValue = poolsOnly ? "0" : "1";
|
|
}
|
|
|
|
if ((secondaryValue !== "0") && (secondaryValue !== primaryValue)) {
|
|
$('#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>" + moverAction1Text);
|
|
} else {
|
|
$('#moverAction2').html("<i class='fa fa-info i'></i>" + moverNoActionText);
|
|
$('#moverDirection1').hide();
|
|
$('#moverDirection2').show();
|
|
}
|
|
$('#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);
|
|
|
|
for (let i = 0; i < secondaryDropdown.options.length; i++) {
|
|
if (secondaryDropdown.options[i].value === primaryValue || (i === 1 && poolsOnly) || secondaryDropdown.options[i].id === 'cachePoolOption2') {
|
|
secondaryDropdown.options[i].disabled = true;
|
|
} else {
|
|
secondaryDropdown.options[i].disabled = false;
|
|
}
|
|
}
|
|
|
|
$('#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>" + moverNoActionText);
|
|
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());
|
|
|
|
for (let i = 0; i < secondaryDropdown.options.length; i++) {
|
|
if (secondaryDropdown.options[i].value === primaryValue || (i === 1 && poolsOnly) || secondaryDropdown.options[i].id === 'cachePoolOption2') {
|
|
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;
|
|
secondaryValue = poolsOnly ? "0" : "1";
|
|
}
|
|
|
|
if ((secondaryValue !== "0") && (secondaryValue !== primaryValue)) {
|
|
$('#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>" + moverAction2Text);
|
|
} else {
|
|
$('#moverAction2').html("<i class='fa fa-info i'></i>" + moverNoActionText);
|
|
$('#moverDirection1').hide();
|
|
$('#moverDirection2').show();
|
|
}
|
|
$('#moreSettings1').hide(slow);
|
|
$('#cow-setting').hide(slow);
|
|
form.shareAllocator.disabled = false;
|
|
form.shareSplitLevel.disabled = false;
|
|
enableDropdownIfExists('#s3');
|
|
enableDropdownIfExists('#s4');
|
|
$('#moverAction2').html("<i class='fa fa-info i'></i>" + moverNoActionText);
|
|
break;
|
|
}
|
|
|
|
/* If primary is "Array", remove secondary storage selection as it does not apply when primary is array. */
|
|
if (primaryDropdown.selectedIndex === 0) {
|
|
$('#secondaryStorage').hide(slow);
|
|
$('#moverAction').hide(slow);
|
|
} else {
|
|
$('#secondaryStorage').show(slow);
|
|
$('#moverAction').show(slow);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* 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 primaryDropdown = document.getElementById('primary');
|
|
const primaryValue = primaryDropdown.value;
|
|
const secondaryDropdown = document.getElementById('secondary');
|
|
const secondaryValue = secondaryDropdown.value;
|
|
|
|
/* Secondary selection is array and array disks are accounted for. */
|
|
let full;
|
|
let free;
|
|
if (secondaryValue === "0" || secondaryValue === "1") {
|
|
/* Size of the primary pool, or smallest array disk if there are any. */
|
|
full = fsSize[primaryValue];
|
|
free = fsFree[primaryValue];
|
|
} else {
|
|
/* Size of the secondary pool. */
|
|
full = fsSize[secondaryValue];
|
|
free = fsFree[secondaryValue];
|
|
}
|
|
|
|
/* 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());
|
|
}
|
|
}
|
|
|
|
/* See if either primary or secondary is an array device. */
|
|
if (primaryValue === "0" || secondaryValue === "1") {
|
|
/* 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() {
|
|
/* Skip the function if readShare() filled the values */
|
|
if (skipPrepareEdit) {
|
|
/* Reset the flag. */
|
|
skipPrepareEdit = false;
|
|
|
|
/* Allow the form to submit. */
|
|
return true;
|
|
}
|
|
|
|
/* Declare variables at the function scope */
|
|
let share, reserved, pools;
|
|
|
|
/* Check if a change was required and not made for primary dropdown */
|
|
if (checkRequiredPrimary && !changeMadePrimary) {
|
|
swal({
|
|
title: "_(Primary Storage)_",
|
|
text: "_(Selected Pool is not available and cannot be used.)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
/* Check if a change was required and not made for secondary dropdown */
|
|
if (checkRequiredSecondary && !changeMadeSecondary) {
|
|
swal({
|
|
title: "_(Secondary Storage)_",
|
|
text: "_(Selected Pool is not available and cannot be used.)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
// Length check
|
|
if (share.length > 40) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Share name must be 40 characters or less)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
|
|
}
|
|
|
|
// Validate share name when creating new share or renaming existing share
|
|
if (share != form.shareNameOrig.value.trim()) {
|
|
// Must start with a letter
|
|
if (!/^[A-Za-z]/.test(share)) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Share name must begin with a letter)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
// Allowed characters check
|
|
if (!/^[A-Za-z0-9._-]+$/.test(share)) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Share name may only contain letters, digits, '.', '_', or '-')_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
// Must not end with '.'
|
|
if (/[.]$/.test(share)) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Share name cannot end with '.')_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
// Must not be one of our reserved strings
|
|
reserved = [<?=implode(',',array_map('escapestring',explode(',',$var['reservedNames'])))?>];
|
|
if (reserved.some(name => name.toLowerCase() === share.toLowerCase())) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Do not use reserved names)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
// Must not be same name as a pool
|
|
pools = [<?=implode(',',array_map('escapestring',$pools))?>];
|
|
if (pools.some(name => name.toLowerCase() === share.toLowerCase())) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Do not use pool names)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
// Must not be same name as existing share
|
|
shares = <?=json_encode(array_column($shares,'name'))?>;
|
|
if (shares.some(name => name.toLowerCase() === share.toLowerCase())) {
|
|
swal({
|
|
title: "_(Invalid share name)_",
|
|
text: "_(Share already exists)_",
|
|
type: 'error',
|
|
html: true,
|
|
confirmButtonText: "_(Ok)_"
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* 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. */
|
|
const secondaryDropdown = document.getElementById('secondary');
|
|
const secondaryValue = secondaryDropdown.value;
|
|
|
|
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() {
|
|
let name, data;
|
|
|
|
name = $('select[name="readshare"]').val();
|
|
|
|
$.get('/webGui/include/ShareData.php', { name: name }, function(json) {
|
|
data = $.parseJSON(json);
|
|
let form = document.forms['share_edit'];
|
|
|
|
/* Fill in the form values */
|
|
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.shareCachePool2.value = data.cachePool2;
|
|
form.shareCOW.value = data.cow;
|
|
|
|
/* Populate the primary storage field (shareCachePool) */
|
|
let primaryField = document.getElementById('primary');
|
|
if (primaryField) {
|
|
/* Set to retrieved value or default. */
|
|
primaryField.value = data.cachePool || '';
|
|
}
|
|
|
|
/* Set flag to skip prepareEdit */
|
|
skipPrepareEdit = true;
|
|
|
|
/* Enable and trigger the submit button */
|
|
const submitButton = document.getElementById('cmdEditShare');
|
|
submitButton.disabled = false;
|
|
submitButton.click();
|
|
});
|
|
}
|
|
|
|
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]['shareCachePool'] = '<?=addslashes(htmlspecialchars($share['cachePool']))?>';
|
|
newData[i]['shareCachePool2'] = '<?=addslashes(htmlspecialchars($share['cachePool2']))?>';
|
|
newData[i]['cmdEditShare'] = 'Apply';
|
|
i++;
|
|
}
|
|
});
|
|
toggleButton('writeshare', true);
|
|
$('div.spinner.fixed').show('slow');
|
|
writeShare(newData, 0, i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This script handles the initialization and dynamic updates for the
|
|
* primary and secondary cache pool dropdowns in the form. It sets
|
|
* default values, detects changes made by the user, and ensures the
|
|
* dropdowns reflect the current configuration status.
|
|
*
|
|
* Key functionalities include:
|
|
* - Setting the default selection for each dropdown based on the defined
|
|
* configuration.
|
|
* - Displaying warnings if the configured pool is missing.
|
|
* - Disabling or enabling dropdown options as necessary.
|
|
* - Preventing form submission if required changes are not made.
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
/* Define primary dropdown variables */
|
|
let primaryDropdown = document.getElementById('primary');
|
|
let primaryDefaultValue = "<?= $share['cachePool'] ?>";
|
|
let cachePool = "<?= $share['cachePool'] ?>";
|
|
let useCache = "<?= $share['useCache'] ?>";
|
|
let cachePoolCapitalized = "<?= $cachePoolCapitalized ?>";
|
|
let poolsOnly = <?= $poolsOnly ? 'true' : 'false' ?>;
|
|
let poolDefined = <?= $poolDefined ? 'true' : 'false' ?>;
|
|
|
|
/* Define secondary dropdown variables */
|
|
let secondaryDropdown = document.getElementById('secondary');
|
|
let secondaryDefaultValue = "<?= $share['cachePool2'] ?>";
|
|
let cachePool2 = "<?= $share['cachePool2'] ?>";
|
|
let cachePoolCapitalized2 = "<?= $cachePoolCapitalized2 ?>";
|
|
let poolDefined2 = <?= $poolDefined2 ? 'true' : 'false' ?>;
|
|
|
|
/* Flag to differentiate between user-initiated and programmatic changes */
|
|
let programmaticChange = false;
|
|
|
|
/* Add event listener to detect changes in the primary dropdown */
|
|
primaryDropdown.addEventListener('change', function() {
|
|
/* Check if the change was made programmatically */
|
|
if (!programmaticChange) {
|
|
/* Mark that a user-initiated change was made */
|
|
changeMadePrimary = true;
|
|
}
|
|
});
|
|
|
|
/* Handle the primary dropdown selection */
|
|
if (!poolDefined) {
|
|
checkRequiredPrimary = true; // Set flag to indicate that a check is required
|
|
|
|
let cachePoolOption = document.getElementById('cachePoolOption');
|
|
let messageSpan = document.getElementById('cachePoolMessage');
|
|
messageSpan.textContent = `${_('Warning: Configured Pool')} '${cachePoolCapitalized || _('Array')}' ${_('is missing')}.`;
|
|
messageSpan.style.display = 'inline';
|
|
cachePoolOption.selected = true;
|
|
|
|
// Ensure the option is part of the dropdown
|
|
if (!cachePoolOption.parentNode) {
|
|
primaryDropdown.appendChild(cachePoolOption);
|
|
}
|
|
|
|
// Select the cachePoolOption
|
|
programmaticChange = true;
|
|
primaryDropdown.value = cachePoolOption.value;
|
|
programmaticChange = false;
|
|
} else {
|
|
let cachePoolOption = document.getElementById('cachePoolOption');
|
|
let messageSpan = document.getElementById('cachePoolMessage');
|
|
messageSpan.style.display = 'none';
|
|
|
|
// Remove the option from the dropdown
|
|
if (cachePoolOption && cachePoolOption.parentNode) {
|
|
cachePoolOption.parentNode.removeChild(cachePoolOption);
|
|
}
|
|
|
|
/* Set the default to the first selection - Array or first pool */
|
|
if (poolsOnly) {
|
|
primaryDropdown.selectedIndex = 1;
|
|
} else {
|
|
primaryDropdown.selectedIndex = 0;
|
|
}
|
|
|
|
/* Loop through the options and set the selected value */
|
|
if (primaryDefaultValue !== "") {
|
|
for (let i = 0; i < primaryDropdown.options.length; i++) {
|
|
if (primaryDropdown.options[i].value == primaryDefaultValue) {
|
|
primaryDropdown.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
} else if (poolsOnly) {
|
|
/* Select the first pool option. */
|
|
primaryDropdown.selectedIndex = 1;
|
|
} else {
|
|
/* Select the array option. */
|
|
primaryDropdown.selectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
/* Add event listener to detect changes in the secondary dropdown */
|
|
secondaryDropdown.addEventListener('change', function() {
|
|
/* Check if the change was made programmatically */
|
|
if (!programmaticChange) {
|
|
/* Mark that a user-initiated change was made */
|
|
changeMadeSecondary = true;
|
|
}
|
|
});
|
|
|
|
/* Handle the secondary dropdown selection */
|
|
if (!poolDefined2) {
|
|
checkRequiredSecondary = true; // Set flag to indicate that a check is required
|
|
|
|
let cachePoolOption2 = document.getElementById('cachePoolOption2');
|
|
let messageSpan2 = document.getElementById('cachePoolMessage2');
|
|
messageSpan2.textContent = `${_('Warning: Configured Pool')} '${cachePoolCapitalized2}' ${_('is missing')}.`;
|
|
messageSpan2.style.display = 'inline';
|
|
cachePoolOption2.selected = true;
|
|
cachePoolOption2.disabled = true;
|
|
|
|
// Ensure the option is part of the dropdown
|
|
if (!cachePoolOption2.parentNode) {
|
|
secondaryDropdown.appendChild(cachePoolOption2);
|
|
}
|
|
|
|
// Select the cachePoolOption2
|
|
programmaticChange = true;
|
|
secondaryDropdown.value = cachePoolOption2.value;
|
|
programmaticChange = false;
|
|
} else {
|
|
let cachePoolOption2 = document.getElementById('cachePoolOption2');
|
|
let messageSpan2 = document.getElementById('cachePoolMessage2');
|
|
messageSpan2.style.display = 'none';
|
|
|
|
// Remove the option from the dropdown
|
|
if (cachePoolOption2 && cachePoolOption2.parentNode) {
|
|
cachePoolOption2.parentNode.removeChild(cachePoolOption2);
|
|
}
|
|
|
|
/* Loop through the options and set the selected value */
|
|
if (secondaryDefaultValue !== "") {
|
|
for (let i = 0; i < secondaryDropdown.options.length; i++) {
|
|
if (secondaryDropdown.options[i].value == secondaryDefaultValue) {
|
|
secondaryDropdown.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
} else if (useCache !== "only") {
|
|
/* Select the array option. */
|
|
secondaryDropdown.selectedIndex = 1;
|
|
} else {
|
|
/* Select the none option. */
|
|
secondaryDropdown.selectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
/* Trigger the onchange event if needed for the secondary dropdown */
|
|
programmaticChange = true;
|
|
secondaryDropdown.dispatchEvent(new Event('change'));
|
|
programmaticChange = false;
|
|
});
|
|
|
|
/* Check for 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) {
|
|
if (button.value === 'Delete') {
|
|
$.post('/webGui/include/ShareList.php', { delete: "<?=$name?>" }, function(response) {
|
|
if (response === 'success') {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
$(function() {
|
|
<?if ($name):?>
|
|
<?
|
|
$tooltip_enabled = _('Share is empty and is safe to delete');
|
|
$tooltip_disabled = _('Share must be empty to be deleted');
|
|
?>
|
|
|
|
$.post('/webGui/include/ShareList.php', { scan: "<?=$name?>" }, function(e) {
|
|
if (e == 1) {
|
|
$('.empty').show();
|
|
$('.full').hide();
|
|
/* Enable delete checkbox and update tooltip. */
|
|
$('input[name="confirmDelete"]').prop('disabled', false).attr('title', '<?= $tooltip_enabled ?>');
|
|
$('#deleteLabel').attr('title', '<?= $tooltip_enabled ?>');
|
|
} else {
|
|
$('.full1').hide();
|
|
$('.full2').show();
|
|
/* Disable delete checkbox and update tooltip. */
|
|
$('input[name="confirmDelete"]').prop('disabled', true).attr('title', '<?= $tooltip_disabled ?>');
|
|
$('#deleteLabel').attr('title', '<?= $tooltip_disabled ?>');
|
|
}
|
|
});
|
|
<?endif;?>
|
|
initDropdown(true);
|
|
<?if ($tabbed):?>
|
|
$('#tab1').on({
|
|
click: function() {
|
|
initDropdown(true);
|
|
}
|
|
});
|
|
<?endif;?>
|
|
updateScreen(form.shareUseCache.value);
|
|
if ($.cookie('autosize-' + $('#shareName').val())) $('#autosize').show();
|
|
checkName($('#shareName').val());
|
|
<?if (($share['hasCfg']??false)=='similar'):?>
|
|
form.shareComment.disabled = true;
|
|
form.shareFloor.disabled = true;
|
|
form.shareCachePool.disabled = true;
|
|
form.shareCOW.disabled = true;
|
|
form.shareAllocator1.disabled = true;
|
|
form.shareSplitLevel1.disabled = true;
|
|
form.shareInclude1.disabled = true;
|
|
form.shareExclude1.disabled = true;
|
|
form.secondary.disabled = true;
|
|
<?endif;?>
|
|
});
|
|
</script>
|