mirror of
https://github.com/unraid/webgui.git
synced 2026-03-09 12:31:56 -05:00
Merge remote-tracking branch 'upstream/master' into New-VM-interface
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -54,7 +54,6 @@ sftp-config.json
|
||||
|
||||
# =========================
|
||||
# Exclude these dirs commonly found in /usr/local
|
||||
bin/
|
||||
games/
|
||||
info/
|
||||
lib64/
|
||||
|
||||
1
bin/firefox
Symbolic link
1
bin/firefox
Symbolic link
@@ -0,0 +1 @@
|
||||
/usr/libexec/unraid/firefox-119.0.r20231019122658-x86_64.AppImage
|
||||
1
bin/unraidwold
Symbolic link
1
bin/unraidwold
Symbolic link
@@ -0,0 +1 @@
|
||||
/usr/libexec/unraid/unraidwold
|
||||
@@ -116,6 +116,7 @@ $bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
<input type="hidden" name="#cleanup" value="true">
|
||||
<input type="hidden" name="DOCKER_CUSTOM_NETWORKS" value="<?=implode(' ',$unset)?> ">
|
||||
<input type="hidden" name="DOCKER_IMAGE_FILE" value="<?=_var($dockercfg,'DOCKER_IMAGE_FILE')?>">
|
||||
|
||||
_(Enable Docker)_:
|
||||
: <select id="DOCKER_ENABLED" name="DOCKER_ENABLED">
|
||||
<?=mk_option(_var($dockercfg,'DOCKER_ENABLED'), 'no', _('No'))?>
|
||||
@@ -183,7 +184,7 @@ _(Docker directory)_:
|
||||
|
||||
</div>
|
||||
_(Default appdata storage location)_:
|
||||
: <input type="text" id="DOCKER_APP_CONFIG_PATH" name="DOCKER_APP_CONFIG_PATH" autocomplete="off" spellcheck="false" value="<?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>" placeholder="_(e.g.)_ /mnt/user/appdata/" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" data-pickfolders="true" pattern="^[^\\]*/$">
|
||||
: <input type="text" id="DOCKER_APP_CONFIG_PATH" name="DOCKER_APP_CONFIG_PATH" autocomplete="off" spellcheck="false" value="<?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>" placeholder="_(e.g.)_ /mnt/user/appdata/" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="/mnt" data-pickfolders="true" pattern="^[^\\]*/$">
|
||||
<?if ($var['fsState'] != "Started"):?>
|
||||
<span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir(_var($dockercfg,'DOCKER_APP_CONFIG_PATH'))):?>
|
||||
|
||||
@@ -14,15 +14,25 @@
|
||||
* Usage:
|
||||
* ```
|
||||
* $rebootDetails = new RebootDetails();
|
||||
* $rebootType = $rebootDetails->getRebootType();
|
||||
* $rebootType = $rebootDetails->rebootType;
|
||||
* ```
|
||||
*/
|
||||
class RebootDetails
|
||||
{
|
||||
/**
|
||||
* @var string $rebootType Stores the type of reboot required, which can be 'update', 'downgrade', or 'thirdPartyDriversDownloading'.
|
||||
*/
|
||||
private $rebootType = '';
|
||||
const CURRENT_CHANGES_TXT_PATH = '/boot/changes.txt';
|
||||
const CURRENT_README_RELATIVE_PATH = 'plugins/unRAIDServer/README.md';
|
||||
const CURRENT_VERSION_PATH = '/etc/unraid-version';
|
||||
const PREVIOUS_BZ_ROOT_PATH = '/boot/previous/bzroot';
|
||||
const PREVIOUS_CHANGES_TXT_PATH = '/boot/previous/changes.txt';
|
||||
|
||||
private $currentVersion = '';
|
||||
|
||||
public $rebootType = ''; // 'update', 'downgrade', 'thirdPartyDriversDownloading'
|
||||
public $rebootReleaseDate = '';
|
||||
public $rebootVersion = '';
|
||||
|
||||
public $previousReleaseDate = '';
|
||||
public $previousVersion = '';
|
||||
|
||||
/**
|
||||
* Constructs a new RebootDetails object and automatically detects the reboot type during initialization.
|
||||
@@ -40,66 +50,119 @@ class RebootDetails
|
||||
{
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
|
||||
$rebootReadme = @file_get_contents("$docroot/plugins/unRAIDServer/README.md", false, null, 0, 20) ?: '';
|
||||
/**
|
||||
* Read the reboot readme, and see if it says "REBOOT REQUIRED" or "DOWNGRADE"
|
||||
* only relying on the README.md file to save reads from the flash drive.
|
||||
* because we started allowing downgrades from the account.unraid.net Update OS page, we can't
|
||||
* fully rely on the README.md value of being accurate.
|
||||
* For instance if on 6.13.0-beta.2.1 then chose to "Downgrade" to 6.13.0-beta.1.10 from the account app
|
||||
* the README.md file would still say "REBOOT REQUIRED".
|
||||
*/
|
||||
$rebootReadme = @file_get_contents("$docroot/" . self::CURRENT_README_RELATIVE_PATH, false, null, 0, 20) ?: '';
|
||||
$rebootDetected = preg_match("/^\*\*(REBOOT REQUIRED|DOWNGRADE)/", $rebootReadme);
|
||||
if (!$rebootDetected) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* if a reboot is required, then:
|
||||
* get current Unraid version from /etc/unraid-version
|
||||
* then get the version of the last update from self::CURRENT_CHANGES_TXT_PATH
|
||||
* if they're different, then a reboot is required
|
||||
* if the version in self::CURRENT_CHANGES_TXT_PATH is less than the current version, then a downgrade is required
|
||||
* if the version in self::CURRENT_CHANGES_TXT_PATH is greater than the current version, then an update is required
|
||||
*/
|
||||
$this->setCurrentVersion();
|
||||
$this->setRebootDetails();
|
||||
if ($this->currentVersion == '' || $this->rebootVersion == '') {
|
||||
return; // return to prevent potential incorrect outcome
|
||||
}
|
||||
|
||||
$rebootForDowngrade = $rebootDetected && strpos($rebootReadme, 'DOWNGRADE') !== false;
|
||||
$rebootForUpdate = $rebootDetected && strpos($rebootReadme, 'REBOOT REQUIRED') !== false;
|
||||
|
||||
$this->rebootType = $rebootForDowngrade ? 'downgrade' : ($rebootForUpdate ? 'update' : '');
|
||||
$compareVersions = version_compare($this->rebootVersion, $this->currentVersion);
|
||||
switch ($compareVersions) {
|
||||
case -1:
|
||||
$this->setRebootType('downgrade');
|
||||
break;
|
||||
case 0:
|
||||
// we should never get here, but if we do, then no reboot is required and just return
|
||||
return;
|
||||
case 1:
|
||||
$this->setRebootType('update');
|
||||
break;
|
||||
}
|
||||
|
||||
// Detect if third-party drivers were part of the update process
|
||||
$processWaitingThirdPartyDrivers = "inotifywait -q /boot/changes.txt -e move_self,delete_self";
|
||||
$processWaitingThirdPartyDrivers = "inotifywait -q " . self::CURRENT_CHANGES_TXT_PATH . " -e move_self,delete_self";
|
||||
// Run the ps command to list processes and check if the process is running
|
||||
$ps_command = "ps aux | grep -E \"$processWaitingThirdPartyDrivers\" | grep -v \"grep -E\"";
|
||||
$output = shell_exec($ps_command) ?? '';
|
||||
if ($this->rebootType != '' && strpos($output, $processWaitingThirdPartyDrivers) !== false) {
|
||||
$this->rebootType = 'thirdPartyDriversDownloading';
|
||||
$this->setRebootType('thirdPartyDriversDownloading');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of reboot required, which can be 'update', 'downgrade', or 'thirdPartyDriversDownloading'.
|
||||
*
|
||||
* @return string The type of reboot required.
|
||||
*/
|
||||
public function getRebootType()
|
||||
{
|
||||
return $this->rebootType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects and retrieves the version information related to the system reboot based on the contents of the '/boot/changes.txt' file.
|
||||
*
|
||||
* @return string The system version information or 'Not found' if not found, or 'File not found' if the file is not present.
|
||||
*/
|
||||
public function getRebootVersion()
|
||||
private function readChangesTxt(string $file_path = self::CURRENT_CHANGES_TXT_PATH)
|
||||
{
|
||||
$file_path = '/boot/changes.txt';
|
||||
|
||||
// Check if the file exists
|
||||
if (file_exists($file_path)) {
|
||||
// Open the file for reading
|
||||
$file = fopen($file_path, 'r');
|
||||
|
||||
// Read the file line by line until we find a line that starts with '# Version'
|
||||
while (($line = fgets($file)) !== false) {
|
||||
if (strpos($line, '# Version') === 0) {
|
||||
// Use a regular expression to extract the full version string
|
||||
if (preg_match('/# Version\s+(\S+)/', $line, $matches)) {
|
||||
$fullVersion = $matches[1];
|
||||
return $fullVersion;
|
||||
} else {
|
||||
return 'Not found';
|
||||
}
|
||||
exec("head -n4 $file_path", $rows);
|
||||
foreach ($rows as $row) {
|
||||
$i = stripos($row,'version');
|
||||
if ($i !== false) {
|
||||
[$version, $releaseDate] = explode(' ', trim(substr($row, $i+7)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the file
|
||||
fclose($file);
|
||||
return [
|
||||
'releaseDate' => $releaseDate ?? 'Not found',
|
||||
'version' => $version ?? 'Not found',
|
||||
];
|
||||
} else {
|
||||
return 'File not found';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current version of the Unraid server for comparison with the reboot version.
|
||||
*/
|
||||
private function setCurrentVersion() {
|
||||
// output ex: version="6.13.0-beta.2.1"
|
||||
$raw = @file_get_contents(self::CURRENT_VERSION_PATH) ?: '';
|
||||
// Regular expression to match the version between the quotes
|
||||
$pattern = '/version="([^"]+)"/';
|
||||
if (preg_match($pattern, $raw, $matches)) {
|
||||
$this->currentVersion = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
private function setRebootDetails()
|
||||
{
|
||||
$rebootDetails = $this->readChangesTxt();
|
||||
$this->rebootReleaseDate = $rebootDetails['releaseDate'];
|
||||
$this->rebootVersion = $rebootDetails['version'];
|
||||
}
|
||||
|
||||
private function setRebootType($rebootType)
|
||||
{
|
||||
$this->rebootType = $rebootType;
|
||||
}
|
||||
|
||||
/**
|
||||
* If self::PREVIOUS_BZ_ROOT_PATH exists, then the user has the option to downgrade to the previous version.
|
||||
* Parse the text file /boot/previous/changes.txt to get the version number of the previous version.
|
||||
* Then we move some files around and reboot.
|
||||
*/
|
||||
public function setPrevious()
|
||||
{
|
||||
if (@file_exists(self::PREVIOUS_BZ_ROOT_PATH) && @file_exists(self::PREVIOUS_CHANGES_TXT_PATH)) {
|
||||
$parseOutput = $this->readChangesTxt(self::PREVIOUS_CHANGES_TXT_PATH);
|
||||
$this->previousVersion = $parseOutput['version'];
|
||||
$this->previousReleaseDate = $parseOutput['releaseDate'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ class ServerState
|
||||
$this->osVersionBranch = trim(@exec('plugin category /var/log/plugins/unRAIDServer.plg') ?? 'stable');
|
||||
|
||||
$caseModelFile = '/boot/config/plugins/dynamix/case-model.cfg';
|
||||
$this->caseModel = file_exists($caseModelFile) ? file_get_contents($caseModelFile) : '';
|
||||
$this->caseModel = file_exists($caseModelFile) ? htmlspecialchars(@file_get_contents($caseModelFile), ENT_HTML5, 'UTF-8') : '';
|
||||
|
||||
$this->rebootDetails = new RebootDetails();
|
||||
|
||||
@@ -236,6 +236,10 @@ class ServerState
|
||||
public function getServerState()
|
||||
{
|
||||
$serverState = [
|
||||
"array" => [
|
||||
"state" => @$this->getWebguiGlobal('var', 'fsState'),
|
||||
"progress" => @$this->getWebguiGlobal('var', 'fsProgress'),
|
||||
],
|
||||
"apiKey" => $this->apiKey,
|
||||
"apiVersion" => $this->apiVersion,
|
||||
"avatar" => $this->avatar,
|
||||
@@ -270,7 +274,8 @@ class ServerState
|
||||
"osVersion" => $this->osVersion,
|
||||
"osVersionBranch" => $this->osVersionBranch,
|
||||
"protocol" => _var($_SERVER, 'REQUEST_SCHEME'),
|
||||
"rebootType" => $this->rebootDetails->getRebootType(),
|
||||
"rebootType" => $this->rebootDetails->rebootType,
|
||||
"rebootVersion" => $this->rebootDetails->rebootVersion,
|
||||
"regDevs" => @(int)$this->var['regDevs'] ?? 0,
|
||||
"regGen" => @(int)$this->var['regGen'],
|
||||
"regGuid" => @$this->var['regGUID'] ?? '',
|
||||
@@ -334,4 +339,4 @@ class ServerState
|
||||
$json = json_encode($this->getServerState());
|
||||
return htmlspecialchars($json, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,8 +89,10 @@ class WebComponentTranslations
|
||||
'Beta' => _('Beta'),
|
||||
'Blacklisted USB Flash GUID' => _('Blacklisted USB Flash GUID'),
|
||||
'BLACKLISTED' => _('BLACKLISTED'),
|
||||
'Calculating OS Update Eligibility…' => _('Calculating OS Update Eligibility…'),
|
||||
'Calculating trial expiration…' => _('Calculating trial expiration…'),
|
||||
'Callback redirect type not present or incorrect' => _('Callback redirect type not present or incorrect'),
|
||||
'Cancel {0}' => sprintf(_('Cancel %s'), '{0}'),
|
||||
'Cancel' => _('Cancel'),
|
||||
'Cannot access your USB Flash boot device' => _('Cannot access your USB Flash boot device'),
|
||||
'Cannot validate Unraid Trial key' => _('Cannot validate Unraid Trial key'),
|
||||
@@ -107,8 +109,10 @@ class WebComponentTranslations
|
||||
'Close' => _('Close'),
|
||||
'Configure Connect Features' => _('Configure Connect Features'),
|
||||
'Confirm and start update' => _('Confirm and start update'),
|
||||
'Confirm to Install Unraid OS {0}' => sprintf(_('Confirm to Install Unraid OS %s'), '{0}'),
|
||||
'Connected' => _('Connected'),
|
||||
'Contact Support' => _('Contact Support'),
|
||||
'Continue' => _('Continue'),
|
||||
'Copied' => _('Copied'),
|
||||
'Copy Key URL' => _('Copy Key URL'),
|
||||
'Copy your Key URL: {0}' => sprintf(_('Copy your Key URL: %s'), '{0}'),
|
||||
@@ -122,24 +126,30 @@ class WebComponentTranslations
|
||||
'Downgrade Unraid OS to {0}' => sprintf(_('Downgrade Unraid OS to %s'), '{0}'),
|
||||
'Downgrade Unraid OS' => _('Downgrade Unraid OS'),
|
||||
'Downgrades are only recommended if you\'re unable to solve a critical issue.' => _('Downgrades are only recommended if you\'re unable to solve a critical issue.'),
|
||||
'Download Diagnostics' => _('Download Diagnostics'),
|
||||
'Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.' => _('Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.'),
|
||||
'Download unraid-api Logs' => _('Download unraid-api Logs'),
|
||||
'Dynamic Remote Access' => _('Dynamic Remote Access'),
|
||||
'Enable update notifications' => _('Enable update notifications'),
|
||||
'Enhance your experience with Unraid Connect' => _('Enhance your experience with Unraid Connect'),
|
||||
'Enhance your Unraid experience with Connect' => _('Enhance your Unraid experience with Connect'),
|
||||
'Enhance your Unraid experience' => _('Enhance your Unraid experience'),
|
||||
'Error creatiing a trial key. Please try again later.' => _('Error creatiing a trial key. Please try again later.'),
|
||||
'Error creating a trial key. Please try again later.' => _('Error creating a trial key. Please try again later.'),
|
||||
'Error Parsing Changelog • {0}' => sprintf(_('Error Parsing Changelog • %s'), '{0}'),
|
||||
'Error' => _('Error'),
|
||||
'Expired {0}' => sprintf(_('Expired %s'), '{0}'),
|
||||
'Expired' => _('Expired'),
|
||||
'Expires at {0}' => sprintf(_('Expires at %s'), '{0}'),
|
||||
'Expires in {0}' => sprintf(_('Expires in %s'), '{0}'),
|
||||
'Extend License to Update' => _('Extend License to Update'),
|
||||
'Extend License' => _('Extend License'),
|
||||
'Extend Trial' => _('Extend Trial'),
|
||||
'Extending your free trial by 15 days' => _('Extending your free trial by 15 days'),
|
||||
'Extension Installed' => _('Extension Installed'),
|
||||
'Failed to {0} {1} Key' => sprintf(_('Failed to %1s %2s Key'), '{0}', '{1}'),
|
||||
'Failed to install key' => _('Failed to install key'),
|
||||
'Failed to update Connect account configuration' => _('Failed to update Connect account configuration'),
|
||||
'Fetching & parsing changelog…' => _('Fetching & parsing changelog…'),
|
||||
'Fix Error' => _('Fix Error'),
|
||||
'Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.' => sprintf(_('Flash Backup is not available. Navigate to %s/Main/Settings/Flash to try again then come back to this page.'), '{0}'),
|
||||
'Flash GUID Error' => _('Flash GUID Error'),
|
||||
@@ -152,18 +162,24 @@ class WebComponentTranslations
|
||||
'Go to Connect plugin settings' => _('Go to Connect plugin settings'),
|
||||
'Go to Connect' => _('Go to Connect'),
|
||||
'Go to Management Access Now' => _('Go to Management Access Now'),
|
||||
'Go to Settings > Notifications to enable automatic OS update notifications for future releases.' => _('Go to Settings > Notifications to enable automatic OS update notifications for future releases.'),
|
||||
'Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.' => _('Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.'),
|
||||
'Go to Tools > Management Access to ensure your backup is up-to-date.' => _('Go to Tools > Management Access to ensure your backup is up-to-date.'),
|
||||
'Go to Tools > Registration to fix' => _('Go to Tools > Registration to fix'),
|
||||
'Go to Tools > Registration to Learn More' => _('Go to Tools > Registration to Learn More'),
|
||||
'Go to Tools > Update OS for more options.' => _('Go to Tools > Update OS for more options.'),
|
||||
'Go to Tools > Update' => _('Go to Tools > Update'),
|
||||
'hour' => sprintf(_('%s hour'), '{n}') . ' | ' . sprintf(_('%s hours'), '{n}'),
|
||||
'I have made a Flash Backup' => _('I have made a Flash Backup'),
|
||||
'If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.' => _('If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.'),
|
||||
'Ignore this message if you are currently connected via Remote Access or VPN.' => _('Ignore this message if you are currently connected via Remote Access or VPN.'),
|
||||
'Ignore this release until next reboot' => _('Ignore this release until next reboot'),
|
||||
'Ignored Releases' => _('Ignored Releases'),
|
||||
'In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.' => _('In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.'),
|
||||
'Install Connect' => _('Install Connect'),
|
||||
'Install Recovered' => _('Install Recovered'),
|
||||
'Install Replaced' => _('Install Replaced'),
|
||||
'Install Unraid OS {0}' => sprintf(_('Install Unraid OS %s'), '{0}'),
|
||||
'Install' => _('Install'),
|
||||
'Installed' => _('Installed'),
|
||||
'Installing Extended Trial' => _('Installing Extended Trial'),
|
||||
@@ -175,6 +191,9 @@ class WebComponentTranslations
|
||||
'Invalid API Key Format' => _('Invalid API Key Format'),
|
||||
'Invalid API Key' => _('Invalid API Key'),
|
||||
'Invalid installation' => _('Invalid installation'),
|
||||
'It\s highly recommended to review the changelog before continuing your update.' => _('It\'s highly recommended to review the changelog before continuing your update.'),
|
||||
'Key ineligible for {0}' => sprintf(_('Key ineligible for %s'), '{0}'),
|
||||
'Key ineligible for future releases' => _('Key ineligible for future releases'),
|
||||
'Keyfile required to check replacement status' => _('Keyfile required to check replacement status'),
|
||||
'LAN IP {0}' => sprintf(_('LAN IP %s'), '{0}'),
|
||||
'LAN IP Copied' => _('LAN IP Copied'),
|
||||
@@ -182,12 +201,15 @@ class WebComponentTranslations
|
||||
'Last checked: {0}' => sprintf(_('Last checked: %s'), '{0}'),
|
||||
'Learn more about the error' => _('Learn more about the error'),
|
||||
'Learn more and fix' => _('Learn more and fix'),
|
||||
'Learn more and link your key to your account' => _('Learn more and link your key to your account'),
|
||||
'Learn More' => _('Learn More'),
|
||||
'Learn more' => _('Learn more'),
|
||||
'Let\'s Unleash your Hardware!' => _('Let\'s Unleash your Hardware!'),
|
||||
'License key actions' => _('License key actions'),
|
||||
'License key type' => _('License key type'),
|
||||
'License Management' => _('License Management'),
|
||||
'Link Key' => _('Link Key'),
|
||||
'Linked to Unraid.net account' => _('Linked to Unraidnet account'),
|
||||
'Loading' => _('Loading'),
|
||||
'Manage Unraid.net Account in new tab' => _('Manage Unraid.net Account in new tab'),
|
||||
'Manage Unraid.net Account' => _('Manage Unraid.net Account'),
|
||||
@@ -196,6 +218,7 @@ class WebComponentTranslations
|
||||
'minute' => sprintf(_('%s minute'), '{n}') . ' | ' . sprintf(_('%s minutes'), '{n}'),
|
||||
'Missing key file' => _('Missing key file'),
|
||||
'month' => sprintf(_('%s month'), '{n}') . ' | ' . sprintf(_('%s months'), '{n}'),
|
||||
'More options' => _('More options'),
|
||||
'Multiple License Keys Present' => _('Multiple License Keys Present'),
|
||||
'Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.' => _('Never ever be left without a backup of your config.') . ' ' . _('If you need to change flash drives, generate a backup from Connect and be up and running in minutes.'),
|
||||
'New Version: {0}' => sprintf(_('New Version: %s'), '{0}'),
|
||||
@@ -204,14 +227,18 @@ class WebComponentTranslations
|
||||
'No Keyfile' => _('No Keyfile'),
|
||||
'No thanks' => _('No thanks'),
|
||||
'No USB flash configuration data' => _('No USB flash configuration data'),
|
||||
'Not Linked' => _('Not Linked'),
|
||||
'On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.' => _('On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.'),
|
||||
'Online Flash Backup' => _('Online Flash Backup'),
|
||||
'Open a bug report' => _('Open a bug report'),
|
||||
'Open Dropdown' => _('Open Dropdown'),
|
||||
'Opens Connect in new tab' => _('Opens Connect in new tab'),
|
||||
'Original release date {0}' => sprintf(_('Original release date %s'), '{0}'),
|
||||
'OS Update Eligibility Expired' => _('OS Update Eligibility Expired'),
|
||||
'Performing actions' => _('Performing actions'),
|
||||
'Please confirm the update details below' => _('Please confirm the update details below'),
|
||||
'Please finish the initiated downgrade to enable updates.' => _('Please finish the initiated downgrade to enable updates.'),
|
||||
'Please finish the initiated update to enable a downgrade.' => _('Please finish the initiated update to enable a downgrade.'),
|
||||
'Please fix any errors and try again.' => _('Please fix any errors and try again.'),
|
||||
'Please keep this window open while we perform some actions' => _('Please keep this window open while we perform some actions'),
|
||||
'Please keep this window open' => _('Please keep this window open'),
|
||||
@@ -238,14 +265,20 @@ class WebComponentTranslations
|
||||
'Recover Key' => _('Recover Key'),
|
||||
'Recovered' => _('Recovered'),
|
||||
'Redeem Activation Code' => _('Redeem Activation Code'),
|
||||
'Refresh' => _('Refresh'),
|
||||
'Registered on' => _('Registered on'),
|
||||
'Registered to' => _('Registered to'),
|
||||
'Registration key / USB Flash GUID mismatch' => _('Registration key / USB Flash GUID mismatch'),
|
||||
'Release date {0}' => sprintf(_('Release date %s'), '{0}'),
|
||||
'Release requires verification to update' => _('Release requires verification to update'),
|
||||
'Reload' => _('Reload'),
|
||||
'Remark: Unraid\'s WAN IPv4 {0} does not match your client\'s WAN IPv4 {1}.' => sprintf(_('Remark: Unraid\'s WAN IPv4 %1s does not match your client\'s WAN IPv4 %2s.'), '{0}', '{1}'),
|
||||
'Remark: your WAN IPv4 is {0}' => sprintf(_('Remark: your WAN IPv4 is %s'), '{0}'),
|
||||
'Remove from ignore list' => _('Remove from ignore list'),
|
||||
'Remove' => _('Remove'),
|
||||
'Replace Key' => _('Replace Key'),
|
||||
'Replaced' => _('Replaced'),
|
||||
'Requires the local unraid-api to be running successfully' => _('Requires the local unraid-api to be running successfully'),
|
||||
'Restarting unraid-api…' => _('Restarting unraid-api…'),
|
||||
'second' => sprintf(_('%s second'), '{n}') . ' | ' . sprintf(_('%s seconds'), '{n}'),
|
||||
'Server Up Since {0}' => sprintf(_('Server Up Since %s'), '{0}'),
|
||||
@@ -257,6 +290,7 @@ class WebComponentTranslations
|
||||
'Sign In to utilize Unraid Connect' => _('Sign In to utilize Unraid Connect'),
|
||||
'Sign In to your Unraid.net account to get started' => _('Sign In to your Unraid.net account to get started'),
|
||||
'Sign In with Unraid.net Account' => _('Sign In with Unraid.net Account'),
|
||||
'Sign In' => _('Sign In'),
|
||||
'Sign Out Failed' => _('Sign Out Failed'),
|
||||
'Sign Out of Unraid.net' => _('Sign Out of Unraid.net'),
|
||||
'Sign Out requires the local unraid-api to be running' => _('Sign Out requires the local unraid-api to be running'),
|
||||
@@ -298,6 +332,7 @@ class WebComponentTranslations
|
||||
'Unable to fetch client WAN IPv4' => _('Unable to fetch client WAN IPv4'),
|
||||
'Unable to open release notes' => _('Unable to open release notes'),
|
||||
'Unknown error' => _('Unknown error'),
|
||||
'Unknown' => _('Unknown'),
|
||||
'unlimited' => _('unlimited'),
|
||||
'Unraid {0} Available' => sprintf(_('Unraid %s Available'), '{0}'),
|
||||
'Unraid {0} Update Available' => sprintf(_('Unraid %s Update Available'), '{0}'),
|
||||
@@ -310,10 +345,13 @@ class WebComponentTranslations
|
||||
'Unraid logo animating with a wave like effect' => _('Unraid logo animating with a wave like effect'),
|
||||
'Unraid OS {0} Released' => sprintf(_('Unraid OS %s Released'), '{0}'),
|
||||
'Unraid OS {0} Update Available' => sprintf(_('Unraid OS %s Update Available'), '{0}'),
|
||||
'Unraid OS is up-to-date' => _('Unraid OS is up-to-date'),
|
||||
'Unraid OS Update Available' => _('Unraid OS Update Available'),
|
||||
'unraid-api is offline' => _('unraid-api is offline'),
|
||||
'Up-to-date with eligible releases' => _('Up-to-date with eligible releases'),
|
||||
'Up-to-date' => _('Up-to-date'),
|
||||
'Update Available' => _('Update Available'),
|
||||
'Update Released' => _('Update Released'),
|
||||
'Update Unraid OS confirmation required' => _('Update Unraid OS confirmation required'),
|
||||
'Update Unraid OS' => _('Update Unraid OS'),
|
||||
'Updating 3rd party drivers' => _('Updating 3rd party drivers'),
|
||||
@@ -322,15 +360,20 @@ class WebComponentTranslations
|
||||
'Uptime {0}' => sprintf(_('Uptime %s'), '{0}'),
|
||||
'USB Flash device error' => _('USB Flash device error'),
|
||||
'USB Flash has no serial number' => _('USB Flash has no serial number'),
|
||||
'Verify to Update' => _('Verify to Update'),
|
||||
'Version available for restore {0}' => sprintf(_('Version available for restore %s'), '{0}'),
|
||||
'Version: {0}' => sprintf(_('Version: %s'), '{0}'),
|
||||
'View Available Updates' => _('View Available Updates'),
|
||||
'View Changelog & Update' => _('View Changelog & Update'),
|
||||
'View Changelog for {0}' => sprintf(_('View Changelog for %s'), '{0}'),
|
||||
'View Changelog on Docs' => _('View Changelog on Docs'),
|
||||
'View Changelog to Start Update' => _('View Changelog to Start Update'),
|
||||
'View Changelog' => _('View Changelog'),
|
||||
'View on Docs' => _('View on Docs'),
|
||||
'View release notes' => _('View release notes'),
|
||||
'We recommend backing up your USB Flash Boot Device before starting the update.' => _('We recommend backing up your USB Flash Boot Device before starting the update.'),
|
||||
'year' => sprintf(_('%s year'), '{n}') . ' | ' . sprintf(_('%s years'), '{n}'),
|
||||
'You are still eligible to access OS updates that were published on or before {1}.' => sprintf(_('You are still eligible to access OS updates that were published on or before %s.'), '{1}'),
|
||||
'You can also manually create a new backup by clicking the Create Flash Backup button.' => _('You can also manually create a new backup by clicking the Create Flash Backup button.'),
|
||||
'You can manually create a backup by clicking the Create Flash Backup button.' => _('You can manually create a backup by clicking the Create Flash Backup button.'),
|
||||
'You have already activated the Flash Backup feature via the Unraid Connect plugin.' => _('You have already activated the Flash Backup feature via the Unraid Connect plugin.'),
|
||||
@@ -339,7 +382,10 @@ class WebComponentTranslations
|
||||
'You may still update to releases dated prior to your update expiration date.' => _('You may still update to releases dated prior to your update expiration date.'),
|
||||
'You\'re one step closer to enhancing your Unraid experience' => _('You\'re one step closer to enhancing your Unraid experience'),
|
||||
'Your {0} Key has been replaced!' => sprintf(_('Your %s Key has been replaced!'), '{0}'),
|
||||
'Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.' => sprintf(_('Your %s license included one year of free updates at the time of purchase.'), '{0}') . ' ' . _('You are now eligible to extend your license and access the latest OS updates.') . ' ' . sprintf(_('You are still eligible to access OS updates that were published on or before %s.'), '{1}'),
|
||||
'Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates.' => sprintf(_('Your %s license included one year of free updates at the time of purchase.'), '{0}') . ' ' . _('You are now eligible to extend your license and access the latest OS updates.'),
|
||||
'Your free Trial key provides all the functionality of an Unleashed Registration key' => _('Your free Trial key provides all the functionality of an Unleashed Registration key'),
|
||||
'Your license key is not eligible for Unraid OS {0}' => sprintf(_('Your license key is not eligible for Unraid OS %s'), '{0}'),
|
||||
'Your Trial has expired' => _('Your Trial has expired'),
|
||||
'Your Trial key has been extended!' => _('Your Trial key has been extended!'),
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,8 +7,9 @@
|
||||
"css": [
|
||||
"_nuxt/unraid-components.client-fad7c220.css"
|
||||
],
|
||||
"file": "_nuxt/unraid-components.client-cd1b3939.js",
|
||||
"file": "_nuxt/unraid-components.client-a155d589.js",
|
||||
"isEntry": true,
|
||||
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs"
|
||||
}
|
||||
},
|
||||
"ts": 1715725157
|
||||
}
|
||||
@@ -13,37 +13,17 @@ Tag="upload"
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
/**
|
||||
* @note icon-update is rotated via CSS in myservers1.php
|
||||
*/
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
|
||||
// Create an instance of the RebootDetails class
|
||||
$rebootDetails = new RebootDetails();
|
||||
/**
|
||||
* @note icon-update is rotated via CSS in myservers1.php
|
||||
*
|
||||
* If /boot/previous/bzroot exists, then the user has the option to downgrade to the previous version.
|
||||
* Parse the text file /boot/previous/changes.txt to get the version number of the previous version.
|
||||
* Then we move some files around and reboot.
|
||||
*/
|
||||
$restoreVersion = $restoreBranch = $restoreVersionReleaseDate = 'unknown';
|
||||
$restoreExists = file_exists('/boot/previous/bzroot');
|
||||
$restoreChangelogPath = '/boot/previous/changes.txt';
|
||||
// Get the current reboot details if there are any
|
||||
$rebootDetails->setPrevious();
|
||||
|
||||
$serverNameEscaped = htmlspecialchars(str_replace(' ', '_', strtolower($var['NAME'])));
|
||||
|
||||
if (file_exists($restoreChangelogPath)) {
|
||||
exec("head -n4 $restoreChangelogPath", $rows);
|
||||
foreach ($rows as $row) {
|
||||
$i = stripos($row,'version');
|
||||
if ($i !== false) {
|
||||
[$restoreVersion, $restoreVersionReleaseDate] = explode(' ', trim(substr($row, $i+7)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
$restoreBranch = strpos($restoreVersion, 'rc') !== false
|
||||
? _('Next')
|
||||
: (strpos($restoreVersion, 'beta') !== false
|
||||
? _('Beta')
|
||||
: _('Stable'));
|
||||
}
|
||||
?>
|
||||
|
||||
<script>
|
||||
@@ -139,7 +119,7 @@ function startDowngrade() {
|
||||
$.get(
|
||||
'/plugins/dynamix.plugin.manager/include/Downgrade.php',
|
||||
{
|
||||
version: '<?=$restoreVersion?>',
|
||||
version: '<?= $rebootDetails->previousVersion ?>',
|
||||
},
|
||||
function() {
|
||||
refresh();
|
||||
@@ -150,7 +130,7 @@ function startDowngrade() {
|
||||
function confirmDowngrade() {
|
||||
swal({
|
||||
title: "_(Confirm Downgrade)_",
|
||||
text: "<?= $restoreVersion ?><br>_(A reboot will be required)_",
|
||||
text: "<?= $rebootDetails->previousVersion ?><br>_(A reboot will be required)_",
|
||||
html: true,
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
@@ -167,7 +147,7 @@ function confirmDowngrade() {
|
||||
|
||||
<unraid-i18n-host>
|
||||
<unraid-downgrade-os
|
||||
reboot-version="<?= $rebootDetails->getRebootVersion() ?>"
|
||||
restore-version="<?= $restoreExists && $restoreVersion != 'unknown' ? $restoreVersion : '' ?>"
|
||||
restore-release-date="<?= $restoreExists && $restoreVersionReleaseDate != 'unknown' ? $restoreVersionReleaseDate : '' ?>"></unraid-downgrade-os>
|
||||
reboot-version="<?= $rebootDetails->rebootVersion ?>"
|
||||
restore-version="<?= $rebootDetails->previousVersion ?>"
|
||||
restore-release-date="<?= $rebootDetails->previousReleaseDate ?>"></unraid-downgrade-os>
|
||||
</unraid-i18n-host>
|
||||
|
||||
@@ -46,5 +46,5 @@ function flashBackup() {
|
||||
</script>
|
||||
|
||||
<unraid-i18n-host>
|
||||
<unraid-update-os reboot-version="<?= $rebootDetails->getRebootVersion() ?>"></unraid-update-os>
|
||||
<unraid-update-os reboot-version="<?= $rebootDetails->rebootVersion ?>"></unraid-update-os>
|
||||
</unraid-i18n-host>
|
||||
|
||||
@@ -37,7 +37,7 @@ class UnraidOsCheck
|
||||
private const JSON_FILE_IGNORED = '/tmp/unraidcheck/ignored.json';
|
||||
private const JSON_FILE_IGNORED_KEY = 'updateOsIgnoredReleases';
|
||||
private const JSON_FILE_RESULT = '/tmp/unraidcheck/result.json';
|
||||
private const PLG_PATH = '/var/log/plugins/unRAIDServer.plg';
|
||||
private const PLG_PATH = '/usr/local/emhttp/plugins/unRAIDServer/unRAIDServer.plg';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -124,7 +124,7 @@ class UnraidOsCheck
|
||||
if ($parsedAltUrl) $params['altUrl'] = $parsedAltUrl;
|
||||
|
||||
$urlbase = $parsedAltUrl ?? $defaultUrl;
|
||||
$url = $urlbase.'?'.http_build_query($params);
|
||||
$url = $urlbase.'?'.http_build_query($params);
|
||||
$curlinfo = [];
|
||||
$response = http_get_contents($url,[],$curlinfo);
|
||||
if (array_key_exists('error', $curlinfo)) {
|
||||
@@ -258,4 +258,4 @@ $isGetRequest = !empty($_SERVER) && isset($_SERVER['REQUEST_METHOD']) && $_SERVE
|
||||
$getHasAction = $_GET !== null && !empty($_GET) && isset($_GET['action']);
|
||||
if ($isGetRequest && $getHasAction) {
|
||||
new UnraidOsCheck();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class UnraidUpdateCancel
|
||||
{
|
||||
private $PLG_FILENAME;
|
||||
private $PLG_BOOT;
|
||||
private $PLG_VAR;
|
||||
private $USR_LOCAL_PLUGIN_UNRAID_PATH;
|
||||
|
||||
public function __construct() {
|
||||
$this->PLG_FILENAME = "unRAIDServer.plg";
|
||||
$this->PLG_BOOT = "/boot/config/plugins/{$this->PLG_FILENAME}";
|
||||
$this->PLG_VAR = "/var/log/plugins/{$this->PLG_FILENAME}";
|
||||
$this->USR_LOCAL_PLUGIN_UNRAID_PATH = "/usr/local/emhttp/plugins/unRAIDServer";
|
||||
|
||||
// Handle the cancellation
|
||||
$revertResult = $this->revertFiles();
|
||||
// Return JSON response for front-end client
|
||||
$statusCode = $revertResult['success'] ? 200 : 500;
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($revertResult);
|
||||
}
|
||||
|
||||
public function revertFiles() {
|
||||
try {
|
||||
$command = '/sbin/mount | grep -q "/boot/previous/bz"';
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
return ['success' => true]; // Nothing to revert
|
||||
}
|
||||
|
||||
// Clear the results of previous unraidcheck run
|
||||
@unlink("/tmp/unraidcheck/result.json");
|
||||
|
||||
// Revert changes made by unRAIDServer.plg
|
||||
shell_exec("mv -f /boot/previous/* /boot");
|
||||
unlink($this->PLG_BOOT);
|
||||
unlink($this->PLG_VAR);
|
||||
symlink("{$this->USR_LOCAL_PLUGIN_UNRAID_PATH}/{$this->PLG_FILENAME}", $this->PLG_VAR);
|
||||
|
||||
// Restore README.md by echoing the content into the file
|
||||
$readmeFile = "{$this->USR_LOCAL_PLUGIN_UNRAID_PATH}/README.md";
|
||||
$readmeContent = "**Unraid OS**\n\n";
|
||||
$readmeContent .= "Unraid OS by [Lime Technology, Inc.](https://lime-technology.com).\n";
|
||||
file_put_contents($readmeFile, $readmeContent);
|
||||
|
||||
return ['success' => true]; // Upgrade handled successfully
|
||||
} catch (\Throwable $th) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $th->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Self instantiate the class and handle the cancellation
|
||||
new UnraidUpdateCancel();
|
||||
@@ -125,7 +125,7 @@ _(Libvirt storage location)_:
|
||||
|
||||
<?endif;?>
|
||||
_(Default VM storage path)_:
|
||||
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
|
||||
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="/mnt" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
|
||||
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir($domain_cfg['DOMAINDIR'])):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span><?endif;?>
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@
|
||||
$audios = $config['audio'];
|
||||
$template = $config['template'];
|
||||
$clocks = $config['clock'];
|
||||
$evdevs = $config['evdev'];
|
||||
|
||||
$type = $domain['type'];
|
||||
$name = $domain['name'];
|
||||
@@ -998,6 +999,16 @@
|
||||
</memballoon>";
|
||||
}
|
||||
#$osbootdev = "" ;
|
||||
$evdevstr = "";
|
||||
foreach($evdevs as $evdev) {
|
||||
if ($evdev['dev'] == "") continue;
|
||||
$evdevstr .= "<input type='evdev'>\n<source dev='{$evdev['dev']}'";
|
||||
if ($evdev['grab'] != "") $evdevstr .= " grab='{$evdev['grab']}' ";
|
||||
if ($evdev['grabToggle'] != "") $evdevstr .= " grabToggle='{$evdev['grabToggle']}' ";
|
||||
if ($evdev['repeat'] != "") $evdevstr .= " repeat='{$evdev['repeat']}' ";
|
||||
$evdevstr .= "/>\n</input>\n";
|
||||
}
|
||||
|
||||
$memorybackingXML = Array2XML::createXML('memoryBacking', $memorybacking);
|
||||
$memoryBackingXML = $memorybackingXML->saveXML($memorybackingXML->documentElement);
|
||||
return "<domain type='$type' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
|
||||
@@ -1043,6 +1054,7 @@
|
||||
$channelscopypaste
|
||||
$swtpm
|
||||
$memballoon
|
||||
$evdevstr
|
||||
</devices>
|
||||
</domain>";
|
||||
|
||||
|
||||
@@ -931,6 +931,19 @@ private static $encoding = 'UTF-8';
|
||||
return $arrValidOtherStubbedDevices;
|
||||
}
|
||||
|
||||
function getValidevDev() {
|
||||
$inputevdev = array_merge(glob("/dev/input/by-id/*event-kbd"),glob("/dev/input/by-id/*event-mouse"));
|
||||
return $inputevdev;
|
||||
}
|
||||
|
||||
function getevDev($res) {
|
||||
global $lv ;
|
||||
$xml = $lv->domain_get_xml($res) ;
|
||||
$xmldoc = new SimpleXMLElement($xml);
|
||||
$xmlpath = $xmldoc->xpath('//devices/input[@type="evdev"] ');
|
||||
return $xmlpath;
|
||||
}
|
||||
|
||||
$cacheValidUSBDevices = null;
|
||||
function getValidUSBDevices() {
|
||||
global $cacheValidUSBDevices;
|
||||
@@ -1331,6 +1344,12 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
if ($lv->domain_get_boot_devices($res)[0] == "fd") $osbootdev = "Yes" ; else $osbootdev = "No" ;
|
||||
$vmname = $lv->domain_get_name($res);
|
||||
$cmdline = null;
|
||||
$QEMUCmdline = getQEMUCmdLine($strDOMXML);
|
||||
$QEMUOverride = getQEMUOverride($strDOMXML);
|
||||
if (isset($QEMUCmdline)) $cmdline = $QEMUCmdline;
|
||||
if (isset($QEMUOverride) && isset($QEMUCmdline)) $cmdline .= "\n".$QEMUOverride;
|
||||
if (isset($QEMUOverride) && !isset($QEMUCmdline)) $cmdline = $QEMUOverride;
|
||||
return [
|
||||
'template' => $arrTemplateValues,
|
||||
'domain' => [
|
||||
@@ -1370,7 +1389,8 @@ private static $encoding = 'UTF-8';
|
||||
'nic' => $arrNICs,
|
||||
'usb' => $arrUSBDevs,
|
||||
'shares' => $lv->domain_get_mount_filesystems($res),
|
||||
'qemucmdline' => getQEMUCmdLine($strDOMXML)."\n".getQEMUOverride($strDOMXML),
|
||||
'evdev' => getevDev($res),
|
||||
'qemucmdline' => $cmdline,
|
||||
'clocks' => getClocks($strDOMXML),
|
||||
'xml' => [
|
||||
'machine' => $lv->domain_get_xml($vmname, "//domain/os/*"),
|
||||
@@ -1426,6 +1446,7 @@ private static $encoding = 'UTF-8';
|
||||
}
|
||||
// settings not in the GUI, but maybe customized
|
||||
unset($old['clock']);
|
||||
unset($old['devices']['input']);
|
||||
// preserve vnc/spice port settings
|
||||
// unset($new['devices']['graphics']['@attributes']['port'],$new['devices']['graphics']['@attributes']['autoport']);
|
||||
if (!$new['devices']['graphics']) unset($old['devices']['graphics']);
|
||||
@@ -1580,6 +1601,18 @@ private static $encoding = 'UTF-8';
|
||||
return substr($xml,$x, ($z + 16) -$x) ;
|
||||
}
|
||||
|
||||
function getQEMUOverride($xml) {
|
||||
$x = strpos($xml,"<qemu:override>", 0) ;
|
||||
if ($x === false) return null ;
|
||||
$y = strpos($xml,"</qemu:override>", 0) ;
|
||||
$z=$y ;
|
||||
while ($y!=false) {
|
||||
$y = strpos($xml,"<qemu:override>", $z +16) ;
|
||||
if ($y != false) $z =$y ;
|
||||
}
|
||||
return substr($xml,$x, ($z + 16) -$x) ;
|
||||
}
|
||||
|
||||
function getchannels($res) {
|
||||
global $lv ;
|
||||
$xml = $lv->domain_get_xml($res) ;
|
||||
@@ -1612,6 +1645,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
Get new VM Name
|
||||
Extract XML for VM to be cloned.
|
||||
Check if snapshots.
|
||||
Check if directory exists.
|
||||
Check for disk space
|
||||
|
||||
@@ -1629,6 +1663,10 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
If option to edit, show VMUpdate
|
||||
*/
|
||||
$snaps = getvmsnapshots($vm);
|
||||
if (is_array($snaps)) {
|
||||
if (count($snaps) ) {write("addLog\0".htmlspecialchars(_("Clone of VM not currently supported if it has snapshots"))); $arrResponse = ['error' => _("Clone of VM not currently supported if it has snapshots")]; return false ;}
|
||||
}
|
||||
$uuid = $lv->domain_get_uuid($clone) ;
|
||||
write("addLog\0".htmlspecialchars(_("Checking if clone exists")));
|
||||
if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return false ;}
|
||||
@@ -1657,7 +1695,7 @@ private static $encoding = 'UTF-8';
|
||||
write("addLog\0".htmlspecialchars("Checking for free space"));
|
||||
$dirfree = disk_free_space($pathinfo["dirname"]) ;
|
||||
$sourcedir = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($pathinfo["dirname"])." 2>/dev/null"));
|
||||
$repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]);
|
||||
if (!empty($sourcedir)) $repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]); else $repdir = $pathinfo["dirname"];
|
||||
$repdirfree = disk_free_space($repdir) ;
|
||||
$reflink = true ;
|
||||
$capacity *= 1 ;
|
||||
@@ -1706,13 +1744,14 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
#Create duplicate files.
|
||||
foreach($file_clone as $diskid => $disk) {
|
||||
$target = $disk['target'] ;
|
||||
$source = $disk['source'] ;
|
||||
$reptgt = $target = $disk['target'] ;
|
||||
$repsrc = $source = $disk['source'] ;
|
||||
if ($target == $source) { write("addLog\0".htmlspecialchars(_("New image file is same as old"))); return( false) ; }
|
||||
if ($storage == "default") $sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null")); else $sourcerealdisk = $storage;
|
||||
if (!empty($sourcerealdisk)) {
|
||||
$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
|
||||
$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
|
||||
|
||||
}
|
||||
$cmdstr = "cp --reflink=always '$repsrc' '$reptgt'" ;
|
||||
if ($reflink == true) { $refcmd = $cmdstr ; } else {$refcmd = false; }
|
||||
$cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$repsrc' '$reptgt'" ;
|
||||
@@ -1884,7 +1923,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
function vm_snapshot($vm,$snapshotname, $snapshotdescinput, $free = "yes", $method = "QEMU", $memorysnap = "yes") {
|
||||
global $lv ;
|
||||
|
||||
$logging = true;
|
||||
#Get State
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
@@ -1945,19 +1984,22 @@ private static $encoding = 'UTF-8';
|
||||
if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;}
|
||||
|
||||
#Copy nvram
|
||||
if ($logging) qemu_log($vm,"Copy NVRAM");
|
||||
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ;
|
||||
|
||||
$xmlfile = $dirpath."/".$name.".running" ;
|
||||
if ($logging) qemu_log($vm,"Save XML if state is running current $state");
|
||||
if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml) ;
|
||||
|
||||
$output= [] ;
|
||||
|
||||
if ($logging) qemu_log($vm,"snap method $method");
|
||||
switch ($method) {
|
||||
case "ZFS":
|
||||
# Create ZFS Snapshot
|
||||
if ($state == "running") exec($cmdstr." 2>&1",$output,$return);
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name -r $dirpath")) ;
|
||||
$fssnapcmd = " zfs snapshot $zfsdataset@$name";
|
||||
if ($logging) qemu_log($vm,"zfs snap: $fssnapcmd");
|
||||
shell_exec($fssnapcmd);
|
||||
# if running resume.
|
||||
if ($state == "running") $lv->domain_resume($vm);
|
||||
@@ -1967,13 +2009,16 @@ private static $encoding = 'UTF-8';
|
||||
break;
|
||||
default:
|
||||
# No Action
|
||||
if ($logging) qemu_log($vm,"Cmd: $cmdstr");
|
||||
exec($cmdstr." 2>&1",$output,$return);
|
||||
}
|
||||
|
||||
if (strpos(" ".$output[0],"error") ) {
|
||||
$arrResponse = ['error' => substr($output[0],6) ] ;
|
||||
if ($logging) qemu_log($vm,"Error");
|
||||
} else {
|
||||
$arrResponse = ['success' => true] ;
|
||||
if ($logging) qemu_log($vm,"Success write snap db");
|
||||
$ret = write_snapshots_database("$vm","$name",$state,$snapshotdescinput,$method) ;
|
||||
#remove meta data
|
||||
if ($ret != "noxml") $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
|
||||
@@ -1984,6 +2029,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryrun = false) {
|
||||
global $lv ;
|
||||
$logging = true;
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
#$disks =$lv->get_disk_stats($vm) ;
|
||||
$snapstate = $snapslist[$snap]['state'];
|
||||
@@ -2031,6 +2077,7 @@ private static $encoding = 'UTF-8';
|
||||
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
|
||||
if (!$dryrun) $new = $lv->domain_define($xml);
|
||||
if ($new) $arrResponse = ['success' => true] ; else $arrResponse = ['error' => $lv->get_last_error()] ;
|
||||
if ($logging) qemu_log($vm,"Create XML $new");
|
||||
}
|
||||
|
||||
# remove snapshot meta data, images, memory, runxml and NVRAM. for all snapshots.
|
||||
@@ -2040,6 +2087,7 @@ private static $encoding = 'UTF-8';
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $disk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($path) && $action == "yes") if (!$dryrun) unlink("$path") ;else echo "unlink $path\n";
|
||||
if ($logging) qemu_log($vm,"unlink $path");
|
||||
$item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ;
|
||||
$item++ ;
|
||||
while($item > 0)
|
||||
@@ -2047,6 +2095,7 @@ private static $encoding = 'UTF-8';
|
||||
if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ;
|
||||
$newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ;
|
||||
if (is_file($newpath) && $action == "yes") if (!$dryrun) unlink("$newpath"); else echo "unlink $newpath\n";
|
||||
if ($logging) qemu_log($vm,"unlink $newpath");
|
||||
$item++ ;
|
||||
}
|
||||
}
|
||||
@@ -2059,6 +2108,7 @@ private static $encoding = 'UTF-8';
|
||||
$name = $s['name'] ;
|
||||
$oldmethod = $s['method'];
|
||||
if ($dryrun) echo "$name $oldmethod\n";
|
||||
if ($logging) qemu_log($vm,"$name $oldmethod");
|
||||
if (!isset($primarypath)) $primarypath = $s['primarypath'];
|
||||
$xmlfile = $primarypath."/$name.running" ;
|
||||
$memoryfile = $primarypath."/memory$name.mem" ;
|
||||
@@ -2070,6 +2120,7 @@ private static $encoding = 'UTF-8';
|
||||
if ($olddiskname == "hda" || $olddiskname == "hdb") continue ;
|
||||
$oldpath = $olddisk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($oldpath) && $action == "yes") if (!$dryrun) unlink("$oldpath"); else echo "$oldpath\n";
|
||||
if ($logging) qemu_log($vm,"unlink $oldpath");
|
||||
}
|
||||
}
|
||||
if ($oldmethod == "ZFS") {
|
||||
@@ -2077,6 +2128,7 @@ private static $encoding = 'UTF-8';
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name -r ".transpose_user_path($primarypath))) ;
|
||||
$fssnapcmd = " zfs destroy $zfsdataset@$name";
|
||||
if (!$dryrun) shell_exec($fssnapcmd); else echo "old $fssnapcmd\n";
|
||||
if ($logging) qemu_log($vm,"old $fssnapcmd");
|
||||
}
|
||||
|
||||
#Delete Metadata
|
||||
@@ -2084,10 +2136,12 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
if (is_file($memoryfile) && $action == "yes") if (!$dryrun) unlink($memoryfile); else echo ("$memoryfile \n") ;
|
||||
if (is_file($xmlfile) && $action == "yes") if (!$dryrun) unlink($xmlfile); else echo ("$xmlfile \n") ;
|
||||
if ($logging) qemu_log($vm,"mem $memoryfile xml $xmlfile");
|
||||
# Delete NVRAM
|
||||
if (!empty($lv->domain_get_ovmf($res)) && $action == "yes") if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; else echo "Remove old NV\n";
|
||||
if ($actionmeta == "yes") {
|
||||
if (!$dryrun) $ret = delete_snapshots_database("$vm","$name"); else echo "Old Delete snapshot meta\n";
|
||||
if ($logging) qemu_log($vm,"Old Delete snapshot meta");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2100,8 +2154,10 @@ private static $encoding = 'UTF-8';
|
||||
}
|
||||
$fssnapcmd = " zfs rollback $zfsdataset@$snap";
|
||||
if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
|
||||
if ($logging) qemu_log($vm,"$fssnapcmd");
|
||||
$fssnapcmd = " zfs destroy $zfsdataset@$snap";
|
||||
if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
|
||||
if ($logging) qemu_log($vm,"$fssnapcmd");
|
||||
}
|
||||
|
||||
if ($snapslist[$snap]['state'] == "running" || $snapslist[$snap]['state'] == "disk-snapshot") {
|
||||
@@ -2113,13 +2169,17 @@ private static $encoding = 'UTF-8';
|
||||
$xml = custom::createXML('domain',$xmlobj)->saveXML();
|
||||
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
|
||||
if (!$dryrun) $rtn = $lv->domain_define($xml) ;
|
||||
if ($logging) qemu_log($vm,"Define XML");
|
||||
|
||||
|
||||
# Restore Memory.
|
||||
if ($snapslist[$snap]['state'] == "running") {
|
||||
if (!$dryrun) $cmdrtn = exec("virsh restore --running ".escapeshellarg($memoryfile)) ;
|
||||
if ($logging) qemu_log($vm,"Restore");
|
||||
if (!$dryrun && !$cmdrtn) unlink($xmlfile);
|
||||
if ($logging) qemu_log($vm,"Unlink XML");
|
||||
if (!$dryrun && !$cmdrtn) unlink($memoryfile);
|
||||
if ($logging) qemu_log($vm,"Unlink memoryfile");
|
||||
}
|
||||
if ($snapslist[$snap]['state'] == "disk-snapshot") if (!$dryrun) unlink($xmlfile);
|
||||
}
|
||||
@@ -2132,12 +2192,14 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
if ($actionmeta == "yes") {
|
||||
if (!$dryrun) $ret = delete_snapshots_database("$vm","$snap"); else echo "Delete snapshot meta\n";
|
||||
if ($logging) qemu_log($vm,"Delete Snapshot DB entry");
|
||||
}
|
||||
|
||||
if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$snap) ; else echo "Delete NV $vm,$snap\n";
|
||||
|
||||
$arrResponse = ['success' => true] ;
|
||||
if ($dryrun) var_dump($arrResponse);
|
||||
if ($logging) qemu_log($vm, "Success");
|
||||
return($arrResponse) ;
|
||||
}
|
||||
|
||||
@@ -2208,7 +2270,7 @@ private static $encoding = 'UTF-8';
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = array() ;
|
||||
exec("qemu-img info --backing-chain -U $file | grep image:",$output) ;
|
||||
exec("qemu-img info --backing-chain -U \"$file\" | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
@@ -2696,4 +2758,12 @@ function build_xml_templates($strXML) {
|
||||
return $xml2;
|
||||
}
|
||||
|
||||
function qemu_log($vm,$m) {
|
||||
$m = print_r($m,true);
|
||||
$m = date("YmdHis")." ".$m;
|
||||
$m = str_replace("\n", " ", $m);
|
||||
$m = str_replace('"', "'", $m);
|
||||
file_put_contents("/var/log/libvirt/qemu/$vm.log",$m."\n",FILE_APPEND);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -78,7 +78,7 @@ while (true) {
|
||||
$echodata .= "<tr><td>$vm</td>" ;
|
||||
$echodata .= "<td class='advanced'><span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'></span><span></span></div></td>";
|
||||
$echodata .= "<td class='advanced'><span class='cpuh-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpuh-".$vm."' style='width:".$vmdata['cpuhost']."%;'></span><span></span></div></td><td>";
|
||||
$echodata .= my_scale($vmdata['mem'],$unit)."$unit / ".my_scale($vmdata['maxmem'],$unit)."$unit</td><td>";
|
||||
$echodata .= my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['maxmem']*1024,$unit)."$unit</td><td>";
|
||||
$echodata .= _("Read").": ".my_scale($vmdata['rdrate'],$unit)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate'],$unit)."$unit/s</td><td>";
|
||||
$echodata .= _("RX").": ".my_scale($vmdata['rxrate'],$unit)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate'],$unit)."$unit/s</td></tr>";
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
'protocol' => 'vnc',
|
||||
'autoport' => 'yes',
|
||||
'model' => 'qxl',
|
||||
'keymap' => 'en-us',
|
||||
'keymap' => 'none',
|
||||
'port' => -1 ,
|
||||
'wsport' => -1,
|
||||
'copypaste' => 'no'
|
||||
@@ -721,7 +721,6 @@
|
||||
if (strpos($domain_cfg['DOMAINDIR'], dirname(dirname($arrDisk['new']))) === false ||
|
||||
basename(dirname($arrDisk['new'])) != $arrConfig['domain']['name'] || (
|
||||
basename($arrDisk['new']) != 'vdisk'.($i+1).'.img') && basename($arrDisk['new']) != 'vdisk'.($i+1).'.qcow2') {
|
||||
if ($arrDisk['driver'] == "qcow2" && (basename($arrDisk['new']) == 'vdisk'.($i+1).'.qcow2')) $default_option = "auto"; else
|
||||
$default_option = 'manual';
|
||||
}
|
||||
if (file_exists(dirname(dirname($arrDisk['new'])).'/'.$arrConfig['domain']['name'].'/vdisk'.($i+1).'.img') || file_exists(dirname(dirname($arrDisk['new'])).'/'.$arrConfig['domain']['name'].'/vdisk'.($i+1).'.qcow2')) {
|
||||
@@ -1627,6 +1626,141 @@
|
||||
<p>Windows and Hyperv Hpet:no Hypervclock: yes Pit no rtc no. </p>
|
||||
</p>
|
||||
</blockquote>
|
||||
<?
|
||||
if ( $arrConfig['evdev'] == false) {
|
||||
|
||||
$evdevxml = "<input type='evdev'>
|
||||
<source dev=''/>
|
||||
</input>";
|
||||
$evdevdoc = new SimpleXMLElement($evdevxml);
|
||||
$arrConfig['evdev']= $evdevdoc->xpath('//input[@type="evdev"] ');
|
||||
}
|
||||
|
||||
foreach ($arrConfig['evdev'] as $i => $arrEvdev) {
|
||||
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
|
||||
?>
|
||||
<table data-category="evdev" data-multiple="true" data-minimum="1" data-index="<?=$i?>" data-prefix="<?=$strLabel?>">
|
||||
<tr>
|
||||
<td>_(Evdev Device)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][dev]" class="dev narrow">
|
||||
<?
|
||||
echo mk_option($arrEvdev->source->attributes()->dev, '', _('None'));
|
||||
foreach(getValidevDev() as $line) echo mk_option($arrEvdev->source->attributes()->dev, $line , $line);
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][grab]" class="evdev_grab" title="_(grab options)_">
|
||||
<?echo mk_option($arrEvdev->source->attributes()->grab, '', _('None'));
|
||||
foreach(["all"] as $line) echo mk_option($arrEvdev->source->attributes()->grab,$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Repeat)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][repeat]" class="evdev_repeat narrow" title="_(grab options)_">
|
||||
<?echo mk_option($arrEvdev->source->attributes()->repeat, '', _('None'));
|
||||
foreach(["on","off"] as $line) echo mk_option($arrEvdev->source->attributes()->repeat,$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab Toggle)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][grabToggle]" class="evdev_grabtoggle narrow" title="_(grab options)_">
|
||||
<?echo mk_option($arrEvdev->source->attributes()->grabToggle, '', _('None'));
|
||||
foreach(["ctrl-ctrl", "alt-alt", "shift-shift", "meta-meta", "scrolllock" , "ctrl-scrolllock"] as $line) echo mk_option($arrEvdev->source->attributes()->grabToggle,$line,$line);?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
</table>
|
||||
<?if ($i == 0) {?>
|
||||
<div class="advanced">
|
||||
<blockquote class="inline_help">
|
||||
<p>
|
||||
<b> Event Devices</b><br>
|
||||
Evdev is an input interface built into the Linux kernel. QEMU’s evdev passthrough support allows a user to redirect evdev events to a guest. These events can include mouse movements and key presses. By hitting both Ctrl keys at the same time, QEMU can toggle the input recipient. QEMU’s evdev passthrough also features almost no latency, making it perfect for gaming. The main downside to evdev passthrough is the lack of button rebinding – and in some cases, macro keys won’t even work at all.
|
||||
Optional items are normally only used for keyboards.
|
||||
</p>
|
||||
<p>
|
||||
<b>Device</b><br>
|
||||
Host device to passthrough to guest.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Grab</b><br>
|
||||
All grabs all input devices instead of just one
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Repeat</b><br>
|
||||
Repeat with value 'on'/'off' to enable/disable auto-repeat events
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>GrabToggle</b><br>
|
||||
GrabToggle with values ctrl-ctrl, alt-alt, shift-shift, meta-meta, scrolllock or ctrl-scrolllock to change the grab key combination</p>
|
||||
|
||||
<p>Additional devices can be added/removed by clicking the symbols to the left.</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
<?}?>
|
||||
<?}?>
|
||||
<script type="text/html" id="tmplevdev">
|
||||
<table data-category="evdev" data-multiple="true" data-minimum="1" data-index="<?=$i?>" data-prefix="<?=$strLabel?>">
|
||||
<tr>
|
||||
<td>_(Evdev Device)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][dev]" class="dev narrow">
|
||||
<?
|
||||
echo mk_option("", '', _('None'));
|
||||
foreach(getValidevDev() as $line) echo mk_option("", $line , $line);
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][grab]" class="evdev_grab" title="_(grab options)_">
|
||||
<?echo mk_option("" , '', _('None'));
|
||||
foreach(["all"] as $line) echo mk_option("",$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Repeat)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][repeat]" class="evdev_repeat narrow" title="_(grab options)_">
|
||||
<?echo mk_option("", '', _('None'));
|
||||
foreach(["on","off"] as $line) echo mk_option("",$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab Toggle)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][grabToggle]" class="evdev_grabtoggle narrow" title="_(grab options)_">
|
||||
<?echo mk_option("", '', _('None'));
|
||||
foreach(["ctrl-ctrl", "alt-alt", "shift-shift", "meta-meta", "scrolllock" , "ctrl-scrolllock"] as $line) echo mk_option("",$line,$line);?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -1913,8 +2047,8 @@ $(function() {
|
||||
$('.advancedview').change(function () {
|
||||
if ($(this).is(':checked')) {
|
||||
setTimeout(function() {
|
||||
var xmlPanelHeight = window.outerHeight - 550;
|
||||
if (xmlPanelHeight < 0) xmlPanelHeight = null;
|
||||
var xmlPanelHeight = window.outerHeight;
|
||||
if (xmlPanelHeight > 1024) xmlPanelHeight = xmlPanelHeight-550;
|
||||
editor.setSize(null,xmlPanelHeight);
|
||||
editor.refresh();
|
||||
}, 100);
|
||||
|
||||
@@ -189,10 +189,10 @@ _(Slots)_:
|
||||
<input type="hidden" name="poolName" value="">
|
||||
_(Name)_:
|
||||
: <select name="subpool">
|
||||
<?=mk_option("","special",_("special - Special Allocation Class"))?>
|
||||
<?=mk_option("","logs",_("logs - ZFS Intent Log"))?>
|
||||
<?=mk_option("","special",_("special - Metadata storage"))?>
|
||||
<?=mk_option("","logs",_("logs - Separate Intent Log (SLOG)"))?>
|
||||
<?=mk_option("","dedup",_("dedup - Deduplication Tables"))?>
|
||||
<?=mk_option("","cache",_("cache - Cache Devices"))?>
|
||||
<?=mk_option("","cache",_("cache - L2ARC"))?>
|
||||
<?=mk_option("","spares",_("spares - Hot Spares"))?>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ $prev = $i>0 ? $sheets[$i-1] : $sheets[$end];
|
||||
$next = $i<$end ? $sheets[$i+1] : $sheets[0];
|
||||
$text = isPool($name) ? _('This will ERASE content of ALL devices in the pool') : _('This will ERASE ALL device content');
|
||||
|
||||
function disabled_if($condition) {
|
||||
if ($condition !== false) echo ' disabled';
|
||||
}
|
||||
function sanitize(&$val) {
|
||||
$data = explode('.',str_replace([' ',','],['','.'],$val));
|
||||
$last = array_pop($data);
|
||||
@@ -610,33 +613,33 @@ _(Spin down delay)_:
|
||||
_(File system status)_:
|
||||
: <?=_(_var($disk,'fsStatus'))?>
|
||||
|
||||
<?$disabled = (_var($var,'fsState')=="Stopped" && !empty(_var($disk,'uuid'))) || (_var($var,'fsState')=="Started" && _var($disk,'fsType')!='auto') ? "disabled" : ""?>
|
||||
<?$fsTypeImmutable = (_var($var,'fsState')=="Stopped" && !empty(_var($disk,'uuid'))) || (_var($var,'fsState')=="Started" && _var($disk,'fsType')!='auto')?>
|
||||
<?if (diskType('Data') || (!isSubpool($name) && _var($disk,'slots',0)==1)):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="changeFsType()" <?=$disabled?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="changeFsType()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "auto", _('auto'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "xfs", _('xfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "btrfs", _('btrfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "reiserfs", _('reiserfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "reiserfs", _('reiserfs'), "disabled")?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:xfs", _('xfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:zfs", _('zfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:btrfs", _('btrfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:reiserfs", _('reiserfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:reiserfs", _('reiserfs')." - "._('encrypted'), "disabled")?>
|
||||
</select><span id="reiserfs" class="warning"<?if (!fsType('reiserfs')):?> style="display:none"<?endif;?>><i class="fa fa-warning"></i> _(ReiserFS is deprecated, please use another file system)_!</span>
|
||||
|
||||
:info_file_system_help:
|
||||
|
||||
<?elseif (!isSubpool($name) && _var($disk,'slots',0)>1):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "auto", _('auto'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "btrfs", _('btrfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:zfs", _('zfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:btrfs", _('btrfs')." - "._('encrypted'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileBTRFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
<select id="diskFsProfileBTRFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsProfile'),"single", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"raid0", _('raid0'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"raid1", _('raid1'))?>
|
||||
@@ -646,42 +649,42 @@ _(File system type)_:
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raid5", _('raid5'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid6", _('raid6'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" onchange="selectDiskFsWidth()" <?=$disabled?>>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" onchange="selectDiskFsWidth()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('raid0'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('stripe'))?>
|
||||
<?if ((_var($disk,'slots',0)%2)==0 || (_var($disk,'slots',0)%3)==0 || (_var($disk,'slots',0)%4)==0) echo mk_option(_var($disk,'fsProfile'),"mirror", _('mirror'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raidz1", _('raidz'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raidz2", _('raidz2'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raidz3", _('raidz3'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="special" || isSubpool($name)=="logs" || isSubpool($name)=="dedup"):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=$disabled?>>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('raid0'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('stripe'))?>
|
||||
<?if ((_var($disk,'slots',0)%2)==0 || (_var($disk,'slots',0)%3)==0 || (_var($disk,'slots',0)%4)==0) echo mk_option(_var($disk,'fsProfile'),"mirror", _('mirror'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="cache"):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=$disabled?>>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('raid0'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('stripe'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="spares"):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<?endif;?>
|
||||
@@ -689,8 +692,7 @@ _(File system type)_:
|
||||
<?if (isSubpool($name)===false):?>
|
||||
<div markdown="1" id="compression" style="display:none">
|
||||
_(Compression)_:
|
||||
<?$disabled = _var($disk,'fsStatus')=='Mounted' ? "disabled" : ""?>
|
||||
: <select id="diskCompression" name="diskCompression.<?=_var($disk,'idx',0)?>" <?=$disabled?>>
|
||||
: <select id="diskCompression" name="diskCompression.<?=_var($disk,'idx',0)?>" <?=disabled_if(_var($disk,'fsStatus')=='Mounted')?>>
|
||||
<?=mk_option(_var($disk,'compression'), "off", _('Off'))?>
|
||||
<?=mk_option(_var($disk,'compression'), "on", _('On'))?>
|
||||
</select>
|
||||
@@ -700,8 +702,7 @@ _(Compression)_:
|
||||
|
||||
<div markdown="1" id="autotrim" style="display:none">
|
||||
_(Autotrim)_:
|
||||
<?$disabled = _var($disk,'fsStatus')=='Mounted' ? "disabled" : ""?>
|
||||
: <select id="diskAutotrim" name="diskAutotrim.<?=_var($disk,'idx',0)?>" <?=$disabled?>>
|
||||
: <select id="diskAutotrim" name="diskAutotrim.<?=_var($disk,'idx',0)?>" <?=disabled_if(_var($disk,'fsStatus')=='Mounted')?>>
|
||||
<?=mk_option(_var($disk,'autotrim'), "on", _('On'))?>
|
||||
<?=mk_option(_var($disk,'autotrim'), "off", _('Off'))?>
|
||||
</select>
|
||||
@@ -710,8 +711,7 @@ _(Autotrim)_:
|
||||
</div>
|
||||
<?if (isPool($name)):?>
|
||||
_(Enable user share assignment)_:
|
||||
<?$disabled = _var($var,'fsState')!="Stopped" ? "disabled" : ""?>
|
||||
: <select id="shareEnabled" name="diskShareEnabled.<?=_var($disk,'idx',0)?>" onchange="freeSpace(this.value)" <?=$disabled?>>
|
||||
: <select id="shareEnabled" name="diskShareEnabled.<?=_var($disk,'idx',0)?>" onchange="freeSpace(this.value)" <?=disabled_if(_var($var,'fsState')!="Stopped")?>>
|
||||
<?=mk_option(_var($disk,'shareEnabled'), "yes", _('Yes'))?>
|
||||
<?=mk_option(_var($disk,'shareEnabled'), "no", _('No'))?>
|
||||
</select>
|
||||
|
||||
@@ -159,11 +159,11 @@ _(Default file system)_:
|
||||
<?=mk_option($var['defaultFsType'], "xfs", _('xfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "zfs", _('zfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "btrfs", _('btrfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "reiserfs", _('reiserfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "reiserfs", _('reiserfs'), "disabled");?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:xfs", _('xfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:zfs", _('zfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:btrfs", _('btrfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:reiserfs", _('reiserfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:reiserfs", _('reiserfs')." - "._('encrypted'), "disabled");?>
|
||||
</select>
|
||||
|
||||
:disk_default_file_system_help:
|
||||
|
||||
@@ -91,69 +91,78 @@ function initDropdown() {
|
||||
</form>
|
||||
<?
|
||||
$fields = ['Event','Subject','Timestamp','Description','Importance','Content','Link'];
|
||||
$xml_file = "webGui/include/NotificationAgents.xml";
|
||||
$xml = @simplexml_load_file($xml_file) or die(_("Failed to open")." $xml_file");
|
||||
$xml_files = glob("/usr/local/emhttp/plugins/dynamix/agents/*.xml");
|
||||
$i = 1;
|
||||
foreach ($xml->Agent as $agent) {
|
||||
$name = str_replace(' ','_',$agent->Name);
|
||||
$enabledAgent = agent_fullname("$name.sh", "enabled");
|
||||
$disabledAgent = agent_fullname("$name.sh", "disabled");
|
||||
if (is_file($disabledAgent)) {
|
||||
$file = $disabledAgent;
|
||||
if (is_file($enabledAgent)) unlink($enabledAgent);
|
||||
} else {
|
||||
$file = $enabledAgent;
|
||||
}
|
||||
$values = [];
|
||||
$script = "";
|
||||
if (is_file($file)) {
|
||||
preg_match("/[#]{6,100}([^#]*?)[#]{6,100}/si", file_get_contents($file), $match);
|
||||
if (isset($match[1])) {
|
||||
foreach (explode(PHP_EOL, $match[1]) as $line) {
|
||||
if (strpos($line, "=")) {
|
||||
[$k, $v] = my_explode("=",str_replace("\"", "", $line),2);
|
||||
$values[$k] = $v;
|
||||
foreach ($xml_files as $xml_file) {
|
||||
$xml = @simplexml_load_file($xml_file);
|
||||
if ( ! $xml ) continue;
|
||||
|
||||
if ( isset($xml->Language) ) {
|
||||
$guiLanguage = ($locale == "" ) ? "en_US" : $locale;
|
||||
$acceptedLanguages = explode(" ",$xml->Language);
|
||||
if ( ! in_array($guiLanguage,$acceptedLanguages) )
|
||||
continue;
|
||||
}
|
||||
$name = str_replace(' ','_',$xml->Name);
|
||||
$enabledAgent = agent_fullname("$name.sh", "enabled");
|
||||
$disabledAgent = agent_fullname("$name.sh", "disabled");
|
||||
if (is_file($disabledAgent)) {
|
||||
$file = $disabledAgent;
|
||||
if (is_file($enabledAgent)) unlink($enabledAgent);
|
||||
} else {
|
||||
$file = $enabledAgent;
|
||||
}
|
||||
$values = [];
|
||||
$script = "";
|
||||
if (is_file($file)) {
|
||||
preg_match("/[#]{6,100}([^#]*?)[#]{6,100}/si", file_get_contents($file), $match);
|
||||
if (isset($match[1])) {
|
||||
foreach (explode(PHP_EOL, $match[1]) as $line) {
|
||||
if (strpos($line, "=")) {
|
||||
[$k, $v] = my_explode("=",str_replace("\"", "", $line),2);
|
||||
$values[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (explode(PHP_EOL,(String) $agent->Script) as $line) if (trim($line)) $script .= trim($line)."{1}";
|
||||
echo '<div class="title shift"><span class="left"><img src="/plugins/dynamix/icons/'.strtolower(str_replace('_','',$name)).'.png" class="icon" style="height:16px;width:16px;">'.str_replace('_',' ',$name).'</span><span class="status vhshift">'.(is_file($enabledAgent) ? '<span class="green">'._("Enabled").'</span>' : '<span class="red">'._("Disabled").'</span>').'</span></div>';
|
||||
echo '<form method="POST" name="'.$name.'" action="/update.php" target="progressFrame">';
|
||||
echo '<input type="hidden" name="#include" value="/webGui/include/update.file.php">';
|
||||
echo '<input type="hidden" name="#file" value="'.$file.'">';
|
||||
echo '<input type="hidden" name="#command" value="/webGui/scripts/agent">';
|
||||
echo '<input type="hidden" name="#arg[1]" value="">';
|
||||
echo '<input type="hidden" name="#arg[2]" value="">';
|
||||
echo '<input type="hidden" name="text" value="">';
|
||||
echo '<dl><dt>'._("Agent function").':</dt><dd><select name="Enabled">';
|
||||
echo mk_option(is_file($disabledAgent), 'no', _('Disabled'));
|
||||
echo mk_option(is_file($enabledAgent), 'yes', _('Enabled'));
|
||||
echo '</select></dd></dl>';
|
||||
echo '<script>scripts["'.$name.'"]='.json_encode($script).';enabledAgents["'.$name.'"]="'.$enabledAgent.'";disabledAgents["'.$name.'"]="'.$disabledAgent.'";</script>';
|
||||
foreach ($agent->Variables->children() as $v) {
|
||||
$vName = preg_replace('#\[([^\]]*)\]#', '<$1>', (string) $v);
|
||||
$vDesc = ucfirst(strtolower(preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Desc)));
|
||||
$vDefault = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Default);
|
||||
$vHelp = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Help);
|
||||
echo "<dl><dt>$vDesc:</dt><dd>";
|
||||
if (preg_match('/title|message/', $vDesc)) {
|
||||
echo '<select id="slot_'.$i++.'" name="'.$vName.'" multiple style="display:none">';
|
||||
$value = str_replace('\n',',',isset($values[$vName]) ? $values[$vName] : $vDefault);
|
||||
foreach ($fields as $field) echo mk_option_check($value,'$'.strtoupper($field),_($field));
|
||||
echo '</select>';
|
||||
} else {
|
||||
echo '<input type="text" name="'.$vName.'" class="variable" required value="'.( isset($values[$vName]) ? $values[$vName] : $vDefault ).'">';
|
||||
foreach (explode(PHP_EOL,(String) $xml->Script) as $line) if (trim($line)) $script .= trim($line)."{1}";
|
||||
echo '<div class="title shift"><span class="left"><img src="/plugins/dynamix/icons/'.strtolower(str_replace('_','',$name)).'.png" class="icon" style="height:16px;width:16px;">'.str_replace('_',' ',$name).'</span><span class="status vhshift">'.(is_file($enabledAgent) ? '<span class="green">'._("Enabled").'</span>' : '<span class="red">'._("Disabled").'</span>').'</span></div>';
|
||||
echo '<form method="POST" name="'.$name.'" action="/update.php" target="progressFrame">';
|
||||
echo '<input type="hidden" name="#include" value="/webGui/include/update.file.php">';
|
||||
echo '<input type="hidden" name="#file" value="'.$file.'">';
|
||||
echo '<input type="hidden" name="#command" value="/webGui/scripts/agent">';
|
||||
echo '<input type="hidden" name="#arg[1]" value="">';
|
||||
echo '<input type="hidden" name="#arg[2]" value="">';
|
||||
echo '<input type="hidden" name="text" value="">';
|
||||
echo '<dl><dt>'._("Agent function").':</dt><dd><select name="Enabled">';
|
||||
echo mk_option(is_file($disabledAgent), 'no', _('Disabled'));
|
||||
echo mk_option(is_file($enabledAgent), 'yes', _('Enabled'));
|
||||
echo '</select></dd></dl>';
|
||||
echo '<script>scripts["'.$name.'"]='.json_encode($script).';enabledAgents["'.$name.'"]="'.$enabledAgent.'";disabledAgents["'.$name.'"]="'.$disabledAgent.'";</script>';
|
||||
foreach ($xml->Variables->children() as $v) {
|
||||
$vName = preg_replace('#\[([^\]]*)\]#', '<$1>', (string) $v);
|
||||
$vDesc = ucfirst(strtolower(preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Desc)));
|
||||
$vDefault = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Default);
|
||||
$vHelp = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Help);
|
||||
echo "<dl><dt>$vDesc:</dt><dd>";
|
||||
if (preg_match('/title|message/', $vDesc)) {
|
||||
echo '<select id="slot_'.$i++.'" name="'.$vName.'" multiple style="display:none">';
|
||||
$value = str_replace('\n',',',isset($values[$vName]) ? $values[$vName] : $vDefault);
|
||||
foreach ($fields as $field) echo mk_option_check($value,'$'.strtoupper($field),_($field));
|
||||
echo '</select>';
|
||||
} else {
|
||||
echo '<input type="text" name="'.$vName.'" class="variable" required value="'.( isset($values[$vName]) ? $values[$vName] : $vDefault ).'">';
|
||||
}
|
||||
echo '</dd></dl>';
|
||||
if ($vHelp) echo '<blockquote class="inline_help">'.$vHelp.'</blockquote>';
|
||||
}
|
||||
echo '</dd></dl>';
|
||||
if ($vHelp) echo '<blockquote class="inline_help">'.$vHelp.'</blockquote>';
|
||||
}
|
||||
echo '<dl><dt> </dt><dd><input type="submit" value='._("Apply").' onclick="prepareService(this, \''.$name.'\')" disabled>';
|
||||
echo '<input type="button" value='._("Done").' onclick="done()">';
|
||||
if (is_file($file)) {
|
||||
echo '<input type="button" value='._("Delete").' onclick="execCmd(\'delete\',\''.$name.'\')">';
|
||||
echo '<input type="button" value='._("Test").' onclick="testService(\''.$name.'\')"'.($file==$enabledAgent ? '>' : ' disabled>');
|
||||
}
|
||||
echo '</dd></dl></form><div style="min-height:50px;"></div>';
|
||||
echo '<dl><dt> </dt><dd><input type="submit" value='._("Apply").' onclick="prepareService(this, \''.$name.'\')" disabled>';
|
||||
echo '<input type="button" value='._("Done").' onclick="done()">';
|
||||
if (is_file($file)) {
|
||||
echo '<input type="button" value='._("Delete").' onclick="execCmd(\'delete\',\''.$name.'\')">';
|
||||
echo '<input type="button" value='._("Test").' onclick="testService(\''.$name.'\')"'.($file==$enabledAgent ? '>' : ' disabled>');
|
||||
}
|
||||
echo '</dd></dl></form><div style="min-height:50px;"></div>';
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -17,6 +17,10 @@ Cond="(($var['shareNFSEnabled']!='no') && (isset($name)?array_key_exists($name,$
|
||||
?>
|
||||
<?
|
||||
$width = [123,300];
|
||||
|
||||
/* Replace spaces in NFS rule with new lines for multi line textarea. */
|
||||
$sec_nfs[$name]['hostList'] = str_replace(" ", "\n", $sec_nfs[$name]['hostList']);
|
||||
|
||||
?>
|
||||
:nfs_security_help:
|
||||
|
||||
@@ -73,10 +77,10 @@ _(Security)_:
|
||||
</form>
|
||||
|
||||
<?if ($sec_nfs[$name]['security']=='private'):?>
|
||||
<form markdown="1" method="POST" name="otherForm" action="/update.htm" target="progressFrame">
|
||||
<form id="nfsHostListForm" markdown="1" method="POST" name="otherForm" action="/update.htm" target="progressFrame">
|
||||
<input type="hidden" name="shareName" value="<?=htmlspecialchars($name)?>">
|
||||
_(Rule)_:
|
||||
: <input type="text" name="shareHostListNFS" maxlength="512" value="<?=htmlspecialchars($sec_nfs[$name]['hostList'])?>">
|
||||
: <textarea name="shareHostListNFS" cols="40" rows="5" style="width:45%" placeholder="Example: *(rw,sec=sys,insecure,anongid=100,anonuid=99,no_root_squash,lock)"><?= htmlspecialchars($sec_nfs[$name]['hostList']) ?></textarea>
|
||||
|
||||
|
||||
: <input type="submit" name="changeShareAccessNFS" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
|
||||
@@ -85,58 +89,140 @@ _(Rule)_:
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
initDropdownNFS(false);
|
||||
if ($.cookie('hostList')!=null) {
|
||||
var host = $('input[name="shareHostListNFS"]');
|
||||
host.val($.cookie('hostList'));
|
||||
setTimeout(function(){host.trigger('change');},100);
|
||||
$.removeCookie('hostList');
|
||||
}
|
||||
<?if ($tabbed):?>
|
||||
<?$path=='Shares/Share' ? $t=2 : $t=1;?>
|
||||
$('#tab<?=$t?>').bind({click:function(){initDropdownNFS(true);}});
|
||||
<?endif;?>
|
||||
/* Initialize dropdown for NFS and check for hostList cookie. */
|
||||
initDropdownNFS(false);
|
||||
if ($.cookie('hostList') != null) {
|
||||
var host = $('input[name="shareHostListNFS"]');
|
||||
host.val($.cookie('hostList'));
|
||||
setTimeout(function() {
|
||||
host.trigger('change');
|
||||
}, 100);
|
||||
$.removeCookie('hostList');
|
||||
}
|
||||
|
||||
<?if ($tabbed):?>
|
||||
/* Conditionally bind click event to tabs if tabbed interface is used. */
|
||||
<?$path=='Shares/Share' ? $t=2 : $t=1;?>
|
||||
$('#tab<?=$t?>').bind({click:function() {
|
||||
initDropdownNFS(true);
|
||||
}});
|
||||
<?endif;?>
|
||||
});
|
||||
|
||||
/* Add an event listener to update the text area to make all rules into a single line before being submitted. */
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var form = document.getElementById('nfsHostListForm');
|
||||
form.addEventListener('submit', function(event) {
|
||||
var textarea = document.querySelector('textarea[name="shareHostListNFS"]');
|
||||
|
||||
/* Split the content into lines. */
|
||||
var lines = textarea.value.split('\n');
|
||||
|
||||
/* Filter out empty lines or lines that contain only whitespace, and remove carriage returns and excessive spaces within lines. */
|
||||
var cleanedLines = lines.map(function(line) {
|
||||
/* Remove carriage returns and spaces within each line. */
|
||||
return line.replace(/[\r\s]+/g, '');
|
||||
}).filter(function(line) {
|
||||
/* Keep only non-empty lines. */
|
||||
return line.length > 0;
|
||||
});
|
||||
|
||||
/* Join the remaining lines with a single space. */
|
||||
textarea.value = cleanedLines.join(' ');
|
||||
});
|
||||
});
|
||||
|
||||
/* Function to initialize or reset the NFS dropdown */
|
||||
function initDropdownNFS(reset) {
|
||||
if (reset) {
|
||||
$('#nfs1').dropdownchecklist('destroy');
|
||||
}
|
||||
$("#nfs1").dropdownchecklist({firstItemChecksAll:true, emptyText:"_(select)_...", width:<?=$width[0]?>, explicitClose:"..._(close)_"});
|
||||
/* Check if reset is required and destroy existing dropdown if true */
|
||||
if (reset) {
|
||||
$('#nfs1').dropdownchecklist('destroy');
|
||||
}
|
||||
/* Initialize or re-initialize the dropdown with specified options */
|
||||
$("#nfs1").dropdownchecklist({
|
||||
firstItemChecksAll: true,
|
||||
emptyText: "_(select)_...",
|
||||
width: <?=$width[0]?>,
|
||||
explicitClose: "..._(close)_"
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to read NFS configuration based on selected options and copy to this share. */
|
||||
function readNFS() {
|
||||
var form = document.nfs_edit;
|
||||
var name = $('select[name="readnfs"]').val();
|
||||
$.get('/webGui/include/ProtocolData.php',{protocol:'nfs',name:name},function(json) {
|
||||
var data = $.parseJSON(json);
|
||||
form.shareExportNFS.value = data.export;
|
||||
form.shareSecurityNFS.value = data.security;
|
||||
if (data.hostList != '') $.cookie('hostList',data.hostList);
|
||||
$(form).find('select').trigger('change');
|
||||
});
|
||||
/* Access the form for NFS editing */
|
||||
var form = document.nfs_edit;
|
||||
|
||||
/* Retrieve selected NFS name from the dropdown */
|
||||
var name = $('select[name="readnfs"]').val();
|
||||
|
||||
/* Perform a GET request to fetch NFS configuration data */
|
||||
$.get('/webGui/include/ProtocolData.php', {protocol: 'nfs', name: name}, function(json) {
|
||||
/* Parse the JSON response */
|
||||
var data = $.parseJSON(json);
|
||||
var textarea = $('textarea[name="shareHostListNFS"]');
|
||||
|
||||
/* Update form fields with fetched data */
|
||||
form.shareExportNFS.value = data.export;
|
||||
form.shareSecurityNFS.value = data.security;
|
||||
|
||||
/* Check if hostList is not empty and save it in a cookie */
|
||||
if (data.hostList != '') {
|
||||
$.cookie('hostList', data.hostList);
|
||||
}
|
||||
|
||||
/* Replace all spaces in data.hostList with new lines. */
|
||||
var formattedHostList = data.hostList.replace(/ /g, '\n');
|
||||
|
||||
/* Update textarea content. Use data from 'hostList'. */
|
||||
textarea.val(formattedHostList);
|
||||
|
||||
/* Trigger change event on select elements to update UI */
|
||||
$(form).find('select').trigger('change');
|
||||
|
||||
/* Trigger an input event as if the user had typed in the textarea. */
|
||||
textarea.trigger('input');
|
||||
});
|
||||
}
|
||||
function writeNFS(data,n,i) {
|
||||
if (data) {
|
||||
if (n<i) {
|
||||
$.post('/update.htm',data[n], function(){setTimeout(function(){writeNFS(data,++n,i);},3000);});
|
||||
} else {
|
||||
toggleButton('writenfs',false);
|
||||
$('div.spinner.fixed').hide();
|
||||
}
|
||||
} else {
|
||||
var data = [], i = 0;
|
||||
$('select#nfs1 option').map(function(i) {
|
||||
if ($(this).prop('selected')==true && $(this).val()!='(_(All)_)') {
|
||||
data[i] = {};
|
||||
data[i]['shareName'] = $(this).val();
|
||||
data[i]['shareExportNFS'] = '<?=addslashes(htmlspecialchars($sec_nfs[$name]['export']))?>';
|
||||
data[i]['shareSecurityNFS'] = '<?=addslashes(htmlspecialchars($sec_nfs[$name]['security']))?>';
|
||||
data[i]['changeShareSecurityNFS'] = 'Apply';
|
||||
i++;
|
||||
}
|
||||
});
|
||||
toggleButton('writenfs',true);
|
||||
$('div.spinner.fixed').show('slow');
|
||||
writeNFS(data,0,i);
|
||||
}
|
||||
|
||||
/* Function to write NFS settings based on user selection to other shares. */
|
||||
function writeNFS(data, n, i) {
|
||||
if (data) {
|
||||
if (n < i) {
|
||||
$.post('/update.htm', data[n], function() {
|
||||
setTimeout(function() { writeNFS(data, ++n, i); }, 3000);
|
||||
});
|
||||
} else {
|
||||
toggleButton('writenfs', false);
|
||||
$('div.spinner.fixed').hide();
|
||||
}
|
||||
} else {
|
||||
var data = [];
|
||||
|
||||
/* Get the setting from the share config. */
|
||||
var hostList = $('textarea[name="shareHostListNFS"]').val().trim();
|
||||
|
||||
/* Replace all new lines in data.hostList with spaces. */
|
||||
var formattedHostList = <?= json_encode($sec_nfs[$name]['hostList']); ?>.replace(/\n/g, ' ');
|
||||
|
||||
$('select#nfs1 option').each(function() {
|
||||
if ($(this).prop('selected') && $(this).val() != '(_(All)_)') {
|
||||
data.push({
|
||||
shareName: $(this).val(),
|
||||
shareExportNFS: '<?=addslashes(htmlspecialchars($sec_nfs[$name]['export']))?>',
|
||||
shareSecurityNFS: '<?=addslashes(htmlspecialchars($sec_nfs[$name]['security']))?>',
|
||||
changeShareSecurityNFS: 'Apply'
|
||||
});
|
||||
|
||||
data.push({
|
||||
shareName: $(this).val(),
|
||||
shareHostListNFS: formattedHostList,
|
||||
changeShareSecurityNFS: 'Apply'
|
||||
});
|
||||
}
|
||||
});
|
||||
toggleButton('writenfs', true);
|
||||
$('div.spinner.fixed').show('slow');
|
||||
writeNFS(data, 0, data.length);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -18,7 +18,7 @@ Tag="share-alt-square"
|
||||
$width = [123,300];
|
||||
|
||||
if ($name == "") {
|
||||
// default values when adding new share
|
||||
/* default values when adding new share. */
|
||||
$share = ["nameOrig" => "",
|
||||
"name" => "",
|
||||
"comment" => "",
|
||||
@@ -32,70 +32,194 @@ if ($name == "") {
|
||||
"cow" => "auto"
|
||||
];
|
||||
} elseif (array_key_exists($name, $shares)) {
|
||||
// edit existing share
|
||||
/* edit existing share. */
|
||||
$share = $shares[$name];
|
||||
} else {
|
||||
// handle share deleted case
|
||||
/* 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 non existent pool device
|
||||
|
||||
/* 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;
|
||||
if (!$val or strcasecmp($val,'NaN')==0) return;
|
||||
sanitize($val);
|
||||
$small = [];
|
||||
foreach (data_filter($disks) as $disk) $small[] = _var($disk,'fsSize');
|
||||
$fsSize[""] = min(array_filter($small));
|
||||
foreach ($pools as $pool) $fsSize[$pool] = _var($disks[$pool],'fsSize',0);
|
||||
$pool = _var($shares[$name],'cachePool');
|
||||
$size = _var($fsSize,$pool,0);
|
||||
$size = $size>0 ? round(100*$val/$size,1) : 0;
|
||||
$units = ['KB','MB','GB','TB','PB','EB','ZB','YB'];
|
||||
$base = $val>0 ? floor(log($val,1000)) : 0;
|
||||
$size = round($val/pow(1000,$base),1);
|
||||
$unit = _var($units,$base);
|
||||
[$dot,$comma] = str_split(_var($display,'number','.,'));
|
||||
return $size>0 ? number_format($size,$size-floor($size)?1:0,$dot,$comma).' '.$unit : '';
|
||||
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');
|
||||
}
|
||||
|
||||
/* Get the maximum value from the large array, filtering out non-numeric values */
|
||||
$fsSize[""] = max(array_filter($large, 'is_numeric'));
|
||||
|
||||
/* Prepare the fsSize array for each pool */
|
||||
foreach ($pools as $pool) {
|
||||
$fsSize[$pool] = _var($disks[$pool], '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;
|
||||
$fsSize = $small = [];
|
||||
foreach (data_filter($disks) as $disk) $small[] = _var($disk,'fsSize');
|
||||
$fsSize[] = '"":"'.min(array_filter($small)).'"';
|
||||
foreach ($pools as $pool) $fsSize[] = '"'.$pool.'":"'._var($disks[$pool],'fsSize',0).'"';
|
||||
return implode(',',$fsSize);
|
||||
global $disks, $pools, $share;
|
||||
|
||||
$fsSize = [];
|
||||
$small = [];
|
||||
|
||||
$enabledDisks = enabledDisks();
|
||||
|
||||
foreach (data_filter($disks) as $key => $disk) {
|
||||
if (in_array($key, $enabledDisks)) {
|
||||
$small[] = _var($disk, 'fsSize');
|
||||
}
|
||||
}
|
||||
|
||||
$fsSize[''] = min(array_filter($small));
|
||||
|
||||
foreach ($pools as $pool) {
|
||||
$fsSize[$pool] = _var($disks[$pool], 'fsSize', 0);
|
||||
}
|
||||
|
||||
return json_encode($fsSize);
|
||||
}
|
||||
|
||||
function fsFree() {
|
||||
global $disks, $pools;
|
||||
|
||||
$fsFree = [];
|
||||
$large = [];
|
||||
|
||||
$enabledDisks = enabledDisks();
|
||||
|
||||
foreach (data_filter($disks) as $key => $disk) {
|
||||
if (in_array($key, $enabledDisks)) {
|
||||
$large[] = _var($disk, 'fsFree');
|
||||
}
|
||||
}
|
||||
|
||||
$fsFree[''] = max(array_filter($large));
|
||||
|
||||
foreach ($pools as $pool) {
|
||||
$fsFree[$pool] = _var($disks[$pool], 'fsFree', 0);
|
||||
}
|
||||
|
||||
return json_encode($fsFree);
|
||||
}
|
||||
|
||||
function fsType() {
|
||||
global $disks,$pools;
|
||||
|
||||
$fsType = [];
|
||||
foreach ($pools as $pool) $fsType[] = '"'.$pool.'":"'.str_replace('luks:','',_var($disks[$pool],'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;
|
||||
return in_array($share['useCache'],['no','only']) ? '0' : '1';
|
||||
}
|
||||
|
||||
function direction() {
|
||||
global $share;
|
||||
return $share['useCache']=='prefer' ? '1' : '0';
|
||||
}
|
||||
// global shares include/exclude
|
||||
|
||||
/* global shares include/exclude. */
|
||||
$myDisks = array_filter(array_diff(array_keys(array_filter($disks,'my_disks')), explode(',',$var['shareUserExclude'])), 'globalInclude');
|
||||
?>
|
||||
:share_edit_global1_help:
|
||||
@@ -334,6 +458,7 @@ _(Delete)_<input type="checkbox" name="confirmDelete" onchange="chkDelete(this.f
|
||||
</div>
|
||||
<?endif;?>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
var form = document.share_edit;
|
||||
|
||||
@@ -351,6 +476,7 @@ $(function() {
|
||||
if ($.cookie('autosize-'+$('#shareName').val())) $('#autosize').show();
|
||||
checkName($('#shareName').val());
|
||||
});
|
||||
|
||||
function initDropdown(remove,create) {
|
||||
if (remove) {
|
||||
$('#s1').dropdownchecklist('destroy');
|
||||
@@ -371,6 +497,7 @@ function initDropdown(remove,create) {
|
||||
<?endif;?>
|
||||
}
|
||||
}
|
||||
|
||||
function z(i) {
|
||||
switch (i) {
|
||||
case 0: return $('#primary').prop('selectedIndex');
|
||||
@@ -380,10 +507,18 @@ function z(i) {
|
||||
case 4: return z(0)==0 ? 'no' : (z(1)==0 ? 'only' : z(3));
|
||||
}
|
||||
}
|
||||
function updateCOW(i,slow) {
|
||||
const fsType = {<?=fsType()?>};
|
||||
if (fsType[i]=='btrfs') $('#cow-setting').show(slow); else $('#cow-setting').hide(slow);
|
||||
|
||||
|
||||
/* Update the Copy-on-Write (COW) setting visibility based on the filesystem type */
|
||||
function updateCOW(i, slow) {
|
||||
const fsType = {<?=fsType()?>};
|
||||
if (fsType[i] === 'btrfs') {
|
||||
$('#cow-setting').show(slow);
|
||||
} else {
|
||||
$('#cow-setting').hide(slow);
|
||||
}
|
||||
}
|
||||
|
||||
function updateScreen(cache,slow) {
|
||||
switch (cache) {
|
||||
case 'no':
|
||||
@@ -452,78 +587,181 @@ function updateScreen(cache,slow) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unite selected options into a comma-separated string */
|
||||
function unite(field) {
|
||||
var list = [];
|
||||
for (var i=0,item; item=field.options[i]; i++) if (item.selected) list.push(item.value);
|
||||
return list.join(',');
|
||||
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 = {<?=fsSize()?>};
|
||||
const units = ['K','M','G','T','P','E','Z','Y'];
|
||||
var full = fsSize[$('#primary').val()];
|
||||
var size = parseInt(full * 0.1); // 10% of available size
|
||||
var number = val.replace(/[A-Z%\s]/gi,'').replace(',','.').split('.');
|
||||
var last = number.pop();
|
||||
number = number.length ? number.join('')+'.'+last : last;
|
||||
if (number==0 && size>0) {
|
||||
size = size.toString()
|
||||
$.cookie('autosize-'+$('#shareName').val(),'1',{expires:365});
|
||||
} else {
|
||||
size = val;
|
||||
$.removeCookie('autosize-'+$('#shareName').val());
|
||||
}
|
||||
var unit = size.replace(/[0-9.,\s]/g,'');
|
||||
if (unit=='%') {
|
||||
number = (number > 0 && number <= 100) ? parseInt(full * number / 100) : '';
|
||||
} else {
|
||||
var base = unit.length==2 ? 1000 : (unit.length==1 ? 1024 : 0);
|
||||
number = base>0 ? number * Math.pow(base,(units.indexOf(unit.toUpperCase().replace('B',''))||0)) : size;
|
||||
}
|
||||
return isNaN(number) ? '' : number;
|
||||
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. */
|
||||
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();
|
||||
}
|
||||
// Compose input fields
|
||||
|
||||
/* 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() {
|
||||
// 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',html:true,confirmButtonText:"_(Ok)_"});
|
||||
return false;
|
||||
}
|
||||
var 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;
|
||||
}
|
||||
var 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;
|
||||
}
|
||||
if (share.match('[:\\\/*<>|"?]')) {
|
||||
swal({title:"_(Invalid Characters)_",text:"_(You cannot use the following within share names)_"+'<b> \\ / : * < > | " ?</b>',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);
|
||||
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;
|
||||
/* 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',
|
||||
html: true,
|
||||
confirmButtonText: "Ok"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
var 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;
|
||||
}
|
||||
|
||||
var 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);
|
||||
|
||||
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() {
|
||||
var name = $('select[name="readshare"]').val();
|
||||
initDropdown(true,false);
|
||||
@@ -542,6 +780,7 @@ function readShare() {
|
||||
});
|
||||
$(form).find('select').trigger('change');
|
||||
}
|
||||
|
||||
function writeShare(data,n,i) {
|
||||
if (data) {
|
||||
if (n<i) {
|
||||
@@ -572,7 +811,38 @@ function writeShare(data,n,i) {
|
||||
writeShare(data,0,i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean up the share name by removing invalid characters. */
|
||||
function safeName(name) {
|
||||
/* Define the allowed characters regex */
|
||||
const validChars = /^[A-Za-z0-9-_.: ]*$/;
|
||||
|
||||
/* Check if the name contains only valid characters */
|
||||
const 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 */
|
||||
let sanitizedString = '';
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
if (validChars.test(name[i])) {
|
||||
sanitizedString += name[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the sanitized string */
|
||||
return sanitizedString;
|
||||
}
|
||||
|
||||
function checkName(name) {
|
||||
if (/^[A-Za-z0-9-_.: ]*$/.test(name)) $('#zfs-name').hide(); else $('#zfs-name').show();
|
||||
var isValidName = /^[A-Za-z0-9-_.: ]*$/.test(name);
|
||||
if (isValidName) {
|
||||
$('#zfs-name').hide();
|
||||
} else {
|
||||
$('#zfs-name').show();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,26 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareUser')=='e'"
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device.
|
||||
function checkDisks($disks) {
|
||||
foreach ($disks as $disk) {
|
||||
// Check the disk type, fsStatus, and ensure the device is not blank.
|
||||
if (!in_array($disk['name'], ['flash', 'parity', 'parity2']) && $disk['fsStatus'] !== "Unmountable: unsupported or no file system" && !empty($disk['device'])) {
|
||||
// A valid disk with a non-blank device is found, return true.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No valid disks found, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Are there any array disks? */
|
||||
$disks = parse_ini_file('state/disks.ini',true) ?? [];
|
||||
$nodisks = checkDisks($disks) ? "" : "disabled";
|
||||
?>
|
||||
|
||||
<table class="unraid share_status">
|
||||
<thead><tr><td>_(Name)_</td><td>_(Comment)_</td><td>_(SMB)_</td><td>_(NFS)_</td><td>_(Storage)_</td><td>_(Size)_</td><td>_(Free)_</td></tr></thead>
|
||||
<tbody id="shareslist"></tbody>
|
||||
@@ -22,7 +42,7 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareUser')=='e'"
|
||||
|
||||
<form name="share_form" method="POST" action="<?=htmlspecialchars($path)?>/Share?name=">
|
||||
<input type="button" id="compute-shares" value="_(Compute All)_" onclick="$(this).prop('disabled',true);shareList('',-1)">
|
||||
<input type="submit" value="_(Add Share)_">
|
||||
<input type="submit" value="_(Add Share)_" <?echo $nodisks;?>>
|
||||
<input type="button" value="_(Clean Up)_" onclick="cleanup()" id="cleanup-button" disabled>
|
||||
</form>
|
||||
|
||||
|
||||
29
emhttp/plugins/dynamix/agents/Bark.xml
Normal file
29
emhttp/plugins/dynamix/agents/Bark.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Bark</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your push url from Bark APP. [a href='https://bark.day.app/#/tutorial' target='_blank'][u]tutorial[/u][/a]." Desc="PushUrl" Default="https://api.day.app/your_key">PUSHURL</Variable>
|
||||
<Variable Help="Optional. Specify the message group used for this push. [b]To disable this feature, specify 'none'.[/b]" Desc="Group" Default="Unraid">GROUP</Variable>
|
||||
<Variable Help="Optional. Specify the message sound, copy the sound name from Bark APP. [b]To disable this feature, specify 'none'.[/b]" Desc="Sound" Default="none">SOUND</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${GROUP}" && "${GROUP}" == "none" ]] && GROUP=""
|
||||
[[ -n "${SOUND}" && "${SOUND}" == "none" ]] && SOUND=""
|
||||
|
||||
curl -X "POST" "$PUSHURL" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d "{\"body\": \"$MESSAGE\", \"title\": \"$TITLE\", \"sound\": \"$SOUND\", \"group\": \"$GROUP\"}" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
|
||||
27
emhttp/plugins/dynamix/agents/Boxcar.xml
Normal file
27
emhttp/plugins/dynamix/agents/Boxcar.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Boxcar</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your access token as explained [a href='http://help.boxcar.io/knowledgebase/articles/314474-how-to-get-my-boxcar-access-token' target='_blank'][u]here[/u].[/a]" Desc="Access Token" Default="">ACCESS_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
curl -s -k \
|
||||
-d "user_credentials=$ACCESS_TOKEN" \
|
||||
-d "notification[title]=$TITLE" \
|
||||
-d "notification[long_message]=$MESSAGE" \
|
||||
-d "notification[source_name]=Unraid" \
|
||||
-d "notification[sound]=bird-1" \
|
||||
-d "notification[icon_url]=http://i.imgur.com/u63iSL1.png" \
|
||||
https://new.boxcar.io/api/notifications 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
199
emhttp/plugins/dynamix/agents/Discord.xml
Normal file
199
emhttp/plugins/dynamix/agents/Discord.xml
Normal file
@@ -0,0 +1,199 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Discord</Name>
|
||||
<Variables>
|
||||
<Variable Help="Add an '#unraid-notifications' channel to your personal Discord server, then get a WebHook URL as explained [a href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'][u]here[/u].[/a] Note that multiple Unraid servers can use the same Webhook." Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Provide the https URL to an icon representing this Unraid server (using different icons for each server can help distinguish between them in the list of notifications.) To disable this feature, specify 'none'." Desc="Server Icon" Default="https://craftassets.unraid.net/uploads/logos/un-mark-gradient@2x.png">SERVER_ICON</Variable>
|
||||
<Variable Help="In Discord, right-click the '#unraid-notifications' channel and choose Notification Settings -> Only @mentions. Then to receive an @mention on 'alert' priority notifications only, provide your personal Discord ID (it is a series of numbers, not letters). To find your ID, in Discord type \@yourusername. To disable this feature, specify 'none'." Desc="Discord Tag ID" Default="none">DISCORD_TAG_ID</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
|
||||
############
|
||||
# Quick test with default values:
|
||||
# bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Quick test with values set through environment (all vars are optional)
|
||||
# EVENT="My Event" SUBJECT="My Subject" DESCRIPTION="My Description" CONTENT="My Message" IMPORTANCE="alert" LINK="/Dashboard" bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Full test of notification system (at least one param is required)
|
||||
# /usr/local/emhttp/webGui/scripts/notify -e "My Event" -s "My Subject" -d "My Description" -m "My Message" -i "alert" -l "/Dashboard"
|
||||
#
|
||||
# If a notification does not go through, check the /var/log/notify_Discord file for hints
|
||||
############
|
||||
|
||||
############
|
||||
# Discord webhooks docs: https://birdie0.github.io/discord-webhooks-guide/
|
||||
#
|
||||
# Available fields from notification system
|
||||
# HOSTNAME
|
||||
# EVENT (notify -e)
|
||||
# IMPORTANCE (notify -i)
|
||||
# SUBJECT (notify -s)
|
||||
# DESCRIPTION (notify -d)
|
||||
# CONTENT (notify -m)
|
||||
# LINK (notify -l)
|
||||
# TIMESTAMP (seconds from epoch)
|
||||
|
||||
SCRIPTNAME=$(basename "$0")
|
||||
LOG="/var/log/notify_${SCRIPTNAME%.*}"
|
||||
|
||||
# for quick test, setup environment to mimic notify script
|
||||
[[ -z "${EVENT}" ]] && EVENT='Unraid Status'
|
||||
[[ -z "${SUBJECT}" ]] && SUBJECT='Notification'
|
||||
[[ -z "${DESCRIPTION}" ]] && DESCRIPTION='No description'
|
||||
[[ -z "${IMPORTANCE}" ]] && IMPORTANCE='normal'
|
||||
[[ -z "${TIMESTAMP}" ]] && TIMESTAMP=$(date +%s)
|
||||
# ensure link has a host
|
||||
if [[ -n "${LINK}" ]] && [[ ${LINK} != http* ]]; then
|
||||
source <(grep "NGINX_DEFAULTURL" /usr/local/emhttp/state/nginx.ini 2>/dev/null)
|
||||
LINK=${NGINX_DEFAULTURL}${LINK}
|
||||
fi
|
||||
# Discord will not allow links with bare hostname, links must have both hostname and tld or no link at all
|
||||
if [[ -n "${LINK}" ]]; then
|
||||
HOST=$(echo "${LINK}" | cut -d'/' -f3)
|
||||
[[ ${HOST} != *.* ]] && LINK=
|
||||
fi
|
||||
|
||||
# note: there is no default for CONTENT
|
||||
|
||||
# send DESCRIPTION and/or CONTENT. Ignore the default DESCRIPTION.
|
||||
[[ "${DESCRIPTION}" == 'No description' ]] && DESCRIPTION=""
|
||||
if [[ -n "${DESCRIPTION}" ]] && [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}\n\n${CONTENT}"
|
||||
elif [[ -n "${DESCRIPTION}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}"
|
||||
elif [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${CONTENT}"
|
||||
fi
|
||||
# split into 1024 character segments
|
||||
[[ -n "${FULL_DETAILS}" ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"name": "Description",
|
||||
"value": "${FULL_DETAILS:0:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 1024 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:1024:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 2048 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:2048:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/timestamp.html
|
||||
# https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
|
||||
FORMATTED_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\" -d @"${TIMESTAMP}")
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/thumbnail.html
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/color.html
|
||||
# vary data based on IMPORTANCE
|
||||
if [[ "${IMPORTANCE}" != "normal" ]] && [[ "${IMPORTANCE}" != "warning" ]] && [[ "${IMPORTANCE}" != "alert" ]]; then
|
||||
IMPORTANCE="normal"
|
||||
fi
|
||||
case "${IMPORTANCE}" in
|
||||
normal)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
warning)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-warning.png"
|
||||
COLOR="16747567"
|
||||
;;
|
||||
alert)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-alert.png"
|
||||
COLOR="14821416"
|
||||
[[ -n "${DISCORD_TAG_ID}" && "${DISCORD_TAG_ID}" == "none" ]] && DISCORD_TAG_ID=""
|
||||
if [[ -n "${DISCORD_TAG_ID}" ]]; then
|
||||
# add leading @ if needed
|
||||
[[ "${DISCORD_TAG_ID:0:1}" != "@" ]] && DISCORD_TAG_ID="@${DISCORD_TAG_ID}"
|
||||
# @mentions only work in the "content" area, not the "embed" area
|
||||
DISCORD_CONTENT_AREA="\"content\": \"<${DISCORD_TAG_ID}>\","
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/author.html
|
||||
# if SERVER_ICON is defined, use it
|
||||
[[ -n "${SERVER_ICON}" && "${SERVER_ICON:0:8}" == "https://" ]] && ICON_URL="\"icon_url\": \"${SERVER_ICON}\","
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/url.html
|
||||
# if LINK is defined, use it
|
||||
[[ -n "${LINK}" ]] && LINK_URL="\"url\": \"${LINK}\","
|
||||
|
||||
DATA=$(
|
||||
cat <<EOF
|
||||
{
|
||||
${DISCORD_CONTENT_AREA}
|
||||
"embeds": [
|
||||
{
|
||||
"title": "${EVENT:0:256}",
|
||||
"description": "${SUBJECT:0:2043}",
|
||||
${LINK_URL}
|
||||
"timestamp": ${FORMATTED_TIMESTAMP},
|
||||
"color": "${COLOR}",
|
||||
"author": {
|
||||
${ICON_URL}
|
||||
"name": "${HOSTNAME}"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "${THUMBNAIL}"
|
||||
},
|
||||
"fields": [
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Priority",
|
||||
"value": "${IMPORTANCE}",
|
||||
"inline": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# echo "${DATA}" >>"${LOG}"
|
||||
|
||||
# try several times in case we are being rate limited
|
||||
# this is not foolproof, messages can still be rejected
|
||||
MAX=4
|
||||
for ((i = 1; i <= "${MAX}"; i++)); do
|
||||
RET=$(
|
||||
curl -s -X "POST" "$WEBH_URL" -H 'Content-Type: application/json' --data-ascii @- <<EOF
|
||||
${DATA}
|
||||
EOF
|
||||
)
|
||||
# if nothing was returned, message was successfully sent. exit loop
|
||||
[[ -z "${RET}" ]] && break
|
||||
# log the attempt
|
||||
{
|
||||
date
|
||||
echo "attempt ${i} of ${MAX} failed"
|
||||
echo "${RET}"
|
||||
} >>"${LOG}"
|
||||
# if there was an error with the submission, log details and exit loop
|
||||
[[ "${RET}" != *"retry_after"* ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification" && break
|
||||
# if retries exhausted, log failure
|
||||
[[ "${i}" -eq "${MAX}" ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification - rate limited" && break
|
||||
# we were rate limited, try again after a delay
|
||||
sleep 1
|
||||
done
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
39
emhttp/plugins/dynamix/agents/Gotify.xml
Normal file
39
emhttp/plugins/dynamix/agents/Gotify.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Gotify</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8888/" Desc="Full Server Base URL" Default="">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="3"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="7"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
40
emhttp/plugins/dynamix/agents/Prowl.xml
Normal file
40
emhttp/plugins/dynamix/agents/Prowl.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Prowl</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your api key as explained [a href='https://www.prowlapp.com/api_settings.php' target='_blank'][u]here[/u].[/a]" Desc="Api Key" Default="">API_KEY</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="2"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "apikey=$API_KEY" \
|
||||
-F "application=$APP_NAME" \
|
||||
-F "event=$TITLE" \
|
||||
-F "description=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
https://api.prowlapp.com/publicapi/add 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
62
emhttp/plugins/dynamix/agents/PushBits.xml
Normal file
62
emhttp/plugins/dynamix/agents/PushBits.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>PushBits</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8080/" Desc="Full Server Base URL" Default="FULL PUSHBITS URL">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="YOUR APP TOKEN">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="21"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushbullet</Name>
|
||||
<Variables>
|
||||
<Variable Help="The Access Token can be found [a href='https://www.pushbullet.com/account' target='_blank'] [u]here[/u].[/a]" Desc="Access Token" Default="">TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
##########
|
||||
{0}
|
||||
##########
|
||||
MESSAGE=$(echo "$MESSAGE" | sed -e 's:<br[ /]*>:\\n:gI' -e 's/<[^>]*>//g')
|
||||
|
||||
curl -s -k \
|
||||
-X POST --header "Authorization: Bearer $TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
-d "{\"type\": \"note\", \"title\": \"$TITLE\", \"body\": \"$MESSAGE\"}" \
|
||||
https://api.pushbullet.com/v2/pushes 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
38
emhttp/plugins/dynamix/agents/Pushover.xml
Normal file
38
emhttp/plugins/dynamix/agents/Pushover.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Pushover</Name>
|
||||
<Variables>
|
||||
<Variable Help="The User Key can be found [a href='https://pushover.net/' target='_blank'][u]here[/u].[/a]" Desc="User Key" Default="">USER_KEY</Variable>
|
||||
<Variable Help="The App Token can be found [a href='https://pushover.net/apps' target='_blank'][u]here[/u][/a]." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$SUBJECT,$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="-1"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "token=$APP_TOKEN" \
|
||||
-F "user=$USER_KEY" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "timestamp=$TIMESTAMP" \
|
||||
-F "priority=$PRIORITY" \
|
||||
-F "html=1" \
|
||||
https://api.pushover.net/1/messages.json 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
40
emhttp/plugins/dynamix/agents/Pushplus.xml
Normal file
40
emhttp/plugins/dynamix/agents/Pushplus.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Pushplus</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your token from [a href='https://www.pushplus.plus/push1.html' target='_blank'][u]here[/u][/a]." Desc="Push Token" Default="FILL WITH YOUR OWN">TOKEN</Variable>
|
||||
<Variable Help="Optional. Specify the group code or the 'topic' mentioned in the [a href='https://www.pushplus.plus/doc/guide/api.html' target='_blank'][u]API docs[/u][/a] used for this push. It's used to [a href='https://www.pushplus.plus/push2.html' target='_blank'][u]push to multiple people[/u][/a] instead of pushing to the owner. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Group Code" Default="none">TOPIC</Variable>
|
||||
<Variable Help="Optional. Specify the message channel used for this push. [b]The default value is 'wechat'.[/b]" Desc="Specific Channel" Default="wechat">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the webhook used for this push when the push channel is 'webhook' or 'cp'. [b]To disable this feature, specify 'none'.[/b]" Desc="Webhook" Default="none">WEBHOOK</Variable>
|
||||
<Variable Help="Optional. Specify the callback url used for this push and the pushplus server will send a post request to it after each push completed. [b]To disable this feature, specify 'none'.[/b]" Desc="Callback Url" Default="none">CALLBACKURL</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
#Pushplus don't allow \n in title and the length limit is 100 for free accounts
|
||||
TITLE=$(echo -e "$TITLE" | tr "\n" " ")
|
||||
TITLE=$(echo "${TITLE:0:95}")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${TOPIC}" && "${TOPIC}" == "none" ]] && TOPIC=""
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL="wechat"
|
||||
[[ -n "${WEBHOOK}" && "${WEBHOOK}" == "none" ]] && WEBHOOK=""
|
||||
[[ -n "${CALLBACKURL}" && "${CALLBACKURL}" == "none" ]] && CALLBACKURL=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "token=$TOKEN" \
|
||||
-F "title=$TITLE" \
|
||||
-F "content=$MESSAGE" \
|
||||
-F "topic=$TOPIC" \
|
||||
-F "template=txt" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "webhook=$WEBHOOK" \
|
||||
-F "callbackUrl=$CALLBACKURL" \
|
||||
"https://www.pushplus.plus/send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
31
emhttp/plugins/dynamix/agents/ServerChan.xml
Normal file
31
emhttp/plugins/dynamix/agents/ServerChan.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>ServerChan</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your SendKey from [a href='https://sct.ftqq.com/sendkey' target='_blank'][u]here[/u][/a]." Desc="SendKey" Default="FILL WITH YOUR OWN">SENDKEY</Variable>
|
||||
<Variable Help="Optional. Specify the message channel(s) used for this push, e.g., '9' or '9|66'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Channel" Default="none">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the openid(s) or UID(s) used for this push, e.g., 'openid1,openid2' or 'UID1|UID2'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Openid" Default="none">OPENID</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE" | sed -z 's/\n/\n\n/g')
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL=""
|
||||
[[ -n "${OPENID}" && "${OPENID}" == "none" ]] && OPENID=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "desp=$MESSAGE" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "openid=$OPENID" \
|
||||
"https://sctapi.ftqq.com/$SENDKEY.send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
22
emhttp/plugins/dynamix/agents/Slack.xml
Normal file
22
emhttp/plugins/dynamix/agents/Slack.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Slack</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your WebHook as explained [a href='https://api.slack.com/incoming-webhooks' target='_blank'][u]here[/u].[/a]" Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
curl -X POST --header 'Content-Type: application/json' \
|
||||
-d "{\"username\": \"$APP_NAME\", \"text\": \"*$TITLE* \n $MESSAGE\"}" $WEBH_URL 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
43
emhttp/plugins/dynamix/agents/Telegram.xml
Normal file
43
emhttp/plugins/dynamix/agents/Telegram.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Telegram</Name>
|
||||
<Variables>
|
||||
<Variable Help="[a href='https://telegram.me/botfather' target='_blank'][u]BotFather[/u][/a] is the one bot to rule them all.[br][br]
|
||||
1. Make a bot using BotFather[br]
|
||||
2. Paste the bot token in this field[br]
|
||||
3. Message the bot via Telegram (either via a direct message or a message via a group)[br]
|
||||
4. Test the bot[br][br]
|
||||
* To reset the notifications receiving user or group, run [i]rm /boot/config/plugins/dynamix/telegram/chatid[/i] in the terminal and re-run steps 3. and 4.[/a]" Desc="Bot Access Token" Default="">BOT_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
LEGACY=/boot/config/telegram
|
||||
TELEGRAM=/boot/config/plugins/dynamix/telegram
|
||||
STORED_TOKEN=$(< $TELEGRAM/token) || "";
|
||||
|
||||
# move legacy folder (if existing)
|
||||
[[ -d $LEGACY && ! -d $TELEGRAM ]] && mv $LEGACY $TELEGRAM
|
||||
|
||||
if [[ ! -f $TELEGRAM/token || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
echo $BOT_TOKEN > $TELEGRAM/token;
|
||||
fi
|
||||
|
||||
if [[ ! -f $TELEGRAM/chatid || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
LASTCHATID=$(curl -s https://api.telegram.org/bot$BOT_TOKEN/getUpdates | jq ".result | last .message .chat .id");
|
||||
[[ $LASTCHATID =~ ^-*[0-9]+$ ]] && echo $LASTCHATID > $TELEGRAM/chatid || exit 1
|
||||
fi
|
||||
|
||||
CHATID=$(< $TELEGRAM/chatid);
|
||||
MESSAGE=$(echo -e "$(hostname): $TITLE\n$MESSAGE");
|
||||
curl -G -s "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" --data-urlencode "chat_id=$CHATID" --data-urlencode "text=$MESSAGE" 2>&1;
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
42
emhttp/plugins/dynamix/agents/ntfy.sh.xml
Normal file
42
emhttp/plugins/dynamix/agents/ntfy.sh.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>ntfy.sh</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://ntfy.sh" Desc="Full Server Base URL" Default="FULL NTFY.SH URL">SERVER_URL</Variable>
|
||||
<Variable Help="The ntfy.sh Token to use." Desc="ntfy.sh Token" Default="YOUR NTFY.SH TOKEN">NTFY_TOKEN</Variable>
|
||||
<Variable Help="The ntfy.sh Topic to use." Desc="ntfy.sh Topic" Default="Unraid">TOPIC</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="default"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="high"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="urgent"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl \
|
||||
-H "Priority: $PRIORITY" \
|
||||
-H "Icon: https://raw.githubusercontent.com/unraid/webgui/master/emhttp/plugins/dynamix.vm.manager/templates/images/unraid.png" \
|
||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
||||
-H "Title: $TITLE" \
|
||||
-d "$MESSAGE" \
|
||||
$SERVER_URL/$TOPIC
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
@@ -17,7 +17,7 @@ if (!empty($_COOKIE['unraid_'.md5($server_name)])) {
|
||||
|
||||
function readFromFile($file): string {
|
||||
$text = "";
|
||||
if (file_exists($file)) {
|
||||
if (file_exists($file) && filesize($file) > 0) {
|
||||
$fp = fopen($file,"r");
|
||||
if (flock($fp, LOCK_EX)) {
|
||||
$text = fread($fp, filesize($file));
|
||||
|
||||
@@ -28,13 +28,30 @@ $var = parse_ini_file('state/var.ini');
|
||||
$sec = parse_ini_file('state/sec.ini',true);
|
||||
$sec_nfs = parse_ini_file('state/sec_nfs.ini',true);
|
||||
|
||||
// exit when no disks
|
||||
// exit when no mountable array disks
|
||||
$nodisks = "<tr><td class='empty' colspan='7'><strong>"._('There are no mountable array or pool disks - cannot add shares').".</strong></td></tr>";
|
||||
if (!checkDisks($disks)) die($nodisks);
|
||||
|
||||
// No shared disks
|
||||
$nodisks = "<tr><td class='empty' colspan='7'><i class='fa fa-folder-open-o icon'></i>"._('There are no exportable disk shares')."</td></tr>";
|
||||
if (!$disks) die($nodisks);
|
||||
|
||||
// GUI settings
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device.
|
||||
function checkDisks($disks) {
|
||||
foreach ($disks as $disk) {
|
||||
// Check the disk type, fsStatus, and ensure the device is not blank.
|
||||
if (!in_array($disk['name'], ['flash', 'parity', 'parity2']) && $disk['fsStatus'] !== "Unmountable: unsupported or no file system" && !empty($disk['device'])) {
|
||||
// A valid disk with a non-blank device is found, return true.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No valid disks found, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Display export settings
|
||||
function disk_share_settings($protocol,$share) {
|
||||
if (empty($share)) return;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*
|
||||
* History:
|
||||
*
|
||||
* 1.2.1 - exclude folders from the /mnt/ root folder
|
||||
* 1.2.0 - adapted by Bergware for use in Unraid - support UTF-8 encoding & hardening
|
||||
* 1.1.1 - SECURITY: forcing root to prevent users from determining system's file structure (per DaveBrad)
|
||||
* 1.1.0 - adding multiSelect (checkbox) support (08/22/2014)
|
||||
@@ -48,27 +49,45 @@ $filters = (array)$_POST['filter'];
|
||||
$match = $_POST['match'];
|
||||
$checkbox = $_POST['multiSelect']=='true' ? "<input type='checkbox'>" : "";
|
||||
|
||||
/* Excluded folders to not show in the dropdown in the '/mnt/' directory only. */
|
||||
$excludedFolders = ["RecycleBin", "addons", "disks", "remotes", "rootshare", "user0"];
|
||||
|
||||
echo "<ul class='jqueryFileTree'>";
|
||||
if ($_POST['show_parent']=='true' && is_top($rootdir)) echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"".htmlspecialchars(dirname($rootdir))."\">..</a></li>";
|
||||
|
||||
if (is_low($rootdir) && is_dir($rootdir)) {
|
||||
$dirs = $files = [];
|
||||
$names = array_filter(scandir($rootdir,SCANDIR_SORT_NONE),function($n){return $n!='.' && $n!='..';});
|
||||
$names = array_filter(scandir($rootdir, SCANDIR_SORT_NONE), function($n) { return $n != '.' && $n != '..'; });
|
||||
natcasesort($names);
|
||||
foreach ($names as $name) if (is_dir($rootdir.$name)) $dirs[] = $name; else $files[] = $name;
|
||||
foreach ($dirs as $name) {
|
||||
$htmlRel = htmlspecialchars($rootdir.$name);
|
||||
$htmlName = htmlspecialchars(mb_strlen($name)<=33 ? $name : mb_substr($name,0,30).'...');
|
||||
if (is_dir($rootdir.$name)) {
|
||||
if (empty($match)||preg_match("/$match/",$rootdir.$name)) echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"$htmlRel/\">$htmlName</a></li>";
|
||||
foreach ($names as $name) {
|
||||
if (is_dir($rootdir . $name)) {
|
||||
$dirs[] = $name;
|
||||
} else {
|
||||
$files[] = $name;
|
||||
}
|
||||
}
|
||||
foreach ($dirs as $name) {
|
||||
$htmlRel = htmlspecialchars($rootdir . $name);
|
||||
$htmlName = htmlspecialchars(mb_strlen($name) <= 33 ? $name : mb_substr($name, 0, 30) . '...');
|
||||
|
||||
/* Exclude '.Recycle.Bin' from all directories */
|
||||
if ($name === ".Recycle.Bin") continue;
|
||||
|
||||
/* Exclude folders only when directory is '/mnt/' */
|
||||
if (in_array($name, $excludedFolders) && $rootdir === "/mnt/") continue;
|
||||
|
||||
echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"$htmlRel/\">$htmlName</a></li>";
|
||||
}
|
||||
foreach ($files as $name) {
|
||||
$htmlRel = htmlspecialchars($rootdir.$name);
|
||||
$htmlRel = htmlspecialchars($rootdir . $name);
|
||||
$htmlName = htmlspecialchars($name);
|
||||
$ext = mb_strtolower(pathinfo($name,PATHINFO_EXTENSION));
|
||||
foreach ($filters as $filter) if (empty($filter)||$ext==$filter) {
|
||||
if (empty($match)||preg_match("/$match/",$name)) echo "<li class='file ext_$ext'>$checkbox<a href='#' rel=\"$htmlRel\">$htmlName</a></li>";
|
||||
$ext = mb_strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
foreach ($filters as $filter) {
|
||||
if (empty($filter) || $ext == $filter) {
|
||||
if (empty($match) || preg_match("/$match/", $name)) {
|
||||
echo "<li class='file ext_$ext'>$checkbox<a href='#' rel=\"$htmlRel\">$htmlName</a></li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,627 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agents>
|
||||
<Agent>
|
||||
<Name>Boxcar</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your access token as explained [a href='http://help.boxcar.io/knowledgebase/articles/314474-how-to-get-my-boxcar-access-token' target='_blank'][u]here[/u].[/a]" Desc="Access Token" Default="">ACCESS_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
curl -s -k \
|
||||
-d "user_credentials=$ACCESS_TOKEN" \
|
||||
-d "notification[title]=$TITLE" \
|
||||
-d "notification[long_message]=$MESSAGE" \
|
||||
-d "notification[source_name]=Unraid" \
|
||||
-d "notification[sound]=bird-1" \
|
||||
-d "notification[icon_url]=http://i.imgur.com/u63iSL1.png" \
|
||||
https://new.boxcar.io/api/notifications 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Discord</Name>
|
||||
<Variables>
|
||||
<Variable Help="Add an '#unraid-notifications' channel to your personal Discord server, then get a WebHook URL as explained [a href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'][u]here[/u].[/a] Note that multiple Unraid servers can use the same Webhook." Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Provide the https URL to an icon representing this Unraid server (using different icons for each server can help distinguish between them in the list of notifications.) To disable this feature, specify 'none'." Desc="Server Icon" Default="https://craftassets.unraid.net/uploads/logos/un-mark-gradient@2x.png">SERVER_ICON</Variable>
|
||||
<Variable Help="In Discord, right-click the '#unraid-notifications' channel and choose Notification Settings -> Only @mentions. Then to receive an @mention on 'alert' priority notifications only, provide your personal Discord ID (it is a series of numbers, not letters). To find your ID, in Discord type \@yourusername. To disable this feature, specify 'none'." Desc="Discord Tag ID" Default="none">DISCORD_TAG_ID</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
|
||||
############
|
||||
# Quick test with default values:
|
||||
# bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Quick test with values set through environment (all vars are optional)
|
||||
# EVENT="My Event" SUBJECT="My Subject" DESCRIPTION="My Description" CONTENT="My Message" IMPORTANCE="alert" LINK="/Dashboard" bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Full test of notification system (at least one param is required)
|
||||
# /usr/local/emhttp/webGui/scripts/notify -e "My Event" -s "My Subject" -d "My Description" -m "My Message" -i "alert" -l "/Dashboard"
|
||||
#
|
||||
# If a notification does not go through, check the /var/log/notify_Discord file for hints
|
||||
############
|
||||
|
||||
############
|
||||
# Discord webhooks docs: https://birdie0.github.io/discord-webhooks-guide/
|
||||
#
|
||||
# Available fields from notification system
|
||||
# HOSTNAME
|
||||
# EVENT (notify -e)
|
||||
# IMPORTANCE (notify -i)
|
||||
# SUBJECT (notify -s)
|
||||
# DESCRIPTION (notify -d)
|
||||
# CONTENT (notify -m)
|
||||
# LINK (notify -l)
|
||||
# TIMESTAMP (seconds from epoch)
|
||||
|
||||
SCRIPTNAME=$(basename "$0")
|
||||
LOG="/var/log/notify_${SCRIPTNAME%.*}"
|
||||
|
||||
# for quick test, setup environment to mimic notify script
|
||||
[[ -z "${EVENT}" ]] && EVENT='Unraid Status'
|
||||
[[ -z "${SUBJECT}" ]] && SUBJECT='Notification'
|
||||
[[ -z "${DESCRIPTION}" ]] && DESCRIPTION='No description'
|
||||
[[ -z "${IMPORTANCE}" ]] && IMPORTANCE='normal'
|
||||
[[ -z "${TIMESTAMP}" ]] && TIMESTAMP=$(date +%s)
|
||||
# ensure link has a host
|
||||
if [[ -n "${LINK}" ]] && [[ ${LINK} != http* ]]; then
|
||||
source <(grep "NGINX_DEFAULTURL" /usr/local/emhttp/state/nginx.ini 2>/dev/null)
|
||||
LINK=${NGINX_DEFAULTURL}${LINK}
|
||||
fi
|
||||
# Discord will not allow links with bare hostname, links must have both hostname and tld or no link at all
|
||||
if [[ -n "${LINK}" ]]; then
|
||||
HOST=$(echo "${LINK}" | cut -d'/' -f3)
|
||||
[[ ${HOST} != *.* ]] && LINK=
|
||||
fi
|
||||
|
||||
# note: there is no default for CONTENT
|
||||
|
||||
# send DESCRIPTION and/or CONTENT. Ignore the default DESCRIPTION.
|
||||
[[ "${DESCRIPTION}" == 'No description' ]] && DESCRIPTION=""
|
||||
if [[ -n "${DESCRIPTION}" ]] && [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}\n\n${CONTENT}"
|
||||
elif [[ -n "${DESCRIPTION}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}"
|
||||
elif [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${CONTENT}"
|
||||
fi
|
||||
# split into 1024 character segments
|
||||
[[ -n "${FULL_DETAILS}" ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"name": "Description",
|
||||
"value": "${FULL_DETAILS:0:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 1024 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:1024:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 2048 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:2048:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/timestamp.html
|
||||
# https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
|
||||
FORMATTED_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\" -d @"${TIMESTAMP}")
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/thumbnail.html
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/color.html
|
||||
# vary data based on IMPORTANCE
|
||||
if [[ "${IMPORTANCE}" != "normal" ]] && [[ "${IMPORTANCE}" != "warning" ]] && [[ "${IMPORTANCE}" != "alert" ]]; then
|
||||
IMPORTANCE="normal"
|
||||
fi
|
||||
case "${IMPORTANCE}" in
|
||||
normal)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
warning)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-warning.png"
|
||||
COLOR="16747567"
|
||||
;;
|
||||
alert)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-alert.png"
|
||||
COLOR="14821416"
|
||||
[[ -n "${DISCORD_TAG_ID}" && "${DISCORD_TAG_ID}" == "none" ]] && DISCORD_TAG_ID=""
|
||||
if [[ -n "${DISCORD_TAG_ID}" ]]; then
|
||||
# add leading @ if needed
|
||||
[[ "${DISCORD_TAG_ID:0:1}" != "@" ]] && DISCORD_TAG_ID="@${DISCORD_TAG_ID}"
|
||||
# @mentions only work in the "content" area, not the "embed" area
|
||||
DISCORD_CONTENT_AREA="\"content\": \"<${DISCORD_TAG_ID}>\","
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/author.html
|
||||
# if SERVER_ICON is defined, use it
|
||||
[[ -n "${SERVER_ICON}" && "${SERVER_ICON:0:8}" == "https://" ]] && ICON_URL="\"icon_url\": \"${SERVER_ICON}\","
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/url.html
|
||||
# if LINK is defined, use it
|
||||
[[ -n "${LINK}" ]] && LINK_URL="\"url\": \"${LINK}\","
|
||||
|
||||
DATA=$(
|
||||
cat <<EOF
|
||||
{
|
||||
${DISCORD_CONTENT_AREA}
|
||||
"embeds": [
|
||||
{
|
||||
"title": "${EVENT:0:256}",
|
||||
"description": "${SUBJECT:0:2043}",
|
||||
${LINK_URL}
|
||||
"timestamp": ${FORMATTED_TIMESTAMP},
|
||||
"color": "${COLOR}",
|
||||
"author": {
|
||||
${ICON_URL}
|
||||
"name": "${HOSTNAME}"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "${THUMBNAIL}"
|
||||
},
|
||||
"fields": [
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Priority",
|
||||
"value": "${IMPORTANCE}",
|
||||
"inline": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# echo "${DATA}" >>"${LOG}"
|
||||
|
||||
# try several times in case we are being rate limited
|
||||
# this is not foolproof, messages can still be rejected
|
||||
MAX=4
|
||||
for ((i = 1; i <= "${MAX}"; i++)); do
|
||||
RET=$(
|
||||
curl -s -X "POST" "$WEBH_URL" -H 'Content-Type: application/json' --data-ascii @- <<EOF
|
||||
${DATA}
|
||||
EOF
|
||||
)
|
||||
# if nothing was returned, message was successfully sent. exit loop
|
||||
[[ -z "${RET}" ]] && break
|
||||
# log the attempt
|
||||
{
|
||||
date
|
||||
echo "attempt ${i} of ${MAX} failed"
|
||||
echo "${RET}"
|
||||
} >>"${LOG}"
|
||||
# if there was an error with the submission, log details and exit loop
|
||||
[[ "${RET}" != *"retry_after"* ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification" && break
|
||||
# if retries exhausted, log failure
|
||||
[[ "${i}" -eq "${MAX}" ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification - rate limited" && break
|
||||
# we were rate limited, try again after a delay
|
||||
sleep 1
|
||||
done
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Gotify</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8888/" Desc="Full Server Base URL" Default="">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="3"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="7"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Join</Name>
|
||||
<Variables>
|
||||
<Variable Help="The API key can be found [a href='https://joinjoaomgcd.appspot.com' target='_blank'] [u]here[/u].[/a]" Desc="API key" Default="">API_KEY</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
##########
|
||||
{0}
|
||||
##########
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
curl -s -k -G \
|
||||
-d "apikey=$API_KEY" \
|
||||
--data-urlencode "title=$TITLE" \
|
||||
--data-urlencode "text=$MESSAGE" \
|
||||
-d "deviceId=group.all" \
|
||||
https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>ntfy.sh</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://ntfy.sh" Desc="Full Server Base URL" Default="FULL NTFY.SH URL">SERVER_URL</Variable>
|
||||
<Variable Help="The ntfy.sh Token to use." Desc="ntfy.sh Token" Default="YOUR NTFY.SH TOKEN">NTFY_TOKEN</Variable>
|
||||
<Variable Help="The ntfy.sh Topic to use." Desc="ntfy.sh Topic" Default="Unraid">TOPIC</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="default"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="high"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="urgent"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl \
|
||||
-H "Priority: $PRIORITY" \
|
||||
-H "Icon: https://raw.githubusercontent.com/unraid/webgui/master/emhttp/plugins/dynamix.vm.manager/templates/images/unraid.png" \
|
||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
||||
-H "Title: $TITLE" \
|
||||
-d "$MESSAGE" \
|
||||
$SERVER_URL/$TOPIC
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Prowl</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your api key as explained [a href='https://www.prowlapp.com/api_settings.php' target='_blank'][u]here[/u].[/a]" Desc="Api Key" Default="">API_KEY</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="2"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "apikey=$API_KEY" \
|
||||
-F "application=$APP_NAME" \
|
||||
-F "event=$TITLE" \
|
||||
-F "description=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
https://api.prowlapp.com/publicapi/add 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>PushBits</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8080/" Desc="Full Server Base URL" Default="FULL PUSHBITS URL">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="YOUR APP TOKEN">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="21"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushbullet</Name>
|
||||
<Variables>
|
||||
<Variable Help="The Access Token can be found [a href='https://www.pushbullet.com/account' target='_blank'] [u]here[/u].[/a]" Desc="Access Token" Default="">TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
##########
|
||||
{0}
|
||||
##########
|
||||
MESSAGE=$(echo "$MESSAGE" | sed -e 's:<br[ /]*>:\\n:gI' -e 's/<[^>]*>//g')
|
||||
|
||||
curl -s -k \
|
||||
-X POST --header "Authorization: Bearer $TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
-d "{\"type\": \"note\", \"title\": \"$TITLE\", \"body\": \"$MESSAGE\"}" \
|
||||
https://api.pushbullet.com/v2/pushes 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushover</Name>
|
||||
<Variables>
|
||||
<Variable Help="The User Key can be found [a href='https://pushover.net/' target='_blank'][u]here[/u].[/a]" Desc="User Key" Default="">USER_KEY</Variable>
|
||||
<Variable Help="The App Token can be found [a href='https://pushover.net/apps' target='_blank'][u]here[/u][/a]." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$SUBJECT,$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="-1"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "token=$APP_TOKEN" \
|
||||
-F "user=$USER_KEY" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "timestamp=$TIMESTAMP" \
|
||||
-F "priority=$PRIORITY" \
|
||||
-F "html=1" \
|
||||
https://api.pushover.net/1/messages.json 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushplus</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your token from [a href='https://www.pushplus.plus/push1.html' target='_blank'][u]here[/u][/a]." Desc="Push Token" Default="FILL WITH YOUR OWN">TOKEN</Variable>
|
||||
<Variable Help="Optional. Specify the group code or the 'topic' mentioned in the [a href='https://www.pushplus.plus/doc/guide/api.html' target='_blank'][u]API docs[/u][/a] used for this push. It's used to [a href='https://www.pushplus.plus/push2.html' target='_blank'][u]push to multiple people[/u][/a] instead of pushing to the owner. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Group Code" Default="none">TOPIC</Variable>
|
||||
<Variable Help="Optional. Specify the message channel used for this push. [b]The default value is 'wechat'.[/b]" Desc="Specific Channel" Default="wechat">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the webhook used for this push when the push channel is 'webhook' or 'cp'. [b]To disable this feature, specify 'none'.[/b]" Desc="Webhook" Default="none">WEBHOOK</Variable>
|
||||
<Variable Help="Optional. Specify the callback url used for this push and the pushplus server will send a post request to it after each push completed. [b]To disable this feature, specify 'none'.[/b]" Desc="Callback Url" Default="none">CALLBACKURL</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
#Pushplus don't allow \n in title and the length limit is 100 for free accounts
|
||||
TITLE=$(echo -e "$TITLE" | tr "\n" " ")
|
||||
TITLE=$(echo "${TITLE:0:95}")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${TOPIC}" && "${TOPIC}" == "none" ]] && TOPIC=""
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL="wechat"
|
||||
[[ -n "${WEBHOOK}" && "${WEBHOOK}" == "none" ]] && WEBHOOK=""
|
||||
[[ -n "${CALLBACKURL}" && "${CALLBACKURL}" == "none" ]] && CALLBACKURL=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "token=$TOKEN" \
|
||||
-F "title=$TITLE" \
|
||||
-F "content=$MESSAGE" \
|
||||
-F "topic=$TOPIC" \
|
||||
-F "template=txt" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "webhook=$WEBHOOK" \
|
||||
-F "callbackUrl=$CALLBACKURL" \
|
||||
"https://www.pushplus.plus/send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>ServerChan</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your SendKey from [a href='https://sct.ftqq.com/sendkey' target='_blank'][u]here[/u][/a]." Desc="SendKey" Default="FILL WITH YOUR OWN">SENDKEY</Variable>
|
||||
<Variable Help="Optional. Specify the message channel(s) used for this push, e.g., '9' or '9|66'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Channel" Default="none">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the openid(s) or UID(s) used for this push, e.g., 'openid1,openid2' or 'UID1|UID2'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Openid" Default="none">OPENID</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE" | sed -z 's/\n/\n\n/g')
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL=""
|
||||
[[ -n "${OPENID}" && "${OPENID}" == "none" ]] && OPENID=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "desp=$MESSAGE" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "openid=$OPENID" \
|
||||
"https://sctapi.ftqq.com/$SENDKEY.send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Slack</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your WebHook as explained [a href='https://api.slack.com/incoming-webhooks' target='_blank'][u]here[/u].[/a]" Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
curl -X POST --header 'Content-Type: application/json' \
|
||||
-d "{\"username\": \"$APP_NAME\", \"text\": \"*$TITLE* \n $MESSAGE\"}" $WEBH_URL 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Telegram</Name>
|
||||
<Variables>
|
||||
<Variable Help="[a href='https://telegram.me/botfather' target='_blank'][u]BotFather[/u][/a] is the one bot to rule them all.[br][br]
|
||||
1. Make a bot using BotFather[br]
|
||||
2. Paste the bot token in this field[br]
|
||||
3. Message the bot via Telegram (either via a direct message or a message via a group)[br]
|
||||
4. Test the bot[br][br]
|
||||
* To reset the notifications receiving user or group, run [i]rm /boot/config/plugins/dynamix/telegram/chatid[/i] in the terminal and re-run steps 3. and 4.[/a]" Desc="Bot Access Token" Default="">BOT_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
LEGACY=/boot/config/telegram
|
||||
TELEGRAM=/boot/config/plugins/dynamix/telegram
|
||||
STORED_TOKEN=$(< $TELEGRAM/token) || "";
|
||||
|
||||
# move legacy folder (if existing)
|
||||
[[ -d $LEGACY && ! -d $TELEGRAM ]] && mv $LEGACY $TELEGRAM
|
||||
|
||||
if [[ ! -f $TELEGRAM/token || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
echo $BOT_TOKEN > $TELEGRAM/token;
|
||||
fi
|
||||
|
||||
if [[ ! -f $TELEGRAM/chatid || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
LASTCHATID=$(curl -s https://api.telegram.org/bot$BOT_TOKEN/getUpdates | jq ".result | last .message .chat .id");
|
||||
[[ $LASTCHATID =~ ^-*[0-9]+$ ]] && echo $LASTCHATID > $TELEGRAM/chatid || exit 1
|
||||
fi
|
||||
|
||||
CHATID=$(< $TELEGRAM/chatid);
|
||||
MESSAGE=$(echo -e "$(hostname): $TITLE\n$MESSAGE");
|
||||
curl -G -s "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" --data-urlencode "chat_id=$CHATID" --data-urlencode "text=$MESSAGE" 2>&1;
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Bark</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your push url from Bark APP. [a href='https://bark.day.app/#/tutorial' target='_blank'][u]tutorial[/u][/a]." Desc="PushUrl" Default="https://api.day.app/your_key">PUSHURL</Variable>
|
||||
<Variable Help="Optional. Specify the message group used for this push. [b]To disable this feature, specify 'none'.[/b]" Desc="Group" Default="Unraid">GROUP</Variable>
|
||||
<Variable Help="Optional. Specify the message sound, copy the sound name from Bark APP. [b]To disable this feature, specify 'none'.[/b]" Desc="Sound" Default="none">SOUND</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${GROUP}" && "${GROUP}" == "none" ]] && GROUP=""
|
||||
[[ -n "${SOUND}" && "${SOUND}" == "none" ]] && SOUND=""
|
||||
|
||||
curl -X "POST" "$PUSHURL" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d "{\"body\": \"$MESSAGE\", \"title\": \"$TITLE\", \"sound\": \"$SOUND\", \"group\": \"$GROUP\"}" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
</Agents>
|
||||
@@ -46,6 +46,10 @@ $var = parse_ini_file('state/var.ini');
|
||||
$sec = parse_ini_file('state/sec.ini',true);
|
||||
$sec_nfs = parse_ini_file('state/sec_nfs.ini',true);
|
||||
|
||||
// exit when no mountable array disks
|
||||
$nodisks = "<tr><td class='empty' colspan='7'><strong>"._('There are no mountable array or pool disks - cannot add shares').".</strong></td></tr>";
|
||||
if (!checkDisks($disks)) die($nodisks);
|
||||
|
||||
// exit when no shares
|
||||
$noshares = "<tr><td class='empty' colspan='7'><i class='fa fa-folder-open-o icon'></i>"._('There are no exportable user shares')."</td></tr>";
|
||||
if (!$shares) die($noshares);
|
||||
@@ -59,6 +63,20 @@ $pools = implode(',', $pools_check);
|
||||
// Natural sorting of share names
|
||||
uksort($shares,'strnatcasecmp');
|
||||
|
||||
// Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device.
|
||||
function checkDisks($disks) {
|
||||
foreach ($disks as $disk) {
|
||||
// Check the disk type, fsStatus, and ensure the device is not blank.
|
||||
if (!in_array($disk['name'], ['flash', 'parity', 'parity2']) && $disk['fsStatus'] !== "Unmountable: unsupported or no file system" && !empty($disk['device'])) {
|
||||
// A valid disk with a non-blank device is found, return true.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No valid disks found, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Display export settings
|
||||
function user_share_settings($protocol,$share) {
|
||||
if (empty($share)) return;
|
||||
|
||||
@@ -22,6 +22,23 @@ $_proxy_ = '__';
|
||||
$_arrow_ = '»';
|
||||
|
||||
// Wrapper functions
|
||||
function file_put_contents_atomic($filename,$data, $flags = 0, $context = null) {
|
||||
while (true) {
|
||||
$suffix = rand();
|
||||
if ( ! is_file("$filename$suffix") )
|
||||
break;
|
||||
}
|
||||
$renResult = false;
|
||||
$writeResult = @file_put_contents("$filename$suffix",$data,$flags,$context) === strlen($data);
|
||||
if ( $writeResult )
|
||||
$renResult = @rename("$filename$suffix",$filename);
|
||||
if ( ! $writeResult || ! $renResult ) {
|
||||
my_logger("File_put_contents_atomic failed to write / rename $filename");
|
||||
@unlink("$filename$suffix");
|
||||
return false;
|
||||
}
|
||||
return strlen($data);
|
||||
}
|
||||
function parse_plugin_cfg($plugin, $sections=false, $scanner=INI_SCANNER_NORMAL) {
|
||||
global $docroot;
|
||||
$ram = "$docroot/plugins/$plugin/default.cfg";
|
||||
@@ -174,22 +191,29 @@ function http_get_contents(string $url, array $opts = [], array &$getinfo = NULL
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_REFERER, "");
|
||||
curl_setopt($ch, CURLOPT_FAILONERROR, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Unraid');
|
||||
if(is_array($opts) && $opts) {
|
||||
foreach($opts as $key => $val) {
|
||||
curl_setopt($ch, $key, $val);
|
||||
}
|
||||
}
|
||||
$out = curl_exec($ch);
|
||||
if(isset($getinfo)) {
|
||||
if (curl_errno($ch) == 23) {
|
||||
// error 23 detected, try CURLOPT_ENCODING = "deflate"
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "deflate");
|
||||
$out = curl_exec($ch);
|
||||
}
|
||||
if (isset($getinfo)) {
|
||||
$getinfo = curl_getinfo($ch);
|
||||
}
|
||||
if (curl_errno($ch)) {
|
||||
$msg = curl_error($ch) . " {$url}";
|
||||
if ($errno = curl_errno($ch)) {
|
||||
$msg = "Curl error $errno: " . (curl_error($ch) ?: curl_strerror($errno)) . ". Requested url: '$url'";
|
||||
if(isset($getinfo)) {
|
||||
$getinfo['error'] = $msg;
|
||||
}
|
||||
my_logger($msg, "http_get_contents");
|
||||
}
|
||||
curl_close($ch);
|
||||
return $out;
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -29,8 +29,7 @@ if (isset($_POST['#apply'])) {
|
||||
$dotm = $_POST['dotm'] ?? '*';
|
||||
$month = $_POST['month'] ?? '*';
|
||||
$day = $_POST['day'] ?? '*';
|
||||
$write = $_POST['write'] ?? '';
|
||||
$write = ($write == "yes") ? "" : "NOCORRECT";
|
||||
$write = $_POST['write'] ?? 'NOCORRECT';
|
||||
$term = $test = $end1 = $end2 = '';
|
||||
switch ($dotm) {
|
||||
case '28-31':
|
||||
|
||||
@@ -182,10 +182,14 @@ function array_offline(&$disk, $pool='') {
|
||||
$echo = []; $warning = '';
|
||||
$status = ['DISK_DSBL','DISK_INVALID','DISK_DSBL_NEW','DISK_NEW','DISK_WRONG'];
|
||||
$text = "<span class='red-text'><em>"._('All existing data on this device will be OVERWRITTEN when array is Started')."</em></span>";
|
||||
if (!str_contains(_var($var,'mdState'),'ERROR:')) {
|
||||
if (_var($disk,'type')=='Cache') {
|
||||
if (!empty(_var($disks[$pool],'uuid')) && _var($disk,'status')=='DISK_NEW') $warning = $text;
|
||||
} else {
|
||||
if (_var($disk,'type')=='Cache') {
|
||||
if (!str_contains(_var($diks[$pool],'state'),'ERROR:')) {
|
||||
if (_var($disks[$pool],'state')!='NEW_ARRAY') {
|
||||
if (in_array(_var($disk,'status'),$status)) $warning = $text;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!str_contains(_var($var,'mdState'),'ERROR:')) {
|
||||
if (_var($var,'mdState')=='NEW_ARRAY') {
|
||||
if (_var($disk,'type')=='Parity') $warning = $text;
|
||||
} elseif (_var($var,'mdNumInvalid',0)<=1) {
|
||||
|
||||
@@ -75,7 +75,7 @@ while (true) {
|
||||
$vmencode = $lv->domain_get_uuid($vm);
|
||||
$echo[$vmencode ]['gcpu'] = "<span class='advanced'>"._("Guest CPU").": <span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'></span><span></span></div></span>";
|
||||
$echo[$vmencode ]['hcpu'] = "<span class='advanced'>"._("Host CPU").": <span class='cpug-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuhost']."%;'></span><span></span></div></span>";
|
||||
$echo[$vmencode ]['mem'] .= "<span>Mem: ".my_scale($vmdata['mem'],$unit)."$unit / ".my_scale($vmdata['maxmem'],$unit)."$unit</span>";
|
||||
$echo[$vmencode ]['mem'] .= "<span>Mem: ".my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['maxmem']*1024,$unit)."$unit</span>";
|
||||
$echo[$vmencode ]['disk'] .= "<span>Disk: "._("Rd").": ".my_scale($vmdata['rdrate'],$unit)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate'],$unit)."$unit/s</span>";
|
||||
$echo[$vmencode ]['net'] .= "<span>Net: "._("RX").": ".my_scale($vmdata['rxrate'],$unit)."$unit/s "._("TX").": ".my_scale($vmdata['txrate'],$unit)."$unit/s</span>";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@font-face{font-family:cases;font-weight:normal;font-style:normal;src:url('/webGui/styles/font-cases.woff?v=20220508') format('woff')}
|
||||
@font-face{font-family:cases;font-weight:normal;font-style:normal;src:url('/webGui/styles/font-cases.woff?v=20240511') format('woff')}
|
||||
[class^='case-'],[class*=' case-']{font-family:cases!important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none}
|
||||
.case-1u-disks:before{content:'\e90d'}
|
||||
.case-1u-vents:before{content:'\e90e'}
|
||||
@@ -29,6 +29,7 @@
|
||||
.case-htpc:before{content:'\e915'}
|
||||
.case-IPC-4U-4424:before{content:'\e92e'}
|
||||
.case-lancool-k12:before{content:'\e935'}
|
||||
.case-lincstation-n1:before{content:'\e948'}
|
||||
.case-mercury-s8:before{content:'\e910'}
|
||||
.case-microserver-gen7:before{content:'\e936'}
|
||||
.case-mid-tower:before{content:'\e916'}
|
||||
@@ -71,4 +72,4 @@
|
||||
.case-vivopc:before{content:'\e925'}
|
||||
.case-vm:before{content:'\e920'}
|
||||
.case-x3650-M3:before{content:'\e924'}
|
||||
.case-zalman-ms800:before{content:'\e947'}
|
||||
.case-zalman-ms800:before{content:'\e947'}
|
||||
|
||||
Binary file not shown.
@@ -40,7 +40,9 @@ function write_log($string) {
|
||||
readfile('update.htm');
|
||||
flush();
|
||||
|
||||
$docroot = $_SERVER['DOCUMENT_ROOT'];
|
||||
$docroot = $_SERVER['DOCUMENT_ROOT'] ?: "/usr/local/emhttp";
|
||||
require_once "$docroot/plugins/dynamix/include/Wrappers.php";
|
||||
|
||||
if (isset($_POST['#file'])) {
|
||||
$file = $_POST['#file'];
|
||||
$raw_file = isset($_POST['#raw_file']) ? ($_POST['#raw_file'] === 'true') : false;
|
||||
@@ -75,7 +77,7 @@ if (isset($_POST['#file'])) {
|
||||
foreach ($keys as $key => $value) if (strlen($value) || !$cleanup) $text .= "$key=\"$value\"\n";
|
||||
}
|
||||
@mkdir(dirname($file));
|
||||
file_put_contents($file, $text);
|
||||
file_put_contents_atomic($file,$text);
|
||||
}
|
||||
}
|
||||
if (isset($_POST['#command'])) {
|
||||
|
||||
@@ -100,7 +100,7 @@ if /sbin/blkid -s TYPE $DEVICE | /bin/grep -q "btrfs" ; then
|
||||
/sbin/mount -v -t btrfs -o auto,rw,noatime,nodiratime,degraded,discard=sync $DEVICE /boot || abort "cannot mount $DEVICE"
|
||||
elif /sbin/blkid -s TYPE $DEVICE | /bin/grep -q "xfs" ; then
|
||||
NONVFAT=xfs
|
||||
/sbin/mount -v -t xfs -o auto,rw,noatime,nodiratime,discard,wsync $DEVICE /boot || abort "cannot mount $DEVICE"
|
||||
/sbin/mount -v -t xfs -o auto,rw,noatime,nodiratime,discard $DEVICE /boot || abort "cannot mount $DEVICE"
|
||||
else
|
||||
/bin/echo "Checking $DEVICE ..."
|
||||
/sbin/fsck.fat -a -w $DEVICE 2>/dev/null
|
||||
|
||||
@@ -208,7 +208,6 @@ fi
|
||||
/bin/echo "$NAME" >/etc/HOSTNAME
|
||||
/bin/echo "# Generated" >/etc/hosts
|
||||
/bin/echo "127.0.0.1 $NAME localhost" >>/etc/hosts
|
||||
/bin/echo "54.149.176.35 keys.lime-technology.com" >>/etc/hosts
|
||||
|
||||
# LimeTech - restore the configured timezone
|
||||
if [[ $timeZone == custom ]]; then
|
||||
@@ -223,7 +222,7 @@ if [[ -r /boot/config/passwd ]]; then
|
||||
if [[ $USERNAME == root ]]; then
|
||||
/bin/sed -i "s|^root:.*|root:x:0:0:$COMMENT:/root:/bin/bash|" /etc/passwd
|
||||
fi
|
||||
if (( USERID >= 1000 )); then
|
||||
if (( USERID >= 1000 )) && ! grep -q ":$USERID:" /etc/passwd ; then
|
||||
/bin/echo "$USERNAME:x:$USERID:$GROUPID:$COMMENT:/:/bin/false" >> /etc/passwd
|
||||
fi
|
||||
done </boot/config/passwd
|
||||
|
||||
@@ -30,6 +30,8 @@ DAEMON="Avahi mDNS/DNS-SD daemon"
|
||||
CALLER="avahi"
|
||||
AVAHI="/usr/sbin/avahi-daemon"
|
||||
CONF="/etc/avahi/avahi-daemon.conf"
|
||||
HOSTS="/etc/hosts"
|
||||
NAME=$(</etc/HOSTNAME)
|
||||
|
||||
# run & log functions
|
||||
. /etc/rc.d/rc.runlog
|
||||
@@ -49,6 +51,22 @@ disable(){
|
||||
sed -ri "s/^#?(use-$1)=.*/\1=no/" $CONF
|
||||
}
|
||||
|
||||
# when starting avahidaemon, add name.local to the hosts file
|
||||
add_local_to_hosts(){
|
||||
local old new
|
||||
old="^127\.0\.0\.1.*"
|
||||
new="127.0.0.1 $NAME $NAME.local localhost"
|
||||
sed -i "s/$old/$new/gm;t" $HOSTS
|
||||
}
|
||||
|
||||
# when stopping avahidaemon, remove name.local from the hosts file
|
||||
remove_local_from_hosts(){
|
||||
local old new
|
||||
old="^127\.0\.0\.1.*"
|
||||
new="127.0.0.1 $NAME localhost"
|
||||
sed -i "s/$old/$new/gm;t" $HOSTS
|
||||
}
|
||||
|
||||
avahid_running(){
|
||||
sleep 0.1
|
||||
$AVAHI -c
|
||||
@@ -67,7 +85,7 @@ avahid_start(){
|
||||
[[ $IPV4 == no ]] && disable ipv4 || enable ipv4
|
||||
[[ $IPV6 == no ]] && disable ipv6 || enable ipv6
|
||||
run $AVAHI -D
|
||||
if avahid_running; then REPLY="Started"; else REPLY="Failed"; fi
|
||||
if avahid_running; then add_local_to_hosts && REPLY="Started"; else REPLY="Failed"; fi
|
||||
else
|
||||
REPLY="Bind failed"
|
||||
fi
|
||||
@@ -82,7 +100,7 @@ avahid_stop(){
|
||||
REPLY="Already stopped"
|
||||
else
|
||||
run $AVAHI -k
|
||||
if ! avahid_running; then REPLY="Stopped"; else REPLY="Failed"; fi
|
||||
if ! avahid_running; then remove_local_from_hosts && REPLY="Stopped"; else REPLY="Failed"; fi
|
||||
fi
|
||||
log "$DAEMON... $REPLY."
|
||||
}
|
||||
|
||||
0
etc/rc.d/rc.dnsmasq
Executable file → Normal file
0
etc/rc.d/rc.dnsmasq
Executable file → Normal file
@@ -29,7 +29,7 @@ proxy_url_1="${proxy}:${port}"
|
||||
proxy_name_1="Imported from CA"
|
||||
EOF
|
||||
fi
|
||||
mv "${CAProxy}" "${CAProxy}.bak"
|
||||
mv "${CAProxy}" "${CAProxy}~"
|
||||
fi
|
||||
|
||||
# load proxy environment vars so it is used for plugin updates and the go script
|
||||
|
||||
@@ -46,7 +46,7 @@ case "${1:-start}" in
|
||||
exit 1
|
||||
fi
|
||||
# start emhttpd
|
||||
/usr/local/bin/emhttpd
|
||||
/usr/libexec/unraid/emhttpd
|
||||
;;
|
||||
'stop')
|
||||
log "Stopping web services..."
|
||||
|
||||
@@ -67,7 +67,7 @@ start() {
|
||||
for SHAREPATH in /mnt/$(basename "$POOL" .cfg)/*/ ; do
|
||||
SHARE=$(basename "$SHAREPATH")
|
||||
if grep -qs 'shareUseCache="yes"' "/boot/config/shares/${SHARE}.cfg" ; then
|
||||
find "${SHAREPATH%/}" -depth | /usr/local/bin/move $DEBUGGING
|
||||
find "${SHAREPATH%/}" -depth | /usr/libexec/unraid/move $DEBUGGING
|
||||
fi
|
||||
done
|
||||
done
|
||||
@@ -81,7 +81,7 @@ start() {
|
||||
shareCachePool="cache"
|
||||
fi
|
||||
if [[ -d "/mnt/$shareCachePool" ]]; then
|
||||
find "${SHAREPATH%/}" -depth | /usr/local/bin/move $DEBUGGING
|
||||
find "${SHAREPATH%/}" -depth | /usr/libexec/unraid/move $DEBUGGING
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -7,4 +7,4 @@ git checkout main
|
||||
PATH="$PATH:/usr/local/go/bin"
|
||||
go mod tidy
|
||||
go build
|
||||
cp /tmp/unraidwol/unraidwold /usr/local/bin
|
||||
cp /tmp/unraidwol/unraidwold /usr/libexec/unraid
|
||||
|
||||
Reference in New Issue
Block a user