mirror of
https://github.com/unraid/webgui.git
synced 2026-01-17 23:20:02 -06:00
475 lines
21 KiB
Plaintext
475 lines
21 KiB
Plaintext
Menu="Share:1"
|
|
Title="Share Settings"
|
|
Tag="share-alt-square"
|
|
---
|
|
<?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.
|
|
*/
|
|
?>
|
|
<?
|
|
$width = [123,300];
|
|
|
|
if ($name == "") {
|
|
/* default values when adding new share */
|
|
$share = ["nameOrig" => "",
|
|
"name" => "",
|
|
"comment" => "",
|
|
"allocator" => "highwater",
|
|
"floor" => "",
|
|
"splitLevel" => "",
|
|
"include" => "",
|
|
"exclude" => "",
|
|
"useCache" => "no",
|
|
"cachePool" => "cache",
|
|
"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;
|
|
}
|
|
|
|
/* check for empty share */
|
|
function shareEmpty($name) {
|
|
return (($files = @scandir("/mnt/user/$name")) && (count($files) <= 2));
|
|
}
|
|
|
|
function globalInclude($name) {
|
|
global $var;
|
|
return substr($name,0,4)=='disk' && (!$var['shareUserInclude'] || strpos("{$var['shareUserInclude']},","$name,")!==false);
|
|
}
|
|
|
|
// global shares include/exclude
|
|
$myDisks = array_filter(array_diff(array_keys(array_filter($disks,'my_disks')), explode(',',$var['shareUserExclude'])), 'globalInclude');
|
|
?>
|
|
:help3
|
|
> A *Share*, also called a *User Share*, is simply the name of a top-level directory that exists on one or more of your
|
|
> storage devices (array and cache). Each share can be exported for network access. When browsing a share, we return the
|
|
> composite view of all files and subdirectories for which that top-level directory exists on each storage device.
|
|
>
|
|
> *Read settings from* is used to preset the settings of the new share with the settings of an existing share.
|
|
>
|
|
> Select the desired share name and press **Read** to copy the settings from the selected source.
|
|
<?if ($name):?>
|
|
>
|
|
> *Write settings to* is used to copy the settings of the current share to one or more other existing shares.
|
|
>
|
|
> Select the desired destinations and press **Write** to copy the settings to the selected shares.
|
|
<?endif;?>
|
|
:end
|
|
|
|
<div id="" 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'], $list['name']);
|
|
?>
|
|
</select></span><input type="button" id="readshare" value="_(Read)_" class="clone" onclick="readShare()" disabled>
|
|
</div>
|
|
<?if ($name):?>
|
|
<div id="" class="clone2">
|
|
<span class="clone">_(Write settings to)_</span><i class="fa fa-arrow-right fa-fw"></i>
|
|
<span class="wrap"><select id="s3" name="writeshare" multiple="multiple" style="display:none" onchange="toggleButton('writeshare',this.id)">
|
|
<?
|
|
$rows = [];
|
|
foreach ($shares as $list) if ($list['name']!=$name) $rows[] = mk_option("", $list['name'], $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(this)"<?=$name?" onchange=\"toggleButton('writeshare',true);$('#s3').dropdownchecklist('disable')\">":">"?>
|
|
<input type="hidden" name="shareNameOrig" value="<?=htmlspecialchars($share['nameOrig'])?>">
|
|
|
|
_(Share name)_:
|
|
: <input type="text" name="shareName" maxlength="40" value="<?=htmlspecialchars($name)?>">
|
|
|
|
:help4
|
|
> The share name can be up to 40 characters, and is case-sensitive with these restrictions:
|
|
>
|
|
> * cannot contain a double-quote character (") or the following characters: / \ * < > |
|
|
> * cannot be one of the reserved share names: flash, cache, cach2, .., disk1, disk2, ..
|
|
>
|
|
> We highly recommend to make your life easier and avoid special characters.
|
|
:end
|
|
|
|
_(Comments)_:
|
|
: <input type="text" name="shareComment" maxlength="256" value="<?=htmlspecialchars($share['comment'])?>">
|
|
|
|
:help5
|
|
> Anything you like, up to 256 characters.
|
|
:end
|
|
|
|
_(Use cache pool (for new files/directories))_:
|
|
: <span class="input"><select name="shareUseCache" onchange="updateScreen(this.value)"<?=$pool_devices?'':' disabled'?>>
|
|
<?=mk_option($share['useCache'], "no", _('No'))?>
|
|
<?=mk_option($share['useCache'], "yes", _('Yes'))?>
|
|
<?=mk_option($share['useCache'], "prefer", _('Prefer'))?>
|
|
<?=mk_option($share['useCache'], "only", _('Only'))?>
|
|
</select></span><span id="moverAction"></span>
|
|
|
|
:help11
|
|
> Specify whether new files and directories written on the share can be written onto the Cache disk/pool if present.
|
|
> This setting also affects *mover* behavior.
|
|
>
|
|
> **No** prohibits new files and subdirectories from being written onto the Cache disk/pool.
|
|
> *Mover* will take no action so any existing files for this share that are on the cache are left there.
|
|
>
|
|
> **Yes** indicates that all new files and subdirectories should be written to the Cache disk/pool, provided
|
|
> enough free space exists on the Cache disk/pool.
|
|
> If there is insufficient space on the Cache disk/pool, then new files and directories are created on the array.
|
|
> When the *mover* is invoked, files and subdirectories are transferred off the Cache disk/pool and onto the array.
|
|
>
|
|
> **Only** indicates that all new files and subdirectories must be writen to the Cache disk/pool.
|
|
> If there is insufficient free space on the Cache disk/pool, *create* operations will fail with *out of space* status.
|
|
> *Mover* will take no action so any existing files for this share that are on the array are left there.
|
|
>
|
|
> **Prefer** indicates that all new files and subdirectories should be written to the Cache disk/pool, provided
|
|
> enough free space exists on the Cache disk/pool.
|
|
> If there is insufficient space on the Cache disk/pool, then new files and directories are created on the array.
|
|
> When the *mover* is invoked, files and subdirectories are transferred off the array and onto the Cache disk/pool.
|
|
>
|
|
> **NOTE:** Mover will never move any files that are currently in use.
|
|
> This means if you want to move files associated with system services such as Docker or VMs then you need to
|
|
> disable these services while mover is running.
|
|
:end
|
|
|
|
_(Select cache pool)_:
|
|
: <select name="shareCachePool">
|
|
<?foreach ($pools as $pool):?>
|
|
<?if ($disks[$pool]['devices']) echo mk_option($share['cachePool'],$pool,ucfirst($pool))?>
|
|
<?endforeach;?>
|
|
</select>
|
|
|
|
_(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):?>_(Set when adding new share only)_<?endif;?>
|
|
|
|
:help12
|
|
> Set to **No** to cause the *btrfs* NOCOW (No Copy-on-Write) attribute to be set on the share directory
|
|
> when created on a device formatted with *btrfs* file system. Once set, newly created files and
|
|
> subdirectories on the device will inherit the NOCOW attribute. We recommend this setting for shares
|
|
> used to store vdisk images, including the Docker loopback image file. This setting has no effect
|
|
> on non-btrfs file systems.
|
|
>
|
|
> Set to **Auto** for normal operation, meaning COW **will** be in effect on devices formatted with *btrfs*.
|
|
:end
|
|
|
|
_(Allocation method)_:
|
|
: <select name="shareAllocator">
|
|
<?=mk_option($share['allocator'], "highwater", _('High-water'))?>
|
|
<?=mk_option($share['allocator'], "fillup", _('Fill-up'))?>
|
|
<?=mk_option($share['allocator'], "mostfree", _('Most-free'))?>
|
|
</select>
|
|
|
|
:help6
|
|
> This setting determines how Unraid OS will choose which disk to use when creating a new file or directory:
|
|
>
|
|
> **High-water**
|
|
> Choose the lowest numbered disk with free space still above the current *high water mark*. The
|
|
> *high water mark* is initialized with the size of the largest data disk divided by 2. If no disk
|
|
> has free space above the current *high water mark*, divide the *high water mark* by 2 and choose again.
|
|
>
|
|
> The goal of **High-water** is to write as much data as possible to each disk (in order to minimize
|
|
> how often disks need to be spun up), while at the same time, try to keep the same amount of free space on
|
|
> each disk (in order to distribute data evenly across the array).
|
|
>
|
|
> **Fill-up**
|
|
> Choose the lowest numbered disk that still has free space above the current **Minimum free space**
|
|
> setting.
|
|
>
|
|
> **Most-free**
|
|
> Choose the disk that currently has the most free space.
|
|
:end
|
|
|
|
_(Minimum free space)_:
|
|
: <input type="text" name="shareFloor" maxlength="16" class="narrow" value="<?=htmlspecialchars($share['floor'])?>">
|
|
|
|
:help7
|
|
> The *minimum free space* available to allow writing to any disk belonging to the share.<br>
|
|
>
|
|
> Choose a value which is equal or greater than the biggest single file size you intend to copy to the share.
|
|
> Include units KB, MB, GB and TB as appropriate, e.g. 10MB.
|
|
:end
|
|
|
|
_(Split level)_:
|
|
: <select name="shareSplitLevel">
|
|
<?=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>
|
|
|
|
:help8
|
|
> Determines whether a directory is allowed to expand onto multiple disks.
|
|
|
|
> **Automatically split any directory as required**
|
|
> When a new file or subdirectory needs to be created in a share, Unraid OS first chooses which disk
|
|
> it should be created on, according to the configured *Allocation method*. If the parent directory containing
|
|
> the new file or or subdirectory does not exist on this disk, then Unraid OS will first create all necessary
|
|
> parent directories, and then create the new file or subdirectory.
|
|
>
|
|
> **Automatically split only the top level directory as required**
|
|
> When a new file or subdirectory is being created in the first level subdirectory of a share, if that first
|
|
> level subdirectory does not exist on the disk being written, then the subdirectory will be created first.
|
|
> If a new file or subdirectory is being created in the second or lower level subdirectory of a share, the new
|
|
> file or subdirectory is created on the same disk as the new file or subdirectory's parent directory.
|
|
>
|
|
> **Automatically split only the top "N" level directories as required**
|
|
> Similar to previous: when a new file or subdirectory is being created, if the parent directory is at level "N",
|
|
> and does not exist on the chosen disk, Unraid OS will first create all necessary parent directories. If the
|
|
> parent directory of the new file or subdirectory is beyond level "N", then the new file or subdirectory is
|
|
> created on the same disk where the parent directory exists.
|
|
>
|
|
> **Manual: do not automatically split directories**
|
|
> When a new file or subdirectory needs to be created in a share, Unraid OS will only consider disks where the
|
|
> parent directory already exists.
|
|
:end
|
|
|
|
_(Included disk(s))_:
|
|
: <select id="s1" name="shareInclude" multiple="multiple" style="display:none">
|
|
<?foreach ($myDisks as $disk):?>
|
|
<?=mk_option_luks($disk, $share['include'], strstr($disks[$disk]['fsType'],':',true))?>
|
|
<?endforeach;?>
|
|
</select>
|
|
|
|
:help9
|
|
> Specify the disks which can be used by the share. By default all disks are included; that is, if specific
|
|
> disks are not selected here, then the share may expand into *all* array disks.
|
|
:end
|
|
|
|
_(Excluded disk(s))_:
|
|
: <select id="s2" name="shareExclude" multiple="multiple" style="display:none">
|
|
<?foreach ($myDisks as $disk):?>
|
|
<?=mk_option_luks($disk, $share['exclude'], strstr($disks[$disk]['fsType'],':',true))?>
|
|
<?endforeach;?>
|
|
</select>
|
|
|
|
:help10
|
|
> Specify the disks which can *not* be used by the share. By default no disks are excluded.
|
|
:end
|
|
|
|
<?if (!$name):?>
|
|
|
|
: <input type="submit" name="cmdEditShare" value="_(Add Share)_" onclick="this.value='Add Share'"><input type="button" value="_(Done)_" onclick="done()">
|
|
<?elseif (shareEmpty($name)):?>
|
|
_(Share status)_:
|
|
: _(Share is empty)_
|
|
|
|
:help13
|
|
> Share does *not* contain any data and may be deleted if not needed any longer.
|
|
:end
|
|
|
|
_(Delete)_<input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, this.form.cmdEditShare);">
|
|
: <input type="submit" name="cmdEditShare" value="_(Apply)_" onclick="if (this.value=='_(Delete)_')this.value='Delete';else this.value='Apply'" disabled><input type="button" value="_(Done)_" onclick="done()">
|
|
<?else:?>
|
|
_(Share status)_:
|
|
: _(Share contains data)_
|
|
|
|
:help14
|
|
> Share can *not* be deleted as long as it contains data. Be aware that some data can be hidden. See also [SMB Settings](/Settings/SMB) -> Hide "dot" files.
|
|
:end
|
|
|
|
|
|
: <input type="submit" name="cmdEditShare" value="_(Apply)_" onclick="this.value='Apply'" disabled><input type="button" value="_(Done)_" onclick="done()">
|
|
<?endif;?>
|
|
</form>
|
|
<script>
|
|
$(function() {
|
|
initDropdown(false,true);
|
|
<?if ($tabbed):?>
|
|
$('#tab1').bind({click:function(){initDropdown(true,true);}});
|
|
<?endif;?>
|
|
updateScreen(document.share_edit.shareUseCache.value);
|
|
presetSpace(document.share_edit.shareFloor);
|
|
});
|
|
function initDropdown(remove,create) {
|
|
if (remove) {
|
|
$('#s1').dropdownchecklist('destroy');
|
|
$('#s2').dropdownchecklist('destroy');
|
|
<?if ($name):?>
|
|
$('#s3').dropdownchecklist('destroy');
|
|
<?endif;?>
|
|
}
|
|
if (create) {
|
|
$('#s1').dropdownchecklist({emptyText:'_(All)_', width:<?=$width[1]?>, explicitClose:'..._(close)_'});
|
|
$('#s2').dropdownchecklist({emptyText:'_(None)_', width:<?=$width[1]?>, explicitClose:'..._(close)_'});
|
|
<?if ($name):?>
|
|
$("#s3").dropdownchecklist({firstItemChecksAll:true, emptyText:'_(select)_...', width:<?=$width[0]?>, explicitClose:'..._(close)_'});
|
|
<?endif;?>
|
|
}
|
|
}
|
|
function updateScreen(cache) {
|
|
switch (cache) {
|
|
case 'yes':
|
|
var textAction = '_(Mover transfers files from cache to array)_';
|
|
break;
|
|
case 'prefer':
|
|
var textAction = '_(Mover transfers files from array to cache)_';
|
|
break;
|
|
default:
|
|
var textAction = '_(Mover takes no action)_';
|
|
break;
|
|
}
|
|
document.getElementById('moverAction').innerHTML = textAction;
|
|
switch (cache) {
|
|
case 'no':
|
|
document.share_edit.shareCOW.disabled = true;
|
|
document.share_edit.shareCachePool.disabled = true;
|
|
document.share_edit.shareAllocator.disabled = false;
|
|
document.share_edit.shareFloor.disabled = false;
|
|
document.share_edit.shareSplitLevel.disabled = false;
|
|
$('#s1').dropdownchecklist('enable');
|
|
$('#s2').dropdownchecklist('enable');
|
|
break;
|
|
case 'only':
|
|
document.share_edit.shareCOW.disabled = false;
|
|
document.share_edit.shareCachePool.disabled = false;
|
|
document.share_edit.shareAllocator.disabled = true;
|
|
document.share_edit.shareFloor.disabled = true;
|
|
document.share_edit.shareSplitLevel.disabled = true;
|
|
$('#s1').dropdownchecklist('disable');
|
|
$('#s2').dropdownchecklist('disable');
|
|
break;
|
|
default:
|
|
document.share_edit.shareCOW.disabled = false;
|
|
document.share_edit.shareCachePool.disabled = false;
|
|
document.share_edit.shareAllocator.disabled = false;
|
|
document.share_edit.shareFloor.disabled = false;
|
|
document.share_edit.shareSplitLevel.disabled = false;
|
|
$('#s1').dropdownchecklist('enable');
|
|
$('#s2').dropdownchecklist('enable');
|
|
break;
|
|
}
|
|
}
|
|
function presetSpace(shareFloor) {
|
|
var unit = ['KB','MB','GB','TB','PB'];
|
|
var scale = shareFloor.value;
|
|
if (scale.replace(/[0-9.,\s]/g,'').length>0) return;
|
|
var base = scale>0 ? Math.floor(Math.log(scale)/Math.log(1000)):0;
|
|
if (base>=unit.length) base = unit.length-1;
|
|
shareFloor.value = (scale/Math.pow(1000, base))+unit[base];
|
|
}
|
|
// Compose input fields
|
|
function prepareEdit(form) {
|
|
// Test share name validity
|
|
var share = form.shareName.value.trim();
|
|
if (share.length==0) {
|
|
swal({title:'_(Missing share name)_',text:'_(Enter a name for the share)_',type:'error',confirmButtonText:'_(Ok)_'});
|
|
return false;
|
|
}
|
|
var reserved = [<?=implode(',',array_map('escapeshellarg',explode(',',$var['reservedNames'])))?>];
|
|
if (reserved.includes(share)) {
|
|
swal({title:'_(Invalid share name)_',text:'_(Do not use reserved names)_',type:'error',confirmButtonText:'_(Ok)_'});
|
|
return false;
|
|
}
|
|
var pools = [<?=implode(',',array_map('escapeshellarg',$pools))?>];
|
|
if (pools.includes(share)) {
|
|
swal({title:'_(Invalid share name)_',text:'_(Do not use pool names)_',type:'error',confirmButtonText:'_(Ok)_'});
|
|
return false;
|
|
}
|
|
if (share.match('[:\\\/*<>|"]')) {
|
|
swal({title:'_(Invalid Characters)_',text:'_(You cannot use the following within share names)_ : \\ / * < > | "',type:'error',confirmButtonText:'_(Ok)_'});
|
|
return false;
|
|
}
|
|
form.shareName.value = share;
|
|
// Adjust minimum free space value to selected unit
|
|
var unit = 'KB,MB,GB,TB,PB';
|
|
var scale = form.shareFloor.value;
|
|
var index = unit.indexOf(scale.replace(/[0-9.,\s]/g,'').toUpperCase());
|
|
form.shareFloor.value = scale.replace(/[A-Z\s]/gi,'') * Math.pow(1000, (index>0 ? index/3 : 0))
|
|
// Return include as single line input
|
|
var include = '';
|
|
for (var i=0,item; item=form.shareInclude.options[i]; i++) {
|
|
if (item.selected) {
|
|
if (include.length) include += ',';
|
|
include += item.value;
|
|
item.selected = false;
|
|
}
|
|
}
|
|
item = form.shareInclude.options[0];
|
|
item.value = include;
|
|
item.selected = true;
|
|
// Return exclude as single line input
|
|
var exclude = '';
|
|
for (var i=0,item; item=form.shareExclude.options[i]; i++) {
|
|
if (item.selected) {
|
|
if (exclude.length) exclude += ',';
|
|
exclude += item.value;
|
|
item.selected = false;
|
|
}
|
|
}
|
|
item = form.shareExclude.options[0];
|
|
item.value = exclude;
|
|
item.selected = true;
|
|
return true;
|
|
}
|
|
function readShare() {
|
|
var form = document.share_edit;
|
|
var name = $('select[name="readshare"]').val();
|
|
initDropdown(true,false);
|
|
$.get('/webGui/include/ShareData.php',{name:name},function(json) {
|
|
var 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 (var i=0,disk; disk=data.include.split(',')[i]; i++) for (var j=0,include; include=form.shareInclude.options[j]; j++) if (include.value==disk) include.selected=true;
|
|
for (var i=0,disk; disk=data.exclude.split(',')[i]; i++) for (var j=0,exclude; exclude=form.shareExclude.options[j]; j++) if (exclude.value==disk) exclude.selected=true;
|
|
initDropdown(false,true);
|
|
});
|
|
$(form).find('select').trigger('change');
|
|
}
|
|
function writeShare(data,n,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 {
|
|
var data = [], i = 0;
|
|
$('select#s3 option').map(function() {
|
|
if ($(this).prop('selected')==true && $(this).val()!='(All)') {
|
|
data[i] = {};
|
|
data[i]['shareName'] = $(this).val();
|
|
data[i]['shareNameOrig'] = $(this).val();
|
|
data[i]['shareAllocator'] = '<?=addslashes(htmlspecialchars($share['allocator']))?>';
|
|
data[i]['shareFloor'] = '<?=addslashes(htmlspecialchars($share['floor']))?>';
|
|
data[i]['shareSplitLevel'] = '<?=addslashes(htmlspecialchars($share['splitLevel']))?>';
|
|
data[i]['shareInclude'] = '<?=addslashes(htmlspecialchars($share['include']))?>';
|
|
data[i]['shareExclude'] = '<?=addslashes(htmlspecialchars($share['exclude']))?>';
|
|
data[i]['shareUseCache'] = '<?=addslashes(htmlspecialchars($share['useCache']))?>';
|
|
data[i]['cmdEditShare'] = 'Apply';
|
|
i++;
|
|
}
|
|
});
|
|
toggleButton('writeshare',true);
|
|
$('div.spinner.fixed').show('slow');
|
|
writeShare(data,0,i);
|
|
}
|
|
}
|
|
</script>
|