Merge remote-tracking branch 'upstream/master' into VM-Avoid-spinup-of-ISOS-location-volume

This commit is contained in:
SimonFair
2024-03-28 20:46:52 +00:00
174 changed files with 4652 additions and 2444 deletions

View File

@@ -1203,7 +1203,7 @@ SSL certificate. Use this URL to access your server:
Note that use of a self-signed SSL certificate will generate a browser
warning.
Select **Strict** to enable *exclusive* use of an Unraid.net SSL
Select **Strict** to enable *exclusive* use of a myunraid.net SSL
certificate for https access (see **Provision** below). Note that a DNS
server must be reachable.
@@ -1267,10 +1267,10 @@ To adjust URLs or redirects, see the help text for "Use SSL/TLS".
:end
:mgmt_certificate_expiration_help:
**Provision** may be used to install a *free* Unraid.net SSL Certificate from
**Provision** may be used to install a *free* myunraid.net SSL Certificate from
[Let's Encrypt](https://letsencrypt.org/).
The Unraid.net SSL certificate can be used in two ways. First,
The myunraid.net SSL certificate can be used in two ways. First,
having the certificate present enables your server to respond to an
alternate URL of the form:
@@ -1284,24 +1284,20 @@ set to `*.<hash>.myunraid.net` thus validating the https connection.
You may enable this URL exclusively on your LAN by setting **Use
SSL/TLS** to **Strict**.
The second use for an Unraid.net certificate is to enable secure
remote access available through the My Servers plugin feature. Note
The second use for a myunraid.net certificate is to enable secure
remote access available through the Unraid Connect plugin feature. Note
that it is possible to use secure remote access in conjunction with
insecure local access.
After an Unraid.net SSL Certificate has been installed, two
background services are activated while the server is signed in to unraid.net:
- *updatedns* - This starts 30 seconds after server reboot has completed and contacts the Lime Technology
DNS service to register the servers local IP address. Thereafter it wakes up every 10 minutes in case
the local IP address has changed.
After a myunraid.net SSL Certificate has been installed, a
background service is activated:
- *renewcert* - This starts 60 seconds after server reboot has completed and contacts the Lime Technology
certificate renewal service to determine if your Unraid.net SSL certificate needs to be renewed.
certificate renewal service to determine if your myunraid.net SSL certificate needs to be renewed.
Thereafter it wakes up every 24 hours. If within 30 days of expiration, a new certificate is automatically
provisioned and downloaded to your server.
**Delete** may be used to delete the Unraid.net certificate file.
**Delete** may be used to delete the myunraid.net certificate file.
**nginx certificate handling details**
@@ -1309,7 +1305,7 @@ nginx makes use of two certificate files stored on the USB flash boot device:<br
- a self-signed certificate: `config/ssl/certs/<server-name>_unraid_bundle.pem`
- an Unraid.net certificate: `config/ssl/certs/certificate_bundle.pem`
- a myunraid.net certificate: `config/ssl/certs/certificate_bundle.pem`
The self-signed SSL certificate file is automatically created when nginx
starts; and re-created if the server hostname or local TLD is changed.
@@ -1493,7 +1489,7 @@ This will increase write performance but might possibly decrease read performanc
Let the server act as a central syslog server and collect syslog messages from other systems.
The server can listen on UDP, TCP or both with a selectable port number.
Syslog information is stored per IP address. That is every system gets its own syslog file.
Syslog information is stored either per IP address or per hostname. That is every system gets its own syslog file.
:end
:syslog_local_folder_help:
@@ -1501,6 +1497,15 @@ Select the share folder where the syslogs will be stored.
It is recommended that you use a share located on the cache drive to prevent array disk spinups.
:end
:syslog_remote_system_identifier_help:
Select the identifier for the remote system (used in the logfile name).
* "IP Address" uses the IP address (IPv4 or IPv6) of the sending system.
* "Hostname (from syslog message)" uses the hostname included in each syslog message.
* "Hostname (from DNS reverse lookup)" performs a DNS reverse lookup for the sending IP and uses the result.
:end
:syslog_local_rotation_help:
By default LOG rotation is disabled and will create a single LOG file of unlimited size.
@@ -1527,6 +1532,13 @@ Change this setting to YES when troubleshooting is required and it is not possib
A mirror of the syslog file is stored in the **logs** folder of the flash device.
:end
:syslog_shutdown_flash_help:
This setting is YES by default and enables the system to copy the syslog file to the USB device on shutdown or reboot.
After rebooting, the syslog from this run will be visible on Tools > Syslog > syslog-previous;
it will also be included in diagnostics as logs/syslog-previous.txt
:end
:confirm_reboot_help:
Choose if rebooting or powering down the server needs a confirmation checkbox.
:end
@@ -1668,6 +1680,14 @@ For setting the console options to show on context menus. Web will show only inb
Virtual Manager Remote Viewer will only show the Remote Viewer option. Both will show both Web and Remote Viewer.
:end
:vms_usage_help:
Show metrics for CPU both guest and host percentage, memory, disk io and network io.
:end
:vms_usage_timer_help:
Setting in seconds for metrics refresh time.
:end
:vms_acs_override_help:
*PCIe ACS override* allows various hardware components to expose themselves as isolated devices.
Typically it is sufficient to isolate *Downstream* ports.
@@ -2474,3 +2494,49 @@ If set to 'Yes' the bash history will persist reboots, set to 'No' to disable.
**Note:** Disabling and Enabling will remove the entire bash history.
:end
:WOL_intro_help:
This page allows the setup to start/resume and stop VMs, Containters(Docker and LXC) using WOL magic packets
It does not setup wake up of the Unraid server.
The process will look for the defined mac address defined within the service(VM or container) with the exception of dockers. Dockers do not have a mac address until they are running so you will need to define a user mac address to allow you to start them
If the service is paused a WOL Packet will resume.
For each service you can set: enable, disable or enable and shutdown
When the enable and shutdown is set if the service is running when the next WOL packet is recieved a shutdown will be performed.
:end
:WOL_enable_help:
If set to yes Unraidwold daemon is set to run.
:end
:WOL_run_docker_help:
If set to yes when wake on lan packets are received checks are carrried out for dockers otherwise dockers will be ignored.
:end
:WOL_run_VM_help:
If set to yes when wake on lan packets are received checks are carrried out for virtual machines otherwise virtual machines will be ignored.
:end
:WOL_run_LXC_help:
If set to yes when wake on lan packets are received checks are carrried out for LXC otherwise LXC will be ignored. The LXC plugin needs to be installed for LXC to be processed.
:end
:WOL_run_shutdown_help:
If set to yes when wake on lan packets are received checks are carrried out and if enabled for service and that service is running the entity will be shutdown. System has to be set to Enabled and Shutdwon for it to be action.
:end
:WOL_interface_help:
Specify the interface to the daemon to bind to. Some interfaces may not be able to recieve etherframe packets. Recommend to bind to a physical interface.
:end
:WOL_promiscuous_mode_help:
Enable to set the NIC not to filer packets.
:end
:WOL_log_file_help:
Default is to log to syslog but if you want a different log location set the file name.
:end

View File

@@ -1,6 +1,7 @@
<?php
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/Wrappers.php";
// add translations
extract(parse_plugin_cfg('dynamix',true));
@@ -27,6 +28,9 @@ if ($_SERVER['REQUEST_URI'] == '/logout') {
$error = _('Successfully logged out');
}
// If issue with license key redirect to Tools/Registration, otherwise go to start page
$start_page = (!empty(_var($var,'regCheck'))) ? 'Tools/Registration' : _var($var,'START_PAGE','Main');
$result = exec( "/usr/bin/passwd --status root");
if (($result === false) || (substr($result, 0, 6) !== "root P"))
include "$docroot/webGui/include/.set-password.php";

View File

@@ -111,6 +111,7 @@ function LockButton() {
}
function loadlist(init) {
timers.docker = setTimeout(function(){$('div.spinner.fixed').show('slow');},500);
docker = [];
$.get('/plugins/dynamix.docker.manager/include/DockerContainers.php',function(d) {
clearTimeout(timers.docker);
var data = d.split(/\0/);

View File

@@ -748,6 +748,7 @@ _(Categories)_:
: <input type="hidden" name="contCategory">
<select id="catSelect" size="1" multiple="multiple" style="display:none" onchange="prepareCategory();">
<optgroup label="_(Categories)_">
<option value="AI:">_(AI)_</option>
<option value="Backup:">_(Backup)_</option>
<option value="Cloud:">_(Cloud)_</option>
<option value="Crypto:">_(Crypto Currency)_</option>

View File

@@ -48,9 +48,12 @@ $port = file_exists('/sys/class/net/br0') ? 'BR0' : (file_exists('/sys/class/net
// Docker configuration file - guaranteed to exist
$docker_cfgfile = '/boot/config/docker.cfg';
if (file_exists($docker_cfgfile) && exec("grep -Pom1 '_{$port}(_[0-9]+)?=' $docker_cfgfile")=='') {
# interface has changed, update configuration
exec("sed -ri 's/_(BR0|BOND0|ETH0)(_[0-9]+)?=/_{$port}\\2=/' $docker_cfgfile");
if (file_exists($docker_cfgfile)) {
exec("grep -Pom2 '_SUBNET_|_{$port}(_[0-9]+)?=' $docker_cfgfile",$cfg);
if (isset($cfg[0]) && $cfg[0]=='_SUBNET_' && empty($cfg[1])) {
# interface has changed, update configuration
exec("sed -ri 's/_(BR0|BOND0|ETH0)(_[0-9]+)?=/_{$port}\\2=/' $docker_cfgfile");
}
}
$defaults = (array)@parse_ini_file("$docroot/plugins/dynamix.docker.manager/default.cfg");
@@ -173,7 +176,7 @@ class DockerTemplates {
}
// if after above we don't have a valid url, check for GitLab
if (empty($github_api['url'])) {
$source = file_get_contents($url);
$source = $this->download_url($url);
// the following should always exist for GitLab Community Edition or GitLab Enterprise Edition
if (preg_match("/<meta content='GitLab (Community|Enterprise) Edition' name='description'>/", $source) > 0) {
$parse = parse_url($url);
@@ -364,7 +367,7 @@ class DockerTemplates {
@copy($iconRAM,$icon);
}
if (!is_file($iconRAM)) {
exec("logger -t webGUI -- \"$contName: Could not download icon $imgUrl\"");
my_logger("$contName: Could not download icon $imgUrl");
}
return (is_file($iconRAM)) ? str_replace($docroot, '', $iconRAM) : '';
@@ -425,7 +428,7 @@ class DockerUpdate{
// DEPRECATED: Only used for Docker Index V1 type update checks
public function getRemoteVersion($image) {
[$strRepo, $strTag] = array_pad(explode(':', DockerUtil::ensureImageTag($image)),2,'');
extract(DockerUtil::parseImageTag($image));
$apiUrl = sprintf('http://index.docker.io/v1/repositories/%s/tags/%s', $strRepo, $strTag);
//$this->debug("API URL: $apiUrl");
$apiContent = $this->download_url($apiUrl);
@@ -433,7 +436,7 @@ class DockerUpdate{
}
public function getRemoteVersionV2($image) {
[$strRepo, $strTag] = array_pad(explode(':', DockerUtil::ensureImageTag($image)),2,'');
extract(DockerUtil::parseImageTag($image));
/*
* Step 1: Check whether or not the image is in a private registry, get corresponding auth data and generate manifest url
*/
@@ -718,7 +721,7 @@ class DockerClient {
$fp = stream_socket_client('unix:///var/run/docker.sock', $errno, $errstr);
if ($fp === false) {
echo "Couldn't create socket: [$errno] $errstr";
return null;
return [];
}
$protocol = $unchunk ? 'HTTP/1.0' : 'HTTP/1.1';
$out = "$method {$api}{$url} $protocol\r\nHost:127.0.0.1\r\nConnection:Close\r\n";
@@ -912,6 +915,7 @@ class DockerClient {
$c['Volumes'] = $info['HostConfig']['Binds'];
$c['Created'] = $this->humanTiming($ct['Created']);
$c['NetworkMode'] = $ct['HostConfig']['NetworkMode'];
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
[$net, $id] = array_pad(explode(':',$c['NetworkMode']),2,'');
$c['CPUset'] = $info['HostConfig']['CpusetCpus'];
$c['BaseImage'] = $ct['Labels']['BASEIMAGE'] ?? false;
@@ -975,9 +979,9 @@ class DockerClient {
$c['Id'] = $this->extractID($ct['Id']);
$c['ParentId'] = $this->extractID($ct['ParentId']);
$c['Size'] = $this->formatBytes($ct['Size']);
$c['VirtualSize'] = $this->formatBytes($ct['VirtualSize']);
$c['VirtualSize'] = $this->formatBytes($ct['VirtualSize'] ?? null);
$c['Tags'] = array_map('htmlspecialchars', $ct['RepoTags'] ?? []);
$c['Repository'] = vsprintf('%1$s/%2$s', preg_split("#[:\/]#", DockerUtil::ensureImageTag($ct['RepoTags'][0]??'')));
$c['Repository'] = DockerUtil::parseImageTag($ct['RepoTags'][0]??'')['strRepo'];
$c['usedBy'] = $this->usedBy($c['Id']);
$this::$imagesCache[$c['Id']] = $c;
}
@@ -990,18 +994,50 @@ class DockerClient {
##################################
class DockerUtil {
public static function ensureImageTag($image) {
[$strRepo, $strTag] = array_map('trim', array_pad(explode(':', $image.':'),2,''));
if (strpos($strRepo, 'sha256:') === 0) {
public static function ensureImageTag($image): string
{
extract(static::parseImageTag($image));
return "$strRepo:$strTag";
}
public static function parseImageTag($image): array
{
if (strpos($image, 'sha256:') === 0) {
// sha256 was provided instead of actual repo name so truncate it for display:
$strRepo = substr($strRepo, 7, 12);
} elseif (strpos($strRepo, '/') === false) {
// Prefix library/ if there's no author (maybe a Docker offical image?)
$strRepo = "library/$strRepo";
$strRepo = substr($image, 7, 12);
} elseif (strpos($image, '/') === false) {
return static::parseImageTag('library/' . $image);
} else {
$parsedImage = static::splitImage($image);
if (!empty($parsedImage)) {
$strRepo = $parsedImage['strRepo'];
$strTag = $parsedImage['strTag'];
} else {
// Unprocessable input
$strRepo = $image;
}
}
// Add :latest tag to image if it's absent
if (empty($strTag)) $strTag = 'latest';
return "$strRepo:$strTag";
return array_map('trim', ['strRepo' => $strRepo, 'strTag' => $strTag]);
}
private static function splitImage($image): ?array
{
if (false === preg_match('@^(.+/)*([^/:]+)(:[^:/]*)*$@', $image, $newSections) || count($newSections) < 3) {
return null;
} else {
[, $strRepo, $image, $strTag] = array_merge($newSections, ['']);
$strTag = str_replace(':','',$strTag??'');
return [
'strRepo' => $strRepo . $image,
'strTag' => $strTag,
];
}
}
public static function loadJSON($path) {

View File

@@ -110,7 +110,7 @@ foreach ($containers as $ct) {
if ($ct['BaseImage']) echo "<i class='fa fa-cubes' style='margin-right:5px'></i>".htmlspecialchars($ct['BaseImage'])."<br>";
echo _('By').": ";
$registry = $info['registry'];
[$author,$version] = my_explode(':',$ct['Image']);
['strRepo' => $author, 'strTag' => $version] = DockerUtil::parseImageTag($ct['Image']);
if ($registry) {
echo "<a href='".htmlspecialchars($registry)."' target='_blank'>".htmlspecialchars(compress($author,24))."</a>";
} else {
@@ -120,20 +120,25 @@ foreach ($containers as $ct) {
switch ($updateStatus) {
case 0:
echo "<span class='green-text' style='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
break;
case 1:
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('update ready')."</span></div>";
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
break;
case 2:
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('rebuild ready')."</span></div>";
echo "<a class='exec'><span style='white-space:nowrap;'><i class='fa fa-recycle fa-fw'></i> "._('rebuilding')."</span></a>";
break;
default:
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
if ($ct['Manager'] == "dockerman")
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
break;
case 1:
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('update ready')."</span></div>";
if ($ct['Manager'] == "dockerman")
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
else
echo "<span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('update available')."</span>";
break;
case 2:
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('rebuild ready')."</span></div>";
echo "<a class='exec'><span style='white-space:nowrap;'><i class='fa fa-recycle fa-fw'></i> "._('rebuilding')."</span></a>";
break;
default:
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
if ($ct['Manager'] == "dockerman")
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
break;
}
echo "<div class='advanced'><i class='fa fa-info-circle fa-fw'></i> ".compress(_($version),12,0)."</div></td>";
echo "<td>{$ct['NetworkMode']}</td>";

View File

@@ -14,8 +14,9 @@
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
require_once "$docroot/webGui/include/Wrappers.php";
$ncsi = exec("wget --spider --no-check-certificate -nv -T10 -t1 https://www.msftncsi.com/ncsi.txt 2>&1|grep -o 'OK'")=='OK';
$ncsi = check_network_connectivity();
$DockerTemplates = new DockerTemplates();
if ($ncsi) $DockerTemplates->downloadTemplates();
$DockerTemplates->getAllInfo($ncsi,$ncsi);

View File

@@ -59,7 +59,7 @@ function postToXML($post, $setOwnership=false) {
$xml->DonateLink = xml_encode($post['contDonateLink']);
$xml->Requires = xml_encode($post['contRequires']);
$size = is_array($post['confName']) ? count($post['confName']) : 0;
$size = is_array($post['confName']??null) ? count($post['confName']) : 0;
for ($i = 0; $i < $size; $i++) {
$Type = $post['confType'][$i];
$config = $xml->addChild('Config', xml_encode($post['confValue'][$i]));
@@ -270,6 +270,8 @@ function xmlToCommand($xml, $create_paths=false) {
$Mode = strval($config['Mode']);
if ($confType != "device" && !strlen($containerConfig)) continue;
if ($confType == "path") {
if ( ! trim($hostConfig) || ! trim($containerConfig) )
continue;
$Volumes[] = escapeshellarg($hostConfig).':'.escapeshellarg($containerConfig).':'.escapeshellarg($Mode);
if (!file_exists($hostConfig) && $create_paths) {
@mkdir($hostConfig, 0777, true);

View File

@@ -32,7 +32,7 @@ input:hover[type=button],input:hover[type=reset],input:hover[type=submit],button
input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled],button[disabled],button[type=button][disabled],a.button[disabled]
input:hover[type=button][disabled],input:hover[type=reset][disabled],input:hover[type=submit][disabled],button:hover[disabled],button:hover[type=button][disabled],a.button:hover[disabled]
input:active[type=button][disabled],input:active[type=reset][disabled],input:active[type=submit][disabled],button:active[disabled],button:active[type=button][disabled],a.button:active[disabled]
{cursor:default;color:#808080;background:-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#404040),to(#404040)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#808080),to(#808080)) 100% 100% no-repeat;background:linear-gradient(90deg,#404040 0,#808080) 0 0 no-repeat,linear-gradient(90deg,#404040 0,#808080) 0 100% no-repeat,linear-gradient(0deg,#404040 0,#404040) 0 100% no-repeat,linear-gradient(0deg,#808080 0,#808080) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%}
{opacity:0.5;cursor:default;color:#808080;background:-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#404040),to(#404040)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#808080),to(#808080)) 100% 100% no-repeat;background:linear-gradient(90deg,#404040 0,#808080) 0 0 no-repeat,linear-gradient(90deg,#404040 0,#808080) 0 100% no-repeat,linear-gradient(0deg,#404040 0,#404040) 0 100% no-repeat,linear-gradient(0deg,#808080 0,#808080) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%}
p.centered{text-align:center}
span.error{color:#D8000C;background-color:#FFBABA;display:block;width:100%}
span.warn{color:#9F6000;background-color:#FEEFB3;display:block;width:100%}

View File

@@ -1,7 +1,7 @@
#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Bergware International.
* Copyright 2014-2021, Guilherme Jardim, Eric Schultz, Jon Panozzo.
*
* This program is free software; you can redistribute it and/or
@@ -16,6 +16,7 @@
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
// this command will set the $notify array
extract(parse_plugin_cfg('dynamix', true));
// Multi-language support
@@ -43,7 +44,7 @@ if (!isset($check)) {
$DockerTemplates->getAllInfo(true);
echo " Done.";
} else {
$notify = "$docroot/webGui/scripts/notify";
$script = "$docroot/webGui/scripts/notify";
$var = (array)@parse_ini_file("/var/local/emhttp/var.ini");
$server = strtoupper(_var($var,'NAME','tower'));
$output = _var($notify,'docker_notify');
@@ -59,7 +60,7 @@ if (!isset($check)) {
$event = str_replace("&apos;","'",_("Docker")." - $name [$new]");
$subject = str_replace("&apos;","'",sprintf(_("Notice [%s] - Version update %s"),$server,$new));
$description = str_replace("&apos;","'",sprintf(_("A new version of %s is available"),$name));
exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Docker' -x");
exec("$script -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Docker' -x");
}
}
}

View File

@@ -178,11 +178,16 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
$startContainer = false;
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
// since container was already running, put it back it to a running state after update
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
$startContainer = true;
// attempt graceful stop of container first
stopContainer_nchan($Name);
}
if ( ($argv[2]??null) == "ca_docker_run_override" )
$startContainer = true;
if ( $startContainer )
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
// force kill container if still running after 10 seconds
if (empty($_GET['communityApplications'])) removeContainer_nchan($Name);
execCommand_nchan($cmd);

View File

@@ -1,4 +1,4 @@
Menu="About"
Menu="About:30"
Type="xmenu"
Title="Registration"
Icon="icon-registration"

View File

@@ -30,8 +30,7 @@ if (!document.getElementsByTagName(modalsWebComponent).length) {
$i18nHost.appendChild($modals);
}
</script>
<?
echo "
<unraid-i18n-host>
<unraid-user-profile server='" . $serverState->getServerStateJson() . "'></unraid-user-profile>
</unraid-i18n-host>";
<unraid-user-profile server="<?= $serverState->getServerStateJsonForHtmlAttr() ?>"></unraid-user-profile>
</unraid-i18n-host>

View File

@@ -15,11 +15,13 @@ $webguiGlobals = $GLOBALS;
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
/**
* ServerState class encapsulates server-related information and settings.
*
* Usage:
* ```
* require_once "$docroot/plugins/dynamix.my.servers/include/state.php";
* $serverStateClass = new ServerState();
*
* $serverStateClass->getServerState();
@@ -32,24 +34,44 @@ class ServerState
protected $webguiGlobals;
private $var;
private $flashbackupIni;
private $flashbackupStatus;
private $nginx;
private $apiKey = '';
private $apiVersion = '';
private $avatar = '';
private $email = '';
private $extraOrigins = [];
private $flashBackupActivated = '';
private $hasRemoteApikey = false;
private $registeredTime = '';
private $username = '';
private $connectPluginInstalled = '';
private $connectPluginVersion;
private $myserversFlashCfgPath;
private $myservers;
private $configErrorEnum = [
"error" => 'UNKNOWN_ERROR',
"ineligible" => 'INELIGIBLE',
"invalid" => 'INVALID',
"nokeyserver" => 'NO_KEY_SERVER',
"withdrawn" => 'WITHDRAWN',
];
private $osVersion;
private $osVersionBranch;
private $registered;
private $rebootDetails;
private $caseModel = '';
private $keyfileBase64UrlSafe = '';
private $updateOsCheck;
private $updateOsNotificationsEnabled = false;
private $updateOsResponse;
private $updateOsIgnoredReleases = [];
public $myServersFlashCfg = [];
public $myServersMemoryCfg = [];
public $host = 'unknown';
public $combinedKnownOrigins = [];
public $nginxCfg = [];
public $flashbackupStatus = [];
public $registered = false;
public $myServersMiniGraphConnected = false;
public $keyfileBase64 = '';
/**
* Constructor to initialize class properties and gather server information.
@@ -65,11 +87,42 @@ class ServerState
// echo "<pre>" . json_encode($this->webguiGlobals, JSON_PRETTY_PRINT) . "</pre>";
$this->var = (array)parse_ini_file('state/var.ini');
$this->nginxCfg = @parse_ini_file('/var/local/emhttp/nginx.ini') ?? [];
$this->flashbackupIni = '/var/local/emhttp/flashbackup.ini';
$this->flashbackupStatus = (file_exists($this->flashbackupIni)) ? @parse_ini_file($this->flashbackupIni) : [];
$this->nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
$this->osVersion = $this->var['version'];
$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->rebootDetails = new RebootDetails();
$this->keyfileBase64 = empty($this->var['regFILE']) ? null : @file_get_contents($this->var['regFILE']);
if ($this->keyfileBase64 !== false) {
$this->keyfileBase64 = @base64_encode($this->keyfileBase64);
$this->keyfileBase64UrlSafe = str_replace(['+', '/', '='], ['-', '_', ''], trim($this->keyfileBase64));
}
$this->updateOsCheck = new UnraidOsCheck();
$this->updateOsIgnoredReleases = $this->updateOsCheck->getIgnoredReleases();
$this->updateOsNotificationsEnabled = !empty(@$this->getWebguiGlobal('notify', 'unraidos'));
$this->updateOsResponse = $this->updateOsCheck->getUnraidOSCheckResult();
$this->setConnectValues();
}
/**
* Retrieve the value of a webgui global setting.
*/
public function getWebguiGlobal(string $key, string $subkey = null) {
if (!$subkey) {
return _var($this->webguiGlobals, $key, '');
}
$keyArray = _var($this->webguiGlobals, $key, []);
return _var($keyArray, $subkey, '');
}
private function setConnectValues() {
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net')) {
$this->connectPluginInstalled = 'dynamix.unraid.net.plg';
}
@@ -80,34 +133,99 @@ class ServerState
$this->connectPluginInstalled .= '_installFailed';
}
// exit early if the plugin is not installed
if (!$this->connectPluginInstalled) {
return;
}
$this->connectPluginVersion = file_exists('/var/log/plugins/dynamix.unraid.net.plg')
? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.plg 2>/dev/null'))
: (file_exists('/var/log/plugins/dynamix.unraid.net.staging.plg')
? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.staging.plg 2>/dev/null'))
: 'base-' . $this->var['version']);
$this->getMyServersCfgValues();
$this->getConnectKnownOrigins();
$this->getFlashBackupStatus();
}
private function getFlashBackupStatus() {
$flashbackupCfg = '/var/local/emhttp/flashbackup.ini';
$this->flashbackupStatus = (file_exists($flashbackupCfg)) ? @parse_ini_file($flashbackupCfg) : [];
$this->flashBackupActivated = empty($this->flashbackupStatus['activated']) ? '' : 'true';
}
private function getMyServersCfgValues() {
/**
* @todo can we read this from somewhere other than the flash? Connect page uses this path and /boot/config/plugins/dynamix.my.servers/myservers.cfg…
* - $myservers_memory_cfg_path ='/var/local/emhttp/myservers.cfg';
* - $mystatus = (file_exists($myservers_memory_cfg_path)) ? @parse_ini_file($myservers_memory_cfg_path) : [];
*/
$this->myserversFlashCfgPath = '/boot/config/plugins/dynamix.my.servers/myservers.cfg';
$this->myservers = file_exists($this->myserversFlashCfgPath) ? @parse_ini_file($this->myserversFlashCfgPath, true) : [];
$flashCfgPath = '/boot/config/plugins/dynamix.my.servers/myservers.cfg';
$this->myServersFlashCfg = file_exists($flashCfgPath) ? @parse_ini_file($flashCfgPath, true) : [];
// ensure some vars are defined here so we don't have to test them later
if (empty($this->myServersFlashCfg['remote']['apikey'])) {
$this->myServersFlashCfg['remote']['apikey'] = "";
}
if (empty($this->myServersFlashCfg['remote']['wanaccess'])) {
$this->myServersFlashCfg['remote']['wanaccess'] = "no";
}
if (empty($this->myServersFlashCfg['remote']['wanport'])) {
$this->myServersFlashCfg['remote']['wanport'] = 33443;
}
if (empty($this->myServersFlashCfg['remote']['upnpEnabled'])) {
$this->myServersFlashCfg['remote']['upnpEnabled'] = "no";
}
if (empty($this->myServersFlashCfg['remote']['dynamicRemoteAccessType'])) {
$this->myServersFlashCfg['remote']['dynamicRemoteAccessType'] = "DISABLED";
}
$this->osVersion = $this->var['version'];
$this->osVersionBranch = trim(@exec('plugin category /var/log/plugins/unRAIDServer.plg') ?? 'stable');
$this->registered = !empty($this->myservers['remote']['apikey']) && $this->connectPluginInstalled;
$caseModelFile = '/boot/config/plugins/dynamix/case-model.cfg';
$this->caseModel = file_exists($caseModelFile) ? file_get_contents($caseModelFile) : '';
$this->rebootDetails = new RebootDetails();
$this->apiKey = $this->myServersFlashCfg['upc']['apikey'] ?? '';
$this->apiVersion = $this->myServersFlashCfg['api']['version'] ?? '';
$this->avatar = (!empty($this->myServersFlashCfg['remote']['avatar']) && $this->connectPluginInstalled) ? $this->myServersFlashCfg['remote']['avatar'] : '';
$this->email = $this->myServersFlashCfg['remote']['email'] ?? '';
$this->hasRemoteApikey = !empty($this->myServersFlashCfg['remote']['apikey']);
$this->registered = !empty($this->myServersFlashCfg['remote']['apikey']) && $this->connectPluginInstalled;
$this->registeredTime = $this->myServersFlashCfg['remote']['regWizTime'] ?? '';
$this->username = $this->myServersFlashCfg['remote']['username'] ?? '';
}
/**
* Retrieve the value of a webgui global setting.
*/
public function getWebguiGlobal(string $key) {
return $this->webguiGlobals[$key];
private function getConnectKnownOrigins() {
/**
* Allowed origins warning displayed when the current webGUI URL is NOT included in the known lists of allowed origins.
* Include localhost in the test, but only display HTTP(S) URLs that do not include localhost.
*/
$this->host = $_SERVER['HTTP_HOST'] ?? "unknown";
$memoryCfgPath = '/var/local/emhttp/myservers.cfg';
$this->myServersMemoryCfg = (file_exists($memoryCfgPath)) ? @parse_ini_file($memoryCfgPath) : [];
$this->myServersMiniGraphConnected = (($this->myServersMemoryCfg['minigraph']??'') === 'CONNECTED');
$allowedOrigins = $this->myServersMemoryCfg['allowedOrigins'] ?? "";
$extraOrigins = $this->myServersFlashCfg['api']['extraOrigins'] ?? "";
$combinedOrigins = $allowedOrigins . "," . $extraOrigins; // combine the two strings for easier searching
$combinedOrigins = str_replace(" ", "", $combinedOrigins); // replace any spaces with nothing
$hostNotKnown = stripos($combinedOrigins, $this->host) === false; // check if the current host is in the combined list of origins
if ($extraOrigins) {
$this->extraOrigins = explode(",", $extraOrigins);
}
if ($hostNotKnown) {
$this->combinedKnownOrigins = explode(",", $combinedOrigins);
if ($this->combinedKnownOrigins) {
foreach($this->combinedKnownOrigins as $key => $origin) {
if ( (strpos($origin, "http") === false) || (strpos($origin, "localhost") !== false) ) {
// clean up $this->combinedKnownOrigins, only display warning if origins still remain to display
unset($this->combinedKnownOrigins[$key]);
}
}
// for some reason the unset creates an associative array, so reindex the array with just the values. Otherwise we get an object passed to the UPC JS instead of an array.
if ($this->combinedKnownOrigins) {
$this->combinedKnownOrigins = array_values($this->combinedKnownOrigins);
}
}
}
}
/**
@@ -118,76 +236,102 @@ class ServerState
public function getServerState()
{
$serverState = [
"apiKey" => $this->myservers['upc']['apikey'] ?? '',
"apiVersion" => $this->myservers['api']['version'] ?? '',
"avatar" => (!empty($this->myservers['remote']['avatar']) && $this->connectPluginInstalled) ? $this->myservers['remote']['avatar'] : '',
"apiKey" => $this->apiKey,
"apiVersion" => $this->apiVersion,
"avatar" => $this->avatar,
"caseModel" => $this->caseModel,
"config" => [
'valid' => ($this->var['configValid'] === 'yes'),
'error' => isset($this->configErrorEnum[$this->var['configValid']]) ? $this->configErrorEnum[$this->var['configValid']] : 'UNKNOWN_ERROR',
'error' => isset($this->configErrorEnum[$this->var['configValid']]) ? $this->configErrorEnum[$this->var['configValid']] : null,
],
"connectPluginInstalled" => $this->connectPluginInstalled,
"connectPluginVersion" => $this->connectPluginVersion,
"csrf" => $this->var['csrf_token'],
"dateTimeFormat" => [
"date" => @$this->getWebguiGlobal('display')['date'] ?? '',
"time" => @$this->getWebguiGlobal('display')['time'] ?? '',
"date" => @$this->getWebguiGlobal('display', 'date') ?? '',
"time" => @$this->getWebguiGlobal('display', 'time') ?? '',
],
"description" => $this->var['COMMENT'] ? htmlspecialchars($this->var['COMMENT']) : '',
"description" => $this->var['COMMENT'] ? htmlspecialchars($this->var['COMMENT'], ENT_HTML5, 'UTF-8') : '',
"deviceCount" => $this->var['deviceCount'],
"email" => $this->myservers['remote']['email'] ?? '',
"email" => $this->email,
"expireTime" => 1000 * (($this->var['regTy'] === 'Trial' || strstr($this->var['regTy'], 'expired')) ? $this->var['regTm2'] : 0),
"extraOrigins" => explode(',', $this->myservers['api']['extraOrigins'] ?? ''),
"extraOrigins" => $this->extraOrigins,
"flashProduct" => $this->var['flashProduct'],
"flashVendor" => $this->var['flashVendor'],
"flashBackupActivated" => empty($this->flashbackupStatus['activated']) ? '' : 'true',
"flashBackupActivated" => $this->flashBackupActivated,
"guid" => $this->var['flashGUID'],
"hasRemoteApikey" => !empty($this->myservers['remote']['apikey']),
"internalPort" => $_SERVER['SERVER_PORT'],
"keyfile" => empty($this->var['regFILE']) ? '' : str_replace(['+', '/', '='], ['-', '_', ''], trim(base64_encode(@file_get_contents($this->var['regFILE'])))),
"hasRemoteApikey" => $this->hasRemoteApikey,
"internalPort" => _var($_SERVER, 'SERVER_PORT'),
"keyfile" => $this->keyfileBase64UrlSafe,
"lanIp" => ipaddr(),
"locale" => (!empty($_SESSION) && $_SESSION['locale']) ? $_SESSION['locale'] : 'en_US',
"model" => $this->var['SYS_MODEL'],
"name" => htmlspecialchars($this->var['NAME']),
"osVersion" => $this->var['version'],
"model" => $this->var['SYS_MODEL'] ? htmlspecialchars($this->var['SYS_MODEL'], ENT_HTML5, 'UTF-8') : '',
"name" => htmlspecialchars($this->var['NAME'], ENT_HTML5, 'UTF-8'),
"osVersion" => $this->osVersion,
"osVersionBranch" => $this->osVersionBranch,
"protocol" => $_SERVER['REQUEST_SCHEME'],
"protocol" => _var($_SERVER, 'REQUEST_SCHEME'),
"rebootType" => $this->rebootDetails->getRebootType(),
"regDev" => @(int)$this->var['regDev'] ?? 0,
"regDevs" => @(int)$this->var['regDevs'] ?? 0,
"regGen" => @(int)$this->var['regGen'],
"regGuid" => @$this->var['regGUID'] ?? '',
"regTo" => @htmlspecialchars($this->var['regTo']) ?? '',
"regTo" => @htmlspecialchars($this->var['regTo'], ENT_HTML5, 'UTF-8') ?? '',
"regTm" => $this->var['regTm'] ? @$this->var['regTm'] * 1000 : '', // JS expects milliseconds
"regTy" => @$this->var['regTy'] ?? '',
"regExp" => $this->var['regExp'] ? @$this->var['regExp'] * 1000 : '', // JS expects milliseconds
"registered" => $this->registered,
"registeredTime" => $this->myservers['remote']['regWizTime'] ?? '',
"site" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'],
"registeredTime" => $this->registeredTime,
"site" => _var($_SERVER, 'REQUEST_SCHEME') . "://" . _var($_SERVER, 'HTTP_HOST'),
"state" => strtoupper(empty($this->var['regCheck']) ? $this->var['regTy'] : $this->var['regCheck']),
"theme" => [
"banner" => !empty($this->getWebguiGlobal('display')['banner']),
"bannerGradient" => $this->getWebguiGlobal('display')['showBannerGradient'] === 'yes' ?? false,
"bgColor" => ($this->getWebguiGlobal('display')['background']) ? '#' . $this->getWebguiGlobal('display')['background'] : '',
"descriptionShow" => (!empty($this->getWebguiGlobal('display')['headerdescription']) && $this->getWebguiGlobal('display')['headerdescription'] !== 'no'),
"metaColor" => ($this->getWebguiGlobal('display')['headermetacolor'] ?? '') ? '#' . $this->getWebguiGlobal('display')['headermetacolor'] : '',
"name" => $this->getWebguiGlobal('display')['theme'],
"textColor" => ($this->getWebguiGlobal('display')['header']) ? '#' . $this->getWebguiGlobal('display')['header'] : '',
"banner" => !empty($this->getWebguiGlobal('display', 'banner')),
"bannerGradient" => $this->getWebguiGlobal('display', 'showBannerGradient') === 'yes' ?? false,
"bgColor" => ($this->getWebguiGlobal('display', 'background')) ? '#' . $this->getWebguiGlobal('display', 'background') : '',
"descriptionShow" => (!empty($this->getWebguiGlobal('display', 'headerdescription')) && $this->getWebguiGlobal('display', 'headerdescription') !== 'no'),
"metaColor" => ($this->getWebguiGlobal('display', 'headermetacolor') ?? '') ? '#' . $this->getWebguiGlobal('display', 'headermetacolor') : '',
"name" => $this->getWebguiGlobal('display', 'theme'),
"textColor" => ($this->getWebguiGlobal('display', 'header')) ? '#' . $this->getWebguiGlobal('display', 'header') : '',
],
"ts" => time(),
"uptime" => 1000 * (time() - round(strtok(exec("cat /proc/uptime"), ' '))),
"username" => $this->myservers['remote']['username'] ?? '',
"wanFQDN" => $this->nginx['NGINX_WANFQDN'] ?? '',
"username" => $this->username,
"wanFQDN" => @$this->nginxCfg['NGINX_WANFQDN'] ?? '',
];
if ($this->combinedKnownOrigins) {
$serverState['combinedKnownOrigins'] = $this->combinedKnownOrigins;
}
if ($this->updateOsIgnoredReleases) {
$serverState['updateOsIgnoredReleases'] = $this->updateOsIgnoredReleases;
}
if ($this->updateOsNotificationsEnabled) {
$serverState['updateOsNotificationsEnabled'] = $this->updateOsNotificationsEnabled;
}
if ($this->updateOsResponse) {
$serverState['updateOsResponse'] = $this->updateOsResponse;
}
return $serverState;
}
/**
* Retrieve the server information as a JSON string
* Retrieve the server information as JSON
*
* @return string A JSON string containing server information.
* @return string
*/
public function getServerStateJson() {
return json_encode($this->getServerState());
}
/**
* Retrieve the server information as JSON string with converted special characters to HTML entities
*
* @return string
*/
public function getServerStateJsonForHtmlAttr() {
$json = json_encode($this->getServerState());
return htmlspecialchars($json, ENT_QUOTES, 'UTF-8');
}
}

View File

@@ -49,336 +49,299 @@ class WebComponentTranslations
private function initializeTranslations()
{
$this->translations[$_SESSION['locale'] ?? 'en_US'] = [
'LAN IP' => _('LAN IP'),
'LAN IP {0}' => sprintf(_('LAN IP %s'), '{0}'),
'LAN IP Copied' => _('LAN IP Copied'),
'Click to Copy LAN IP {0}' => sprintf(_('Click to copy LAN IP %s'), '{0}'),
'Trial Key Expired at {0}' => sprintf(_('Trial Key Expired at %s'), '{0}'),
'Trial Key Expires at {0}' => sprintf(_('Trial Key Expires at %s'), '{0}'),
'Trial Key Expired {0}' => sprintf(_('Trial Key Expired %s'), '{0}'),
'Trial Key Expires in {0}' => sprintf(_('Trial Key Expires in %s'), '{0}'),
'Server Up Since {0}' => sprintf(_('Server Up Since %s'), '{0}'),
'Uptime {0}' => sprintf(_('Uptime %s'), '{0}'),
'year' => sprintf(_('%s year'), '{n}') . ' | ' . sprintf(_('%s years'), '{n}'),
'month' => sprintf(_('%s month'), '{n}') . ' | ' . sprintf(_('%s months'), '{n}'),
'day' => sprintf(_('%s day'), '{n}') . ' | ' . sprintf(_('%s days'), '{n}'),
'hour' => sprintf(_('%s hour'), '{n}') . ' | ' . sprintf(_('%s hours'), '{n}'),
'minute' => sprintf(_('%s minute'), '{n}') . ' | ' . sprintf(_('%s minutes'), '{n}'),
'second' => sprintf(_('%s second'), '{n}') . ' | ' . sprintf(_('%s seconds'), '{n}'),
'{0} {1} Key…' => sprintf(_('%1s %2s Key…'), '{0}', '{1}'),
'{0} devices' => sprintf(_('%s devices'), '{0}'),
'{0} out of {1} allowed devices upgrade your key to support more devices' => sprintf(_('%1s out of %2s allowed devices upgrade your key to support more devices'), '{0}', '{1}'),
'{0} out of {1} devices' => sprintf(_('%1s out of %2s devices'), '{0}', '{1}'),
'{0} Release Notes' => sprintf(_('%s Release Notes'), '{0}'),
'{0} Signed In Successfully' => sprintf(_('%s Signed In Successfully'), '{0}'),
'{0} Signed Out Successfully' => sprintf(_('%s Signed Out Successfully'), '{0}'),
'{0} Update Available' => sprintf(_('%s Update Available'), '{0}'),
'{1} Key {0} Successfully' => sprintf(_('%2s Key %1s Successfully'), '{0}', '{1}'),
'<p>It is not possible to use a Trial key with an existing Unraid OS installation.</p><p>You may purchase a license key corresponding to this USB Flash device to continue using this installation.</p>' => '<p>' . _('It is not possible to use a Trial key with an existing Unraid OS installation') . '</p><p>' . _('You may purchase a license key corresponding to this USB Flash device to continue using this installation.') . '</p>',
'<p>Please refresh the page to ensure you load your latest configuration</p>' => '<p>' . _('Please refresh the page to ensure you load your latest configuration') . '</p>',
'<p>Register for Connect by signing in to your Unraid.net account</p>' => '<p>' . _('Register for Connect by signing in to your Unraid.net account') . '</p>',
'<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('The license key file does not correspond to the USB Flash boot device.') . ' ' . _('Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key') . '</p><p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
'<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>' => '<p>' . _('The license key file does not correspond to the USB Flash boot device.') . ' ' . _('Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key') . '</p><p>' . _('Your Unraid registration key is ineligible for replacement as it is blacklisted.') . '</p>',
'<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>' => '<p>' . _('The license key file does not correspond to the USB Flash boot device.') . ' ' . _('Please copy the correct key file to the /config directory on your USB Flash boot device') . '</p><p>' . _('You may also attempt to Purchase or Replace your key.') . '</p>',
'<p>There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device.</p><p>Alternately you may purchase a license key for this USB flash device.</p><p>If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.</p>' => '<p>' . _('There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device.') . ' ' . _('Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device') . '</p><p>' . _('Alternately you may purchase a license key for this USB flash device') . '</p><p>' . _('If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.') . '</p>',
'<p>There is a physical problem accessing your USB Flash boot device</p>' => '<p>' . _('There is a physical problem accessing your USB Flash boot device') . '</p>',
'<p>There is a problem with your USB Flash device</p>' => '<p>' . _('There is a problem with your USB Flash device') . '</p>',
'<p>This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.</p><p>A USB Flash device may also be blacklisted if we discover the serial number is not unique this is common with USB card readers.</p>' => '<p>' . _('This USB Flash boot device has been blacklisted.') . ' ' . _('This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.') . '</p><p>' . _('A USB Flash device may also be blacklisted if we discover the serial number is not unique this is common with USB card readers.') . '</p>',
'<p>This USB Flash device has an invalid GUID. Please try a different USB Flash device</p>' => '<p>' . _('This USB Flash device has an invalid GUID. Please try a different USB Flash device') . '</p>',
'<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>' => '<p>' . _('To continue using Unraid OS you may purchase a license key.') . ' ' . _('Alternately, you may request a Trial extension.') . '</p>',
'<p>To support more storage devices as your server grows, click Upgrade Key.</p>' => '<p>' . _('To support more storage devices as your server grows, click Upgrade Key.') . '</p>',
'<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>' => '<p>' . _('You have used all your Trial extensions.') . ' ' . _('To continue using Unraid OS you may purchase a license key.') . '</p>',
'<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>' => '<p>' . _('Your **Trial** key includes all the functionality and device support of a **Pro** key') . '</p><p>' . _('After your **Trial** has reached expiration, your server *still functions normally* until the next time you Stop the array or reboot your server') . '</p><p>' . _('At that point you may either purchase a license key or request a *Trial* extension.') . '</p>',
'<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>' => '<p>' . _('Your license key file is corrupted or missing.') . ' ' . _('The key file should be located in the /config directory on your USB Flash boot device') . '</p><p>' . _('If you do not have a backup copy of your license key file you may attempt to recover your key with your Unraid.net account') . '</p><p>' . _('If this was an expired Trial installation, you may purchase a license key.') . '</p>',
'<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>' => '<p>' . _('Your license key file is corrupted or missing.') . ' ' . _('The key file should be located in the /config directory on your USB Flash boot device') . '</p><p>' . _('If you do not have a backup copy of your license key file you may attempt to recover your key with your Unraid.net account') . '</p><p>' . _('If this was an expired Trial installation, you may purchase a license key.') . '</p>',
'<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class="list-disc pl-16px"><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>' => '<p>' . _('Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key.') . ' ' . _('A <em>Trial</em> key provides all the functionality of a Pro Registration key.') . '</p><p>' . _('Registration keys are bound to your USB Flash boot device serial number (GUID).') . ' ' . _('Please use a high quality name brand device at least 1GB in size.') . '</p><p>' . _('Note: USB memory card readers are generally not supported because most do not present unique serial numbers.') . '</p><p>' . _('*Important:*') . '</p><ul class="list-disc pl-16px"><li>' . _('Please make sure your server time is accurate to within 5 minutes') . '</li><li>' . _('Please make sure there is a DNS server specified') . '</li>>' . '</ul>',
'<p>Your Trial key requires an internet connection.</p><p><a href="/Settings/NetworkSettings" class="underline">Please check Settings > Network</a></p>' => '<p>' . _('Your Trial key requires an internet connection') . '</p><p><a href="/Settings/NetworkSettings" class="underline">' . _('Please check Settings > Network') . '</a></p>',
'<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
'A Trial key provides all the functionality of a Pro Registration key' => _('A Trial key provides all the functionality of a Pro Registration key'),
'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'),
'ago' => _('ago'),
'Purchase' => _('Purchase'),
'Upgrade' => _('Upgrade'),
'Fix Error' => _('Fix Error'),
'Get Started' => _('Get Started'),
'Trial Expired, see options below' => _('Trial Expired, see options below'),
'Learn more about the error' => _('Learn more about the error'),
'Close Dropdown' => _('Close Dropdown'),
'Open Dropdown' => _('Open Dropdown'),
'Thank you for installing Connect!' => _('Thank you for installing Connect!'),
'Sign In to your Unraid.net account to get started' => _('Sign In to your Unraid.net account to get started'),
'Go to Connect' => _('Go to Connect'),
'Opens Connect in new tab' => _('Opens Connect in new tab'),
'Manage Unraid.net Account' => _('Manage Unraid.net Account'),
'Manage Unraid.net Account in new tab' => _('Manage Unraid.net Account in new tab'),
'Settings' => _('Settings'),
'Go to Connect plugin settings' => _('Go to Connect plugin settings'),
'Enhance your Unraid experience with Connect' => _('Enhance your Unraid experience with Connect'),
'Beta' => _('Beta'),
'Loading' => _('Loading'),
'Restarting unraid-api…' => _('Restarting unraid-api…'),
'unraid-api is offline' => _('unraid-api is offline'),
'Introducing Unraid Connect' => _('Introducing Unraid Connect'),
'Enhance your Unraid experience' => _('Enhance your Unraid experience'),
'Connected' => _('Connected'),
'Dynamic Remote Access' => _('Dynamic Remote Access'),
'Toggle on/off server accessibility with dynamic remote access. Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.' => _('Toggle on/off server accessibility with dynamic remote access.') . ' ' . _('Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.'),
'Manage Your Server Within Connect' => _('Manage Your Server Within Connect'),
'Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.' => _('Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI.') . ' ' . _('Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.'),
'Deep Linking' => _('Deep Linking'),
'The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.' => _('The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.'),
'Online Flash Backup' => _('Online Flash Backup'),
'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.'),
'Real-time Monitoring' => _('Real-time Monitoring'),
'Get an overview of your server\'s state, storage space, apps and VMs status, and more.' => _('Get an overview of your server\'s state, storage space, apps and VMs status, and more.'),
'Customizable Dashboard Tiles' => _('Customizable Dashboard Tiles'),
'Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.' => _('Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.'),
'License Management' => _('License Management'),
'Manage your license keys at any time via the My Keys section.' => _('Manage your license keys at any time via the My Keys section.'),
'Plus more on the way' => _('Plus more on the way'),
'All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.' => _('All you need is an active internet connection, an Unraid.net account, and the Connect plugin.') . ' ' . _('Get started by installing the plugin.'),
'Attached Storage Devices' => _('Attached Storage Devices'),
'Backing up...this may take a few minutes' => _('Backing up...this may take a few minutes'),
'Basic' => _('Basic'),
'Begin downgrade to {0}' => sprintf(_('Begin downgrade to %s'), '{0}'),
'Beta' => _('Beta'),
'Blacklisted USB Flash GUID' => _('Blacklisted USB Flash GUID'),
'BLACKLISTED' => _('BLACKLISTED'),
'Calculating trial expiration…' => _('Calculating trial expiration…'),
'Callback redirect type not present or incorrect' => _('Callback redirect type not present or incorrect'),
'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'),
'Check for OS Updates' => _('Check for OS Updates'),
'check for OS updates' => _('check for OS updates'),
'Check for Prereleases' => _('Check for Prereleases'),
'Checking WAN IPs…' => _('Checking WAN IPs…'),
'Checking...' => _('Checking...'),
'Checkout the Connect Documentation' => _('Checkout the Connect Documentation'),
'No thanks' => _('No thanks'),
'Learn more' => _('Learn more'),
'Install Connect' => _('Install Connect'),
'Click to close modal' => _('Click to close modal'),
'Click to Copy LAN IP {0}' => sprintf(_('Click to copy LAN IP %s'), '{0}'),
'Close Dropdown' => _('Close Dropdown'),
'Close Modal' => _('Close Modal'),
'Close' => _('Close'),
'Reload' => _('Reload'),
'Unraid logo animating with a wave like effect' => _('Unraid logo animating with a wave like effect'),
'Click to close modal' => _('Click to close modal'),
'Error' => _('Error'),
'Performing actions' => _('Performing actions'),
'Success!' => _('Success!'),
'Something went wrong' => _('Something went wrong'),
'Please keep this window open while we perform some actions' => _('Please keep this window open while we perform some actions'),
'You\'re one step closer to enhancing your Unraid experience' => _('You\'re one step closer to enhancing your Unraid experience'),
'Thank you for purchasing an Unraid {0} Key!' => sprintf(_('Thank you for purchasing an Unraid %s Key!'), '{0}'),
'Thank you for upgrading to an Unraid {0} Key!' => sprintf(_('Thank you for upgrading to an Unraid %s Key!'), '{0}'),
'Your {0} Key has been replaced!' => sprintf(_('Your %s Key has been replaced!'), '{0}'),
'Your Trial key has been extended!' => _('Your Trial key has been extended!'),
'Configure Connect Features' => _('Configure Connect Features'),
'Confirm and start update' => _('Confirm and start update'),
'Connected' => _('Connected'),
'Contact Support' => _('Contact Support'),
'Copied' => _('Copied'),
'Copy Key URL' => _('Copy Key URL'),
'Copy your Key URL: {0}' => sprintf(_('Copy your Key URL: %s'), '{0}'),
'Then go to Tools > Registration to manually install it' => _('Then go to Tools > Registration to manually install it'),
'Enhance your experience with Unraid Connect' => _('Enhance your experience with Unraid Connect'),
'Sign In to utilize Unraid Connect' => _('Sign In to utilize Unraid Connect'),
'Configure Connect Features' => _('Configure Connect Features'),
'The primary method of support for Unraid Connect is through our forums and Discord.' => _('The primary method of support for Unraid Connect is through our forums and Discord.'),
'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.'),
'The logs may contain sensitive information so do not post them publicly.' => _('The logs may contain sensitive information so do not post them publicly.'),
'Download unraid-api Logs' => _('Download unraid-api Logs'),
'Unraid Connect Forums' => _('Unraid Connect Forums'),
'Unraid Discord' => _('Unraid Discord'),
'Unraid Contact Page' => _('Unraid Contact Page'),
'DNS issue, unable to resolve wanip4.unraid.net' => _('DNS issue, unable to resolve wanip4.unraid.net'),
'Unable to fetch client WAN IPv4' => _('Unable to fetch client WAN IPv4'),
'Checking WAN IPs…' => _('Checking WAN IPs…'),
'Remark: your WAN IPv4 is {0}' => sprintf(_('Remark: your WAN IPv4 is %s'), '{0}'),
'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}'),
'This may indicate a complex network that will not work with this Remote Access solution.' => _('This may indicate a complex network that will not work with this Remote Access solution.'),
'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.'),
'Ready to update Connect account configuration' => _('Ready to update Connect account configuration'),
'Signing in {0}…' => sprintf(_('Signing in %s…'), '{0}'),
'Signing out {0}…' => sprintf(_('Signing out %s…'), '{0}'),
'{0} Signed In Successfully' => sprintf(_('%s Signed In Successfully'), '{0}'),
'{0} Signed Out Successfully' => sprintf(_('%s Signed Out Successfully'), '{0}'),
'Sign In Failed' => _('Sign In Failed'),
'Sign Out Failed' => _('Sign Out Failed'),
'Failed to update Connect account configuration' => _('Failed to update Connect account configuration'),
'Callback redirect type not present or incorrect' => _('Callback redirect type not present or incorrect'),
'Failed to install key' => _('Failed to install key'),
'Ready to Install Key' => _('Ready to Install Key'),
'Installing Extended Trial' => _('Installing Extended Trial'),
'Installing Recovered' => _('Installing Recovered'),
'Installing Replaced' => _('Installing Replaced'),
'{0} {1} Key…' => sprintf(_('%1s %2s Key…'), '{0}', '{1}'),
'{1} Key {0} Successfully' => sprintf(_('%2s Key %1s Successfully'), '{0}', '{1}'),
'Failed to {0} {1} Key' => sprintf(_('Failed to %1s %2s Key'), '{0}', '{1}'),
'Purchase Key' => _('Purchase Key'),
'Upgrade Key' => _('Upgrade Key'),
'Recover Key' => _('Recover Key'),
'Redeem Activation Code' => _('Redeem Activation Code'),
'Replace Key' => _('Replace Key'),
'Sign In with Unraid.net Account' => _('Sign In with Unraid.net Account'),
'Sign Out of Unraid.net' => _('Sign Out of Unraid.net'),
'Extend Trial' => _('Extend Trial'),
'Start Free 30 Day Trial' => _('Start Free 30 Day Trial'),
'Go to Management Access Now' => _('Go to Management Access Now'),
'Contact Support' => _('Contact Support'),
'Learn More' => _('Learn More'),
'No Keyfile' => _('No Keyfile'),
'Let\'s Unleash your Hardware!' => _('Let\'s Unleash your Hardware!'),
'<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class="list-disc pl-16px"><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>' => '<p>' . _('Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key.') . ' ' . _('A <em>Trial</em> key provides all the functionality of a Pro Registration key.') . '</p><p>' . _('Registration keys are bound to your USB Flash boot device serial number (GUID).') . ' ' . _('Please use a high quality name brand device at least 1GB in size.') . '</p><p>' . _('Note: USB memory card readers are generally not supported because most do not present unique serial numbers.') . '</p><p>' . _('*Important:*') . '</p><ul class="list-disc pl-16px"><li>' . _('Please make sure your server time is accurate to within 5 minutes') . '</li><li>' . _('Please make sure there is a DNS server specified') . '</li>>' . '</ul>',
'Trial' => _('Trial'),
'Thank you for choosing Unraid OS!' => _('Thank you for choosing Unraid OS!'),
'<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>' => '<p>' . _('Your **Trial** key includes all the functionality and device support of a **Pro** key') . '</p><p>' . _('After your **Trial** has reached expiration, your server *still functions normally* until the next time you Stop the array or reboot your server') . '</p><p>' . _('At that point you may either purchase a license key or request a *Trial* extension.') . '</p>',
'Trial Expired' => _('Trial Expired'),
'Your Trial has expired' => _('Your Trial has expired'),
'<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>' => '<p>' . _('To continue using Unraid OS you may purchase a license key.') . ' ' . _('Alternately, you may request a Trial extension.') . '</p>',
'<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>' => '<p>' . _('You have used all your Trial extensions.') . ' ' . _('To continue using Unraid OS you may purchase a license key.') . '</p>',
'Basic' => _('Basic'),
'<p>Register for Connect by signing in to your Unraid.net account</p>' => '<p>' . _('Register for Connect by signing in to your Unraid.net account') . '</p>',
'<p>To support more storage devices as your server grows, click Upgrade Key.</p>' => '<p>' . _('To support more storage devices as your server grows, click Upgrade Key.') . '</p>',
'Plus' => _('Plus'),
'Pro' => _('Pro'),
'Flash GUID Error' => _('Flash GUID Error'),
'Registration key / USB Flash GUID mismatch' => _('Registration key / USB Flash GUID mismatch'),
'<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
'<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>' => '<p>' . _('The license key file does not correspond to the USB Flash boot device.') . ' ' . _('Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key') . '</p><p>' . _('Your Unraid registration key is ineligible for replacement as it is blacklisted.') . '</p>',
'<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('The license key file does not correspond to the USB Flash boot device.') . ' ' . _('Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key') . '</p><p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
'<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>' => '<p>' . _('The license key file does not correspond to the USB Flash boot device.') . ' ' . _('Please copy the correct key file to the /config directory on your USB Flash boot device') . '</p><p>' . _('You may also attempt to Purchase or Replace your key.') . '</p>',
'Multiple License Keys Present' => _('Multiple License Keys Present'),
'<p>There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device.</p><p>Alternately you may purchase a license key for this USB flash device.</p><p>If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.</p>' => '<p>' . _('There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device.') . ' ' . _('Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device') . '</p><p>' . _('Alternately you may purchase a license key for this USB flash device') . '</p><p>' . _('If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.') . '</p>',
'Missing key file' => _('Missing key file'),
'<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>' => '<p>' . _('Your license key file is corrupted or missing.') . ' ' . _('The key file should be located in the /config directory on your USB Flash boot device') . '</p><p>' . _('If you do not have a backup copy of your license key file you may attempt to recover your key with your Unraid.net account') . '</p><p>' . _('If this was an expired Trial installation, you may purchase a license key.') . '</p>',
'<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>' => '<p>' . _('Your license key file is corrupted or missing.') . ' ' . _('The key file should be located in the /config directory on your USB Flash boot device') . '</p><p>' . _('If you do not have a backup copy of your license key file you may attempt to recover your key with your Unraid.net account') . '</p><p>' . _('If this was an expired Trial installation, you may purchase a license key.') . '</p>',
'Invalid installation' => _('Invalid installation'),
'<p>It is not possible to use a Trial key with an existing Unraid OS installation.</p><p>You may purchase a license key corresponding to this USB Flash device to continue using this installation.</p>' => '<p>' . _('It is not possible to use a Trial key with an existing Unraid OS installation') . '</p><p>' . _('You may purchase a license key corresponding to this USB Flash device to continue using this installation.') . '</p>',
'No USB flash configuration data' => _('No USB flash configuration data'),
'<p>There is a problem with your USB Flash device</p>' => '<p>' . _('There is a problem with your USB Flash device') . '</p>',
'No Flash' => _('No Flash'),
'Cannot access your USB Flash boot device' => _('Cannot access your USB Flash boot device'),
'<p>There is a physical problem accessing your USB Flash boot device</p>' => '<p>' . _('There is a physical problem accessing your USB Flash boot device') . '</p>',
'BLACKLISTED' => _('BLACKLISTED'),
'Blacklisted USB Flash GUID' => _('Blacklisted USB Flash GUID'),
'<p>This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.</p><p>A USB Flash device may also be blacklisted if we discover the serial number is not unique this is common with USB card readers.</p>' => '<p>' . _('This USB Flash boot device has been blacklisted.') . ' ' . _('This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.') . '</p><p>' . _('A USB Flash device may also be blacklisted if we discover the serial number is not unique this is common with USB card readers.') . '</p>',
'USB Flash device error' => _('USB Flash device error'),
'<p>This USB Flash device has an invalid GUID. Please try a different USB Flash device</p>' => '<p>' . _('This USB Flash device has an invalid GUID. Please try a different USB Flash device') . '</p>',
'USB Flash has no serial number' => _('USB Flash has no serial number'),
'Trial Requires Internet Connection' => _('Trial Requires Internet Connection'),
'Cannot validate Unraid Trial key' => _('Cannot validate Unraid Trial key'),
'<p>Your Trial key requires an internet connection.</p><p><a href="/Settings/NetworkSettings" class="underline">Please check Settings > Network</a></p>' => '<p>' . _('Your Trial key requires an internet connection') . '</p><p><a href="/Settings/NetworkSettings" class="underline">' . _('Please check Settings > Network') . '</a></p>',
'Stale' => _('Stale'),
'Stale Server' => _('Stale Server'),
'<p>Please refresh the page to ensure you load your latest configuration</p>' => '<p>' . _('Please refresh the page to ensure you load your latest configuration') . '</p>',
'Invalid API Key' => _('Invalid API Key'),
'Please sign out then sign back in to refresh your API key.' => _('Please sign out then sign back in to refresh your API key.'),
'Invalid API Key Format' => _('Invalid API Key Format'),
'Too Many Devices' => _('Too Many Devices'),
'You have exceeded the number of devices allowed for your license. Please remove a device before adding another.' => _('You have exceeded the number of devices allowed for your license. Please remove a device before adding another.'),
'Unraid Connect Install Failed' => _('Unraid Connect Install Failed'),
'Rebooting will likely solve this.' => _('Rebooting will likely solve this.'),
'SSL certificates for unraid.net deprecated' => _('SSL certificates for unraid.net deprecated'),
'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.'),
'Unraid Connect Error' => _('Unraid Connect Error'),
'Trial Key Creation Failed' => _('Trial Key Creation Failed'),
'Error creatiing a trial key. Please try again later.' => _('Error creatiing a trial key. Please try again later.'),
'Extending your free trial by 15 days' => _('Extending your free trial by 15 days'),
'Please keep this window open' => _('Please keep this window open'),
'Starting your free 30 day trial' => _('Starting your free 30 day trial'),
'Trial Key Created' => _('Trial Key Created'),
'Please wait while the page reloads to install your trial key' => _('Please wait while the page reloads to install your trial key'),
'A Trial key provides all the functionality of a Pro Registration key' => _('A Trial key provides all the functionality of a Pro Registration key'),
'Extension Installed' => _('Extension Installed'),
'Recovered' => _('Recovered'),
'Replaced' => _('Replaced'),
'Installing' => _('Installing'),
'Installed' => _('Installed'),
'Install' => _('Install'),
'Install Extended' => _('Install Extended'),
'Install Recovered' => _('Install Recovered'),
'Install Replaced' => _('Install Replaced'),
'Your free Trial key provides all the functionality of a Pro Registration key' => _('Your free Trial key provides all the functionality of a Pro Registration key'),
'Calculating trial expiration…' => _('Calculating trial expiration…'),
'Signing In' => _('Signing In'),
'Signing Out' => _('Signing Out'),
'Sign In requires the local unraid-api to be running' => _('Sign In requires the local unraid-api to be running'),
'Sign Out requires the local unraid-api to be running' => _('Sign Out requires the local unraid-api to be running'),
'Unraid OS {0} Released' => sprintf(_('Unraid OS %s Released'), '{0}'),
'Unraid OS {0} Update Available' => sprintf(_('Unraid OS %s Update Available'), '{0}'),
'Unraid {0} Update Available' => sprintf(_('Unraid %s Update Available'), '{0}'),
'{0} Update Available' => sprintf(_('%s Update Available'), '{0}'),
'Unraid OS Update Available' => _('Unraid OS Update Available'),
'Update Unraid OS confirmation required' => _('Update Unraid OS confirmation required'),
'Please confirm the update details below' => _('Please confirm the update details below'),
'Create Flash Backup' => _('Create Flash Backup'),
'Current Version {0}' => sprintf(_('Current Version %s'), '{0}'),
'Current Version: Unraid {0}' => sprintf(_('Current Version: Unraid %s'), '{0}'),
'New Version: {0}' => sprintf(_('New Version: %s'), '{0}'),
'Version: {0}' => sprintf(_('Version: %s'), '{0}'),
'This update will require a reboot' => _('This update will require a reboot'),
'Confirm and start update' => _('Confirm and start update'),
'Update Unraid OS' => _('Update Unraid OS'),
'Last checked: {0}' => sprintf(_('Last checked: %s'), '{0}'),
'Downgrade Unraid OS' => _('Downgrade Unraid OS'),
'Customizable Dashboard Tiles' => _('Customizable Dashboard Tiles'),
'day' => sprintf(_('%s day'), '{n}') . ' | ' . sprintf(_('%s days'), '{n}'),
'Deep Linking' => _('Deep Linking'),
'DNS issue, unable to resolve wanip4.unraid.net' => _('DNS issue, unable to resolve wanip4.unraid.net'),
'Downgrade Unraid OS to {0}' => sprintf(_('Downgrade Unraid OS to %s'), '{0}'),
'No downgrade available' => _('No downgrade available'),
'Begin downgrade to {0}' => sprintf(_('Begin downgrade to %s'), '{0}'),
'Version available for restore {0}' => sprintf(_('Version available for restore %s'), '{0}'),
'check for OS updates' => _('check for OS updates'),
'Check for Prereleases' => _('Check for Prereleases'),
'Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes keeping your server up-to-date ensures the best experience that Unraid has to offer.' => _('Receive the latest and greatest for Unraid OS.') . ' ' . _('Whether it new features, security patches, or bug fixes keeping your server up-to-date ensures the best experience that Unraid has to offer.'),
'Check for OS Updates' => _('Check for OS Updates'),
'Checking...' => _('Checking...'),
'View release notes' => _('View release notes'),
'View Changelog for {0}' => sprintf(_('View Changelog for %s'), '{0}'),
'View Changelog Update' => _('View Changelog & Update'),
'{0} Release Notes' => sprintf(_('%s Release Notes'), '{0}'),
'Unable to open release notes' => _('Unable to open release notes'),
'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.'),
'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.'),
'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.'),
'Reboot Now to Downgrade' => _('Reboot Now to Downgrade'),
'Reboot Now to Update' => _('Reboot Now to Update'),
'Reboot Now to Downgrade to {0}' => sprintf(_('Reboot Now to Downgrade to %s'), '{0}'),
'Reboot Now to Update to {0}' => sprintf(_('Reboot Now to Update to %s'), '{0}'),
'Reboot Required for Downgrade' => _('Reboot Required for Downgrade'),
'Reboot Required for Update' => _('Reboot Required for Update'),
'Reboot Required for Downgrade to {0}' => sprintf(_('Reboot Required for Downgrade to %s'), '{0}'),
'Reboot Required for Update to {0}' => sprintf(_('Reboot Required for Update to %s'), '{0}'),
'Updating 3rd party drivers' => _('Updating 3rd party drivers'),
'Update Available' => _('Update Available'),
'Up-to-date' => _('Up-to-date'),
'Open a bug report' => _('Open a bug report'),
'Go to Tools > Update' => _('Go to Tools > Update'),
'A valid keyfile and USB Flash boot device are required to check for OS updates.' => _('A valid keyfile and USB Flash boot device are required to check for OS updates.'),
'Please fix any errors and try again.' => _('Please fix any errors and try again.'),
'Go to Tools > Registration to fix' => _('Go to Tools > Registration to fix'),
'Original release date {0}' => sprintf(_('Original release date %s'), '{0}'),
'Registered to' => _('Registered to'),
'Registered on' => _('Registered on'),
'Updates Expire' => _('Updates Expire'),
'Flash GUID' => _('Flash GUID'),
'Flash Vendor' => _('Flash Vendor'),
'Flash Product' => _('Flash Product'),
'Attached Storage Devices' => _('Attached Storage Devices'),
'{0} out of {1} devices' => sprintf(_('%1s out of %2s devices'), '{0}', '{1}'),
'{0} out of {1} allowed devices upgrade your key to support more devices' => sprintf(_('%1s out of %2s allowed devices upgrade your key to support more devices'), '{0}', '{1}'),
'{0} devices' => sprintf(_('%s devices'), '{0}'),
'unlimited' => _('unlimited'),
'Unable to check for OS updates' => _('Unable to check for OS updates'),
'License key actions' => _('License key actions'),
'License key type' => _('License key type'),
'OS Update Eligibility Expiration' => _('OS Update Eligibility Expiration'),
'Ineligible for updates released after {0}' => sprintf(_('Ineligible for updates released after %s'), '{0}'),
'Eligible for updates until {0}' => sprintf(_('Eligible for updates until %s'), '{0}'),
'Ineligible as of {0}' => sprintf(_('Ineligible as of %s'), '{0}'),
'Eligible for updates for {0}' => sprintf(_('Eligible for updates for %s'), '{0}'),
'Renew your license key now' => _('Renew your license key now'),
'Extend License to Enable OS Updates' => _('Extend License to Enable OS Updates'),
'Check Eligibility' => _('Check Eligibility'),
'Eligible' => _('Eligible'),
'Ineligible' => _('Ineligible'),
'Flash GUID required to check replacement status' => _('Flash GUID required to check replacement status'),
'Keyfile required to check replacement status' => _('Keyfile required to check replacement status'),
'Unraid {0}' => sprintf(_('Unraid %s'), '{0}'),
'OS Update Eligibility' => _('OS Update Eligibility'),
'Transfer License to New Flash' => _('Transfer License to New Flash'),
'Starter' => _('Starter'),
'Unleashed' => _('Unleashed'),
'Lifetime' => _('Lifetime'),
'Pay your annual fee to continue receiving OS updates.' => _('Pay your annual fee to continue receiving OS updates.'),
'Renew Key' => _('Renew Key'),
'A valid GUID is required to check for OS updates.' => _('A valid GUID is required to check for OS updates.'),
'A valid keyfile is required to check for OS updates.' => _('A valid keyfile is required to check for OS updates.'),
'A valid OS version is required to check for OS updates.' => _('A valid OS version is required to check for OS updates.'),
'Your license key\'s OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.' => _('Your license key\'s OS update eligibility has expired.') . ' ' . _('Please renew your license key to enable updates released after your expiration date.'),
'Key ineligible for new updates' => _('Key ineligible for new updates'),
'Ineligible for Unraid OS updates' => _('Ineligible for Unraid OS updates'),
'Learn more and fix' => _('Learn more and fix'),
'Download unraid-api Logs' => _('Download unraid-api Logs'),
'Dynamic Remote Access' => _('Dynamic Remote Access'),
'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' => _('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}'),
'Expired' => _('Expired'),
'Expired {0}' => sprintf(_('Expired %s'), '{0}'),
'Create Flash Backup' => _('Create Flash Backup'),
'Get a Lifetime Key' => _('Get a Lifetime Key'),
'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.'),
'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.'),
'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.'),
'You have not activated the Flash Backup feature via the Unraid Connect plugin.' => _('You have not activated the Flash Backup feature via the Unraid Connect plugin.'),
'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.'),
'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'),
'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}'),
'Backing up...this may take a few minutes' => _('Backing up...this may take a few minutes'),
'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'),
'Flash GUID Error' => _('Flash GUID Error'),
'Flash GUID required to check replacement status' => _('Flash GUID required to check replacement status'),
'Flash GUID' => _('Flash GUID'),
'Flash Product' => _('Flash Product'),
'Flash Vendor' => _('Flash Vendor'),
'Get an overview of your server\'s state, storage space, apps and VMs status, and more.' => _('Get an overview of your server\'s state, storage space, apps and VMs status, and more.'),
'Get Started' => _('Get Started'),
'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 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 > 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.'),
'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' => _('Install'),
'Installed' => _('Installed'),
'Installing Extended Trial' => _('Installing Extended Trial'),
'Installing Extended' => _('Installing Extended'),
'Installing Recovered' => _('Installing Recovered'),
'Installing Replaced' => _('Installing Replaced'),
'Installing' => _('Installing'),
'Introducing Unraid Connect' => _('Introducing Unraid Connect'),
'Invalid API Key Format' => _('Invalid API Key Format'),
'Invalid API Key' => _('Invalid API Key'),
'Invalid installation' => _('Invalid installation'),
'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'),
'LAN IP' => _('LAN IP'),
'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' => _('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'),
'Loading' => _('Loading'),
'Manage Unraid.net Account in new tab' => _('Manage Unraid.net Account in new tab'),
'Manage Unraid.net Account' => _('Manage Unraid.net Account'),
'Manage your license keys at any time via the My Keys section.' => _('Manage your license keys at any time via the My Keys section.'),
'Manage Your Server Within Connect' => _('Manage Your Server Within Connect'),
'minute' => sprintf(_('%s minute'), '{n}') . ' | ' . sprintf(_('%s minutes'), '{n}'),
'Missing key file' => _('Missing key file'),
'month' => sprintf(_('%s month'), '{n}') . ' | ' . sprintf(_('%s months'), '{n}'),
'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}'),
'No downgrade available' => _('No downgrade available'),
'No Flash' => _('No Flash'),
'No Keyfile' => _('No Keyfile'),
'No thanks' => _('No thanks'),
'No USB flash configuration data' => _('No USB flash configuration data'),
'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}'),
'Performing actions' => _('Performing actions'),
'Please confirm the update details below' => _('Please confirm the update details below'),
'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'),
'Please sign out then sign back in to refresh your API key.' => _('Please sign out then sign back in to refresh your API key.'),
'Please wait while the page reloads to install your trial key' => _('Please wait while the page reloads to install your trial key'),
'Plus more on the way' => _('Plus more on the way'),
'Plus' => _('Plus'),
'Pro' => _('Pro'),
'Purchase Key' => _('Purchase Key'),
'Purchase' => _('Purchase'),
'Ready to Install Key' => _('Ready to Install Key'),
'Ready to update Connect account configuration' => _('Ready to update Connect account configuration'),
'Real-time Monitoring' => _('Real-time Monitoring'),
'Reboot Now to Downgrade to {0}' => sprintf(_('Reboot Now to Downgrade to %s'), '{0}'),
'Reboot Now to Downgrade' => _('Reboot Now to Downgrade'),
'Reboot Now to Update to {0}' => sprintf(_('Reboot Now to Update to %s'), '{0}'),
'Reboot Now to Update' => _('Reboot Now to Update'),
'Reboot Required for Downgrade to {0}' => sprintf(_('Reboot Required for Downgrade to %s'), '{0}'),
'Reboot Required for Downgrade' => _('Reboot Required for Downgrade'),
'Reboot Required for Update to {0}' => sprintf(_('Reboot Required for Update to %s'), '{0}'),
'Reboot Required for Update' => _('Reboot Required for Update'),
'Rebooting will likely solve this.' => _('Rebooting will likely solve this.'),
'Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes keeping your server up-to-date ensures the best experience that Unraid has to offer.' => _('Receive the latest and greatest for Unraid OS.') . ' ' . _('Whether it new features, security patches, or bug fixes keeping your server up-to-date ensures the best experience that Unraid has to offer.'),
'Recover Key' => _('Recover Key'),
'Recovered' => _('Recovered'),
'Redeem Activation Code' => _('Redeem Activation Code'),
'Registered on' => _('Registered on'),
'Registered to' => _('Registered to'),
'Registration key / USB Flash GUID mismatch' => _('Registration key / USB Flash GUID mismatch'),
'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}'),
'Replace Key' => _('Replace Key'),
'Replaced' => _('Replaced'),
'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}'),
'Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.' => _('Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI.') . ' ' . _('Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.'),
'Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.' => _('Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.'),
'Settings' => _('Settings'),
'Sign In Failed' => _('Sign In Failed'),
'Sign In requires the local unraid-api to be running' => _('Sign In requires the local unraid-api to be running'),
'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 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'),
'Signing in {0}…' => sprintf(_('Signing in %s…'), '{0}'),
'Signing In' => _('Signing In'),
'Signing out {0}…' => sprintf(_('Signing out %s…'), '{0}'),
'Signing Out' => _('Signing Out'),
'Something went wrong' => _('Something went wrong'),
'SSL certificates for unraid.net deprecated' => _('SSL certificates for unraid.net deprecated'),
'Stale Server' => _('Stale Server'),
'Stale' => _('Stale'),
'Start Free 30 Day Trial' => _('Start Free 30 Day Trial'),
'Starting your free 30 day trial' => _('Starting your free 30 day trial'),
'Success!' => _('Success!'),
'Thank you for choosing Unraid OS!' => _('Thank you for choosing Unraid OS!'),
'Thank you for installing Connect!' => _('Thank you for installing Connect!'),
'Thank you for purchasing an Unraid {0} Key!' => sprintf(_('Thank you for purchasing an Unraid %s Key!'), '{0}'),
'Thank you for upgrading to an Unraid {0} Key!' => sprintf(_('Thank you for upgrading to an Unraid %s Key!'), '{0}'),
'The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.' => _('The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.'),
'The logs may contain sensitive information so do not post them publicly.' => _('The logs may contain sensitive information so do not post them publicly.'),
'The primary method of support for Unraid Connect is through our forums and Discord.' => _('The primary method of support for Unraid Connect is through our forums and Discord.'),
'Then go to Tools > Registration to manually install it' => _('Then go to Tools > Registration to manually install it'),
'This may indicate a complex network that will not work with this Remote Access solution.' => _('This may indicate a complex network that will not work with this Remote Access solution.'),
'This update will require a reboot' => _('This update will require a reboot'),
'Toggle on/off server accessibility with dynamic remote access. Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.' => _('Toggle on/off server accessibility with dynamic remote access.') . ' ' . _('Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.'),
'Too Many Devices' => _('Too Many Devices'),
'Transfer License to New Flash' => _('Transfer License to New Flash'),
'Trial Expired, see options below' => _('Trial Expired, see options below'),
'Trial Expired' => _('Trial Expired'),
'Trial Key Created' => _('Trial Key Created'),
'Trial Key Creation Failed' => _('Trial Key Creation Failed'),
'Trial Key Expired {0}' => sprintf(_('Trial Key Expired %s'), '{0}'),
'Trial Key Expired at {0}' => sprintf(_('Trial Key Expired at %s'), '{0}'),
'Trial Key Expires at {0}' => sprintf(_('Trial Key Expires at %s'), '{0}'),
'Trial Key Expires in {0}' => sprintf(_('Trial Key Expires in %s'), '{0}'),
'Trial Requires Internet Connection' => _('Trial Requires Internet Connection'),
'Trial' => _('Trial'),
'Unable to check for OS updates' => _('Unable to check for OS updates'),
'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'),
'unlimited' => _('unlimited'),
'Unraid {0} Available' => sprintf(_('Unraid %s Available'), '{0}'),
'Unraid {0} Update Available' => sprintf(_('Unraid %s Update Available'), '{0}'),
'Unraid {0}' => sprintf(_('Unraid %s'), '{0}'),
'Unraid Connect Error' => _('Unraid Connect Error'),
'Unraid Connect Forums' => _('Unraid Connect Forums'),
'Unraid Connect Install Failed' => _('Unraid Connect Install Failed'),
'Unraid Contact Page' => _('Unraid Contact Page'),
'Unraid Discord' => _('Unraid Discord'),
'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 Update Available' => _('Unraid OS Update Available'),
'unraid-api is offline' => _('unraid-api is offline'),
'Up-to-date' => _('Up-to-date'),
'Update Available' => _('Update Available'),
'Update Unraid OS confirmation required' => _('Update Unraid OS confirmation required'),
'Update Unraid OS' => _('Update Unraid OS'),
'Updating 3rd party drivers' => _('Updating 3rd party drivers'),
'Upgrade Key' => _('Upgrade Key'),
'Upgrade' => _('Upgrade'),
'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'),
'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' => _('View Changelog'),
'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 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.'),
'I have made a Flash Backup' => _('I have made a Flash Backup'),
'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.'),
'You have exceeded the number of devices allowed for your license. Please remove a device before adding another.' => _('You have exceeded the number of devices allowed for your license. Please remove a device before adding another.'),
'You have not activated the Flash Backup feature via the Unraid Connect plugin.' => _('You have not activated the Flash Backup feature via the Unraid Connect plugin.'),
'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.'),
'View Available Updates' => _('View Available Updates'),
'Your license key is not eligible for Unraid OS {0}' => sprintf(_('Your license key is not eligible for Unraid OS %s'), '{0}'),
'Unraid {0} Available' => sprintf(_('Unraid %s Available'), '{0}'),
'Key ineligible for {0}' => sprintf(_('Key ineligible for %s'), '{0}'),
'Up-to-date with eligible releases' => _('Up-to-date with eligible releases'),
'Key ineligible for future releases' => _('Key ineligible for future releases'),
'View Changelog' => _('View Changelog'),
'You are still eligible to access OS updates that were published on or before {0}.' => sprintf(_('You are still eligible to access OS updates that were published on or before %s'), '{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.' => 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 {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.'), '{0}'),
'Extend License' => _('Extend License'),
'Calculating OS Update Eligibility…' => _('Calculating OS Update Eligibility…'),
'Cancel' => _('Cancel'),
'Unknown error' => _('Unknown error'),
'Installing Extended' => _('Installing Extended'),
'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.'),
'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 free Trial key provides all the functionality of a Pro Registration key' => _('Your free Trial key provides all the functionality of a Pro Registration key'),
'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

View File

@@ -1,13 +1,13 @@
{
".nuxt/nuxt-custom-elements/entries/unraid-components.client.css": {
"file": "_nuxt/unraid-components.client-5fd7df52.css",
"file": "_nuxt/unraid-components.client-fad7c220.css",
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.css"
},
".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs": {
"css": [
"_nuxt/unraid-components.client-5fd7df52.css"
"_nuxt/unraid-components.client-fad7c220.css"
],
"file": "_nuxt/unraid-components.client-9b004c50.js",
"file": "_nuxt/unraid-components.client-0d4e65cb.js",
"isEntry": true,
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs"
}

View File

@@ -1,4 +1,4 @@
Menu="About"
Menu="About:20"
Title="Downgrade OS"
Icon="icon-update"
Tag="upload"

View File

@@ -151,7 +151,7 @@ $(function() {
$('.tabs').append("<span id='removeall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Remove Selected Plugins)_\" onclick='removeList()'></span>");
});
</script>
<table class='tablesorter plugins shift' id='plugin_table'>
<table class='unraid tablesorter plugins shift' id='plugin_table'>
<thead><tr><th></th><th>_(Plugin)_</th><th>_(Author)_</th><th>_(Version)_</th><th>_(Status)_</th><th>_(Uninstall)_</th></tr></thead>
<tbody id="plugin_list"><tr><td colspan="6"></td><tr></tbody>
</table>

View File

@@ -1,4 +1,4 @@
Menu="About"
Menu="About:10"
Title="Update OS"
Icon="icon-update"
Tag="upload"

View File

@@ -11,6 +11,9 @@
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
// Invoke the plugin command with indicated method
function plugin($method, $arg = '') {
global $docroot;
@@ -27,7 +30,7 @@ function language($method, $arg = '') {
function check_plugin($arg, &$ncsi) {
// Get network connection status indicator (NCSI)
if ($ncsi===null) $ncsi = exec("wget --spider --no-check-certificate -nv -T10 -t1 https://www.msftncsi.com/ncsi.txt 2>&1|grep -o 'OK'");
if ($ncsi===null) $ncsi = check_network_connectivity();
return $ncsi ? plugin('check',$arg) : false;
}

View File

@@ -0,0 +1,261 @@
<?php
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
/**
* Abstracting this code into a separate file allows us to use it in multiple places without duplicating code.
* 1. unraidcheck script can call this
* require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
* $unraidOsCheck = new UnraidOsCheck();
* $unraidOsCheck->checkForUpdate();
*
* 2. Unraid webgui web components can GET this file with action params to get updates, ignore updates, etc.
* - EX: Unraid webgui web components can check for updates via a GET request and receive a response with the json file directly
* - this is useful for the UPC to check for updates and display a model based on the value
* - `/plugins/dynamix.plugin.manager/scripts/unraidcheck.php?json=true`
* - note the json=true query param to receive a json response
*
* @param action {'check'|'removeAllIgnored'|'removeIgnoredVersion'|'ignoreVersion'} - the action to perform
* @param version {string} - the version to ignore or remove
* @param json {string} - if set to true, will return the json response from the external request
* @param altUrl {URL} - if set, will use this url instead of the default
*/
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
class UnraidOsCheck
{
private const BASE_RELEASES_URL = 'https://releases.unraid.net/os';
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';
public function __construct()
{
$isGetRequest = !empty($_SERVER) && isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET';
$getHasAction = $_GET !== null && !empty($_GET) && isset($_GET['action']);
if ($isGetRequest && $getHasAction) {
$this->handleGetRequestWithActions();
}
}
private function handleGetRequestWithActions()
{
switch ($_GET['action']) {
case 'check':
$this->checkForUpdate();
break;
case 'removeAllIgnored':
$this->removeAllIgnored();
break;
case 'removeIgnoredVersion':
if (isset($_GET['version'])) {
$this->removeIgnoredVersion($_GET['version']);
}
break;
case 'ignoreVersion':
if (isset($_GET['version'])) {
$this->ignoreVersion($_GET['version']);
}
break;
default:
$this->respondWithError(400, "Unhandled action");
break;
}
}
public function getUnraidOSCheckResult()
{
if (file_exists(self::JSON_FILE_RESULT)) {
return $this->readJsonFile(self::JSON_FILE_RESULT);
}
}
public function getIgnoredReleases()
{
if (!file_exists(self::JSON_FILE_IGNORED)) {
return [];
}
$ignoredData = $this->readJsonFile(self::JSON_FILE_IGNORED);
if (is_array($ignoredData) && array_key_exists(self::JSON_FILE_IGNORED_KEY, $ignoredData)) {
return $ignoredData[self::JSON_FILE_IGNORED_KEY];
}
return [];
}
/** @todo clean up this method to be more extensible */
public function checkForUpdate()
{
// Multi-language support
if (!function_exists('_')) {
function _($text) {return $text;}
}
// this command will set the $notify array
extract(parse_plugin_cfg('dynamix', true));
$var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
$params = [];
$params['branch'] = plugin('category', self::PLG_PATH, 'stable');
$params['current_version'] = plugin('version', self::PLG_PATH) ?: _var($var,'version');
if (_var($var,'regExp')) $params['update_exp'] = date('Y-m-d', _var($var,'regExp')*1);
$defaultUrl = self::BASE_RELEASES_URL;
// pass a param of altUrl to use the provided url instead of the default
$parsedAltUrl = (array_key_exists('altUrl',$_GET) && $_GET['altUrl']) ? $_GET['altUrl'] : null;
// if $parsedAltUrl pass to params
if ($parsedAltUrl) $params['altUrl'] = $parsedAltUrl;
$urlbase = $parsedAltUrl ?? $defaultUrl;
$url = $urlbase.'?'.http_build_query($params);
$curlinfo = [];
$response = http_get_contents($url,[],$curlinfo);
if (array_key_exists('error', $curlinfo)) {
$response = json_encode(array('error' => $curlinfo['error']), JSON_PRETTY_PRINT);
}
$responseMutated = json_decode($response, true);
if (!$responseMutated) {
$response = json_encode(array('error' => 'Invalid response from '.$urlbase), JSON_PRETTY_PRINT);
$responseMutated = json_decode($response, true);
}
// add params that were used for debugging
$responseMutated['params'] = $params;
// store locally for UPC to access
$this->writeJsonFile(self::JSON_FILE_RESULT, $responseMutated);
// if we have a query param of json=true then just output the json
if (array_key_exists('json',$_GET) && $_GET['json']) {
header('Content-Type: application/json');
echo $response;
exit(0);
}
// send notification if a newer version is available and not ignored
$isNewerVersion = array_key_exists('isNewer',$responseMutated) ? $responseMutated['isNewer'] : false;
$isReleaseIgnored = array_key_exists('version',$responseMutated) ? in_array($responseMutated['version'], $this->getIgnoredReleases()) : false;
if ($responseMutated && $isNewerVersion && !$isReleaseIgnored) {
$output = _var($notify,'plugin');
$server = strtoupper(_var($var,'NAME','server'));
$newver = (array_key_exists('version',$responseMutated) && $responseMutated['version']) ? $responseMutated['version'] : 'unknown';
$script = '/usr/local/emhttp/webGui/scripts/notify';
$event = "System - Unraid [$newver]";
$subject = "Notice [$server] - Version update $newver";
$description = "A new version of Unraid is available";
exec("$script -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Tools/Update' -x");
}
exit(0);
}
private function removeAllIgnored()
{
if (file_exists(self::JSON_FILE_IGNORED)) {
$this->deleteJsonFile(self::JSON_FILE_IGNORED);
$this->respondWithSuccess([]);
}
// fail silently if file doesn't exist
}
private function removeIgnoredVersion($removeVersion)
{
if ($this->isValidSemVerFormat($removeVersion)) {
if (file_exists(self::JSON_FILE_IGNORED)) {
$existingData = $this->readJsonFile(self::JSON_FILE_IGNORED);
if (isset($existingData[self::JSON_FILE_IGNORED_KEY])) {
$existingData[self::JSON_FILE_IGNORED_KEY] = array_diff($existingData[self::JSON_FILE_IGNORED_KEY], [$removeVersion]);
$this->writeJsonFile(self::JSON_FILE_IGNORED, $existingData);
$this->respondWithSuccess($existingData);
} else {
$this->respondWithError(400, "No versions to remove in the JSON file");
}
} else {
$this->respondWithError(400, "No JSON file found");
}
} else {
$this->respondWithError(400, "Invalid removeVersion format");
}
}
private function ignoreVersion($version)
{
if ($this->isValidSemVerFormat($version)) {
$newData = [$this::JSON_FILE_IGNORED_KEY => [$version]];
$existingData = file_exists(self::JSON_FILE_IGNORED) ? $this->readJsonFile(self::JSON_FILE_IGNORED) : [];
if (isset($existingData[self::JSON_FILE_IGNORED_KEY])) {
$existingData[self::JSON_FILE_IGNORED_KEY][] = $version;
} else {
$existingData[self::JSON_FILE_IGNORED_KEY] = [$version];
}
$this->writeJsonFile(self::JSON_FILE_IGNORED, $existingData);
$this->respondWithSuccess($existingData);
} else {
$this->respondWithError(400, "Invalid version format");
}
}
private function isValidSemVerFormat($version)
{
return preg_match('/^\d+\.\d+(\.\d+)?(-.+)?$/', $version);
}
private function readJsonFile($file)
{
return @json_decode(@file_get_contents($file), true) ?? [];
}
private function writeJsonFile($file, $data)
{
if (!is_dir(dirname($file))) { // prevents errors when directory doesn't exist
mkdir(dirname($file));
}
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
private function deleteJsonFile($file)
{
unlink($file);
}
private function respondWithError($statusCode, $message)
{
http_response_code($statusCode);
echo $message;
}
private function respondWithSuccess($data)
{
http_response_code(200);
header('Content-Type: application/json');
echo json_encode($data, JSON_PRETTY_PRINT);
}
}
// Instantiate and handle the request for GET requests with actions vars are duplicated here for multi-use of this file
$isGetRequest = !empty($_SERVER) && isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET';
$getHasAction = $_GET !== null && !empty($_GET) && isset($_GET['action']);
if ($isGetRequest && $getHasAction) {
new UnraidOsCheck();
}

View File

@@ -24,7 +24,7 @@ function newurl($url) {
return str_replace($oldURL,$newURL,$url);
}
function searchLink(&$db,$url) {
if ($url) for ($i = 0; $i < count($db); $i++) if ($db[$i]['PluginURL']==$url) return $db[$i]['Support'];
if ($url) for ($i = 0; $i < count($db); $i++) if ( ($db[$i]['PluginURL']??null)==$url) return $db[$i]['Support']??null;
}
$type = $argv[1]??''; // plugin or language

View File

@@ -44,7 +44,7 @@ function write(...$messages){
}
write(_("Checking connectivity")." ...\n");
if (exec("wget --spider --no-check-certificate -nv -T10 -t1 https://www.msftncsi.com/ncsi.txt 2>&1|grep -om1 'OK'")) {
if (check_network_connectivity()) {
$check = popen('plugin checkall','r');
while (!feof($check)) write(fgets($check));
pclose($check);

View File

@@ -6,6 +6,10 @@
// Program updates made by Bergware International (April 2020)
// Program updates made by Bergware International (June 2022)
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
$logger = 'language-manager';
$usage = <<<EOF
Process language files.
@@ -148,12 +152,6 @@ function download($url, $name, &$error) {
}
}
// Deal with logging message.
//
function logger($message) {
exec("logger -t 'language-manager' -- \"$message\"");
}
// Interpret a language file
// Returns TRUE if success, else FALSE and fills in error string.
//
@@ -314,7 +312,7 @@ if ($method == 'install') {
copy($xml_file, $lang_file);
symlink($lang_file, $link_file);
write("language: $lang language pack installed\n");
logger("$lang language pack installed");
my_logger("$lang language pack installed", $logger);
// run hook scripts for post processing
post_hooks();
done(0);
@@ -396,7 +394,7 @@ if ($method == 'update') {
copy($xml_file, $lang_file);
symlink($lang_file, $link_file);
write("language: $lang language pack updated\n");
logger("$lang language pack updated");
my_logger("$lang language pack updated", $logger);
// run hook scripts for post processing
post_hooks();
done(0);
@@ -423,7 +421,7 @@ if ($method == 'remove') {
done(1);
}
write("language: $lang language pack removed\n");
logger("$lang language pack removed");
my_logger("$lang language pack removed", $logger);
// run hook scripts for post processing
post_hooks();
done(0);

View File

@@ -1,7 +1,7 @@
#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -15,6 +15,7 @@
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
// this command will set the $notify array
extract(parse_plugin_cfg('dynamix', true));
// Multi-language support

View File

@@ -6,6 +6,10 @@
// Program updates made by Bergware International (April 2020)
// Program updates made by Bergware International (June 2022)
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
$logger = 'plugin-manager';
$usage = <<<EOF
Process plugin files.
@@ -275,18 +279,12 @@ function filter_url($url) {
if (strpos($url, '.cdn') !== false) {
$new_url = str_replace('"', '', $url);
$new_url = str_replace('.cdn', '', $new_url);
} else {
} else {
$new_url = "";
}
return($new_url);
}
// Deal with logging message.
//
function logger($message) {
exec("logger -t 'plugin-manager' -- \"$message\"");
}
// Interpret a plugin file
// Returns TRUE if success, else FALSE and fills in error string.
//
@@ -295,7 +293,7 @@ function logger($message) {
// is processed for any of those methods.
//
function plugin($method, $plugin_file, &$error) {
global $unraid;
global $unraid, $logger;
$methods = ['install', 'remove'];
// parse plugin definition XML file
@@ -382,12 +380,12 @@ function plugin($method, $plugin_file, &$error) {
// If file already exists, check the SHA256/MD5 (if supplied)
if (file_exists($name)) {
if ($file->SHA256) {
logger("checking: $name - SHA256");
my_logger("checking: $name - SHA256", $logger);
if (hash_file('sha256', $name) != $file->SHA256) {
unlink($name);
}
} elseif ($file->MD5) {
logger("checking: $name - MD5");
my_logger("checking: $name - MD5", $logger);
if (md5_file($name) != $file->MD5) {
unlink($name);
}
@@ -396,12 +394,12 @@ function plugin($method, $plugin_file, &$error) {
// If file already exists, do not overwrite
//
if (file_exists($name)) {
logger("skipping: $name already exists");
my_logger("skipping: $name already exists", $logger);
} elseif ($file->LOCAL) {
// Create the file
//
// for local file, just copy it
logger("creating: $name - copying LOCAL file $file->LOCAL");
my_logger("creating: $name - copying LOCAL file $file->LOCAL", $logger);
if (!copy($file->LOCAL, $name)) {
$error = "unable to copy LOCAL file: $name";
@unlink($name);
@@ -409,10 +407,10 @@ function plugin($method, $plugin_file, &$error) {
}
} elseif ($file->INLINE) {
// for inline file, create with inline contents
logger("creating: $name - from INLINE content");
my_logger("creating: $name - from INLINE content", $logger);
$contents = trim($file->INLINE).PHP_EOL;
if ($file->attributes()->Type == 'base64') {
logger("decoding: $name as base64");
my_logger("decoding: $name as base64", $logger);
$contents = base64_decode($contents);
if ($contents === false) {
$error = "unable to decode inline base64: $name";
@@ -426,20 +424,20 @@ function plugin($method, $plugin_file, &$error) {
}
} elseif ($file->URL) {
// for download file, download and maybe verify the file MD5
logger("creating: $name - downloading from URL $file->URL");
my_logger("creating: $name - downloading from URL $file->URL", $logger);
if ( (download($file->URL, $name, $error) === false) && (download(filter_url($file->URL), $name, $error) === false) ) {
@unlink($name);
return false;
}
if ($file->SHA256) {
logger("checking: $name - SHA256");
my_logger("checking: $name - SHA256", $logger);
if (hash_file('sha256', $name) != $file->SHA256) {
$error = "bad file SHA256: $name";
unlink($name);
return false;
}
} elseif ($file->MD5) {
logger("checking: $name - MD5");
my_logger("checking: $name - MD5", $logger);
if (md5_file($name) != $file->MD5) {
$error = "bad file MD5: $name";
unlink($name);
@@ -452,7 +450,7 @@ function plugin($method, $plugin_file, &$error) {
if ($file->attributes()->Mode) {
// if file has 'Mode' attribute, apply it
$mode = $file->attributes()->Mode;
logger("setting: $name - mode to $mode");
my_logger("setting: $name - mode to $mode", $logger);
if (!chmod($name, octdec($mode))) {
$error = "chmod failure: $name";
return false;
@@ -464,13 +462,13 @@ function plugin($method, $plugin_file, &$error) {
if ($file->attributes()->Run) {
$command = $file->attributes()->Run;
if ($name) {
logger("running: $command $name");
my_logger("running: $command $name", $logger);
$retval = run("$command $name");
} elseif ($file->LOCAL) {
logger("running: $command $file->LOCAL");
my_logger("running: $command $file->LOCAL", $logger);
$retval = run("$command $file->LOCAL");
} elseif ($file->INLINE) {
logger("running: 'anonymous'");
my_logger("running: 'anonymous'", $logger);
$name = '/tmp/inline.sh';
file_put_contents($name, $file->INLINE);
$retval = run("$command $name");
@@ -691,7 +689,7 @@ if ($method == 'install') {
$event = "Install error";
$subject = "plugin: ".basename($plugin_file);
$description = "Plugin failed to install";
exec("$notify -e $event -s $subject -d $description) -i 2");
exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i 'warning'");
// run hook scripts for post processing
post_hooks($error);
done(1);
@@ -718,10 +716,10 @@ if ($method == 'install') {
if ($target != $plugin_file) copy($plugin_file, $target);
symlink($target, $symlink);
write("plugin: $plugin installed\n");
logger("$plugin installed");
my_logger("$plugin installed", $logger);
} else {
write("script: $plugin executed\n");
logger("script: $plugin executed");
my_logger("script: $plugin executed", $logger);
}
// run hook scripts for post processing
post_hooks();
@@ -835,7 +833,7 @@ if ($method == 'update') {
copy($plugin_file, $target);
symlink($target, $symlink);
write("plugin: $plugin updated\n");
logger("$plugin updated");
my_logger("$plugin updated", $logger);
// run hook scripts for post processing
post_hooks();
done(0);
@@ -867,7 +865,7 @@ if ($method == 'remove') {
// remove the plugin file
move($installed_plugin_file, "$boot-removed");
write("plugin: $plugin removed\n");
logger("$plugin removed");
my_logger("$plugin removed", $logger);
exec("/usr/local/sbin/update_cron");
// run hook scripts for post processing
post_hooks();

View File

@@ -1,7 +1,7 @@
#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -15,6 +15,7 @@
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
// this command will set the $notify array
extract(parse_plugin_cfg('dynamix',true));
// Multi-language support

View File

@@ -13,33 +13,7 @@
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
// Multi-language support
if (!function_exists('_')) {
function _($text) {return $text;}
}
extract(parse_plugin_cfg('dynamix', true));
$var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
$script = "$docroot/webGui/scripts/notify";
$server = strtoupper(_var($var,'NAME','server'));
$output = _var($notify,'plugin');
$builtin = ['unRAIDServer'];
foreach ($builtin as $name) {
$plg = "$name.plg";
plugin('check',$plg);
$file = "/tmp/plugins/$plg";
$old = plugin('version', "/var/log/plugins/$plg");
$new = plugin('version', $file);
// silently suppress bad download of PLG file
if (version_compare($new,$old,'>')) {
exec("$script -e ".escapeshellarg("System - $name [$new]")." -s ".escapeshellarg("Notice [$server] - Version update $new")." -d ".escapeshellarg("A new version of $name is available")." -i ".escapeshellarg("normal $output")." -l '/Tools/Update' -x");
}
}
exit(0);
?>
$unraidOsCheck = new UnraidOsCheck();
$unraidOsCheck->checkForUpdate();

View File

@@ -238,12 +238,14 @@ function VMClone(uuid, name){
});
dialogStyle();
}
function selectsnapshot(uuid, name ,snaps, opt, getlist,state){
function selectsnapshot(uuid, name ,snaps, opt, getlist,state ,fstype){
var box = $("#dialogWindow");
box.html($("#templatesnapshot"+opt).html());
const capopt = opt.charAt(0).toUpperCase() + opt.slice(1);
var optiontext = capopt + " _(Snapshot)_";
box.find('#VMName').html(name);
box.find('#fstype').html(fstype);
if (fstype == "QEMU") box.find('#fstypeline').prop('hidden',true);
box.find('#targetsnap').val(snaps);
box.find('#targetsnapl').html(snaps);
if (getlist) {
@@ -282,9 +284,11 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist,state){
}
if (opt == "create") {
free = box.find('#targetsnapfspc').prop('checked') ? 'yes' : 'no';
fstypeuse = box.find('#targetsnapfstype').prop('checked') ? 'yes' : 'no';
if (fstypeuse == "no") fstype ="QEMU";
desc = box.find("#targetsnapdesc").prop('value');
}
ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid, snapshotname:target, remove:remove, free:free, removemeta:removemeta, keep:keep, desc:desc}, "loadlist");
ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid, snapshotname:target, remove:remove, free:free, removemeta:removemeta, keep:keep, desc:desc, fstype:fstype}, "loadlist");
box.dialog('close');
},
"_(Cancel)_": function(){
@@ -492,6 +496,7 @@ $(function() {
<tr><td>_(VM Name)_:</td><td><label id="VMName"></label></td></tr>
<tr><td>_(Snapshot Name)_:</td><td><input type="text" id="targetsnap" autocomplete="off" spellcheck="false" value="--generate" onclick="this.select()">_(Check free space)_: <input type="checkbox" id="targetsnapfspc" checked></td></tr>
<tr><td>_(Description )_:</td><td><input type="text" id="targetsnapdesc" autocomplete="off" spellcheck="false" value="" onclick="this.select()"></td></tr>
<tr id="fstypeline"><td>_(FS Native Snapshot )_:</td><td><label id="fstype"></label><input type="checkbox" id="targetsnapfstype" checked>_(Unchecked will use QEMU External Snapshot)_</td></tr>
</table>
</div>
@@ -529,6 +534,6 @@ _(Snapshot Name)_: <input type="text" id="targetsnap" hidden><label id="targetsn
<tr><td>_(Overwrite)_:</td><td><input type="checkbox" id="Overwrite" value="" ></td></tr>
<tr hidden><td>_(Start Cloned VM)_:</td><td><input type="checkbox" id="Start" value="" ></td></tr>
<tr hidden><td>_(Edit VM after clone)_:</td><td><input type="checkbox" id="Edit" value="" ></td></tr>
<tr><td>_(Check Free Space)_:</td><td><input type="checkbox" id="Free" value="" ></td></tr>
<tr><td>_(Check free space)_:</td><td><input type="checkbox" id="Free" value="" ></td></tr>
</table>
</div>

View File

@@ -151,12 +151,8 @@ _(Default Windows VirtIO driver ISO)_ (_(optional)_):
_(Default network source)_:
: <select id="network" name="BRNAME">
<?foreach (array_keys($arrValidNetworks) as $key) {
echo mk_option("", $key, "- "._($key)." -", "disabled");
foreach ($arrValidNetworks[$key] as $strNetwork) {
echo mk_option($domain_cfg['BRNAME'], $strNetwork, $strNetwork);
}
foreach ($arrValidNetworks[$key] as $strNetwork) echo mk_option($domain_cfg['BRNAME'], $strNetwork, $strNetwork);
}?>
</select>
@@ -187,6 +183,19 @@ _(Console Options)_:
:vms_console_help:
_(Show VM Usage)_:
: <select id="vmusage" name="USAGE">
<?=mk_option($domain_cfg['USAGE'], 'N', _('Disabled'))?>
<?=mk_option($domain_cfg['USAGE'], 'Y', _('Enabled'))?>
</select>
:vms_usage_help:
_(VM Usage refresh timer(seconds))_:
: <input type="number" id="vm_usage_timer" name="USAGETIMER" value="<?=htmlspecialchars($domain_cfg['USAGETIMER'] ?? 3) ?>" class="narrow">
:vms_usage_timer_help:
_(PCIe ACS override)_:
: <select id="pcie_acs_override"<?=$safemode?' disabled':''?>>
<?= mk_option($pcie_acs_override, '', _('Disabled'))?>
@@ -211,6 +220,8 @@ _(VFIO allow unsafe interrupts)_:
<?else:?>
&nbsp;
<?endif;?>
<?else:?>
&nbsp;
<?endif;?>
: <input type="button" id="applyBtn" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
</form>

View File

@@ -20,20 +20,54 @@ Markdown="false"
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
$templateslocation = "/boot/config/plugins/dynamix.vm.manager/savedtemplates.json";
if (is_file($templateslocation)){
$arrAllTemplates["User-templates"] = "";
$ut = json_decode(file_get_contents($templateslocation),true) ;
if (is_array($ut)) ksort($ut,SORT_NATURAL);
$arrAllTemplates = array_merge($arrAllTemplates, $ut);
}
foreach($arrAllTemplates as $strName => $arrTemplate):
if (empty($arrTemplate)) {
// render header
echo '<div class="vmheader">'.$strName.'</div>';
continue;
}
if (strpos($strName,"User-") === false) $user = ""; else $user = ' class="user"';
?>
<div class="vmtemplate">
<a href="/VMs/AddVM?template=<?=htmlspecialchars(urlencode($strName))?>">
<img src="/plugins/dynamix.vm.manager/templates/images/<?=htmlspecialchars($arrTemplate['icon'])?>" title="<?=htmlspecialchars($strName)?>">
<span name="<?=htmlspecialchars($strName)?>" <?=$user?>><img src="/plugins/dynamix.vm.manager/templates/images/<?=htmlspecialchars($arrTemplate['icon'])?>" title="<?=htmlspecialchars($strName)?>"></span>
<p><?=htmlspecialchars($strName)?></p>
</a>
</div>
<? endforeach; ?>
<br>
<center><button type='button' onclick='done()'>_(Cancel)_</button></center>
<br>
<br>
<script>
function removeUserTemplate(template) {
$.post('/plugins/dynamix.vm.manager/include/VMajax.php',{action:'vm-template-remove',template:template},function(){
refresh();});
}
function confirmRemoveUserTemplate(template) {
swal({title:"_(Proceed)_?",text:"Remove user template: " + template ,type:'warning',html:true,showCancelButton:true,confirmButtonText:"_(Proceed)_",cancelButtonText:"_(Cancel)_"},function(p){if (p) removeUserTemplate(template); else refresh();});
}
$(function(){
$('div.vmtemplate').each(function(){
var templatename = $(this).find('span').attr('name');
$(this).find('span.user').append('<i class="fa fa-trash bin" title="_(Remove User Template)_" onclick="confirmRemoveUserTemplate(&quot;' + templatename + '&quot;);return false"></i>');
$(this).hover(function(){$(this).find('i.bin').show();},function(){$(this).find('i.bin').hide();});
});
});
</script>
<style>
i.bin{display:none;font-size:1.8rem;position:absolute;margin-left:12px}
</style>

View File

@@ -0,0 +1,41 @@
Menu="VMs:0"
Title="VM Usage Statisics"
Nchan="vm_usage:stop"
Cond="exec(\"grep -o '^USAGE=.Y' /boot/config/domain.cfg 2>/dev/null\") && is_file('/var/run/libvirt/libvirtd.pid')"
---
<?PHP
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Simon Fairweather.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
?>
<table id="vmstats" class="tablesorter four shift">
<thead class='child'><tr><th class="th1">_(Name)_</th><th class="th2">_(Guest CPU)_</th><th>_(Host CPU)_</th><th>_(Memory)_</th><th>_(Disk IO)_</th><th>_(Network IO)_</th></tr></thead>
<tbody id ="vmstatsbody" class='child'>
</tbody>
</table>
<script>
var vmusage = new NchanSubscriber('/sub/vm_usage',{subscriber:'websocket'});
vmusage.on('message', function(msg){
var data = JSON.parse(msg);
$('#vmstatsbody').html(data);
});
$(function(){
vmusage.start();
});
window.onunload = function(){
vmusage.stop();
}
</script>

View File

@@ -66,9 +66,11 @@ foreach ($vms as $vm) {
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
$disks = '-';
$diskdesc = '';
$fstype ="QEMU";
if (($diskcnt = $lv->get_disk_count($res)) > 0) {
$disks = $diskcnt.' / '.$lv->get_disk_capacity($res);
$diskdesc = 'Current physical size: '.$lv->get_disk_capacity($res, true);
$fstype = $lv->get_disk_fstype($res);
$diskdesc = 'Current physical size: '.$lv->get_disk_capacity($res, true)."\nDefault snapshot type: $fstype";
}
$arrValidDiskBuses = getValidDiskBuses();
$vmrcport = $lv->domain_get_vnc_port($res);
@@ -108,7 +110,7 @@ foreach ($vms as $vm) {
}
unset($dom);
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole,$vmpreview);
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log),addslashes($fstype), $vmrcconsole,$vmpreview);
$kvm[] = "kvm.push({id:'$uuid',state:'$state'});";
switch ($state) {
case 'running':
@@ -130,7 +132,7 @@ foreach ($vms as $vm) {
}
/* VM information */
if ($snapshots != null) $snapshotstr = _("(Snapshots :").count($snapshots).')'; else $snapshotstr = _("(Snapshots :None)");
if ($snapshots != null) $snapshotstr = '('._('Snapshots').': '.count($snapshots).")"; else $snapshotstr = '('._('Snapshots').': '._('None').")";
$cdbus = $cdbus2 = $cdfile = $cdfile2 = "";
$cdromcount = 0;
foreach ($cdroms as $arrCD) {
@@ -267,7 +269,7 @@ foreach ($vms as $vm) {
if ($snap['parent'] == "" || $snap['parent'] == "Base") $j++;
$steps[$j] .= $snap['name'].';';
}
echo "<thead class='child' child-id='$i'><tr><th><i class='fa fa-clone'></i> <b>",_('Snapshots'),"</b></th><th></th><th>",_('Date/Time'),"</th><th>",_('Type'),"</th><th>",_('Parent'),"</th><th>",_('Memory'),"</th></tr></thead>";
echo "<thead class='child' child-id='$i'><tr><th><i class='fa fa-clone'></i> <b>",_('Snapshots'),"</b></th><th></th><th>",_('Date/Time'),"</th><th>",_('Type (Method)'),"</th><th>",_('Parent'),"</th><th>",_('Memory'),"</th></tr></thead>";
echo "<tbody class='child'child-id='$i'>";
foreach ($steps as $stepsline) {
$snapshotlist = explode(";",$stepsline);
@@ -275,12 +277,12 @@ foreach ($vms as $vm) {
foreach ($snapshotlist as $snapshotitem) {
if ($snapshotitem == "") continue;
$snapshot = $snapshots[$snapshotitem] ;
$snapshotstate = _(ucfirst($snapshot["state"]));
$snapshotstate = _(ucfirst($snapshot["state"]))." ({$snapshot["method"]})";
$snapshotdesc = $snapshot["desc"];
$snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"]));
$snapshotparent = $snapshot["parent"] ? $snapshot["parent"] : "None";
$snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."<br>".my_time($snapshot["creationtime"],"H:i:s");
$snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$vmpreview);
$snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$snapshot["method"],$vmpreview);
echo "<tr><td><span id='vmsnap-$uuid' $snapmenu class='hand'>$tab|__&nbsp;&nbsp;<i class='fa fa-clone'></i></span>&nbsp;",$snapshot["name"],"</td><td>$snapshotdesc</td><td><span class='inner' style='font-size:1.1rem;'>$snapshotdatetime</span></td><td>$snapshotstate</td><td>$snapshotparent</td><td>$snapshotmemory</td></tr>";
$tab .="&nbsp;&nbsp;&nbsp;&nbsp;";
}

View File

@@ -338,7 +338,7 @@ case 'snap-create':
case 'snap-create-external':
requireLibvirt();
$arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ;
$arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free'],$_REQUEST['fstype']) ;
break;
case 'snap-images':
@@ -397,6 +397,7 @@ case 'disk-create':
$size = str_replace(["KB","MB","GB","TB","PB", " ", ","], ["K","M","G","T","P", "", ""], strtoupper($_REQUEST['size']));
$dir = dirname($disk);
if (!is_dir($dir)) mkdir($dir);
#if (!is_dir($dir)) my_mkdir($dir);
// determine the actual disk if user share is being used
$dir = transpose_user_path($dir);
#@exec("chattr +C -R ".escapeshellarg($dir)." >/dev/null");
@@ -726,6 +727,17 @@ case 'virtio-win-iso-remove':
}
break;
case 'vm-template-remove':
$template = $_REQUEST['template'];
$templateslocation = "/boot/config/plugins/dynamix.vm.manager/savedtemplates.json";
if (is_file($templateslocation)){
$ut = json_decode(file_get_contents($templateslocation),true) ;
unset($ut[$template]);
file_put_contents($templateslocation,json_encode($ut,JSON_PRETTY_PRINT));
}
$arrResponse = ['success' => true];
break;
default:
$arrResponse = ['error' => _('Unknown action')." '$action'"];
break;

View File

@@ -29,6 +29,14 @@ switch ($display['theme']) {
default : $bgcolor = '#ededed'; $border = '#e3e3e3'; $top = -58; break;
}
$templateslocation = "/boot/config/plugins/dynamix.vm.manager/savedtemplates.json";
if (is_file($templateslocation)){
$arrAllTemplates["User-templates"] = "";
$ut = json_decode(file_get_contents($templateslocation),true) ;
$arrAllTemplates = array_merge($arrAllTemplates, $ut);
}
$strSelectedTemplate = array_keys($arrAllTemplates)[1];
if (isset($_GET['template']) && isset($arrAllTemplates[unscript($_GET['template'])])) {
$strSelectedTemplate = unscript($_GET['template']);
@@ -85,6 +93,8 @@ if (isset($_GET['uuid'])) {
}
$arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form'];
}
$strSelectedTemplateUT = $strSelectedTemplate;
if (strpos($strSelectedTemplate,"User-") !== false) $strSelectedTemplateUT = str_replace("User-","",$strSelectedTemplateUT);
?>
<link type="text/css" rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css')?>">
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
@@ -94,7 +104,7 @@ if (isset($_GET['uuid'])) {
<div class="domain">
<form id="vmform" method="POST">
<input type="hidden" name="domain[type]" value="kvm" />
<input type="hidden" name="template[name]" value="<?=htmlspecialchars($strSelectedTemplate)?>" />
<input type="hidden" name="template[name]" value="<?=htmlspecialchars($strSelectedTemplateUT)?>" />
<table>
<tr>

View File

@@ -176,6 +176,7 @@
// create folder if needed
if (!is_dir($strImgFolder)) {
mkdir($strImgFolder, 0777, true);
#my_mkdir($strImgFolder, 0777, true);
chown($strImgFolder, 'nobody');
chgrp($strImgFolder, 'users');
}
@@ -192,13 +193,15 @@
// create parent folder if needed
if (!is_dir($path_parts['dirname'])) {
mkdir($path_parts['dirname'], 0777, true);
#my_mkdir($path_parts['dirname'], 0777, true);
chown($path_parts['dirname'], 'nobody');
chgrp($path_parts['dirname'], 'users');
}
$this->set_folder_nodatacow($path_parts['dirname']);
$strImgPath = $strImgFolder;
$strExt = ($disk['driver'] == 'raw') ? 'img' : $disk['driver'];
$strImgPath = $path_parts['dirname'] . '/vdisk' . $diskid . '.' . $strExt;
}
@@ -217,6 +220,7 @@
$strImgRawLocationParent = dirname($strImgRawLocationPath);
if (!is_dir($strImgRawLocationParent)) {
mkdir($strImgRawLocationParent, 0777, true);
#my_mkdir($strImgRawLocationParent, 0777, true);
chown($strImgRawLocationParent, 'nobody');
chgrp($strImgRawLocationParent, 'users');
}
@@ -257,6 +261,9 @@
if (!empty($disk['boot'])) {
$arrReturn['boot'] = $disk['boot'];
}
if (!empty($disk['rotation'])) {
$arrReturn['rotation'] = $disk['rotation'];
}
if (!empty($disk['serial'])) {
$arrReturn['serial'] = $disk['serial'];
}
@@ -683,13 +690,18 @@
if ($disk["serial"] != "") $serial = "<serial>".$disk["serial"]."</serial>" ; else $serial = "" ;
$rotation_rate = "";
if ($disk['bus'] == "scsi" || $disk['bus'] == "sata" || $disk['bus'] == "ide" ) {
if ($disk['rotation']) $rotation_rate = " rotation_rate='1' ";
}
if ($strDevType == 'file' || $strDevType == 'block') {
$strSourceType = ($strDevType == 'file' ? 'file' : 'dev');
$diskstr .= "<disk type='" . $strDevType . "' device='disk'>
<driver name='qemu' type='" . $disk['driver'] . "' cache='writeback'/>
<source " . $strSourceType . "='" . htmlspecialchars($disk['image'], ENT_QUOTES | ENT_XML1) . "'/>
<target bus='" . $disk['bus'] . "' dev='" . $disk['dev'] . "'/>
<target bus='" . $disk['bus'] . "' dev='" . $disk['dev'] . "' $rotation_rate />
$bootorder
$readonly
$serial
@@ -1314,6 +1326,7 @@
if ($tmp) {
$tmp['bus'] = $disk->target->attributes()->bus->__toString();
$tmp["boot order"] = $disk->boot->attributes()->order ?? "";
$tmp["rotation"] = $disk->target->attributes()->rotation_rate ?? "0";
$tmp['serial'] = $disk->serial ;
// Libvirt reports 0 bytes for raw disk images that haven't been
@@ -1342,6 +1355,7 @@
'physical' => '-',
'bus' => $disk->target->attributes()->bus->__toString(),
'boot order' => $disk->boot->attributes()->order ,
'rotation' => $disk->target->attributes()->rotation_rate ?? "0",
'serial' => $disk->serial
];
}
@@ -1420,6 +1434,20 @@
return $ret;
}
function get_disk_fstype($domain) {
$dom = $this->get_domain_object($domain);
$tmp = $this->get_disk_stats($dom);
$dirname = transpose_user_path($tmp[0]['file']);
$pathinfo = pathinfo($dirname);
$parent = $pathinfo["dirname"];
$fstype = strtoupper(trim(shell_exec(" stat -f -c '%T' $parent")));
if ($fstype != "ZFS") $fstype = "QEMU";
#if ($fstype != "ZFS" && $fstype != "BTRFS") $fstype = "QEMU";
unset($tmp);
return $fstype;
}
function format_size($value, $decimals, $unit='?') {
if ($value == '-')
return 'unknown';
@@ -1677,8 +1705,8 @@
else {
$doms = libvirt_list_domains($this->conn);
foreach ($doms as $dom) {
$tmp = $this->domain_get_name($dom);
$ret[$tmp] = libvirt_domain_get_info($dom);
$tmp = $this->get_domain_object($dom);
$ret[$dom] = libvirt_domain_get_info($tmp);
}
}
@@ -1754,6 +1782,11 @@
return ($tmp) ? $tmp : $this->_set_last_error();
}
function domain_get_all_domain_stats() {
$tmp = libvirt_connect_get_all_domain_stats($this->conn);
return ($tmp) ? $tmp : $this->_set_last_error();
}
function domain_start($dom) {
$dom=$this->get_domain_object($dom);
if ($dom) {
@@ -2358,6 +2391,7 @@
foreach ($objNodes as $objNode) {
$dom = $xpath->query('source/address/@domain', $objNode)->Item(0)->nodeValue;
$bus = $xpath->query('source/address/@bus', $objNode)->Item(0)->nodeValue;
$rotation = $xpath->query('target/address/@rotation_rate', $objNode)->Item(0)->nodeValue;
$slot = $xpath->query('source/address/@slot', $objNode)->Item(0)->nodeValue;
$func = $xpath->query('source/address/@function', $objNode)->Item(0)->nodeValue;
$rom = $xpath->query('rom/@file', $objNode);
@@ -2381,6 +2415,7 @@
'product' => $tmp2['product_name'],
'product_id' => $tmp2['product_id'],
'boot' => $boot,
'rotation' => $rotation,
'rom' => $rom,
'guest' => $guest
];

View File

@@ -1025,6 +1025,16 @@ private static $encoding = 'UTF-8';
return $arrValidMachineTypes;
}
function ValidateMachineType($machinetype) {
$machinetypes=getValidMachineTypes();
$type = substr($machinetype,0,strpos($machinetype,'-',3));
foreach($machinetypes as $machinetypekey => $machinedetails){
$check_type = substr($machinetypekey,0,strlen($type));
if ($check_type == $type) break;
}
return($machinetypekey) ;
}
function getLatestMachineType($strType = 'i440fx') {
$arrMachineTypes = getValidMachineTypes();
@@ -1268,19 +1278,18 @@ private static $encoding = 'UTF-8';
!file_exists($domain_cfg['DOMAINDIR']) ||
!is_file($strPath) ||
strpos($domain_cfg['DOMAINDIR'], dirname(dirname($strPath))) === false ||
basename($strPath) != 'vdisk'.($i+1).'.img') {
$default_option = 'manual';
basename($strPath) != 'vdisk'.($i+1).'.img' || basename($strPath) != 'vdisk'.($i+1).'.qcow2') {
if (($disk['type'] == "qcow2" && (basename($strPath) == 'vdisk'.($i+1).'.qcow2')) || ($disk['type'] == "raw" && (basename($strPath) == 'vdisk'.($i+1).'.img'))) $default_option = "auto"; else $default_option = 'manual';
}
$arrDisks[] = [
'new' => $strPath,
'size' => '',
'driver' => $disk['type'],
'driver' => 'raw',
'dev' => $disk['device'],
'bus' => $disk['bus'],
'boot' => $disk['boot order'],
'rotation' => $disk['rotation'],
'serial' => $disk['serial'],
'select' => $default_option
];
@@ -1292,7 +1301,8 @@ private static $encoding = 'UTF-8';
'driver' => 'raw',
'dev' => 'hda',
'select' => '',
'bus' => 'virtio'
'bus' => 'virtio',
'rotation' => "0"
];
}
@@ -1399,10 +1409,11 @@ private static $encoding = 'UTF-8';
if ($p2['bus'] && $p2['slot'] && $p2['function'] && $p2['bus']==$pci['bus'] && $p2['slot']==$pci['slot'] && $p2['function']==$function) unset($old['devices']['hostdev'][$k]);
}
}
// remove and rebuild usb controllers
// remove and rebuild usb + scsi controllers
$devices = $old['devices']['controller'];
foreach ($devices as $key => $controller) {
if ($controller['@attributes']['type']=='usb') unset($old['devices']['controller'][$key]);
if ($controller['@attributes']['type']=='scsi') unset($old['devices']['controller'][$key]);
}
// preserve existing disk driver settings
foreach ($new['devices']['disk'] as $key => $disk) {
@@ -1670,7 +1681,8 @@ private static $encoding = 'UTF-8';
if ($storage == "default") $clonedir = $domain_cfg['DOMAINDIR'].$clone ; else $clonedir = str_replace('/mnt/user/', "/mnt/$storage/", $domain_cfg['DOMAINDIR']).$clone;
if (!is_dir($clonedir)) {
mkdir($clonedir,0777,true) ;
mkdir($clonedir,0777,true);
#my_mkdir($clonedir,0777,true);
chown($clonedir, 'nobody');
chgrp($clonedir, 'users');
}
@@ -1714,24 +1726,41 @@ private static $encoding = 'UTF-8';
function getvmsnapshots($vm) {
$snaps=array() ;
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
$dbpath = "/etc/libvirt/qemu/snapshotdb/$vm" ;
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
$snaps = json_decode($snaps_json,true) ;
if (is_array($snaps)) uasort($snaps,'compare_creationtime') ;
return $snaps ;
}
function write_snapshots_database($vm,$name) {
function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
global $lv ;
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
$dbpath = "/etc/libvirt/qemu/snapshotdb/$vm" ;
if (!is_dir($dbpath)) mkdir($dbpath) ;
$noxml = "";
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
$snaps = json_decode($snaps_json,true) ;
$snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name) ;
if (!$snapshot_res) {
# Manual Snap no XML
if ($state == "shutoff" && ($method == "ZFS" || $method == "BTRFS")) {
# Create Snapshot info
$vmsnap = $name;
$snaps[$vmsnap]["name"]= $name;
$snaps[$vmsnap]["parent"]= "None" ;
$snaps[$vmsnap]["state"]= "shutoff";
$snaps[$vmsnap]["desc"]= $desc;
$snaps[$vmsnap]["memory"]= ['@attributes' => ['snapshot' => 'no']];
$snaps[$vmsnap]["creationtime"]= date("U");
$snaps[$vmsnap]["method"]= $method;
$snaps[$vmsnap]['xml'] = $lv->domain_get_xml($vm);
$noxml = "noxml";
}
} else {
$snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ;
$a = simplexml_load_string($snapshot_xml) ;
$a = json_encode($a) ;
$b= json_decode($a, TRUE);
$b = json_decode($a, TRUE);
$vmsnap = $b["name"] ;
$snaps[$vmsnap]["name"]= $b["name"];
$snaps[$vmsnap]["parent"]= $b["parent"] ;
@@ -1739,10 +1768,13 @@ private static $encoding = 'UTF-8';
$snaps[$vmsnap]["desc"]= $b["description"];
$snaps[$vmsnap]["memory"]= $b["memory"];
$snaps[$vmsnap]["creationtime"]= $b["creationTime"];
$snaps[$vmsnap]["method"]= $method;
}
$disks =$lv->get_disk_stats($vm) ;
foreach($disks as $disk) {
$file = $disk["file"] ;
if ($disk['device'] == "hdc" ) $primarypath = dirname(transpose_user_path($file));
$output = array() ;
exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ;
foreach($output as $key => $line) {
@@ -1755,22 +1787,24 @@ private static $encoding = 'UTF-8';
$reversed = array_reverse($output) ;
$snaps[$vmsnap]['backing'][$rev] = $reversed ;
}
$snaps[$vmsnap]["primarypath"]= $primarypath;
$parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ;
$parendfileinfo = pathinfo($parentfind[1]) ;
$snaps[$vmsnap]["parent"]= $parendfileinfo["extension"];
$snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]) ;
if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base" ;
if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"];
if (isset($b)) if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"];
$value = json_encode($snaps,JSON_PRETTY_PRINT) ;
file_put_contents($dbpath."/snapshots.db",$value) ;
return $noxml;
}
function refresh_snapshots_database($vm) {
global $lv ;
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
$dbpath = "/etc/libvirt/qemu/snapshotdb/$vm" ;
if (!is_dir($dbpath)) mkdir($dbpath) ;
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
$snaps = json_decode($snaps_json,true) ;
@@ -1806,7 +1840,6 @@ private static $encoding = 'UTF-8';
# Get uuid
$vmuuid = $lv->domain_get_uuid($vm) ;
#Get list of files
#$filepath = "/etc/libvirt/qemu/nvram/'.$uuid*" ; #$snapshotname"
$filepath = "/etc/libvirt/qemu/nvram/$vmuuid*" ; #$snapshotname"
$nvram_files=glob($filepath) ;
foreach($nvram_files as $key => $nvram_file) {
@@ -1826,7 +1859,7 @@ private static $encoding = 'UTF-8';
function delete_snapshots_database($vm,$name) {
global $lv ;
$dbpath = "/etc/libvirt/qemu/snapshot/$vm" ;
$dbpath = "/etc/libvirt/qemu/snapshotdb/$vm" ;
$snaps_json = file_get_contents($dbpath."/snapshots.db") ;
$snaps = json_decode($snaps_json,true) ;
unset($snaps[$name]) ;
@@ -1835,8 +1868,7 @@ private static $encoding = 'UTF-8';
return true ;
}
function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") {
function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $method = "QEMU", $memorysnap = "yes") {
global $lv ;
#Get State
@@ -1864,7 +1896,14 @@ private static $encoding = 'UTF-8';
$dirpath= str_replace('/mnt/user/', "/mnt/$storagelocation/", $dirpath);
}
$filenew = $dirpath.'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
$diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ;
switch ($method) {
case "QEMU" :
$diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ;
break;
case "ZFS":
case "BTRFS":
$diskspec .= " --diskspec '".$disk["device"]."',snapshot=manual " ;
}
$capacity = $capacity + $disk["capacity"] ;
}
@@ -1872,7 +1911,7 @@ private static $encoding = 'UTF-8';
$mem = $lv->domain_get_memory_stats($vm) ;
$memory = $mem[6] ;
if ($memorysnap = "yes") $memspec = ' --memspec "'.$dirpath.'/memory'.$name.'.mem",snapshot=external' ; else $memspec = "" ;
if ($memorysnap == "yes") $memspec = ' --memspec "'.$dirpath.'/memory'.$name.'.mem",snapshot=external' ; else $memspec = "" ;
$cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ;
@@ -1899,128 +1938,197 @@ private static $encoding = 'UTF-8';
if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml) ;
$output= [] ;
$test = false ;
if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ;
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";
shell_exec($fssnapcmd);
# if running resume.
if ($state == "running") $lv->domain_resume($vm);
break;
case "BTRFS":
# Create BTRFS Snapshot
break;
default:
# No Action
exec($cmdstr." 2>&1",$output,$return);
}
if (strpos(" ".$output[0],"error") ) {
$arrResponse = ['error' => substr($output[0],6) ] ;
} else {
$arrResponse = ['success' => true] ;
write_snapshots_database("$vm","$name") ;
$ret = write_snapshots_database("$vm","$name",$state,$snapshotdesc,$method) ;
#remove meta data
$ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
if ($ret != "noxml") $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
}
return $arrResponse ;
}
function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') {
global $lv ;
$snapslist= getvmsnapshots($vm) ;
$disks =$lv->get_disk_stats($vm) ;
function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryrun = false) {
global $lv ;
$snapslist= getvmsnapshots($vm) ;
#$disks =$lv->get_disk_stats($vm) ;
$snapstate = $snapslist[$snap]['state'];
$method = $snapslist[$snap]['method'];
#VM must be shutdown.
$res = $lv->get_domain_by_name($vm);
$dom = $lv->domain_get_info($res);
$state = $lv->domain_state_translate($dom['state']);
# if VM running shutdown. Record was running.
if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ;
# Wait for shutdown?
# GetXML
$strXML= $lv->domain_get_xml($res) ;
$xmlobj = custom::createArray('domain',$strXML) ;
# Process disks and update path for method QEMU.
if ($method == "QEMU") {
$disks=($snapslist[$snap]['disks']) ;
foreach ($disks as $disk) {
$diskname = $disk["@attributes"]["name"] ;
if ($diskname == "hda" || $diskname == "hdb") continue ;
$path = $disk["source"]["@attributes"]["file"] ;
if ($diskname == "hdc") {
$primarypathinfo = pathinfo($path) ;
$primarypath = $primarypathinfo['dirname'] ;
}
if ($snapstate != "running") {
$item = array_search($path,$snapslist[$snap]['backing'][$diskname]) ;
$newpath = $snapslist[$snap]['backing'][$diskname][$item + 1];
$json_info = getDiskImageInfo($newpath) ;
foreach($xmlobj['devices']['disk'] as $ddk => $dd){
if ($dd['target']["@attributes"]['dev'] == $diskname) {
$xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = "$newpath" ;
$xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ;
}
}
}
}
}
switch ($snapslist[$snap]['state']) {
case "shutoff":
case "running":
#VM must be shutdown.
$res = $lv->get_domain_by_name($vm);
$dom = $lv->domain_get_info($res);
$state = $lv->domain_state_translate($dom['state']);
# if VM running shutdown. Record was running.
if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ;
# Wait for shutdown?
# GetXML
$strXML= $lv->domain_get_xml($res) ;
$xmlobj = custom::createArray('domain',$strXML) ;
# If Snapstate not running create new XML.
if ($snapstate != "running") {
if ($method == "ZFS") $xml = $snapslist[$snap]['xml']; else $xml = custom::createXML('domain',$xmlobj)->saveXML();
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
if (!$dryrun) $new = $lv->domain_define($xml);
file_put_contents("/tmp/xmlrevert", "$xml" ) ;## Remove before stable.
if ($new) $arrResponse = ['success' => true] ; else $arrResponse = ['error' => $lv->get_last_error()] ;
}
# Process disks and update path.
$disks=($snapslist[$snap]['disks']) ;
foreach ($disks as $disk) {
$diskname = $disk["@attributes"]["name"] ;
if ($diskname == "hda" || $diskname == "hdb") continue ;
$path = $disk["source"]["@attributes"]["file"] ;
if ($diskname == "hdc") {
$primarypathinfo = pathinfo($path) ;
$primarypath = $primarypathinfo['dirname'] ;
}
$item = array_search($path,$snapslist[$snap]['backing'][$diskname]) ;
$newpath = $snapslist[$snap]['backing'][$diskname][$item + 1];
$json_info = getDiskImageInfo($newpath) ;
foreach($xmlobj['devices']['disk'] as $ddk => $dd){
if ($dd['target']["@attributes"]['dev'] == $diskname) {
$xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = "$newpath" ;
$xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ;
}
}
}
$xml = custom::createXML('domain',$xmlobj)->saveXML();
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
$new = $lv->domain_define($xml);
file_put_contents("/tmp/xmlrevert", "$xml" ) ;## Remove before stable.
if ($new)
$arrResponse = ['success' => true] ; else
$arrResponse = ['error' => $lv->get_last_error()] ;
# remove snapshot meta data, images, memory, runxml and NVRAM. for all snapshots.
# remove snapshot meta data and images for all snpahots.
foreach ($disks as $disk) {
$diskname = $disk["@attributes"]["name"] ;
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";
file_put_contents("/tmp/rmvsnaps",$path,FILE_APPEND);
$item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ;
$item++ ;
while($item > 0)
{
if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ;
$newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ;
file_put_contents("/tmp/rmvsnaps",$newpath,FILE_APPEND);
if (is_file($newpath) && $action == "yes") if (!$dryrun) unlink("$newpath"); else echo "unlink $newpath\n";
$item++ ;
}
}
# Remove later snapshots
if (!is_null($snapslist)) uasort($snapslist,'compare_creationtimelt') ;
foreach ($disks as $disk) {
$diskname = $disk["@attributes"]["name"] ;
if ($diskname == "hda" || $diskname == "hdb") continue ;
$path = $disk["source"]["@attributes"]["file"] ;
if (is_file($path) && $action == "yes") unlink("$path") ;
$item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ;
$item++ ;
while($item > 0)
{
if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ;
$newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ;
if (is_file($newpath) && $action == "yes") unlink("$newpath") ;
$item++ ;
}
}
foreach($snapslist as $s) {
if ($s['name'] == $snap) break ;
$name = $s['name'] ;
$oldmethod = $s['method'];
if ($dryrun) echo "$name $oldmethod\n";
if (!isset($primarypath)) $primarypath = $s['primarypath'];
$xmlfile = $primarypath."/$name.running" ;
$memoryfile = $primarypath."/memory$name.mem" ;
$olddisks = $snapslist[$name]['disks'] ;
uasort($snapslist,'compare_creationtimelt') ;
foreach($snapslist as $s) {
$name = $s['name'] ;
if ($oldmethod == "QEMU") {
foreach ($olddisks as $olddisk) {
$olddiskname = $olddisk["@attributes"]["name"] ;
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 ($oldmethod == "ZFS") {
# Rollback ZFS Snapshot
$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";
}
$xmlfile = $primarypath."/$name.running" ;
$memoryfile = $primarypath."/memory$name.mem" ;
#Delete Metadata
#if ($actionmeta == "yes") if (!$dryrun) $ret = delete_snapshots_database("$vm","$name") ;
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") ;
# 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 ($snapslist[$snap]['state'] == "running") {
# Set XML to saved XML
$xml = file_get_contents($xmlfile) ;
$xmlobj = custom::createArray('domain',$xml) ;
$xml = custom::createXML('domain',$xmlobj)->saveXML();
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
file_put_contents("/tmp/xmlrevert2", "$xml" ) ;## Remove before stable.
$rtn = $lv->domain_define($xml) ;
if ($method == "ZFS") {
if (!isset($primarypath)) $primarypath = $snapslist[$snap]['primarypath'];
# Restore Memory.
$zfsdataset = trim(shell_exec("zfs list -H -o name -r ".transpose_user_path($primarypath))) ;
if ($dryrun) {
var_dump(transpose_user_path($primarypath));
}
$fssnapcmd = " zfs rollback $zfsdataset@$snap";
if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
$fssnapcmd = " zfs destroy $zfsdataset@$snap";
if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
}
$makerun = true ;
if ($makerun == true) exec("virsh restore ".escapeshellarg($memoryfile)) ;
}
#Delete Metadata only.
if ($actionmeta == "yes") {
$ret = delete_snapshots_database("$vm","$name") ;
}
if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ;
if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ;
if ($s['name'] == $snap) break ;
}
#if VM was started restart.
if ($state == 'running' && $snapslist[$snap]['state'] != "running") {
$arrResponse = $lv->domain_start($vm) ;
}
if ($snapslist[$snap]['state'] == "running") {
$xmlfile = $primarypath."/$snap.running" ;
$memoryfile = $primarypath."/memory$snap.mem" ;
# Set XML to saved XML
$xml = file_get_contents($xmlfile) ;
$xmlobj = custom::createArray('domain',$xml) ;
$xml = custom::createXML('domain',$xmlobj)->saveXML();
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
file_put_contents("/tmp/xmlrevert2", "$xml" ) ;## Remove before stable.
if (!$dryrun) $rtn = $lv->domain_define($xml) ;
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ;
break ;
}
$arrResponse = ['success' => true] ;
return($arrResponse) ;
}
# Restore Memory.
if (!$dryrun) $cmdrtn = exec("virsh restore --running ".escapeshellarg($memoryfile)) ;
if (!$dryrun && !$cmdrtn) unlink($xmlfile);
if (!$dryrun && !$cmdrtn) unlink($memoryfile);
}
#if VM was started restart.
if ($state == 'running' && $snapslist[$snap]['state'] != "running") {
if (!$dryrun) $arrResponse = $lv->domain_start($vm) ;
}
if ($actionmeta == "yes") {
if (!$dryrun) $ret = delete_snapshots_database("$vm","$snap"); else echo "Delete snapshot meta\n";
}
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);
return($arrResponse) ;
}
function vm_snapimages($vm, $snap, $only) {
global $lv ;
$snapslist= getvmsnapshots($vm) ;
@@ -2359,6 +2467,143 @@ OPTIONS
*/
}
function addtemplatexml($post) {
global $templateslocation,$lv,$config;
$savedtemplates = json_decode(file_get_contents($templateslocation),true);
if (isset($post['xmldesc'])) {
$data = explode("\n",$post['xmldesc']);
foreach ($data as $k => $line)
{
if (strpos($line,"uuid")) unset($data[$k]);
if (strpos($line,"<nvram>")) unset($data[$k]);
if (strpos($line,"<name>")) $data[$k] = "<name>#template123456</name>";
}
$data = implode("\n",$data);
$new = $lv->domain_define($data);
$dom = $lv->get_domain_by_name("#template123456") ;
$uuid = $lv->domain_get_uuid("#template123456") ;
$usertemplate = domain_to_config($uuid);
$lv->domain_undefine($dom);
$usertemplate['templatename'] = $post['templatename'];
$usertemplate['template'] = $post['template'];
unset($usertemplate['domain']['uuid']);
unset($usertemplate['domain']['name']);
} else {
// form view
$usertemplate = $post;
// generate xml for this domain
$strXML = $lv->config_to_xml($usertemplate);
$qemucmdline = $config['qemucmdline'];
$strXML = $lv->appendqemucmdline($strXML,$qemucmdline) ;
}
foreach($usertemplate['disk'] as $diskid => $diskdata) { unset($usertemplate['disk'][$diskid]['new']);}
foreach($usertemplate['gpu'] as $gpuid => $gpudata) { $usertemplate['gpu'][$gpuid]['guest']['multi'] = $usertemplate['gpu'][$gpuid]['multi']; unset($usertemplate['gpu'][$gpuid]['multi']);}
unset($usertemplate['createvmtemplate']);
unset($usertemplate['domain']['xmlstart']);
unset($usertemplate['pci']) ;
unset($usertemplate['usb']) ;
unset($usertemplate['usbboot']) ;
unset($usertemplate['nic']['mac']) ;
$templatename=$usertemplate['templatename'];
if ($templatename == "") $templatename=$usertemplate['template']['os'];
unset($usertemplate['templatename']) ;
if (strpos($templatename,"User-") === false) $templatename = "User-".$templatename ;
if (is_array($usertemplate['clock'])) $usertemplate['clocks'] = json_encode($usertemplate['clock']);
unset($usertemplate['clock']);
$savedtemplates[$templatename] = [
'icon' => $usertemplate['template']['icon'],
'form' => 'Custom.form.php',
'os' => $usertemplate['template']['os'],
'overrides' => $usertemplate
];
if (!is_dir(dirname($templateslocation))) mkdir(dirname($templateslocation));
file_put_contents($templateslocation,json_encode($savedtemplates,JSON_PRETTY_PRINT));
$reply = ['success' => true];
return $reply;
}
function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$collectnicstats = true, $collectmemstats = true) {
global $lv, $vmusagestats;
$hostcpus = $lv->host_get_node_info();
$timestamp = time();
$allstats=$lv->domain_get_all_domain_stats();
foreach ($allstats as $vm => $data) {
$state = $data["state.state"];
# CPU Metrics
$cpuTime = 0;
$cpuHostPercent = 0;
$cpuGuestPercent = 0;
$cpuTimeAbs = $data["cpu.time"];
if ($state == 1 && $collectcpustats == true) {
$guestcpus = $data["vcpu.current"];
$cpuTime = $cpuTimeAbs - $vmusagestats[$vm]["cpuTimeAbs"];
$pcentbase = ((($cpuTime) * 100.0) / ((($timestamp) - $vmusagestats[$vm]["timestamp"] ) * 1000.0 * 1000.0 * 1000.0));
$cpuHostPercent = round($pcentbase / $hostcpus['cpus'],1);
$cpuGuestPercent = round($pcentbase / $guestcpus, 1) ;
$cpuHostPercent = max(0.0, min(100.0, $cpuHostPercent));
$cpuGuestPercent = max(0.0, min(100.0, $cpuGuestPercent));
}
# Memory Metrics
if ($state == 1 && $collectmemstats) {
$currentmem = $data["balloon.current"];
$unusedmem = $data["balloon.unused"];
$meminuse = $currentmem - $unusedmem;
} else $currentmem = $meminuse = 0;
# Disk
if ($state == 1 && $collectdiskstats) {
$disknum = $data["block.count"];
$rd=$wr=$i=0;
for ($i = 0; $i < $disknum; $i++) {
if ($data["block.$i.name"] == "hda" || $data["block.$i.name"] == "hdb") continue;
$rd += $data["block.$i.rd.bytes"] ;
$wr += $data["block.$i.wr.bytes"] ;
}
$rdrate = ($rd - $vmusagestats[$vm]['rdp']);
$wrrate = ($wr - $vmusagestats[$vm]['wrp']);
} else $rdrate=$wrrate=0;
# Net
if ($state == 1 && $collectnicstats) {
$nicnum = $data["net.count"];
$rx=$tx=$i=0;
for ($i = 0; $i < $nicnum; $i++) {
$rx += $data["net.$i.rx.bytes"] ;
$tx += $data["net.$i.tx.bytes"] ;
}
$rxrate = round(($rx - $vmusagestats[$vm]['rxp']),0);
$txrate = round(($tx - $vmusagestats[$vm]['txp']),0);
} else $rxrate=$txrate=0;
$vmusagestats[$vm] = [
"cpuTime" => $cpuTime,
"cpuTimeAbs" => $cpuTimeAbs,
"cpuhost" => $cpuHostPercent,
"cpuguest" => $cpuGuestPercent,
"timestamp" => $timestamp,
"mem" => $meminuse,
"maxmem" => $currentmem,
"rxrate" => $rxrate,
"rxp" => $rx,
"txrate" => $txrate,
"txp" => $tx,
"rdp" => $rd,
"rdrate" => $rdrate,
"wrp" => $wr,
"wrrate" => $wrrate,
"state" => $state,
];
}
}
?>

View File

@@ -62,7 +62,7 @@ function ajaxVMDispatchconsoleRV(params, spin){
}
},'json');
}
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, console="web", preview=false){
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, fstype="QEMU",console="web", preview=false){
var opts = [];
var path = location.pathname;
var x = path.indexOf("?");
@@ -110,7 +110,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c
opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) {
e.preventDefault();
selectsnapshot(uuid , name, "--generate" , "create",false,state) ;
selectsnapshot(uuid , name, "--generate" , "create",false,state,fstype) ;
}});
} else if (state == "pmsuspended") {
opts.push({text:_("Resume"), icon:"fa-play", action:function(e) {
@@ -165,7 +165,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c
opts.push({divider:true});
opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) {
e.preventDefault();
selectsnapshot(uuid , name, "--generate" , "create",false,state) ;
selectsnapshot(uuid , name, "--generate" , "create",false,state,fstype) ;
}});
opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) {
e.preventDefault();
@@ -200,7 +200,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c
}
context.attach('#vm-'+uuid, opts);
}
function addVMSnapContext(name, uuid, template, state, snapshotname, preview=false){
function addVMSnapContext(name, uuid, template, state, snapshotname, method, preview=false){
var opts = [];
var path = location.pathname;
var x = path.indexOf("?");
@@ -213,7 +213,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal
e.preventDefault();
selectsnapshot(uuid, name, snapshotname, "revert",true) ;
}});
if (method == "QEMU") {
opts.push({text:_("Block Commit"), icon:"fa-hdd-o", action:function(e) {
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
e.preventDefault();
@@ -230,6 +230,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal
e.preventDefault();
ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist");
}}); }
}
} else {
opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) {
e.preventDefault();

View File

@@ -0,0 +1,98 @@
#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2024, Lime Technology
* Copyright 2024-2024, Simon Fairweather.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot = '/usr/local/emhttp';
$varroot = '/var/local/emhttp';
$md5_old = -1;
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/publish.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
global $vmusagestats;
extract(parse_plugin_cfg('dynamix',true));
get_vm_usage_stats();
sleep(1);
// add translations
$_SERVER['REQUEST_URI'] = 'dashboard';
$login_locale = _var($display,'locale');
require_once "$docroot/webGui/include/Translations.php";
// remember current language
$locale_init = $locale;
function update_translation($locale) {
global $docroot,$language;
$language = [];
if ($locale) {
$text = "$docroot/languages/$locale/translations.txt";
if (file_exists($text)) {
$store = "$docroot/languages/$locale/translations.dot";
if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
$language = unserialize(file_get_contents($store));
}
$text = "$docroot/languages/$locale/dashboard.txt";
if (file_exists($text)) {
$store = "$docroot/languages/$locale/dashboard.dot";
if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
$language = array_merge($language,unserialize(file_get_contents($store)));
}
}
}
$domain_cfgfile = "/boot/config/domain.cfg";
$domain_cfg = parse_ini_file($domain_cfgfile);
if (!isset($domain_cfg['USAGETIMER'])) $timer = 3 ; else $timer = $domain_cfg['USAGETIMER'];
while (true) {
extract(parse_plugin_cfg('dynamix',true));
if (_var($display,'locale') != $locale_init) {
$locale_init = _var($display,'locale');
update_translation($locale_init);
}
get_vm_usage_stats();
$echo = [];
$echo = [];
$ts = $time1 - $time0;
$echodata = "";
$running = 0;
ksort($vmusagestats);
foreach ($vmusagestats as $vm => $vmdata) {
if ($vmdata['state'] == 1) {
$running++;
$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 .= _("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>";
}
$echo = $echodata ;
}
if ($running < 1) $echo = "<tr><td colspan='7' style='text-align:center;padding-top:12px'>"._('No VMs running')."</td></tr>";
$echo = json_encode($echo);
$md5_new = md5($echo,true);
if ($md5_new !== $md5_old) {
$md5_old = publish('vm_usage',$echo)!==false ? $md5_new : -1;
$time0 = $time1;
}
sleep($timer);
}
?>

View File

@@ -62,7 +62,7 @@ function execCommand_nchan_clone($command,$idx,$refcmd=false) {
$out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out);
$out = trim($out);
$values = explode(' ',$out);
$string = _("Data copied: ").$values[0].' '._(" Percentage: ").$values[1].' '._(" Transfer Rate: ").$values[2].' '._(" Time remaining: ").$values[4].$values[5];
$string = _('Data copied').': '.$values[0].' '._('Percentage').': '.$values[1].' '._('Transfer Rate').': '.$values[2].' '._('Time remaining').': '.$values[4].$values[5];
write("progress\0$idx\0".htmlspecialchars($string));
if ($out) $stringsave=$string;
}

View File

@@ -14,6 +14,7 @@ table.domdisk tbody tr td:nth-child(1){padding-left:72px}
table.domdisk tbody tr:nth-child(even){background-color:transparent!important}
table.domdisk tbody tr:nth-child(4n-1){background-color:transparent!important}
table.snapshot{margin-top:0}
table tbody td{line-height:normal}
i.mover{margin-right:8px;display:none}
i.fa-dot-circle-o{padding-left:12px}
#resetsort{margin-left:12px;display:inline-block;width:32px}

View File

@@ -37,6 +37,14 @@
$arrValidNetworks = getValidNetworks();
$strCPUModel = getHostCPUModel();
$templateslocation = "/boot/config/plugins/dynamix.vm.manager/savedtemplates.json";
if (is_file($templateslocation)){
$arrAllTemplates["User-templates"] = "";
$ut = json_decode(file_get_contents($templateslocation),true) ;
$arrAllTemplates = array_merge($arrAllTemplates, $ut);
}
$arrConfigDefaults = [
'template' => [
'name' => $strSelectedTemplate,
@@ -50,7 +58,7 @@
'uuid' => $lv->domain_generate_uuid(),
'clock' => 'localtime',
'arch' => 'x86_64',
'machine' => 'pc',
'machine' => 'pc-i440fx',
'mem' => 1024 * 1024,
'maxmem' => 1024 * 1024,
'password' => '',
@@ -154,6 +162,13 @@
exit;
}
// create new VM template
if (isset($_POST['createvmtemplate'])) {
$reply = addtemplatexml($_POST);
echo json_encode($reply);
exit;
}
// update existing VM
if (isset($_POST['updatevm'])) {
$uuid = $_POST['domain']['uuid'];
@@ -286,6 +301,8 @@
if ($arrConfig['domain']['hyperv'] == 1) $arrClocks = $arrDefaultClocks['hyperv'] ; else $arrClocks = $arrDefaultClocks['windows'] ;
} else $arrClocks = $arrDefaultClocks['other'] ;
}
if (strpos($arrConfig['template']['name'],"User-") !== false) $arrConfig['template']['name'] = str_replace("User-","",$arrConfig['template']['name']);
?>
<link rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/lib/codemirror.css')?>">
@@ -498,6 +515,12 @@
</blockquote>
</div>
<?
if (!isset($arrValidMachineTypes[$arrConfig['domain']['machine']])) {
$arrConfig['domain']['machine'] = ValidateMachineType($arrConfig['domain']['machine']);
}
?>
<table>
<tr class="advanced">
<td>_(Machine)_:</td>
@@ -696,11 +719,12 @@
if (!empty($arrDisk['new'])) {
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(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')) {
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')) {
// hide all the disks because the auto disk already has been created
$boolShowAllDisks = false;
}
@@ -771,7 +795,7 @@
<tr class="advanced disk_file_options">
<td>_(vDisk Type)_:</td>
<td>
<select name="disk[<?=$i?>][driver]" class="narrow" title="_(type of storage image)_">
<select name="disk[<?=$i?>][driver]" class="disk_driver narrow" title="_(type of storage image)_">
<?mk_dropdown_options($arrValidDiskDrivers, $arrDisk['driver']);?>
</select>
</td>
@@ -780,11 +804,14 @@
<tr class="advanced disk_bus_options">
<td>_(vDisk Bus)_:</td>
<td>
<select name="disk[<?=$i?>][bus]" class="disk_bus narrow">
<select name="disk[<?=$i?>][bus]" class="disk_bus narrow" onchange="BusChange(this)">
<?mk_dropdown_options($arrValidDiskBuses, $arrDisk['bus']);?>
</select>
_(Boot Order)_:
<input type="number" size="5" maxlength="5" id="disk[<?=$i?>][boot]" class="narrow bootorder" style="width: 50px;" name="disk[<?=$i?>][boot]" title="_(Boot order)_" value="<?=$arrDisk['boot']?>" >
<? if ($arrDisk['bus'] == "virtio" || $arrDisk['bus'] == "usb") $ssddisabled = "hidden "; else $ssddisabled = " ";?>
<span id="disk[<?=$i?>][rotatetext]" <?=$ssddisabled?>>_(SSD)_:</span>
<input type="checkbox" id="disk[<?=$i?>][rotation]" class="narrow rotation" onchange="updateSSDCheck(this)"style="width: 50px;" name="disk[<?=$i?>][rotation]" <?=$ssddisabled ?> <?=$arrDisk['rotation'] ? "checked ":"";?> title="_(Set SDD flag)_" value="<?=$arrDisk['rotation']?>" >
</td>
</tr>
<tr class="advanced disk_bus_options">
@@ -832,6 +859,11 @@
Specify the order the devices are used for booting.
</p>
<p class="advanced">
<b>vDisk SSD Flag</b><br>
Specify the vdisk shows as SSD within the guest, only supported on SCSI, SATA and IDE bus types.
</p>
<p class="advanced">
<b>vDisk Serial</b><br>
Set the device serial number presented to the VM.
@@ -915,7 +947,7 @@
<tr class="advanced disk_file_options">
<td>_(vDisk Type)_:</td>
<td>
<select name="disk[{{INDEX}}][driver]" class="narrow" title="_(type of storage image)_">
<select name="disk[{{INDEX}}][driver]" class="disk_driver narrow" title="_(type of storage image)_">
<?mk_dropdown_options($arrValidDiskDrivers, '');?>
</select>
</td>
@@ -924,12 +956,14 @@
<tr class="advanced disk_bus_options">
<td>_(vDisk Bus)_:</td>
<td>
<select name="disk[{{INDEX}}][bus]" class="disk_bus narrow">
<select name="disk[{{INDEX}}][bus]" class="disk_bus narrow" onchange="BusChange(this)">
<?mk_dropdown_options($arrValidDiskBuses, '');?>
</select>
_(Boot Order)_:
<input type="number" size="5" maxlength="5" id="disk[{{INDEX}}][boot]" class="narrow bootorder" style="width: 50px;" name="disk[{{INDEX}}][boot]" title="_(Boot order)_" value="" >
<span id="disk[{{INDEX}}][rotatetext]" hidden>_(SSD)_:</span>
<input type="checkbox" id="disk[{{INDEX}}][rotation]" class="narrow rotation" onchange="updateSSDCheck(this)"style="width: 50px;" name="disk[{{INDEX}}[rotation]" hidden title="_(Set SSD flag)_" value='0' >
</td>
<tr class="advanced disk_bus_options">
<td>_(Serial)_:</td>
@@ -1104,7 +1138,7 @@
</select>
</td>
</tr>
<tr id="copypasteline" name="copypaste" class="<?if ($arrGPU['id'] != 'virtual') echo 'was';?>advanced ">
<tr id="copypasteline" name="copypaste" class="<?if ($arrGPU['id'] != 'virtual') echo 'was';?>advanced copypaste">
<td>_(VM Console enable Copy/paste)_:</td>
<td>
<select id="copypaste" name="gpu[<?=$i?>][copypaste]" class="narrow" >
@@ -1606,6 +1640,7 @@
<input type="button" value="_(Create)_" busyvalue="_(Creating)_..." readyvalue="_(Create)_" id="btnSubmit" />
<?}?>
<input type="button" value="_(Cancel)_" id="btnCancel" />
<input type="button" value=" _(Create/Modify Template)_" busyvalue="_(Creating)_..." readyvalue="_(Create)_" id="btnTemplateSubmit" />
</td>
</tr>
</table>
@@ -1634,6 +1669,7 @@
<input type="button" value="_(Create)_" busyvalue="_(Creating)_..." readyvalue="_(Create)_" id="btnSubmit" />
<?}?>
<input type="button" value="_(Cancel)_" id="btnCancel" />
<input type="button" value=" _(Create/Modify Template)_" busyvalue="_(Creating)_..." readyvalue="_(Create)_" id="btnTemplateSubmit" />
<?} else {?>
<input type="button" value="_(Back)_" id="btnCancel" />
<?}?>
@@ -1673,6 +1709,28 @@ function ShareChange(share) {
}
}
function BusChange(bus) {
var value = bus.value;
var index = bus.name.indexOf("]") + 1;
var name = bus.name.substr(0,index) ;
if (value == "virtio" || value == "usb" ) {
document.getElementById(name+"[rotatetext]").style.visibility="hidden";
document.getElementById(name+"[rotation]").style.visibility="hidden";
} else {
document.getElementById(name+"[rotation]").style.display="inline";
document.getElementById(name+"[rotation]").style.visibility="visible";
document.getElementById(name+"[rotatetext]").style.display="inline";
document.getElementById(name+"[rotatetext]").style.visibility="visible";
}
}
function updateSSDCheck(ssd) {
var value = ssd.value;
var index = ssd.name.indexOf("]") + 1;
var name = ssd.name.substr(0,index) ;
if (document.getElementById(name+"[rotation]").checked) ssd.value = "1"; else ssd.value = "0";
}
function BIOSChange(bios) {
var value = bios.value;
if (value == "0") {
@@ -1878,6 +1936,10 @@ $(function() {
var $disk_input = $table.find('.disk');
var $disk_preview = $table.find('.disk_preview');
var $disk_serial = $table.find('.disk_serial');
var $disk_driver = $table.find('.disk_driver').val();
var $disk_ext = "img";
if ($disk_driver == "raw") $disk_ext = "img";
else if(disk_select != 'manual') $disk_ext = $disk_driver;
if (disk_select == 'manual') {
@@ -1906,7 +1968,7 @@ $(function() {
} else if (disk_select !== '') {
// Auto disk
var auto_disk_path = domaindir + '/vdisk' + (index+1) + '.img';
var auto_disk_path = domaindir + '/vdisk' + (index+1) + '.' + $disk_ext;
$disk_preview.html(auto_disk_path);
$disk_input.fadeOut('fast', function() {
$disk_preview.fadeIn('fast');
@@ -2038,6 +2100,10 @@ $(function() {
regenerateDiskPreview($(this).closest('table').data('index'));
});
$("#vmform").on("change", ".disk_driver", function changeDiskSelectEvent() {
regenerateDiskPreview($(this).closest('table').data('index'));
});
$("#vmform").on("input change", ".disk", function changeDiskEvent() {
var $input = $(this);
var config = $input.data();
@@ -2070,7 +2136,7 @@ $(function() {
var myindex = $(this).closest('table').data('index');
if (myindex == 0) {
$vnc_sections = $('.autoportline,.protocol,.vncmodel,.vncpassword,.vnckeymap');
$vnc_sections = $('.autoportline,.protocol,.vncmodel,.vncpassword,.vnckeymap,.copypaste');
if (myvalue == 'virtual') {
$vnc_sections.filter('.wasadvanced').removeClass('wasadvanced').addClass('advanced');
slideDownRows($vnc_sections.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
@@ -2181,6 +2247,96 @@ $(function() {
}, "json");
});
$("#vmform .formview #btnTemplateSubmit").click(function frmSubmit() {
var $button = $(this);
var $panel = $('.formview');
var form = $button.closest('form');
form.append('<input type="hidden" name="createvmtemplate" value="1" />');
var createVmInput = form.find('input[name="createvm"],input[name="updatevm"]');
createVmInput.remove();
$("#vmform .disk_select option:selected").not("[value='manual']").closest('table').each(function () {
var v = $(this).find('.disk_preview').html();
$(this).find('.disk').val(v);
});
$panel.find('input').prop('disabled', false); // enable all inputs otherwise they wont post
<?if (!$boolNew):?>
// signal devices to be added or removed
form.find('input[name="usb[]"],input[name="pci[]"],input[name="usbopt[]"]').each(function(){
if (!$(this).prop('checked')) $(this).prop('checked',true).val($(this).val()+'#remove');
});
// remove unused graphic cards
var gpus = [], i = 0;
do {
var gpu = form.find('select[name="gpu['+(i++)+'][id]"] option:selected').val();
if (gpu) gpus.push(gpu);
} while (gpu);
form.find('select[name="gpu[0][id]"] option').each(function(){
var gpu = $(this).val();
if (gpu != 'virtual' && !gpus.includes(gpu)) form.append('<input type="hidden" name="pci[]" value="'+gpu+'#remove">');
});
// remove unused sound cards
var sound = [], i = 0;
do {
var audio = form.find('select[name="audio['+(i++)+'][id]"] option:selected').val();
if (audio) sound.push(audio);
} while (audio);
form.find('select[name="audio[0][id]"] option').each(function(){
var audio = $(this).val();
if (audio && !sound.includes(audio)) form.append('<input type="hidden" name="pci[]" value="'+audio+'#remove">');
});
<?endif?>
var postdata = form.find('input,select,textarea[name="qemucmdline"]').serialize().replace(/'/g,"%27");
<?if (!$boolNew):?>
// keep checkbox visually unchecked
form.find('input[name="usb[]"],input[name="usbopt[]"],input[name="pci[]"]').each(function(){
if ($(this).val().indexOf('#remove')>0) $(this).prop('checked',false);
});
<?endif?>
$panel.find('input').prop('disabled', true);
$button.val($button.attr('busyvalue'));
swal({
title: _("Template Name")_,
text: _("Enter name:\nIf name already exists it will be replaced.")_,
type: "input",
showCancelButton: true,
closeOnConfirm: false,
//animation: "slide-from-top",
inputPlaceholder: _("Leaving blank will use OS name.")_
},
function(inputValue){
postdata=postdata+"&templatename="+inputValue;
$.post("/plugins/dynamix.vm.manager/templates/Custom.form.php", postdata, function( data ) {
if (data.success) {
if (data.vmrcurl) {
var vmrc_window=window.open(data.vmrcurl, '_blank', 'scrollbars=yes,resizable=yes');
try {
vmrc_window.focus();
} catch (e) {
swal({title:"_(Browser error)_",text:"_(Pop-up Blocker is enabled! Please add this site to your exception list)_",type:"warning",confirmButtonText:"_(Ok)_"},function(){ done() });
return;
}
}
done();
}
if (data.error) {
swal({title:"_(VM creation error)_",text:data.error,type:"error",confirmButtonText:"_(Ok)_"});
$panel.find('input').prop('disabled', false);
$button.val($button.attr('readyvalue'));
resetForm();
}
}, "json");
});
});
$("#vmform .xmlview #btnSubmit").click(function frmSubmit() {
var $button = $(this);
var $panel = $('.xmlview');
@@ -2207,6 +2363,52 @@ $(function() {
}, "json");
});
$("#vmform .xmlview #btnTemplateSubmit").click(function frmSubmit() {
var $button = $(this);
var $panel = $('.xmlview');
editor.save();
$panel.find('input').prop('disabled', false); // enable all inputs otherwise they wont post
var form = $button.closest('form');
form.append('<input type="hidden" name="createvmtemplate" value="1" />');
var createVmInput = form.find('input[name="createvm"],input[name="updatevm"]');
createVmInput.remove();
var postdata = $panel.closest('form').serialize().replace(/'/g,"%27");
$panel.find('input').prop('disabled', true);
$button.val($button.attr('busyvalue'));
swal({
title: _("Template Name")_,
text: _("Enter name:\nIf name already exists it will be replaced.")_,
type: "input",
showCancelButton: true,
closeOnConfirm: false,
//animation: "slide-from-top",
inputPlaceholder: _("Leaving blank will use OS name.")_
},
function(inputValue){
postdata=postdata+"&templatename="+inputValue;
$.post("/plugins/dynamix.vm.manager/templates/Custom.form.php", postdata, function( data ) {
if (data.success) {
done();
}
if (data.error) {
swal({title:"_(VM creation error)_",text:data.error,type:"error",confirmButtonText:"_(Ok)_"});
$panel.find('input').prop('disabled', false);
$button.val($button.attr('readyvalue'));
resetForm();
}
}, "json");
});
});
// Fire events below once upon showing page
var os = $("#vmform #template_os").val() || 'linux';
var os_casted = (os.indexOf('windows') == -1 ? 'other' : 'windows');

View File

@@ -22,6 +22,14 @@
require_once "$docroot/webGui/include/Translations.php";
}
$templateslocation = "/boot/config/plugins/dynamix.vm.manager/savedtemplates.json";
if (is_file($templateslocation)){
$arrAllTemplates["User-templates"] = "";
$ut = json_decode(file_get_contents($templateslocation),true) ;
$arrAllTemplates = array_merge($arrAllTemplates, $ut);
}
$hdrXML = "<?xml version='1.0' encoding='UTF-8'?>\n"; // XML encoding declaration
// create new VM
@@ -37,6 +45,15 @@
exit;
}
// create new VM template
if (isset($_POST['createvmtemplate'])) {
$reply = addtemplatexml($_POST);
echo json_encode($reply);
exit;
}
// update existing VM
if (isset($_POST['updatevm'])) {
$uuid = $_POST['domain']['uuid'];
@@ -99,6 +116,9 @@
<input type="button" value="_(Create)_" busyvalue="_(Creating)_..." readyvalue="_(Create)_" id="btnSubmit" />
<? } ?>
<input type="button" value="_(Cancel)_" id="btnCancel" />
<input type="button" value=" _(Create/Modify Template)_" busyvalue="_(Creating)_..." readyvalue="_(Create)_" id="btnTemplateSubmit" />
<? } else { ?>
<input type="button" value="_(Done)_" id="btnCancel" />
<? } ?>
@@ -180,5 +200,43 @@ $(function() {
}
}, "json");
});
$("#vmform #btnTemplateSubmit").click(function frmSubmit() {
var $button = $(this);
var $form = $button.closest('form');
editor.save();
$form.find('input').prop('disabled', false); // enable all inputs otherwise they wont post
$form.append('<input type="hidden" name="createvmtemplate" value="1" />');
var createVmInput = $form.find('input[name="createvm"],input[name="updatevm"]');
createVmInput.remove();
var postdata = $form.serialize().replace(/'/g,"%27");
$form.find('input').prop('disabled', true);
$button.val($button.attr('busyvalue'));
swal({
title: _("Template Name")_,
text: _("Enter name:\nIf name already exists it will be replaced.")_,
type: "input",
showCancelButton: true,
closeOnConfirm: false,
//animation: "slide-from-top",
inputPlaceholder: _("Leaving blank will use OS name.")_
},
function(inputValue){
postdata=postdata+"&templatename="+inputValue;
$.post("/plugins/dynamix.vm.manager/templates/XML_Expert.form.php", postdata, function( data ) {
if (data.success) {
done();
}
if (data.error) {
swal({title:"_(VM creation error)_",text:data.error,type:"error",confirmButtonText:"_(Ok)_"});
$form.find('input').prop('disabled', false);
$button.val($button.attr('readyvalue'));
}
}, "json");
});
});
});
</script>

View File

@@ -15,7 +15,7 @@ Tag="database"
*/
?>
<?
$power = in_array('nvme',array_column(main_filter($disks),'transport')) ? ' / '._('Power') : '';
$power = _var($display,'power') && in_array('nvme',array_column(main_filter($disks),'transport')) ? _('Power').' / ' : '';
?>
<script>
String.prototype.no_tilde = function(){return this.replace('<?=$_tilde_?>','<?=$_proxy_?>');}
@@ -70,15 +70,12 @@ $('#tab1').bind({click:function() {$('i.toggle').show('slow');}});
<?endif;?>
</script>
<table class="disk_status wide">
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td>_(Temp)_<?=$power?></td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<table class="unraid disk_status">
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td><?=$power?>_(Temp)_</td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<tbody id="array_devices">
<?
foreach ($disks as $disk):
if (_var($disk,'type')=='Parity' or _var($disk,'type')=='Data')
echo "<tr><td colspan='11'>&nbsp;</td></tr>";
endforeach;
if (_var($display,'total')) echo "<tr class='tr_last'><td colspan='11'>&nbsp;</td></tr>";
foreach (main_filter($disks) as $disk) if (substr($disk['status'],0,7)!='DISK_NP') echo "<tr><td colspan='10'></td></tr>";
if (_var($display,'total') && _var($var,'mdNumDisks',0)>1) echo "<tr class='tr_last'><td colspan='10'></td></tr>";
?>
</tbody>
</table>

View File

@@ -83,6 +83,8 @@ function print_error($error) {
?>
<script>
var ctrl = '<span class="status <?=$tabbed?"":"vhshift"?>"><a style="cursor:pointer" class="tooltip_diskio" title="_(Toggle reads/writes display)_" onclick="toggle_diskio();return false"><i class="toggle fa"></i></a></span>';
var recall = null;
var recover = null;
function base64(str) {
return window.btoa(unescape(encodeURIComponent(str)));
@@ -307,28 +309,31 @@ var devices = new NchanSubscriber('/sub/devices<?=$spot?",parity":""?>',{subscri
devices.on('message', function(msg,meta) {
switch (<?if($spot):?>meta.id.channel()<?else:?>0<?endif;?>) {
case 0:
// array + pool + ua devices
var tables = msg.split('\0');
// get array state
var stopped = tables.pop();
for (var n=0; n < tables.length; n++) {
// get table name and content
var table = tables[n].split('\n');
$('#'+table[0]).html(table[1]);
}
// array + pool + ua + flash devices
var get = JSON.parse(msg);
for (var name in get) $('#'+name).html(get[name]);
if (recall !== null) recall.html('&nbsp;');
display_diskio();
// stop updating when array is stopped
if (stopped==1) {
if (get.stop==1) {
$('thead tr').removeClass().addClass('offline');
<?if (_var($var,'fsState')=='Started'):?>
setTimeout(refresh);
<?else:?>
if (!timers.stopped) timers.stopped = setTimeout(function(){devices.stop(); arraymonitor.start();},1500);
<?endif;?>
} else {
// make truncated descriptions fully visible when hovering over them
$('td.desc').hover(
function(){if ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {recall=$(this).next(); recover=recall.html(); recall.html('&nbsp;');}},
function(){if ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {recall.html(recover); recall=null;}}
);
}
break;
case 1:
// running parity status
$.each(msg.split(';'),function(k,v) {if ($('#line'+k).length>0) $('#line'+k).html(v);});
var get = JSON.parse(msg);
$.each(get,function(k,v) {if ($('#line'+k).length>0) $('#line'+k).html(v);});
// button control
if ($('#pauseButton').length>0 && $('#pauseButton').prop('disabled')==false) {
if (!msg && $('#cancelButton').length>0 && $('#cancelButton').val()=="_(Cancel)_") {
@@ -338,9 +343,9 @@ devices.on('message', function(msg,meta) {
$('#line4').html("_(completed)_");
} else {
var form = document.arrayOps;
if ($('#pauseButton').val()=="_(Pause)_" && msg.search("_(paused)_")!=-1) {
if ($('#pauseButton').val()=="_(Pause)_" && get[1].search("_(paused)_")!=-1) {
$('#pauseButton').val("_(Resume)_").prop('onclick',null).off('click').click(function(){resumeParity(form);});
} else if ($('#pauseButton').val()=="_(Resume)_" && msg.search("_(paused)_")==-1) {
} else if ($('#pauseButton').val()=="_(Resume)_" && get[1].search("_(paused)_")==-1) {
$('#pauseButton').val("_(Pause)_").prop('onclick',null).off('click').click(function(){pauseParity(form);});
}
}
@@ -540,6 +545,9 @@ window.onunload = function(){
<? elseif (_var($var,'configValid')=="invalid"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Too many attached devices. Please consider upgrading your)_ <a href="/Tools/Registration">_(registration key)_</a>.</td></tr>
<? elseif (_var($var,'configValid')=="ineligible"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Ineligible to run this version of Unraid OS. Please consider extending your)_ <a href="/Tools/Registration">_(registration key)_</a>.</td></tr>
<? elseif (_var($var,'configValid')=="nokeyserver"):?>
<tr><td><?status_indicator()?>**_(Stopped)_.**</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
<td>_(Cannot contact key-server. Please check your)_ <a href="/Settings/NetworkSettings">_(network settings)_</a>.</td></tr>

View File

@@ -20,10 +20,10 @@ $('#tab3').bind({click:function() {$('i.toggle').show('slow');}});
<?endif;?>
</script>
<table class="disk_status wide">
<table class="unraid disk_status">
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td>_(Temp)_</td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<tbody id="boot_device">
<?if (isset($disks['flash'])) echo "<tr><td colspan='11'>&nbsp;</td></tr>";?>
<?if (isset($disks['flash'])) echo "<tr><td colspan='10'></td></tr>";?>
</tbody>
</table>

View File

@@ -132,22 +132,28 @@ $('#tab2').bind({click:function() {$('i.toggle').show('slow');}});
<?endif;?>
</script>
<table class="unraid disk_status">
<?$i = 0?>
<?foreach ($pools as $pool):?>
<?if (isset($disks[$pool]['devices']) or _var($var,'fsState')=="Stopped"):?>
<table class="disk_status wide<?=$i?' divider':''?>">
<?if (!isSubpool($pool)):
$cache = array_filter(cache_filter($disks),function($disk) use ($pool){return prefix($disk['name'])==$pool;});
$power = in_array('nvme',array_column($cache,'transport')) ? ' / '._('Power') : '';
$power = _var($display,'power') && in_array('nvme',array_column($cache,'transport')) ? _('Power').' / ' : '';
$root = explode($_tilde_,$pool)[0];
?>
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td>_(Temp)_<?=$power?></td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<?if ($i==0):?>
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td><?=$power?>_(Temp)_</td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<?else:?>
<thead><tr><td class="divider" colspan="10"></td></td></tr></thead>
<?endif;?>
<?endif;?>
<tbody id="pool_device<?=$i++?>">
<?foreach (cache_filter($disks) as $disk) if (prefix($disk['name'])==$pool) echo "<tr><td colspan='11'>&nbsp;</td></tr>"?>
<?foreach ($cache as $disk) if (substr($disk['status'],0,7)!='DISK_NP') echo "<tr><td colspan='10'></td></tr>"?>
<?if (_var($display,'total') && _var($cache[$root],'devices',0)>1) echo "<tr class='tr_last'><td colspan='10'></td></tr>"?>
</tbody>
</table>
<?endif;?>
<?endforeach;?>
</table>
:cache_devices_help:

View File

@@ -1,9 +1,8 @@
Menu="Dashboard"
Nchan="wg_poller,update_1,update_2,update_3,ups_status:stop"
Nchan="wg_poller,update_1,update_2,update_3,ups_status:stop,vm_dashusage:stop"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2023, Lime Technology * Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -70,9 +69,18 @@ $cache_type = $cache_rate = [];
$parity = _var($var,'mdResync');
$mover = file_exists('/var/run/mover.pid');
$btrfs = exec('pgrep -cf /sbin/btrfs');
$vdisk = exec("grep -Pom1 '^DOCKER_IMAGE_TYPE=\"\\K[^\"]+' /boot/config/docker.cfg 2>/dev/null")!='folder' ? _('Docker vdisk') : _('Docker folder');
$dot = _var($display,'number','.,')[0];
$zfs = count(array_filter(array_column($disks,'fsType'),function($fs){return str_replace('luks:','',$fs??'')=='zfs';}));
$domain_cfgfile = "/boot/config/domain.cfg";
$domain_cfg = parse_ini_file($domain_cfgfile);
if (!isset($domain_cfg['USAGE'])) $vmusage = "N" ; else $vmusage = $domain_cfg['USAGE'];
// enable/disable graph elements by making hook script executable or not
chmod("$docroot/webGui/system/VM_usage",$libvirtd ? 0755 : 0644);
chmod("$docroot/webGui/system/ZFS_cache",$zfs ? 0755 : 0644);
foreach ($disks as $disk) {
switch (_var($disk,'type')) {
case 'Data':
@@ -169,11 +177,11 @@ $low = $memory_maximum < $memory_installed;
if ($low) $memory_maximum = pow(2,ceil(log($memory_installed)/log(2)));
switch ($theme) {
case 'white': $color = '#1c1b1b'; $grid = '#e3e3e3'; $c0 = '#dcdcdc'; $c1 = '#a8a8a8'; $c2 = '#d4ac0d'; break;
case 'black': $color = '#f2f2f2'; $grid = '#2b2b2b'; $c0 = '#444444'; $c1 = '#787878'; $c2 = '#d4ac0d'; break;
case 'azure': $color = '#606e7f'; $grid = '#f3f0f4'; $c0 = '#eceaec'; $c1 = '#606e7f'; $c2 = '#d4ac0d'; break;
case 'gray' : $color = '#606e7f'; $grid = '#0c0f0b'; $c0 = '#232523'; $c1 = '#606e7f'; $c2 = '#d4ac0d'; break;
default : $color = '#1c1b1b'; $grid = '#e3e3e3'; $c0 = '#dcdcdc'; $c1 = '#a8a8a8'; $c2 = '#d4ac0d'; break;
case 'white': $color = '#1c1b1b'; $grid = '#e3e3e3'; $c0 = '#a8a8a8'; $c1 = '#dcdcdc'; break;
case 'black': $color = '#f2f2f2'; $grid = '#2b2b2b'; $c0 = '#787878'; $c1 = '#444444'; break;
case 'azure': $color = '#606e7f'; $grid = '#f3f0f4'; $c0 = '#606e7f'; $c1 = '#eceaec'; break;
case 'gray' : $color = '#606e7f'; $grid = '#0c0f0b'; $c0 = '#606e7f'; $c1 = '#232523'; break;
default : $color = '#1c1b1b'; $grid = '#e3e3e3'; $c0 = '#a8a8a8'; $c1 = '#dcdcdc'; break;
}
?>
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
@@ -205,7 +213,7 @@ switch ($theme) {
</td></tr>
<tr><td>
<div class='leftside'>
<a href="/Settings/DateTime" class="hand none" title="_(Go to date and time settings)_"><span id="current_time"></span></a><br><span id="current_date"></span><br><br>
<a href="/Settings/DateTime" class="hand none"><span id="current_time"></span></a><br><span id="current_date"></span><br><br>
<span class='header'><i class='indent fa fa-file-text-o'></i>_(Model)_</span><br><i class='indent'></i><?=_var($var,'SYS_MODEL')?:'---'?><br><br>
<span class='header'><i class='indent fa fa-id-badge'></i>_(Registration)_</span><br><i class='indent'></i>Unraid OS <b><em><?=_var($var,'regTy')?></em></b><br><br>
<span class='header'><i class='indent fa fa-clock-o'></i>_(Uptime)_</span><br><i class='indent'></i><span class='uptime'></span>
@@ -269,22 +277,18 @@ foreach ($cpus as $pair) {
<span class="head_info"><span><i class='ups fa fa-line-chart'></i>_(Memory)_: <?="$memory_installed $unit $memory_type $ecc"?></span></span>
<span class="switch">_(RAM)_:<span class="head_bar"><span class='sys0_ load'>0%</span><div class='usage-disk sys'><span id='sys0_'></span><span></span></div></span></span><br></div>
<a href='/Dashboard/Tools/Processes' title="_(View Running Processes)_"><i class='fa fa-fw fa-info-circle control'></i></a>
</td></tr>
<tr><td>
<span class='w26'><div class='more'>
<i class='ups fa fa-compress'></i>_(Usable size)_: <?=$ramsize?><br>
<i class='ups fa fa-expand'></i>_(Maximum size)_: <?="$memory_maximum $unit"?><?=$low?'*':''?><br><br>
<legend>_(Legend)_</legend>
<i class='ups fa fa-circle used'></i>_(Services)_: <span id='used'></span><br>
<?if($zfs):?>
<i class='ups fa fa-circle zfs'></i>_(ZFS cache)_: <span id='zfs'></span><br>
<?endif;?>
<i class='ups fa fa-circle free'></i>_(Free)_: <span id='free'></span><br>
</div></span>
<span class='w18 center'><span class='center'><a class='info hand none'>_(RAM usage)_<span>_(Percent of total used memory)_ (<?=$ramsize?>)</span></a></span><div class='pie' id='sys0'><span class='sys0'></span><span class='var0'></span></div></span>
<span class='w18 center'><span class='center'><a class='info hand none'>_(Flash device)_<span>_(Percent usage of flash usb device)_ (<?=$flashsize?>)</span></a></span><div class='pie' id='sys1'><span class='sys1'></span><span class='var1'></span></div></span>
<span class='w18 center'><span class='center'><a class='info hand none'>_(Log filesystem)_<span>_(Percent usage of LOG file system)_ (<?=$logsize?>)</span></a></span><div class='pie' id='sys2'><span class='sys2'></span><span class='var2'></span></div></span>
<span class='w18 center'><span class='center'><a class='info hand none'>_(Docker vDisk)_<span>_(Percent usage of Docker image)_ (<?=$dockersize?>)</span></a></span><div class='pie' id='sys3'><span class='sys3'></span><span class='var3'></span></div></span>
</td></tr><tr><td>
<span class='w26'><i class='ups fa fa-compress'></i>_(Usable size)_: <?=$ramsize?><br><i class='ups fa fa-expand'></i>_(Maximum size)_: <?="$memory_maximum $unit"?><?=$low?'*':''?></span>
<span class='w18 center static'><a class='info hand none'>_(RAM usage)_<span>_(Percent of total used memory)_ (<?=$ramsize?>)</span></a></span>
<span class='w18 center static'><a class='info hand none'>_(Flash device)_<span>_(Percent usage of flash usb device)_ (<?=$flashsize?>)</span></a></span>
<span class='w18 center static'><a class='info hand none'>_(Log filesystem)_<span>_(Percent usage of LOG file system)_ (<?=$logsize?>)</span></a></span>
<span class='w18 center static'><a class='info hand none'><?=$vdisk?><span><?=_("Percent usage of $vdisk")?> (<?=$dockersize?>)</span></a></span>
</td></tr><tr><td>
<span class='w26'><legend>_(Legend)_</legend><span id='dynamic'></span></span>
<span class='w18 center'><div class='pie' id='sys0'><span class='sys0'></span><span class='var0'></span></div></span>
<span class='w18 center'><div class='pie' id='sys1'><span class='sys1'></span><span class='var1'></span></div></span>
<span class='w18 center'><div class='pie' id='sys2'><span class='sys2'></span><span class='var2'></span></div></span>
<span class='w18 center'><div class='pie' id='sys3'><span class='sys3'></span><span class='var3'></span></div></span>
</td></tr>
</tbody>
@@ -424,6 +428,13 @@ echo "</td></tr>";
<a href='/Dashboard/Settings/VMSettings' title="_(Go to VM settings)_"><i class='fa fa-fw fa-cog control'></i></a>
</td></tr>
</tbody>
<?if ($vmusage == "Y"):?>
<tbody id='vm_view_usage' title="_(Virtual Machines Usage)_" >
<tr><td><i class='icon-virtualization f32'></i><div class='section'>_(Virtual Machines Usage)_</div>
<a href='/Dashboard/Settings/VMSettings' title="_(Go to VM settings)_"><i class='fa fa-fw fa-cog control'></i></a>
</td></tr>
</tbody>
<?endif;?>
<?endif;?>
<tbody title="_(Shares Information)_"<?if ($group):?> class="mixed"<?endif;?>>
@@ -448,14 +459,13 @@ echo "</td></tr>";
if (_var($var,'shareSMBEnabled')=='yes') {
$i = 0;
foreach ($shares as $name => $share) {
$i++;
$list = "<a href=\"/Dashboard/Shares/Share?name=".urlencode($name)."\" class=\"blue-text\" title=\"$name settings\">$name</a>";
if ($share['luksStatus']>0) $list = str_replace('blue-text','green-text',$list);
elseif ($share['useCache']=='only') $list = str_replace('blue-text','orange-text',$list);
$comment = $share['comment'] ?: '&nbsp;';
$security = export_settings(_var($var,'shareSMBEnabled'), $sec[$name]);
$last = $name==array_key_last($shares) ? ' last' : '';
echo "<tr class='smb share share1{$last}'><td><span class='w26'><i class='icon-folder f14'></i>$list</span><span class='w44'>$comment</span><span class='w18'>$security</span><span id='share{$i}'>0</span></td></tr>";
echo "<tr class='smb share share1{$last}'><td><span class='w26'><i class='icon-folder f14'></i>$list</span><span class='w44'>$comment</span><span class='w18'>$security</span><span id='share",$i++,"'>0</span></td></tr>";
}
if (!count($shares)) echo "<tr class='smb share share1'><td class='none'>"._("No shares present")."</td></tr>";
}
@@ -573,7 +583,7 @@ if (!$group) {
<tr><td id='program' class="wrap"></td></tr>
</tbody>
<?$power = in_array('nvme',array_column(main_filter($disks),'transport')) ? ' / '._('Power') : '';?>
<?$power = _var($display,'power') && in_array('nvme',array_column(main_filter($disks),'transport')) ? ' / '._('Power') : '';?>
<tbody id='array_list' title="_(Array Information)_">
<tr><td><i class='icon-disks f32'></i><div class='section'>_(Array)_<?if (!$started):?> (_(stopped)_)<?endif;?><br>
<span><?if ($started):?><?=sprintf(_("%s used of %s (%s %%)"),my_scale($array_used*1024,$unit)." $unit",my_scale($array_size*1024,$unit,-1,-1)." $unit",$array_percent)?><?endif;?></span><br></div>
@@ -586,7 +596,7 @@ if (!$group) {
<?$i=0?>
<?foreach ($pools as $pool):
$cache = array_filter(cache_filter($disks),function($disk) use ($pool){return prefix($disk['name'])==$pool;});
$power = in_array('nvme',array_column($cache,'transport')) ? ' / '._('Power') : '';
$power = _var($display,'power') && in_array('nvme',array_column($cache,'transport')) ? ' / '._('Power') : '';
?>
<tbody id='pool_list<?=$i?>' title="_(<?=ucfirst($pool)?> Information)_">
<tr><td><i class='icon-disk f32'></i><div class='section'><?=_(native($pool),3).($started ? '' : ' ('._('stopped').')')?><br>
@@ -599,7 +609,7 @@ if (!$group) {
<?endforeach;?>
<?if ($devs):?>
<?$power = in_array('nvme',array_column($devs,'transport')) ? ' / '._('Power') : '';?>
<?$power = _var($display,'power') && in_array('nvme',array_column($devs,'transport')) ? ' / '._('Power') : '';?>
<tbody id='devs_list' title="_(Unassigned Devices)_">
<tr><td><i class='icon-disc f32'></i><div class='section'>_(Unassigned)_<?if (!$started):?> (_(stopped)_)<?endif;?><br>
<span></span><br></div>
@@ -677,7 +687,7 @@ function hideShow() {
<tr><td>_(Overwrite)_:</td><td><input type="checkbox" id="Overwrite" value="" ></td></tr>
<tr hidden><td>_(Start Cloned VM)_:</td><td><input type="checkbox" id="Start" value="" ></td></tr>
<tr hidden><td>_(Edit VM after clone)_:</td><td><input type="checkbox" id="Edit" value="" ></td></tr>
<tr><td>_(Check Free Space)_:</td><td><input type="checkbox" id="Free" value="" ></td></tr>
<tr><td>_(Check free space)_:</td><td><input type="checkbox" id="Free" value="" ></td></tr>
</table>
</div>
@@ -686,6 +696,7 @@ function hideShow() {
<tr><td>_(VM Name)_:</td><td><label id="VMName"></label></td></tr>
<tr><td>_(Snapshot Name)_:</td><td><input type="text" id="targetsnap" autocomplete="off" spellcheck="false" value="--generate" onclick="this.select()">_(Check free space)_:<input type="checkbox" id="targetsnapfspc" checked></td></tr>
<tr><td>_(Description)_:</td><td><input type="text" id="targetsnapdesc" autocomplete="off" spellcheck="false" value="" onclick="this.select()"></td></tr>
<tr id="fstypeline"><td>_(FS Native Snapshot )_:</td><td><label id="fstype"></label><input type="checkbox" id="targetsnapfstype" checked>_(Unchecked will use QEMU External Snapshot)_</td></tr>
</table>
</div>
@@ -693,7 +704,8 @@ function hideShow() {
Number.prototype.pad = function(size){var s=String(this);while(s.length<(size||2)){s='0'+s;}return s;}
Array.prototype.tail = function(t){return this.slice(-t).map(function(o){return o.y;}).join(';');}
String.prototype.build = function(){return this.replace(/\n(<!--!|!-->)\n/g,'');}
String.prototype.md5 = function() {
jQuery.prototype.alive = function(width,color){return this.finish().animate({width:width},{step:function(){$(this).css({'overflow':'visible'}).removeClass().addClass(color);}});}
String.prototype.md5 = function(){
// Original copyright (c) Paul Johnston & Greg Holt.
var hc = '0123456789abcdef';
function rh(n){var j,s='';for (j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
@@ -786,6 +798,8 @@ function sanitizeMultiCookie(cookieName, delimiter, removeDuplicates = false) {
}
}
var colors = ['<?=$c0?>','<?=$c1?>','#d77e0d','#d4ac0d','#cd5c5c','#ffc0cb','#e6e6fa','#9370db','#7cfc00','#228b22','#00ffff','#4682b4'];
var blue = '#486dba'; // fallback color when too many graph elements exist
var ports = [<?=implode(',',array_map('escapestring',$ports))?>];
var cpu = [];
var rxd = [];
@@ -798,6 +812,8 @@ var update2 = true;
var box = null;
var startup = true;
var stopgap = '<thead class="stopgap"><tr><td class="stopgap"></td></tr></thead>';
var recall = null;
var recover = null;
var options_cpu = {
series:[{name:'load', data:cpu.slice()}],
@@ -927,7 +943,7 @@ function loadlist(init) {
$('#vms').is(':checked') ? $.cookie('my_vms','startedOnly',{expires:3650}) : $.removeCookie('my_vms');
});
}
$.post('/webGui/include/DashboardApps.php',{docker:'<?=$dockerd?>',vms:'<?=$libvirtd?>'},function(d) {
$.post('/webGui/include/DashboardApps.php',{docker:'<?=$dockerd?>',vms:'<?=$libvirtd?>',vmusage:'<?=$vmusage?>'},function(d) {
var data = d.split('\0');
$('#docker_view tr.updated').remove();
$('#docker_view').append(data[0]).hideMe();
@@ -939,6 +955,8 @@ function loadlist(init) {
var started_vms = $('#vm_view').find('span.outer.vms.started').length;
var stopped_vms = $('#vm_view').find('span.outer.vms.stopped').length;
var paused_vms = $('#vm_view').find('span.outer.vms.paused').length;
$('#vm_view_usage tr.useupdated').remove();
$('#vm_view_usage').append(data[2]).hideMe();
$('.apps.switch').html("_(Containers)_ -- _(Started)_: "+started_apps+", _(Stopped)_: "+stopped_apps+", _(Paused)_: "+paused_apps);
$('.vms.switch').html("_(VMs)_ -- _(Started)_: "+started_vms+", _(Stopped)_: "+stopped_vms+", _(Paused)_: "+paused_vms);
if ($.cookie('my_apps')!=null) $('span.apps.stopped').hide(0,noApps());
@@ -1028,10 +1046,10 @@ function portSelect(name) {
}
function moreInfo(data,table) {
var info = [];
if (data[1]>0) info.push(data[1]+" _(failed device)_"+(data[1]==1?'':'s'));
if (data[2]>0) info.push(data[2]+" _(heat warning)_"+(data[2]==1?'':'s'));
if (data[3]>0) info.push(data[3]+" _(SMART error)_"+(data[3]==1?'':'s'));
if (data[4]>0) info.push(data[4]+" _(utilization warning)_"+(data[4]==1?'':'s'));
if (data[1]>0) info.push(data[1]+' '+(data[1]==1 ? "_(failed device)_" : "_(failed devices)_"));
if (data[2]>0) info.push(data[2]+' '+(data[2]==1 ? "_(heat warning)_" : "_(heat warnings)_"));
if (data[3]>0) info.push(data[3]+' '+(data[3]==1 ? "_(SMART error)_" : "_(SMART errors)_"));
if (data[4]>0) info.push(data[4]+' '+(data[4]==1 ? "_(utilization warning)_" : "_(utilization warnings)_"));
return info.length ? "<div class='last'><i class='icon-u-triangle failed'></i><span class='failed'>"+table+" _(has)_ "+info.join('. ')+".</span></div>" : "";
}
function autoscale(value,text,size,kilo) {
@@ -1116,7 +1134,7 @@ function StopArray() {
}
function StopArrayNow() {
$('span.hand').prop('onclick',null).off('click').addClass('busy').css({'cursor':'default'});
$.post('/update.htm',{startState:'<?=_var($var,'mdState')?>',cmdStop:'Stop',csrf_token:'<?=_var($var,'csrf_token')?>'},function(){refresh();});
$.post('/update.htm',{cmdStop:'Stop'},function(){refresh();});
}
function StartArray() {
<?if ($confirm['stop']):?>
@@ -1127,7 +1145,7 @@ function StartArray() {
}
function StartArrayNow() {
$('span.hand').prop('onclick',null).off('click').addClass('busy').css({'cursor':'default'});
$.post('/update.htm',{startState:'<?=_var($var,'mdState')?>',cmdStart:'Start',csrf_token:'<?=_var($var,'csrf_token')?>'},function(){refresh();});
$.post('/update.htm',{cmdStart:'Start'},function(){refresh();});
}
function Reboot() {
<?if ($confirm['down']):?>
@@ -1202,6 +1220,12 @@ function addProperties() {
$('div#sys1').hover(function(){$('.sys1').hide();$('.var1').show();},function(){$('.sys1').show();$('.var1').hide();});
$('div#sys2').hover(function(){$('.sys2').hide();$('.var2').show();},function(){$('.sys2').show();$('.var2').hide();});
$('div#sys3').hover(function(){$('.sys3').hide();$('.var3').show();},function(){$('.sys3').show();$('.var3').hide();});
$('#current_time').hover(function(){$.post('/webGui/include/DashboardApps.php',{ntp:'ntp'},function(ntp){$('#current_time').prop('title',ntp+"\n_(Go to date and time settings)_");});});
// make truncated descriptions fully visible when hovering over them
$('span.w18').not('.static').hover(
function(){if ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {recall = $(this).next(); recover=recall.html(); recall.html('&nbsp;');}},
function(){if ($(this)[0].offsetWidth < $(this)[0].scrollWidth) {recall.html(recover); recall=null;}}
);
}
function showContent() {
var count = {'db-box1':$('table#db-box1 tbody').length, 'db-box2':$('table#db-box2 tbody').length, 'db-box3':$('table#db-box3 tbody').length}
@@ -1369,17 +1393,19 @@ function VMClone(uuid, name){
});
dialogStyle();
}
function selectsnapshot(uuid, name ,snaps, opt, getlist){
function selectsnapshot(uuid, name ,snaps, opt, getlist, status,fstype){
box = $("#iframe-popup");
box.html($("#templatesnapshot"+opt).html());
const capopt = opt.charAt(0).toUpperCase() + opt.slice(1);
var optiontext = capopt + " _(Snapshot)_";
box.find('#VMName').html(name);
box.find('#fstype').html(fstype);
if (fstype == "QEMU") box.find('#fstypeline').prop('hidden',true);
box.find('#targetsnap').val(snaps);
box.find('#targetsnapl').html(snaps);
if (getlist) {
var only = (opt == "remove") ? 0 : 1;
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-images", uuid:uuid, snapshotname:snaps, only:only}, function(data){if (data.html) box.find('#targetsnapimages').html(data.html);},'json');
$.post("/plugins/dynamix.vm.manager/include/VMajax.php",{action:"snap-images",uuid:uuid,snapshotname:snaps,only:only},function(data){if (data.html) box.find('#targetsnapimages').html(data.html);},'json');
}
document.getElementById("targetsnapfspc").checked = true;
box.dialog({
@@ -1409,8 +1435,10 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){
if (opt == "create") {
free = box.find('#targetsnapfspc').prop('checked') ? 'yes' : 'no';
desc = box.find("#targetsnapdesc").prop('value');
fstypeuse = box.find('#targetsnapfstype').prop('checked') ? 'yes' : 'no';
if (fstypeuse == "no") fstype ="QEMU";
}
ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid, snapshotname:target, remove:remove, free:free, desc:desc}, "loadlist");
ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid, snapshotname:target, remove:remove, free:free, desc:desc, fstype:fstype}, "loadlist");
box.dialog('close');
},
"_(Cancel)_": function(){
@@ -1471,128 +1499,155 @@ function LockButton() {
$('table.dashboard').sortable('destroy');
}
}
function cpu_parse(msg) {
var parse = {}, section = '';
msg.split('\n').forEach(function(row) {
if (row.substr(0,1) == '[') {
section = row.substr(1,row.length-2);
parse[section] = {};
} else {
var data = row.split('=');
parse[section][data[0]] = data[1];
}
});
return parse;
}
<?if ($vmusage == "Y"):?>
var vmdashusage = new NchanSubscriber('/sub/vm_dashusage',{subscriber:'websocket'});
vmdashusage.on('message', function(msg){
var data = JSON.parse(msg);
for (const [vm, vmdata] of Object.entries(data)) {
for (const [displayitem, value] of Object.entries(vmdata)) {
$('#vmmetrics-'+displayitem + '-' + vm ).html(value);
}
}
});
<?endif;?>
var dashboard = new NchanSubscriber('/sub/cpuload,update1,update2,update3<?=$wireguard?",wireguard":""?>',{subscriber:'websocket'});
dashboard.on('message',function(msg,meta) {
switch (meta.id.channel()) {
case 0:
var ini = parseINI(msg);
var get = cpu_parse(msg);
// cpu load
$.each(ini,function(k,v) {
var load = v['host'];
$.each(get,function(k,v) {
var load = v.host;
var color = setColor(load, 90, 70);
if (k=='cpu') {
addChartCpu(load);
cpuchart.updateSeries([{data:cpu}]);
$('.cpu_').text(load+'%');
$('#cpu_').finish().animate({width:load+'%'},{step:function(){$('#'+k).css({'overflow':'visible'}).removeClass().addClass(color);}});
$('#cpu_').alive(load+'%',color);
}
$('.'+k).text(load+'%');
$('#'+k).finish().animate({width:load+'%'},{step:function(){$('#'+k).css({'overflow':'visible'}).removeClass().addClass(color);}});
$('#'+k).alive(load+'%',color);
});
break;
case 1:
var part = msg.split('\1');
var get = JSON.parse(msg);
// memory & disk load
$.each(part[0].split('\0'),function(k,v) {
v = v.split(';');
var load = v[0].slice(0,-1);
$('.sys'+k).text(v[0]).css({'color':fontColor(load,<?=$display['critical']?>,<?=$display['warning']?>)});
$('.var'+k).text(v[1]);
if (k == 0) {
$('#used').text(v[4]);
$('#free').text(v[5]);
<?if ($zfs):?>
$('#zfs').text(v[6]);
<?endif;?>
var color = setColor(load,<?=$display['critical']?>,<?=$display['warning']?>);
$('.sys0_').text(v[0]);
$('#sys0_').finish().animate({width:v[0]},{step:function(){$('#sys0_').css('overflow','visible').removeClass().addClass(color);}});
$('#sys0').css({'background':'conic-gradient(<?=$c1?> 0% '+v[2]+',<?=$c2?> '+v[2]+' '+v[0]+',<?=$c0?> '+v[0]+' 100%'});
} else {
$('#sys'+k).css({'background':'conic-gradient(<?=$c1?> 0% '+v[0]+',<?=$c0?> '+v[0]+' 100%'});
}
});
var load = get.ram[0].slice(0,-1);
$('.sys0').text(get.ram[0]).css({'color':fontColor(load,<?=$display['critical']?>,<?=$display['warning']?>)});
$('.var0').text(get.ram[1]);
var color = setColor(load,<?=$display['critical']?>,<?=$display['warning']?>);
$('.sys0_').text(get.ram[0]);
$('#sys0_').alive(get.ram[0],color);
var start = 0;
var end = parseInt(get.ram[4]);
var ring = [colors[0]+' '+start+'% '+end+'%']
// create individual elements of the RAM graph
for (var i=6; i < get.ram.length; i=i+2) {
start = end;
end += parseInt(get.ram[i]);
ring.push((colors[(i-2)/2]||blue)+' '+start+'% '+end+'%');
}
ring.push(colors[1]+' '+get.ram[0]+' 100%');
$('#sys0').css({'background':'conic-gradient('+ring.join(',')+')'});
// dynamic info from hook scripts
var html = [];
for (var i=0,name; name=get.name[i]; i++) if (i!=1) html.push("<i class='ups fa fa-circle' style='color:"+(colors[i]||blue)+"'></i>"+name+": "+(i==0?get.ram[5]:get.ram[i*2+3]));
html.push("<i class='ups fa fa-circle' style='color:"+colors[1]+"'></i>"+get.name[1]+": "+get.ram[3]);
$('#dynamic').html(html.join('<br>'));
// flash, Log & Docker graphs
for (var k=1,sys; sys=get.sys[k-1]; k++) {
var load = sys[0].slice(0,-1);
$('.sys'+k).text(sys[0]).css({'color':fontColor(load,<?=$display['critical']?>,<?=$display['warning']?>)});
$('.var'+k).text(sys[1]);
$('#sys'+k).css({'background':'conic-gradient('+colors[0]+' 0% '+sys[0]+','+colors[1]+' '+sys[0]+' 100%)'});
}
<?if ($fans):?>
// fans rpm
$.each(part[1].split('\0'),function(k,v) {$('#fan'+k).html(v);});
for (var k=0; k < get.fan.length; k++) $('#fan'+k).html(get.fan[k]);
<?endif;?>
<?if (_var($var,'fsState')=='Started' && $group):?>
// stream counters (smb only)
var tag = $('.smb').is(':visible') ? 'smb' : $('.nfs').is(':visible') ? 'nfs' : '';
if (tag == 'smb') $.each(part[2].split('\0'),function(k,v) {$('#share'+(k+1)).html(v);});
if ($('.smb').is(':visible')) for (var k=0; k < get.stream.length; k++) {$('#share'+k).html(get.stream[k]); $('#share'+k).removeClass(); if (get.stream[k]>0) $('#share'+k).addClass('orange-text');}
<?endif;?>
break;
case 2:
if (!update2) break;
var part = msg.split('\1');
var data = part[0].split('\0');
var info = moreInfo(data,"_(Array)_");
var get = JSON.parse(msg);
var info = moreInfo(get.disk,"_(Array)_");
// array devices
$('#array_list tr.updated').remove();
$('#array_list').append(data[0]).hideMe();
$('#array_list').append(get.disk[0]).hideMe();
$('#array_info').parent().css({'display':info?'':'none'});
$('#array_info').html(info);
smartMenu('#array_list');
// pool devices
var text = part[1].split('\r');
for (var i=0,t; t=text[i]; i++) {
var data = t.split('\0');
var info = moreInfo(data,"_(Pool)_");
for (let i=0; i < get.pool.length; i++) {
var info = moreInfo(get.pool[i],"_(Pool)_");
$('#pool_list'+i+' tr.updated').remove();
$('#pool_list'+i).append(t).hideMe();
$('#pool_list'+i).append(get.pool[i][0]).hideMe();
$('#pool_info'+i).parent().css({'display':info?'':'none'});
$('#pool_info'+i).html(info);
smartMenu('#pool_list'+i);
}
<?if ($devs):?>
// unassigned devices
var data = part[2].split('\0');
var info = moreInfo(data,"_(Unassigned)_");
var info = moreInfo(get.open,"_(Unassigned)_");
$('#devs_list tr.updated').remove();
$('#devs_list').append(data[0]).hideMe();
$('#devs_list').append(get.open[0]).hideMe();
$('#devs_info').parent().css({'display':info?'':'none'});
$('#devs_info').html(info);
smartMenu('#devs_list');
<?endif;?>
// parity status
$('span.parity').html(part[3]);
$('span.parity').html(get.parity);
// parity schedule
var data = part[4].split('\0');
$('#parity').html(data[0]);
$('#program').html(data[1]);
$('#parity').html(get.schedule[0]);
$('#program').html(get.schedule[1]);
break;
case 3:
var part = msg.split('\1');
var ports = part[0].split('\n');
var get = JSON.parse(msg);
// rx & tx speeds
for (var i=0,port; port=ports[i]; i++) {
var data = port.split('\0');
if (data[0] == port_select) {
$('#inbound').text(data[1]);
$('#outbound').text(data[2]);
addChartNet(data[3], data[4]);
for (let i=0,port; port=get.port[i]; i++) {
if (port[0] == port_select) {
$('#inbound').text(port[1]);
$('#outbound').text(port[2]);
addChartNet(port[3], port[4]);
netchart.updateSeries([{data:rxd},{data:txd}]);
break;
}
}
// port counters
$.each(part[1].split('\0'),function(k,v) {$('#main'+k).html(v);});
$.each(part[2].split('\0'),function(k,v) {$('#port'+k).html(v);});
$.each(part[3].split('\0'),function(k,v) {$('#link'+k).html(v);});
for (let k=0; k < get.mode.length; k++) $('#main'+k).html(get.mode[k]);
for (let k=0; k < get.rxtx.length; k++) $('#port'+k).html(get.rxtx[k]);
for (let k=0; k < get.stat.length; k++) $('#link'+k).html(get.stat[k]);
// current date and time
var timedate = part[4].split('\n');
$('#current_time').html(timedate[0]);
$('#current_time_').html(timedate[0]);
$('#current_date').html(timedate[1]);
$('#current_time').html(get.time[0]);
$('#current_time_').html(get.time[0]);
$('#current_date').html(get.time[1]);
break;
case 4:
// wireguard tunnels
var get = JSON.parse(msg);
var n = {};
var rows = msg.split('\0');
for (var i=0,row; row=rows[i]; i++) {
var info = row.split(';');
for (var i=0,info; info=get[i]; i++) {
var vtun = info[0];
if (typeof n[vtun]=='undefined') n[vtun] = 0; else n[vtun]++;
if (typeof n[vtun]=='undefined') n[vtun]=0; else n[vtun]++;
if (info[1] == 0) {
$('span#'+vtun+'-hs-'+n[vtun]).text("_(not received)_");
} else if (info[1] > 86400) {
@@ -1612,17 +1667,17 @@ dashboard.on('message',function(msg,meta) {
});
<?if ($apcupsd):?>
var apcups = new NchanSubscriber('/sub/apcups',{subscriber:'websocket'});
apcups.on('message',function(data) {
data = data.split(';');
$('#ups_model').html(data[0]);
$('#ups_status').html(data[1]);
$('#ups_status_').html(data[1]);
$('#ups_bcharge').html(data[2]);
$('#ups_timeleft').html(data[3]);
$('#ups_nompower').html(data[4]);
$('#ups_loadpct').html(data[5]);
$('#ups_loadpct_').html(data[5]);
$('#ups_outputv').html(data[6]);
apcups.on('message',function(msg) {
var get = JSON.parse(msg);
$('#ups_model').html(get[0]);
$('#ups_status').html(get[1]);
$('#ups_status_').html(get[1]);
$('#ups_bcharge').html(get[2]);
$('#ups_timeleft').html(get[3]);
$('#ups_nompower').html(get[4]);
$('#ups_loadpct').html(get[5]);
$('#ups_loadpct_').html(get[5]);
$('#ups_outputv').html(get[6]);
});
<?endif;?>
@@ -1637,6 +1692,9 @@ $(function() {
dropdown('enter_view');
startup = false;
dashboard.start();
<?if ($vmusage == "Y"):?>
vmdashusage.start();
<?endif;?>
<?if ($apcupsd):?>
apcups.start();
<?endif;?>
@@ -1662,6 +1720,9 @@ $(function() {
window.onunload = function(){
dashboard.stop();
<?if ($vmusage == "Y"):?>
vmdashusage.stop();
<?endif;?>
<?if ($apcupsd):?>
apcups.stop();
<?endif;?>

View File

@@ -22,7 +22,7 @@ $(function() {
});
});
</script>
<table class='share_status small'>
<table class="unraid">
<thead><td style="width:40px">#</td><td>_(Attribute Name)_</td><td>_(Flag)_</td><td>_(Value)_</td><td>_(Worst)_</td><td>_(Threshold)_</td><td>_(Type)_</td><td>_(Updated)_</td><td>_(Failed)_</td><td style="width:145px">_(Raw Value)_</td></thead>
<tbody id="disk_attributes"><tr><td colspan='10'><div class="spinner"></div></td></tr></tbody>
</table>

View File

@@ -22,7 +22,7 @@ $(function() {
});
});
</script>
<div id="disk_capabilities_div" class='share_status small'>
<div id="disk_capabilities_div">
</div>
<input type="button" value="_(Done)_" onclick="done()">

View File

@@ -26,7 +26,7 @@ $(function() {
});
</script>
<table class='share_status small'>
<table class="unraid">
<thead><td style="width:33%">_(Title)_</td><td>_(Information)_</td></thead>
<tbody id="disk_identify"><tr><td colspan='2'><div class="spinner"></div></td></tr></tbody>
</table>

View File

@@ -603,7 +603,7 @@ _(Spin down delay)_:
_(File system status)_:
: <?=_(_var($disk,'fsStatus'))?>&nbsp;
<?$disabled = (_var($var,'fsState')=="Stopped" && !empty(_var($disk,'uuid'))) || (_var($var,'fsState')=="Started" && _var($disk,'fsStatus')=='Mounted') ? "disabled" : ""?>
<?$disabled = (_var($var,'fsState')=="Stopped" && !empty(_var($disk,'uuid'))) || (_var($var,'fsState')=="Started" && _var($disk,'fsType')!='auto') ? "disabled" : ""?>
<?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?>>

View File

@@ -15,7 +15,7 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareDisk')!='no'"
* all copies or substantial portions of the Software.
*/
?>
<table class="share_status">
<table class="unraid share_status">
<thead><tr><td>_(Name)_</td><td>_(Comment)_</td><td>_(SMB)_</td><td>_(NFS)_</td><td>_(Type)_</td><td>_(Size)_</td><td>_(Free)_</td></tr></thead>
<tbody id="disk_list"></tbody>
</table>

View File

@@ -36,11 +36,13 @@ function doDispatch(form) {
<?if ($display['unit']=='F'):?>
form.display_hot.value = form.display_hot.value.celsius();
form.display_max.value = form.display_max.value.celsius();
form.display_ssd.value = form.display_ssd.value.celsius();
form.display_hotssd.value = form.display_hotssd.value.celsius();
form.display_maxssd.value = form.display_maxssd.value.celsius();
<?endif;?>
fields['#cfg'] = "/boot/config/plugins/dynamix/dynamix.cfg";
fields['#cleanup'] = true;
$(form).find('input[name^="display_"]').each(function(){fields[$(this).attr('name')] = $(this).val(); $(this).prop('disabled',true);});
$(form).find('select[name^="display_"]').each(function(){fields[$(this).attr('name')] = $(this).val(); $(this).prop('disabled',true);});
$.post('/webGui/include/Dispatcher.php',fields);
}
function prepareDiskSettings(form) {
@@ -224,6 +226,12 @@ _(Tunable (md_write_method))_:
:disk_tunable_md_write_method_help:
_(Enable NVME power monitoring)_:
: <select name="display_power">
<?=mk_option($display['power'], "", _('No'))?>
<?=mk_option($display['power'], "1", _('Yes'))?>
</select>
_(Default warning disk utilization threshold)_ (%):
: <input type="number" min="0" max="100" name="display_warning" class="narrow" value="<?=$display['warning']?>" placeholder="<?=$default['display']['warning']?>">
@@ -259,7 +267,7 @@ _(Default critical SSD temperature threshold)_ (&deg;<?=_var($display,'unit','C'
</form>
<?if ($encrypt && $var['fsState']=='Started'):?>
<div class="title"><span class="left"><i class="title fa fa-key"></i>_(Change Encryption Key)_</span></div>
<div class="title"><span class="left"><i class="title fa fa-key"></i>_(Change encryption key)_</span></div>
<form markdown="1" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareForm(this)">
<input type="hidden" name="#file" value="">
<input type="hidden" name="#include" value="/webGui/include/update.encryption.php">

View File

@@ -0,0 +1,19 @@
Menu="Tasks:2"
Type="xmenu"
Code="f08a"
Cond="@filesize('/boot/config/favorites.cfg')"
---
<script>
function delPage(page) {
$.post('/webGui/include/MyFavorites.php',{action:'del',page:page},function(){refresh();});
}
$(function(){
$('div.Panel').each(function(){
var page = $(this).find('a').prop('href').split('/').pop();
$(this).find('span').append('<i class="fa fa-trash-o favo" title="_(Remove from favorites)_" onclick="delPage(&quot;'+page+'&quot;);return false"></i>');
$(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});
});
$.post('/webGui/include/MyFavorites.php',{action:'clear'},function(){if ($('div.Panel').length==0) $('#nofavs').show();});
});
</script>

View File

@@ -1,5 +1,5 @@
Menu="Flash"
Title="GRUB Configuration"
Title="GRUB configuration"
Tag="edit"
Cond="file_exists('/boot/grub/grub.cfg')"
---

View File

@@ -19,10 +19,8 @@ Code="e934"
function HelpButton() {
if ($('.nav-item.HelpButton').toggleClass('active').hasClass('active')) {
$('.inline_help').show('slow');
$.cookie('help','help');
} else {
$('.inline_help').hide('slow');
$.removeCookie('help');
}
}
</script>
</script>

View File

@@ -89,15 +89,9 @@ $(function() {
_(Installed languages)_:
: <span class="installed"><?=implode(', ',$installed)?></span>
_(Enter URL of language pack XML file)_
: &nbsp;
_(Enter URL of language pack XML file)_:
: <input type="text" id="xml_file" maxlength="1024" value=""><input type="button" id="install" value="_(Install)_" onclick="installXML($('#xml_file').val())">
<dl>
<form name="xml_install" method="POST" target="progressFrame">
<dt><input type="text" name="file" id="xml_file" maxlength="1024" value="" style="width:90%"></dt>
<dd><input type="button" value="_(Install)_" onclick="installXML(this.form.file.value)" style="margin:0"></dd>
</form>
</dl>
</div>
<div markdown="1" class="developer" style="display:none">

View File

@@ -5,8 +5,8 @@ Icon="icon-key"
Tag="expeditedssl"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -75,7 +75,7 @@ if ($cert1Present) {
$cert1SelfSigned = ($cert1Subject == $cert1Issuer);
}
// unraid.net, myunraid.net LE cert. could potentially be user provided as well
// myunraid.net LE cert. could potentially be user provided as well
$cert2File = "/boot/config/ssl/certs/certificate_bundle.pem";
$cert2Present = file_exists("$cert2File");
if ($cert2Present) {
@@ -84,6 +84,8 @@ if ($cert2Present) {
$cert2Expires = exec("/usr/bin/openssl x509 -in $cert2File -noout -text | sed -n -e 's/^.*Not After : //p'");
$isWildcardCert = preg_match('/.*\.myunraid\.net$/', $cert2Subject);
$subject2URL = $cert2Subject;
$dnsValid = false;
$dnsRebindingProtection = false;
if ($isWildcardCert) {
exec("openssl x509 -checkend 2592000 -noout -in $cert2File 2>/dev/null", $arrout, $retval_expired);
if (!$addr) {
@@ -115,12 +117,12 @@ if ($cert2Present) {
}
}
$http_port = _var($var,'PORT',80) != 80 ? ":{$var['PORT']}" : '';
$https_port = _var($var,'PORTSSL',443) != 443 ? ":{$var['PORTSSL']}" : '';
$http_port = _var($var,'PORT','80') != '80' ? ":{$var['PORT']}" : '';
$https_port = _var($var,'PORTSSL','443') != '443' ? ":{$var['PORTSSL']}" : '';
$http_ip_url = "http://"._var($nginx,'NGINX_LANIP')."{$http_port}/";
$https_ip_url = "https://"._var($nginx,'NGINX_LANIP')."{$https_port}/";
$http_ip6_url = "http://"._var($nginx,'NGINX_LANIP6')."{$http_port}/";
$https_ip6_url = "https://"._var($nginx,'NGINX_LANIP6')."{$https_port}/";
$http_ip6_url = "http://["._var($nginx,'NGINX_LANIP6')."]{$http_port}/";
$https_ip6_url = "https://["._var($nginx,'NGINX_LANIP6')."]{$https_port}/";
$http_mdns_url = "http://"._var($nginx,'NGINX_LANMDNS')."{$http_port}/";
$https_mdns_url = "https://"._var($nginx,'NGINX_LANMDNS')."{$https_port}/";
$https_fqdn_url = "https://"._var($nginx,'NGINX_LANFQDN')."{$https_port}/";
@@ -164,7 +166,6 @@ $cert_time_format = $display['date'].($display['date']!='%c' ? ', '.str_replac
$provisionlabel = $isWildcardCert ? _('Renew') : _('Provision');
$disabled_provision = $keyfile===false || ($isWildcardCert && $retval_expired===0) || !$addr ? 'disabled' : '';
$disabled_provision_msg = !$addr ? _('Ensure the primary network card eth0 has an IP address.') : '';
$disabled_updatedns = $keyfile!==false && $isWildcardCert ? '' : 'disabled';
$disabled_delete = $cert2Present && $var['USE_SSL']!='auto' ? '' : 'disabled';
$disabled_auto = $isWildcardCert && !$dnsRebindingProtection && $dnsValid ? '' : 'disabled';
@@ -189,23 +190,6 @@ function provisionHandler(event, form) { // provisions and renewals require bein
if (event.submitter.value === 'Renew') return true; // always allow renewals
};
function updateDNS(button) {
$(button).prop("disabled", true).html("<i class='fa fa-circle-o-notch fa-spin fa-fw'></i>_(Update DNS)_");
var failure = function(data) {
var status = data.status;
var obj = data.responseJSON;
var msg = "_(Sorry, an error occurred updating unraid.net DNS records)_. _(The error is)_: "+obj.error+".";
$(button).prop("disabled", false).html("_(Update DNS)_");
swal({title:"_(Oops)_",text:msg,type:"error",html:true,confirmButtonText:"_(Ok)_"});
};
var success = function(data) {
$(button).prop("disabled", false).html("_(Update DNS)_");
<?$text = _('Your local IP address %s has been updated for unraid.net')?>
swal({title:"",text:"<?=sprintf($text,$addr)?>",type:"success",html:true,confirmButtonText:"_(Ok)_"});
};
$.post("/webGui/include/UpdateDNS.php",success).fail(failure);
}
function checkPorts(form) {
var portsInUse = [<?=implode(',',$portsInUse)?>];
var range = [], list = [], duplicates = [];
@@ -359,12 +343,10 @@ _(Local access URLs)_:
$n = 0;
foreach($urls as $url) {
$msg = "";
$url0 = substr_count($url[0]??'',':')>3 ? preg_replace('#(://)(.+?)(:?\d*)/$#','$1[$2]$3/',$url[0]) : $url[0]; // IPv6 - IPv4 notation
$url1 = substr_count($url[1]??'',':')>3 ? preg_replace('#(://)(.+?)(:?\d*)/$#','$1[$2]$3/',$url[1]) : $url[1]; // IPv6 - IPv4 notation
if ($url[1]) $msg .= " "._("redirects to")." <a href='$url1'>$url1</a>";
if ($url[1]) $msg .= " "._("redirects to")." <a href='$url[1]'>$url[1]</a>";
if ($url[2]) $msg .= " "._("uses")." ".$url[2];
if ($url[3]) $msg .= "<span class='warning'> <i class='fa fa-warning fa-fw'></i> "._("is a self-signed certificate, ignore the browser's warning and proceed to the GUI")."</span>";
echo ($n ? "<dt>&nbsp;</dt><dd>" : ""),"<a href='$url0'>$url0</a>$msg",($n++ ? "</dd>" : "");
echo ($n ? "<dt>&nbsp;</dt><dd>" : ""),"<a href='$url[0]'>$url[0]</a>$msg",($n++ ? "</dd>" : "");
}?>
:mgmt_local_access_urls_help:
@@ -431,7 +413,7 @@ _(CA-signed certificate file)_:
<?endif;?>
&nbsp;
: <button type="submit" name="changePorts" value="Provision" <?=$disabled_provision?>><?=$provisionlabel?></button><button type="submit" name="changePorts" value="Delete" <?=$disabled_delete?> >_(Delete)_</button><!-- <button type="button" onclick="updateDNS(this)" <?=$disabled_updatedns?>>_(Update DNS)_</button> --><?=$disabled_provision_msg?>
: <button type="submit" name="changePorts" value="Provision" <?=$disabled_provision?>><?=$provisionlabel?></button><button type="submit" name="changePorts" value="Delete" <?=$disabled_delete?> >_(Delete)_</button><?=$disabled_provision_msg?>
:mgmt_certificate_expiration_help:

View File

@@ -0,0 +1,6 @@
Menu="Favorites"
Type="menu"
Title="My Favorites"
Tag="heart"
---
<div id="nofavs" style="display:none;width:100%"><center>_(No favorites available)_</center></div>

View File

@@ -101,7 +101,7 @@ $(function(){
// Adjust the width of thead cells when window resizes
</script>
<table class="tablesorter left shift" id="archive_table">
<table class="unraid tablesorter left shift" id="archive_table">
<thead><tr><th>_(Time)_</th><th>_(Event)_</th><th>_(Subject)_</th><th>_(Description)_</th><th>_(Importance)_</th><th><a id="deleteAll" href="#" onclick="askConfirmation();return false" title="_(Delete all notifications)_" style="display:none"><i class="fa fa-trash-o red-text"></i></a></th></tr></thead>
<tbody id="archive_list"><tr><td colspan="6"></td></tr></tbody>
</table>

View File

@@ -16,7 +16,7 @@ Cond="(count($devs)>0)"
*/
?>
<?
$power = in_array('nvme',array_column($devs,'transport')) ? ' / '._('Power') : '';
$power = _var($display,'power') && in_array('nvme',array_column($devs,'transport')) ? _('Power').' / ' : '';
$tabX = '#tab'.($var['fsState']=='Stopped'||$pool_devices ? '4' : '3');
?>
<script>
@@ -25,12 +25,12 @@ $('<?=$tabX?>').bind({click:function() {$('i.toggle').show('slow');}});
<?endif;?>
</script>
<table class="disk_status wide">
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td>_(Temp)_<?=$power?></td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<table class="unraid disk_status">
<thead><tr><td>_(Device)_</td><td>_(Identification)_</td><td><?=$power?>_(Temp)_</td><td>_(Reads)_</td><td>_(Writes)_</td><td>_(Errors)_</td><td>_(FS)_</td><td>_(Size)_</td><td>_(Used)_</td><td>_(Free)_</td></tr></thead>
<tbody id="open_devices">
<?
foreach ($devs as $dev):
echo "<tr><td colspan='11'>&nbsp;</td></tr>";
echo "<tr><td colspan='10'></td></tr>";
endforeach;
?>
</tbody>

View File

@@ -0,0 +1,68 @@
Menu="OtherSettings"
Type="xmenu"
Title="Power Mode"
Icon="icon-energysaving"
Tag="icon-energysaving"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$cpufreq = '/sys/devices/system/cpu/cpu0/cpufreq';
$current = exec("cat $cpufreq/scaling_governor 2>/dev/null");
exec("cat $cpufreq/scaling_available_governors 2>/dev/null | tr ' ' '\n' | sed '/^$/d' | sort -u",$governor);
function value(...$modes) {
global $current, $governor;
$checked = $value = '';
$disabled = ' disabled';
foreach ($modes as $mode) {
if ($mode==$current) $checked = ' checked';
if (in_array($mode,$governor)) {$value = "value=\"$mode\""; $disabled = '';}
}
return $value.$checked.$disabled;
}
?>
<script>
function preparePowermode(form) {
$(form).find('[name="#arg[1]"]').val(form.powermode.value);
}
$(function(){
$('input[type=radio]').each(function(){
if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>");
});
<?if (exec("dmesg | grep -Pom1 'Hypervisor detected'")):?>
$('#vm').show();
<?endif;?>
});
</script>
<form markdown="1" name="PowerMode" method="POST" action="/update.php" target="progressFrame" onsubmit="preparePowermode(this)">
<input type="hidden" name="#file" value="dynamix/dynamix.cfg">
<input type="hidden" name="#section" value="powermode">
<input type="hidden" name="#command" value="/webGui/scripts/powermode">
<input type="hidden" name="#arg[1]" value="">
_(Change power mode)_:
: <input name="powermode" type="radio"<?=value('powersave')?>>_(Best power efficiency)_<span></span>
&nbsp;
: <input name="powermode" type="radio"<?=value('ondemand','balance_power')?>>_(Balanced operation)_<span></span>
&nbsp;
: <input name="powermode" type="radio"<?=value('performance')?>>_(Best performance)_<span></span>
&nbsp;
: <input type="submit" name="#apply" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
</form>
<div id="vm" class="notice">_(When running Unraid virtualized, there are no available power modes)_</div>

View File

@@ -74,7 +74,7 @@ $(function(){
<?endif;?>
</script>
<div markdown="1" id="route_table" style="display:none">
<table class="share_status">
<table class="unraid">
<thead><tr><td>_(Protocol)_</td><td>_(Route)_</td><td>_(Gateway)_</td><td>_(Metric)_</td><td style="width:8%;text-align:center">_(Delete)_</td></tr></thead>
<tbody id="route_list"></tbody>
</table>

View File

@@ -225,7 +225,6 @@ function readSMB() {
$.get('/webGui/include/ProtocolData.php',{protocol:'smb',name:name},function(json) {
var data = $.parseJSON(json);
form.shareExport.value = data.export;
form.shareFruit.value = data.fruit;
form.shareSecurity.value = data.security;
});
$(form).find('select').trigger('change');
@@ -245,7 +244,6 @@ function writeSMB(data,n,i) {
data[i] = {};
data[i]['shareName'] = $(this).val();
data[i]['shareExport'] = '<?=addslashes(htmlspecialchars($sec[$name]['export']))?>';
data[i]['shareFruit'] = '<?=addslashes(htmlspecialchars($sec[$name]['fruit']))?>';
data[i]['shareSecurity'] = '<?=addslashes(htmlspecialchars($sec[$name]['security']))?>';
data[i]['changeShareSecurity'] = 'Apply';
i++;

View File

@@ -1,4 +1,20 @@
Menu="Tasks:4"
Type="xmenu"
Tabs="false"
Code="e924"
Code="e924"
---
<script>
function addPage(page) {
$.post('/webGui/include/MyFavorites.php',{action:'add',page:page},function(){
swal({title:"_(Added to Favorites)_",text:"",type:"success",html:true,confirmButtonText:"_(Ok)_"},function(x){refresh();});
});
}
$(function(){
$('div.Panel').each(function(){
var page = $(this).find('a').prop('href').split('/').pop();
$(this).find('span').append('<i class="fa fa-heart favo" title="_(Add to favorites)_" onclick="addPage(&quot;'+page+'&quot;);return false"></i>');
$(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});
});
});
</script>

View File

@@ -15,7 +15,7 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareUser')=='e'"
* all copies or substantial portions of the Software.
*/
?>
<table class="share_status">
<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>
</table>

View File

@@ -101,7 +101,7 @@ function textsave(module,remove = false) {
if (data.modprobe == "") $('#text'+module).attr('hidden', true); else $('#text'+module).attr('rows', 3);
if (data.supportpage == true) {
if (data.support == true) {
document.getElementById("link" + module).innerHTML = "<a href='" + data.supporturl + "'target='_blank'><i title='" + _("Support page")_ + "' class='fa fa-phone-square'></i></a>";
document.getElementById("link" + module).innerHTML = "<a href='" + data.supporturl + "'target='_blank'><i title='"+"_("Support page")_"+"' class='fa fa-phone-square'></i></a>";
}
}
}
@@ -119,5 +119,5 @@ showDrivers("all",true);
:sysdrivers_intro_help:
<form autocomplete="off" onsubmit="return false;"><span><input class="t1 search" id="driversearch" type="search" placeholder="Search..." onchange="filterDrivers();"></span></form>
<pre><form id="sysdrivers" class="js-confirm-leave" onsubmit="return false"><table id='t1' class="t1 disk_status tablesorter"><tr><td><div class="spinner"></div></td></tr></table></form></pre><br>
<pre><form id="sysdrivers" class="js-confirm-leave" onsubmit="return false"><table id='t1' class="unraid t1 tablesorter"><tr><td><div class="spinner"></div></td></tr></table></form></pre><br>
<input type="button" value="_(Done)_" onclick="done()"><input type="button" id="rebuild" value="_(Rebuild Modules)_" onclick="showDriversupdate()">

View File

@@ -108,6 +108,15 @@ _(Local syslog folder)_:
:syslog_local_folder_help:
_(System identifier for logfile name)_:
: <select name="server_filename">
<?=mk_option($syslog['server_filename'], "syslog-%FROMHOST-IP%.log", _("IP Address"))?>
<?=mk_option($syslog['server_filename'], "syslog-%HOSTNAME%.log", _("Hostname (from syslog message)"))?>
<?=mk_option($syslog['server_filename'], "syslog-%FROMHOST%.log", _("Hostname (from DNS reverse lookup)"))?>
</select>
:syslog_remote_system_identifier_help:
_(Local syslog rotation)_:
: <select name="log_rotation" onchange="logOptions(this.value,'slow')">
<?=mk_option(_var($syslog,'log_rotation'), "", _("Disabled"))?>
@@ -146,8 +155,8 @@ _(Local syslog number of files)_:
</div>
_(Remote syslog server)_:
: <span class="span"><input type="text" name="remote_server" class="narrow" value="<?=_var($syslog,'remote_server')?>" maxlength="23" placeholder="_(name or ip address)_"></span>
<select name="remote_protocol" class="narrow" size="1">
: <span class="span"><input type="text" name="remote_server" class="narrow" value="<?=_var($syslog,'remote_server')?>" maxlength="50" placeholder="_(name or ip address)_"></span>
<select name="remote_protocol" class="narrow">
<?=mk_option(_var($syslog,'remote_protocol'), "udp", _("UDP"))?>
<?=mk_option(_var($syslog,'remote_protocol'), "tcp", _("TCP"))?>
</select>
@@ -156,13 +165,21 @@ _(Remote syslog server)_:
:syslog_remote_server_help:
_(Mirror syslog to flash)_:
: <select name="syslog_flash" size="1">
: <select name="syslog_flash">
<?=mk_option(_var($syslog,'syslog_flash'), "", _("No"))?>
<?=mk_option(_var($syslog,'syslog_flash'), "1", _("Yes"))?>
</select>
:syslog_mirror_flash_help:
_(Copy syslog to flash on shutdown)_:
: <select name="syslog_shutdown">
<?=mk_option(_var($syslog,'syslog_shutdown'), "", _("Yes"))?>
<?=mk_option(_var($syslog,'syslog_shutdown'), "1", _("No"))?>
</select>
:syslog_shutdown_flash_help:
&nbsp;
: <input type="button" value="_(Apply)_" onclick='validatePort(this.form)' disabled><input type="button" value="_(Done)_" onclick="done()">
</form>

View File

@@ -1,4 +1,20 @@
Menu="Tasks:90"
Type="xmenu"
Tabs="false"
Code="e909"
Code="e909"
---
<script>
function addPage(page) {
$.post('/webGui/include/MyFavorites.php',{action:'add',page:page},function(){
swal({title:"_(Added to Favorites)_",text:"",type:"success",html:true,confirmButtonText:"_(Ok)_"},function(x){refresh();});
});
}
$(function(){
$('div.Panel').each(function(){
var page = $(this).find('a').prop('href').split('/').pop();
$(this).find('span').append('<i class="fa fa-heart favo" title="_(Add to favorites)_" onclick="addPage(&quot;'+page+'&quot;);return false"></i>');
$(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});
});
});
</script>

View File

@@ -296,7 +296,7 @@ function updateAccess(form,data,n,i) {
</script>
<form markdown="1" method="POST">
<?
echo "<table class='share_status' style='margin-top:20px'>";
echo "<table class='unraid'>";
echo "<thead><tr><td>"._('Share')."</td><td>"._('Security')."</td><td>"._('User Access')."</td></tr></thead>";
echo "<tbody>";
foreach ($shares as $share => $data) {

View File

@@ -27,10 +27,11 @@ $(function() {
</script>
<?endif;?>
<?
$myPage['text'] = $page['text'] = $pages['Vars']['text'] = $language = $text = $notes = $site = $webComponentTranslations = '...';
$globals = $GLOBALS;
ksort($globals);
if (isset($globals['_SERVER']['PHP_AUTH_PW'])) $globals['_SERVER']['PHP_AUTH_PW'] = "***";
$globals = [];
$names = ['_SERVER','devs','disks','sec','sec_nfs','shares','users','var'];
foreach ($names as $name) $globals[$name] = $$name;
// show outgoing proxy information, is in the environment but not a superglobal
$globals['environment'] = ['http_proxy' => getenv('http_proxy'), 'no_proxy' => getenv('no_proxy')];
echo "<pre class='up'>",htmlspecialchars(print_r($globals,true)),"</pre>";
?>
<input type="button" value="_(Done)_" onclick="done()">

View File

@@ -26,6 +26,7 @@ hot="45"
max="55"
hotssd="60"
maxssd="70"
power=""
theme="white"
locale=""
raw=""

View File

@@ -0,0 +1,2 @@
#!/bin/bash
/usr/local/emhttp/webGui/scripts/update_services 10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

View File

@@ -9,8 +9,8 @@ if (!empty($_COOKIE['unraid_'.md5($server_name)])) {
// Check if the user is already logged in
if ($_SESSION && !empty($_SESSION['unraid_user'])) {
// If so redirect them to the start page
header("Location: /".$var['START_PAGE']);
// Redirect the user to the start page
header("Location: /".$start_page);
exit;
}
}
@@ -119,12 +119,12 @@ function verifyTwoFactorToken(string $username, string $token): bool {
// This should accept 200 or 204 status codes
if ($httpCode !== 200 && $httpCode !== 204) {
// Log error to syslog
exec("logger -t webGUI -- \"2FA code for {$username} is invalid, blocking access!\"");
my_logger("2FA code for {$username} is invalid, blocking access!");
return false;
}
// Log success to syslog
exec("logger -t webGUI -- \"2FA code for {$username} is valid, allowing login!\"");
my_logger("2FA code for {$username} is valid, allowing login!");
// Success
return true;
@@ -199,7 +199,7 @@ if (!empty($username) && !empty($password)) {
// Check if we're limited
if ($failCount >= $maxFails) {
if ($failCount == $maxFails) exec("logger -t webGUI -- \"Ignoring login attempts for {$username} from {$remote_addr}\"");
if ($failCount == $maxFails) my_logger("Ignoring login attempts for {$username} from {$remote_addr}");
throw new Exception(_('Too many invalid login attempts'));
}
@@ -216,17 +216,17 @@ if (!empty($username) && !empty($password)) {
$_SESSION['unraid_user'] = $username;
session_regenerate_id(true);
session_write_close();
exec("logger -t webGUI -- \"Successful login user {$username} from {$remote_addr}\"");
my_logger("Successful login user {$username} from {$remote_addr}");
// Redirect the user to the start page
header("Location: /".$var['START_PAGE']);
header("Location: /".$start_page);
exit;
} catch (Exception $exception) {
// Set error message
$error = $exception->getMessage();
// Log error to syslog
exec("logger -t webGUI -- \"Unsuccessful login user {$username} from {$remote_addr}\"");
my_logger("Unsuccessful login user {$username} from {$remote_addr}");
appendToFile($failFile, $time."\n");
}
}

View File

@@ -30,12 +30,13 @@ if (!empty($_POST['password']) && !empty($_POST['confirmPassword'])) {
session_regenerate_id(true);
session_write_close();
header("Location: /".$var['START_PAGE']);
// Redirect the user to the start page
header("Location: /".$start_page);
exit;
}
// Error when attempting to set password
exec("logger -t webGUI -- \"{$VALIDATION_MESSAGES['saveError']} [REMOTE_ADDR]: {$REMOTE_ADDR}\"");
my_logger("{$VALIDATION_MESSAGES['saveError']} [REMOTE_ADDR]: {$REMOTE_ADDR}");
return $POST_ERROR = $VALIDATION_MESSAGES['saveError'];
}

View File

@@ -72,6 +72,57 @@ function my_devs(&$devs,$name,$menu) {
}
return implode($text);
}
function icon_class($ext) {
switch ($ext) {
case '3gp': case 'asf': case 'avi': case 'f4v': case 'flv': case 'm4v': case 'mkv': case 'mov': case 'mp4': case 'mpeg': case 'mpg': case 'm2ts': case 'ogm': case 'ogv': case 'vob': case 'webm': case 'wmv':
return 'fa fa-film';
case '7z': case 'bz2': case 'gz': case 'rar': case 'tar': case 'xz': case 'zip':
return 'fa fa-file-archive-o';
case 'aac': case 'ac3': case 'dsf': case 'flac': case 'm4a': case 'mka': case 'mp2': case 'mp3': case 'oga': case 'ogg': case 'tds': case 'wav': case 'wma':
return 'fa fa-music';
case 'ai': case 'eps': case 'fla': case 'psd': case 'swf':
return 'fa fa-file-image-o';
case 'avif': case 'bmp': case 'gif': case 'ico': case 'jp2': case 'jpc': case 'jpeg': case 'jpg': case 'jpx': case 'png': case 'svg': case 'tif': case 'tiff': case 'wbmp': case 'webp': case 'xbm':
return 'fa fa-picture-o';
case 'bak': case 'swp':
return 'fa fa-clipboard';
case 'bat':
return 'fa fa-terminal';
case 'bot': case 'cfg': case 'conf': case 'dat': case 'htaccess': case 'htpasswd': case 'ini': case 'log': case 'pl': case 'tmp': case 'toml': case 'top': case 'txt': case 'yaml': case 'yml':
return 'fa fa-file-text-o';
case 'c': case 'config': case 'cpp': case 'cs': case 'dtd': case 'exe': case 'ftpquota': case 'gitignore': case 'hbs': case 'json': case 'jsx': case 'lock': case 'map': case 'md': case 'msi': case 'passwd': case 'rs': case 'sh': case 'sql': case 'tpl': case 'ts': case 'tsx': case 'twig':
return 'fa fa-file-code-o';
case 'css': case 'less': case 'sass': case 'scss':
return 'fa fa-css3';
case 'csv':
return 'fa fa-file-text-o';
case 'cue': case 'm3u': case 'm3u8': case 'pls': case 'xspf':
return 'fa fa-headphones';
case 'doc': case 'docm': case 'docx': case 'dot': case 'dotm': case 'dotx': case 'odt':
return 'fa fa-file-word-o';
case 'eml': case 'msg':
return 'fa fa-envelope-o';
case 'eot': case 'fon': case 'otf': case 'ttc': case 'ttf': case 'woff': case 'woff2':
return 'fa fa-font';
case 'htm': case 'html': case 'shtml': case 'xhtml':
return 'fa fa-html5';
case 'js': case 'php': case 'php4': case 'php5': case 'phps': case 'phtml': case 'py':
return 'fa fa-code';
case 'key':
return 'fa fa-key';
case 'ods': case 'xla': case 'xls': case 'xlsb': case 'xlsm': case 'xlsx': case 'xlt': case 'xltm': case 'xltx':
return 'fa fa-file-excel-o';
case 'pdf':
return 'fa fa-file-pdf-o';
case 'pot': case 'potx': case 'ppt': case 'pptm': case 'pptx':
return 'fa fa-file-powerpoint-o';
case 'xml': case 'xsl':
return 'fa fa-file-excel-o';
default:
return 'fa fa-file-o';
}
}
$dir = validdir(htmlspecialchars_decode(rawurldecode($_GET['dir'])));
if (!$dir) {echo '<tbody><tr><td></td><td></td><td colspan="6">',_('Invalid path'),'</td><td></td></tr></tbody>'; exit;}
@@ -102,7 +153,7 @@ while (($row = fgets($stat))!==false) {
$text = [];
if ($type[0]=='d') {
$text[] = '<tr><td><i id="check_'.$objs.'" class="fa fa-fw fa-square-o" onclick="selectOne(this.id)"></i></td>';
$text[] = '<td data=""><div class="icon-dir"></div></td>';
$text[] = '<td data=""><i class="fa fa-folder-o"></i></td>';
$text[] = '<td><a id="name_'.$objs.'" oncontextmenu="folderContextMenu(this.id,\'right\');return false" href="/'.$path.'?dir='.rawurlencode(htmlspecialchars($name)).'">'.htmlspecialchars(basename($name)).'</a></td>';
$text[] = '<td id="owner_'.$objs.'">'.$owner.'</td>';
$text[] = '<td id="perm_'.$objs.'">'.$perm.'</td>';
@@ -115,7 +166,7 @@ while (($row = fgets($stat))!==false) {
$ext = strtolower(pathinfo($name,PATHINFO_EXTENSION));
$tag = count($devs)>1 ? 'warning' : '';
$text[] = '<tr><td><i id="check_'.$objs.'" class="fa fa-fw fa-square-o" onclick="selectOne(this.id)"></i></td>';
$text[] = '<td class="ext" data="'.$ext.'"><div class="icon-file icon-'.$ext.'"></div></td>';
$text[] = '<td class="ext" data="'.$ext.'"><i class="'.icon_class($ext).'"></i></td>';
$text[] = '<td id="name_'.$objs.'" class="'.$tag.'" onclick="fileEdit(this.id)" oncontextmenu="fileContextMenu(this.id,\'right\');return false">'.htmlspecialchars(basename($name)).'</td>';
$text[] = '<td id="owner_'.$objs.'" class="'.$tag.'">'.$owner.'</td>';
$text[] = '<td id="perm_'.$objs.'" class="'.$tag.'">'.$perm.'</td>';
@@ -128,6 +179,6 @@ while (($row = fgets($stat))!==false) {
}
}
pclose($stat);
if ($link = parent_link()) echo '<tbody class="tablesorter-infoOnly"><tr><td></td><td><div><img src="/webGui/icons/folderup.png"></div></td><td>',$link,'</td><td colspan="6"></td></tr></tbody>';
if ($link = parent_link()) echo '<tbody class="tablesorter-infoOnly"><tr><td></td><td><i class="fa fa-folder-open-o"></i></td><td>',$link,'</td><td colspan="6"></td></tr></tbody>';
echo write($dirs),write($files),'<tfoot><tr><td></td><td></td><td colspan="7">',add($objs,'object'),': ',add($dirs,'director','y','ies'),', ',add($files,'file'),' (',my_scale($total,$unit),' ',$unit,' ',_('total'),')</td></tr></tfoot>';
?>

View File

@@ -13,13 +13,17 @@
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
// add translations
$_SERVER['REQUEST_URI'] = 'dashboard';
require_once "$docroot/webGui/include/Translations.php";
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
// add translations
$_SERVER['REQUEST_URI'] = 'dashboard';
require_once "$docroot/webGui/include/Translations.php";
if (isset($_POST['ntp'])) {
$ntp = exec("ntpq -pn|awk '{if (NR>3 && $2!=\".INIT.\") c++} END {print c}'");
die($ntp ? sprintf(_('Clock synchronized with %s NTP server'.($ntp==1?'':'s')),$ntp) : _('Clock is unsynchronized with no NTP servers'));
}
if ($_POST['docker']) {
$user_prefs = $dockerManPaths['user-prefs'];
@@ -64,6 +68,7 @@ if ($_POST['docker']) {
}
echo "\0";
if ($_POST['vms']) {
$vmusage = $_POST['vmusage'];
$user_prefs = '/boot/config/plugins/dynamix.vm.manager/userprefs.cfg';
$vms = $lv->get_domains() ?: [];
if (file_exists($user_prefs)) {
@@ -74,11 +79,14 @@ if ($_POST['vms']) {
natcasesort($vms);
}
echo "<tr title='' class='updated'><td>";
$running = 0;
foreach ($vms as $vm) {
$res = $lv->get_domain_by_name($vm);
$uuid = libvirt_domain_get_uuid_string($res);
$dom = $lv->domain_get_info($res);
$id = $lv->domain_get_id($res);
$fstype ="QEMU";
if (($diskcnt = $lv->get_disk_count($res)) > 0) $fstype = $lv->get_disk_fstype($res);
$state = $lv->domain_state_translate($dom['state']);
$vmrcport = $lv->domain_get_vnc_port($res);
$autoport = $lv->domain_get_vmrc_autoport($res);
@@ -110,13 +118,14 @@ if ($_POST['vms']) {
if (empty($template)) $template = 'Custom';
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log), $vmrcconsole);
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole);
$icon = $lv->domain_get_icon_url($res);
switch ($state) {
case 'running':
$shape = 'play';
$status = 'started';
$color = 'green-text';
$running++;
break;
case 'paused':
case 'pmsuspended':
@@ -136,4 +145,44 @@ if ($_POST['vms']) {
$none = count($vms) ? _('No running virtual machines') : _('No virtual machines defined');
echo "<span id='no_vms' style='display:none'>$none<br><br></span>";
echo "</td></tr>";
echo "\0";
echo "<tr title='' class='useupdated'><td>";
$running = 0;
if ($vmusage == "Y") {
foreach ($vms as $vm) {
$res = $lv->get_domain_by_name($vm);
$uuid = libvirt_domain_get_uuid_string($res);
$dom = $lv->domain_get_info($res);
$id = $lv->domain_get_id($res);
$fstype ="QEMU";
if (($diskcnt = $lv->get_disk_count($res)) > 0) $fstype = $lv->get_disk_fstype($res);
$state = $lv->domain_state_translate($dom['state']);
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
if (empty($template)) $template = 'Custom';
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
$icon = $lv->domain_get_icon_url($res);
if ($state != "running") continue;
$running++;
switch ($state) {
case 'running':
$shape = 'play';
$status = 'started';
$color = 'green-text';
break;
}
$image = substr($icon,-4)=='.png' ? "<img src='$icon' class='img'>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
echo "<span class='outer solid vmsuse $status'><span id='vmusage-$uuid' >$image</span><span class='inner'>$vm<br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span>";
echo "<br><br><span id='vmmetrics-gcpu-".$uuid."'>",_("Loading")."....</span>";
echo "<br><span id='vmmetrics-hcpu-".$uuid."'>"._("Loading")."....</span>";
echo "<br><span id='vmmetrics-mem-".$uuid."'>"._("Loading")."....</span>";
echo "<br><span id='vmmetrics-disk-".$uuid."'>"._("Loading")."....</span>";
echo "<br><span id='vmmetrics-net-".$uuid."'>"._("Loading")."....</span>";
echo "</span>";
}
if (!count($vms)) echo "<span id='no_usagevms'><br> "._('No running virtual machines')."<br></span>";
if ($running < 1 && count($vms)) echo "<span id='no_usagevms'><br>". _('No running virtual machines')."<br></span>";
echo "</td></tr>";
}
}

View File

@@ -1,6 +1,6 @@
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2024, Lime Technology
* Copyright 2012-2024, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -388,6 +388,7 @@ function openChanges(cmd,title,nchan,button=0) {
$('div.spinner.fixed').hide();
swal({title:title,text:"<pre id='swalbody'></pre><hr>",html:true,animation:'none',showConfirmButton:button!=0,confirmButtonText:"<?=_('Close')?>"},function(close){
$('.sweet-alert').hide('fast').removeClass('nchan');
if ($('#submit_button').length > 0) $('#submit_button').remove();
});
$('.sweet-alert').addClass('nchan');
$('pre#swalbody').html(data);
@@ -408,6 +409,12 @@ function openDone(data) {
if (data == '_DONE_') {
$('div.spinner.fixed').hide();
$('button.confirm').text("<?=_('Done')?>").prop('disabled',false).show();
if ( typeof ca_done_override !== 'undefined' ) {
if (ca_done_override == true) {
$("button.confirm").trigger("click");
ca_done_override = false;
}
}
return true;
}
return false;
@@ -581,7 +588,6 @@ function flashReport() {
$(function() {
var tab = $.cookie('one')||$.cookie('tab')||'tab1';
if (tab=='tab0') tab = 'tab'+$('input[name$="tabs"]').length; else if ($('#'+tab).length==0) {initab(); tab = 'tab1';}
if ($.cookie('help')=='help') {$('.inline_help').show(); $('.nav-item.HelpButton').addClass('active');}
$('#'+tab).attr('checked', true);
updateTime();
$.jGrowl.defaults.closeTemplate = '<i class="fa fa-close"></i>';
@@ -673,7 +679,7 @@ foreach ($buttons as $button) {
if (isset($button['Nchan'])) nchan_merge($button['root'], $button['Nchan']);
}
echo "<div class='nav-user show'><a id='board' href='#'><b id='bell' class='icon-u-bell system'></b></a></div>";
echo "<div class='nav-user show'><a id='board' href='#' class='hand'><b id='bell' class='icon-u-bell system'></b></a></div>";
if ($themes2) echo "</div>";
echo "</div></div>";
@@ -755,7 +761,7 @@ foreach ($pages as $page) {
if ($close) echo "</div></div>";
}
if (count($pages)) {
$running = file_exists($nchan_pid) ? file($nchan_pid,FILE_IGNORE_NEW_LINES) : [];
$running = file_exists($nchan_pid) ? file($nchan_pid,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) : [];
$start = array_diff($nchan, $running); // returns any new scripts to be started
$stop = array_diff($running, $nchan); // returns any old scripts to be stopped
$running = array_merge($start, $running); // update list of current running nchan scripts
@@ -768,7 +774,7 @@ if (count($pages)) {
foreach ($stop as $row) {
[$script,$opt] = my_explode(':',$row);
if ($opt == 'stop') {
exec("pkill -f $docroot/$script >/dev/null &");
exec("pkill -f $docroot/$script &>/dev/null &");
array_splice($running,array_search($row,$running),1);
}
}
@@ -796,7 +802,7 @@ default:
echo "<span class='green strong'><i class='fa fa-play-circle'></i> ",_('Array Started'),"</span>$progress"; break;
}
echo "</span></span><span id='countdown'></span><span id='user-notice' class='red-text'></span>";
echo "<span id='copyright'>Unraid&reg; webGui &copy;2023, Lime Technology, Inc.";
echo "<span id='copyright'>Unraid&reg; webGui &copy;2024, Lime Technology, Inc.";
echo " <a href='https://docs.unraid.net/category/manual' target='_blank' title=\""._('Online manual')."\"><i class='fa fa-book'></i> "._('manual')."</a>";
echo "</span></div>";
?>

View File

@@ -14,7 +14,7 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
if (array_key_exists('getdiagnostics', $_GET)) {
$anonymize = empty($_GET['anonymize']) ? '-a' : '';
$diag_file = '/tmp/feedback_diagnostics_'.time().'.zip';
$diag_file = '/tmp/feedback-diagnostics-'.date('Ymd-Hi').'.zip';
exec("$docroot/webGui/scripts/diagnostics $anonymize $diag_file");
echo base64_encode(@file_get_contents($diag_file));
@unlink($diag_file);

View File

@@ -270,4 +270,25 @@ function my_preg_split($split, $text, $count=2) {
function delete_file(...$file) {
array_map('unlink',array_filter($file,'file_exists'));
}
function my_mkdir($dirname,$permissions = 0777,$recursive = false) {
$dirname = transpose_user_path($dirname);
$pathinfo = pathinfo($dirname);
$parent = $pathinfo["dirname"];
$fstype = trim(shell_exec(" stat -f -c '%T' $parent"));
$rtncode = false;
switch ($fstype) {
case "zfs":
$zfsdataset = trim(shell_exec("zfs list -H -o name $parent")) ;
$rtncode=exec("zfs create $zfsdataset/{$pathinfo['filename']}");
if (!$rtncode) mkdir($dirname, $permissions, $recursive);
break;
case "btrfs":
$rtncode=exec("btrfs subvolume create $dirname");
if (!$rtncode) mkdir($dirname, $permissions, $recursive);
break;
default:
mkdir($dirname, $permissions, $recursive);
break;
}
}
?>

View File

@@ -1,4 +1,4 @@
<?PHP
<?php
/* Copyright 2005-2023, Lime Technology
*
* This program is free software; you can redistribute it and/or
@@ -8,56 +8,76 @@
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
// add translations
$_SERVER['REQUEST_URI'] = 'settings';
$docroot = ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/Translations.php";
/**
* @name response_complete
* @param {HTTP Response Status Code} $httpcode https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
* @param {String|Array} $result - strings are assumed to be encoded JSON. Arrays will be encoded to JSON.
* @param {String} $cli_success_msg
*/
class KeyInstaller
{
private $isGetRequest;
private $getHasUrlParam;
function response_complete($httpcode, $result, $cli_success_msg='') {
global $cli;
$mutatedResult = is_array($result) ? json_encode($result) : $result;
if ($cli) {
$json = @json_decode($mutatedResult,true);
if (!empty($json['error'])) {
echo 'Error: '.$json['error'].PHP_EOL;
exit(1);
public function __construct()
{
$this->isGetRequest = !empty($_SERVER) && isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET';
$this->getHasUrlParam = $_GET !== null && !empty($_GET) && isset($_GET['url']);
if ($this->isGetRequest && $this->getHasUrlParam) {
$this->installKey();
}
}
/**
* @param int $httpcode https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
* @param string|array $result - strings are assumed to be encoded JSON. Arrays will be encoded to JSON.
*/
private function responseComplete($httpcode, $result)
{
$mutatedResult = is_array($result) ? json_encode($result) : $result;
if ($this->isGetRequest && $this->getHasUrlParam) { // return JSON to the caller
header('Content-Type: application/json');
http_response_code($httpcode);
exit((string)$mutatedResult);
} else { // return the result to the caller
return $mutatedResult;
}
}
public function installKey($keyUrl = null)
{
$url = unscript($keyUrl ?? _var($_GET, 'url'));
$host = parse_url($url)['host'] ?? '';
if (!function_exists('_')) {
function _($text) {return $text;}
}
if ($host && in_array($host, ['keys.lime-technology.com', 'lime-technology.com'])) {
$keyFile = basename($url);
exec("/usr/bin/wget -q -O " . escapeshellarg("/boot/config/$keyFile") . " " . escapeshellarg($url), $output, $returnVar);
if ($returnVar === 0) {
$var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
if (_var($var, 'mdState') == "STARTED") {
return $this->responseComplete(200, ['status' => _('Please Stop array to complete key installation')], _('success') . ', ' . _('Please Stop array to complete key installation'));;
} else {
return $this->responseComplete(200, ['status' => ''], _('success'));;
}
} else {
return $this->responseComplete(406, ['error' => _('download error') . " $returnVar"]);;
}
} else {
return $this->responseComplete(406, ['error' => _('bad or missing key file') . ": $url"]);;
}
}
exit($cli_success_msg.PHP_EOL);
}
header('Content-Type: application/json');
http_response_code($httpcode);
exit((string)$mutatedResult);
}
$cli = php_sapi_name()=='cli';
$url = unscript(_var($_GET,'url'));
$host = parse_url($url)['host']??'';
$isGetRequest = !empty($_SERVER) && isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET';
$getHasUrlParam = $_GET !== null && !empty($_GET) && isset($_GET['url']);
if ($host && in_array($host,['keys.lime-technology.com','lime-technology.com'])) {
$key_file = basename($url);
exec("/usr/bin/wget -q -O ".escapeshellarg("/boot/config/$key_file")." ".escapeshellarg($url), $output, $return_var);
if ($return_var === 0) {
$var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
if (_var($var,'mdState')=="STARTED") {
response_complete(200, array('status' => _('Please Stop array to complete key installation')), _('success').', '._('Please Stop array to complete key installation'));
} else {
response_complete(200, array('status' => ''), _('success'));
}
} else {
response_complete(406, array('error' => _('download error') . " $return_var"));
}
} else {
response_complete(406, array('error' => _('bad or missing key file') . ": $url"));
if ($isGetRequest && $getHasUrlParam) {
$keyInstaller = new KeyInstaller();
$keyInstaller->installKey();
}
?>

View File

@@ -0,0 +1,58 @@
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
$permit = ['del','add'];
$action = $_POST['action']??'';
$cfg = '/boot/config/favorites.cfg';
// remove non-existing pages
if ($action=='clear') {
if (file_exists($cfg)) foreach (file($cfg,FILE_IGNORE_NEW_LINES) as $page) {
if (!file_exists($page)) {
$page = str_replace('/','\/',$page);
exec("sed -i '/$page/d' $cfg 2>/dev/null");
}
}
exit;
}
// validate input
$page = glob("$docroot/plugins/*/{$_POST['page']}.page",GLOB_NOSORT)[0];
if (!$page || !in_array($action,$permit)) exit;
$file = fopen($page,'r');
// get current Menu settings
extract(parse_ini_string(fgets($file)));
fclose($file);
// remove label and escape single quotes for sed command
$Menu = str_replace([' MyFavorites',"'"],['',"'\''"],$Menu);
switch ($action) {
case $permit[0]: // del
$del = str_replace('/','\/',$page);
exec("sed -i '/$del/d' $cfg 2>/dev/null");
break;
case $permit[1]: // add
$file = fopen($cfg,'a+');
fseek($file,0,0);
while (($line = fgets($file))!==false) {
if (rtrim($line) == $page) break;
}
if (feof($file)) fwrite($file, $page."\n");
fclose($file);
$Menu .= ' MyFavorites';
break;
}
// update Menu settings
exec("sed -ri '0,/^Menu=\".+\"$/s//Menu=\"$Menu\"/' $page 2>/dev/null");

View File

@@ -39,13 +39,13 @@ foreach ($files as $file) {
$c = 0;
foreach ($fields as $field) {
if ($c==5) break;
$text = $field ? explode('=',$field,2)[1] : "-";
$text = $field ? (explode('=',$field,2)[1]??"") : "-";
$tag = ($c<4) ? "" : " data='".str_replace(['alert','warning','normal'],['0','1','2'],$text)."'";
echo (!$c++) ? "<tr>".str_replace('*',$text,$td_).date($dynamix['notify']['date'].' '.$dynamix['notify']['time'],$text)."$_td" : "<td$tag>"._($text)."</td>";
}
echo "<td><a href='#' onclick='$(this).hide();$.post(\"/webGui/include/DeleteLogFile.php\",{log:\"$archive\"},function(){archiveList();});return false' title=\""._('Delete notification')."\"><i class='fa fa-trash-o'></i></a></td></tr>";
if ($extra) {
$text = explode('=',$field,2)[1];
$text = explode('=',$field,2)[1]??"";
echo "<tr class='tablesorter-childRow row$row'><td colspan='4'>$text</td><td></td></tr><tr class='tablesorter-childRow row$row'><td colspan='5'></td></tr>";
$row++;
}

View File

@@ -12,6 +12,7 @@
?>
<?
require_once "$docroot/webGui/include/MarkdownExtra.inc.php";
require_once "$docroot/webGui/include/Wrappers.php";
function get_ini_key($key,$default) {
$x = strpos($key, '[');
@@ -32,7 +33,7 @@ function build_pages($pattern) {
foreach (glob($pattern,GLOB_NOSORT) as $entry) {
[$header, $content] = my_explode("\n---\n",file_get_contents($entry));
$page = @parse_ini_string($header);
if (!$page) {exec("logger -t webGUI -- \"Invalid .page format: $entry\""); continue;}
if (!$page) {my_logger("Invalid .page format: $entry"); continue;}
$page['file'] = $entry;
$page['root'] = dirname($entry);
$page['name'] = basename($entry, '.page');

View File

@@ -1,85 +1,196 @@
<?PHP
/* Copyright 2005-2023, Lime Technology
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
extract(parse_plugin_cfg('dynamix',true));
<?php
$webguiGlobals = $GLOBALS;
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
// add translations
$_SERVER['REQUEST_URI'] = 'tools';
require_once "$docroot/webGui/include/Translations.php";
class ReplaceKey
{
private const KEY_SERVER_URL = 'https://keys.lime-technology.com';
$var = parse_ini_file('state/var.ini');
$keyfile = base64_encode(file_get_contents($var['regFILE']));
?>
<!DOCTYPE html>
<html <?=$display['rtl']?>lang="<?=strtok($locale,'_')?:'en'?>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=1300">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="same-origin">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-fonts.css")?>">
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-popup.css")?>">
<script src="<?autov('/webGui/javascript/dynamix.js')?>"></script>
<script>
function replaceKey(email, guid, keyfile) {
if (email.length) {
var timestamp = <?=time()?>;
$('#status_panel').slideUp('fast');
$('#input_form').find('input').prop('disabled', true);
// Nerds love spinners, Maybe place a spinner image next to the submit button; we'll show it now:
$('#spinner_image').fadeIn('fast');
private $docroot;
private $var;
private $guid;
private $keyfile;
private $regExp;
$.post('https://keys.lime-technology.com/account/license/transfer',{timestamp:timestamp,guid:guid,email:email,keyfile:keyfile},function(data) {
$('#spinner_image').fadeOut('fast');
var msg = "<p><?=_('A registration replacement key has been created for USB Flash GUID')?> <strong>"+guid+"</strong></p>" +
"<p><?=_('An email has been sent to')?> <strong>"+email+"</strong> <?=_('containing your key file URL')?>." +
" <?=_('When received, please paste the URL into the *Key file URL* box and')?>" +
" <?=_('click <i>Install Key</i>')?>.</p>" +
"<p><?=_('If you do not receive an email, please check your spam or junk-email folder')?>.</p>";
public function __construct()
{
$this->docroot = $GLOBALS['docroot'] ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$('#status_panel').hide().html(msg).slideDown('fast');
$('#input_form').fadeOut('fast');
}).fail(function(data) {
$('#input_form').find('input').prop('disabled', false);
$('#spinner_image').fadeOut('fast');
var status = data.status;
var obj = data.responseJSON;
var msg = "<p><?=_('Sorry, an error occurred')?> <?=_('registering USB Flash GUID')?> <strong>"+guid+"</strong><p>"+"<p><?=_('The error is')?>: "+obj.error+"</p>";
$this->var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
$this->guid = @$this->var['regGUID'] ?? null;
$('#status_panel').hide().html(msg).slideDown('fast');
});
}
$keyfileBase64 = empty($this->var['regFILE']) ? null : @file_get_contents($this->var['regFILE']);
if ($keyfileBase64 !== false) {
$keyfileBase64 = @base64_encode($keyfileBase64);
$this->keyfile = str_replace(['+', '/', '='], ['-', '_', ''], trim($keyfileBase64));
}
$this->regExp = @$this->var['regExp'] ?? null;
}
private function request($url, $method, $payload = null, $headers = null)
{
$ch = curl_init($url);
// Set the request method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// store the response in a variable instead of printing it
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Set the payload if present
if ($payload !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
}
if ($headers !== null) {
// Set the headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
// Set additional options as needed
// Execute the request
$response = curl_exec($ch);
// Check for errors
if (curl_errno($ch)) {
$error = [
'heading' => 'CurlError',
'message' => curl_error($ch),
'level' => 'error',
'ref' => 'curlError',
'type' => 'request',
];
// @todo store error
}
// Close the cURL session
curl_close($ch);
return $response;
}
private function validateGuid()
{
$headers = [
'Content-Type: application/x-www-form-urlencoded',
];
$params = [
'guid' => $this->guid,
'keyfile' => $this->keyfile,
];
/**
* returns {JSON}
* hasNewerKeyfile : boolean;
* purchaseable: true;
* registered: false;
* replaceable: false;
* upgradeable: false;
* upgradeAllowed: string[];
* updatesRenewable: false;
*/
$response = $this->request(
self::KEY_SERVER_URL . '/validate/guid',
'POST',
http_build_query($params),
$headers,
);
// Handle the response as needed (parsing JSON, etc.)
$decodedResponse = json_decode($response, true);
if (!empty($decodedResponse)) {
return $decodedResponse;
}
// @todo save error response somewhere
return [];
}
private function getLatestKey()
{
$headers = [
'Content-Type: application/x-www-form-urlencoded',
];
$params = [
'keyfile' => $this->keyfile,
];
/**
* returns {JSON}
* license: string;
*/
$response = $this->request(
self::KEY_SERVER_URL . '/key/latest',
'POST',
http_build_query($params),
$headers,
);
// Handle the response as needed (parsing JSON, etc.)
$decodedResponse = json_decode($response, true);
if (!empty($decodedResponse) && !empty($decodedResponse['license'])) {
return $decodedResponse['license'];
}
return null;
}
private function installNewKey($key)
{
require_once "$this->docroot/webGui/include/InstallKey.php";
$KeyInstaller = new KeyInstaller();
$KeyInstaller->installKey($key);
}
private function writeJsonFile($file, $data)
{
if (!is_dir(dirname($file))) { // prevents errors when directory doesn't exist
mkdir(dirname($file));
}
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
public function check()
{
// we don't need to check
if (empty($this->guid) || empty($this->keyfile) || empty($this->regExp)) {
return;
}
// set $isAlreadyExpired to true if regExp is in the past
$isAlreadyExpired = $this->regExp <= time();
// if regExp is seven days or less from now, we need to check
$isWithinSevenDays = $this->regExp <= strtotime('+7 days');
$shouldCheck = $isAlreadyExpired || $isWithinSevenDays;
if (!$shouldCheck) {
return;
}
// see if we have a new key
$validateGuidResponse = $this->validateGuid();
$hasNewerKeyfile = @$validateGuidResponse['hasNewerKeyfile'] ?? false;
if (!$hasNewerKeyfile) {
return; // if there is no newer keyfile, we don't need to do anything
}
$latestKey = $this->getLatestKey();
if (!$latestKey) {
// we supposedly have a new key, but didn't get it back…
$this->writeJsonFile(
'/tmp/ReplaceKey/error.json',
[
'error' => 'Failed to retrieve latest key after getting a `hasNewerKeyfile` in the validation response.',
]
);
return;
}
$this->installNewKey($latestKey);
}
}
</script>
</head>
<body>
<div style="margin-top:20px;line-height:30px;margin-left:40px">
<div id="status_panel"></div>
<form markdown="1" id="input_form">
Email address: <input type="text" name="email" maxlength="1024" value="" style="width:33%">
<input type="button" value="Replace Key" onclick="replaceKey(this.form.email.value.trim(), '<?=$var['flashGUID']?>', '<?=$keyfile?>')">
<p>A link to your replacement key will be delivered to this email address.
<p><strong>Note:</strong>
Once a replacement key is generated, your old USB Flash device will be <b>blacklisted</b>.
</form>
</div>
</body>
</html>

View File

@@ -38,6 +38,9 @@ function duration(&$hrs) {
$age = date_diff($poh,$now);
$hrs = "$hrs (".($age->y?"{$age->y}y, ":"").($age->m?"{$age->m}m, ":"").($age->d?"{$age->d}d, ":"")."{$age->h}h)";
}
function blocks_size(&$blks,$blk_size) {
$blks = "$blks (".my_scale($blks*$blk_size,$unit)." $unit)";
}
function append(&$ref, &$info) {
if ($info) $ref .= ($ref ? " " : "").$info;
}
@@ -62,6 +65,7 @@ case "attributes":
$hot = _var($disk,'hotTemp',-1)>=0 ? $disk['hotTemp'] : ($hotNVME>=0 ? $hotNVME : (_var($disk,'rotational',1)==0 && $display['hotssd']>=0 ? $display['hotssd'] : $display['hot']));
$max = _var($disk,'maxTemp',-1)>=0 ? $disk['maxTemp'] : ($maxNVME>=0 ? $maxNVME : (_var($disk,'rotational',1)==0 && $display['maxssd']>=0 ? $display['maxssd'] : $display['max']));
$top = $_POST['top'] ?? 120;
$ssd_remaining = NULL;
$empty = true;
exec("smartctl -n standby -A $type ".escapeshellarg("/dev/$port"),$output);
// remove empty rows
@@ -83,6 +87,8 @@ case "attributes":
}
if ($info[8]=='-') $info[8] = 'Never';
if ($info[0]==9 && is_numeric(size($info[9]))) duration($info[9]);
if (str_starts_with($info[1], 'Total_LBAs_')) blocks_size($info[9],512); // Assumes 512 byte sectors
if (str_ends_with($info[1], '_32MiB')) blocks_size($info[9],32*1024*1024);
echo "<tr{$color}>".implode('',array_map('normalize', $info))."</tr>";
$empty = false;
}
@@ -101,15 +107,42 @@ case "attributes":
case 'Power on hours':
if (is_numeric(size($value))) duration($value);
break;
case 'Percentage used':
$ssd_remaining = 100 - str_replace('%', '', $value);
break;
}
if (str_ends_with($name, ', hours') && str_starts_with($value, 'minutes ')) {
$name = substr($name, 0, -7);
$value = substr($value, 8);
if (is_numeric(size($value))) duration($value);
}
echo "<tr{$color}><td>-</td><td>$name</td><td colspan='8'>$value</td></tr>";
$empty = false;
}
}
if (is_null($ssd_remaining)) {
// Try to look up SSD's 'Percentage Used Endurance Indicator' with special command
exec("smartctl -n standby -l ssd $type ".escapeshellarg("/dev/$port"), $ssd_out);
$ssd_out = array_filter($ssd_out);
foreach ($ssd_out as $row) {
if (str_ends_with($row, 'Percentage Used Endurance Indicator')) {
// Probably a SATA SSD
$info = explode(' ', trim(preg_replace('/\s+/',' ',$row)), 6);
$ssd_remaining = 100 - $info[3];
} elseif (str_starts_with($row, 'Percentage used endurance indicator:')) {
// Probably a SAS SSD
[$name,$value] = array_map('trim',explode(':', $row));
$ssd_remaining = 100 - str_replace('%','',$value);
}
}
}
if (!is_null($ssd_remaining)) {
echo "<tr><td>-</td><td>SSD endurance remaining</td><td colspan='8'>$ssd_remaining %</td></tr>";
}
if ($empty) echo "<tr><td colspan='10' style='text-align:center;padding-top:12px'>"._('Attributes not available')."</td></tr>";
break;
case "capabilities":
echo '<table id="disk_capabilities_table" class="share_status small"><thead><td style="width:33%">'._('Feature').'</td><td>'._('Value').'</td><td>'._('Information').'</td></thead><tbody>' ;
echo '<table id="disk_capabilities_table" class="unraid"><thead><td style="width:33%">'._('Feature').'</td><td>'._('Value').'</td><td>'._('Information').'</td></thead><tbody>' ;
exec("smartctl -n standby -c $type ".escapeshellarg("/dev/$port")."|awk 'NR>5'",$output);
$row = ['','',''];
$empty = true;
@@ -120,27 +153,25 @@ case "capabilities":
$line = preg_replace('/^_/','__',preg_replace(['/__+/','/_ +_/'],'_',str_replace([chr(9),')','('],'_',$line)));
$info = array_map('trim', explode('_', preg_replace('/_( +)_ /','__',$line), 3));
if ($nvme && $info[0]=="Supported Power States" ) { $nvme_section="psheading" ;echo "</body></table><div class='title'><span>{$line}</span></div>"; $row = ['','',''] ; continue ;}
if ($nvme && $info[0]=="Supported LBA Sizes" ) {
if ($nvme && $info[0]=="Supported LBA Sizes" ) {
echo "</body></table><div class='title'>{$info[0]} {$info[1]} {$info[2]}</span></div>";
$row = ['','',''];
$nvme_section="lbaheading" ;
$nvme_section="lbaheading" ;
continue ;
}
}
append($row[0],$info[0]);
append($row[1],$info[1]);
append($row[2],$info[2]);
if (substr($row[2],-1)=='.' || ($nvme && $nvme_section=="info")) {
echo "<tr><td>{$row[0]}</td><td>{$row[1]}</td><td>{$row[2]}</td></tr>";
$row = ['','',''];
$empty = false;
}
if ($nvme && $nvme_section == "psheading") {
echo '<table id="disk_capabilities_table2" class="share_status small"><thead>' ;
echo '<table id="disk_capabilities_table2" class="unraid"><thead>' ;
$nvme_section = "psdetail";
preg_match('/^(?P<data1>.\S+)\s+(?P<data2>\S+)\s+(?P<data3>\S+)\s+(?P<data4>\S+)\s+(?P<data5>\S+)\s+(?P<data6>\S+)\s+(?P<data7>\S+)\s+(?P<data8>\S+)\s+(?P<data9>\S+)\s+(?P<data10>\S+)\s+(?P<data11>\S+)$/',$line, $psheadings);
for ($i = 1; $i <= 11; $i++) {
for ($i = 1; $i <= 11; $i++) {
echo "<td>"._var($psheadings,'data'.$i)."</td>" ;
}
$row = ['','',''];
@@ -150,17 +181,17 @@ case "capabilities":
$nvme_section = "psdetail";
echo '<tr>' ;
preg_match('/^(?P<data1>.\S+)\s+(?P<data2>\S\s+)\s+(?P<data3>\S+)\s+(?P<data4>\S\s+)\s+(?P<data5>\S+)\s+(?P<data6>\S+)\s+(?P<data7>\S+)\s+(?P<data8>\S+)\s+(?P<data9>\S+)\s+(?P<data10>\S+)\s+(?P<data11>\S+)$/',$line, $psdetails);
for ($i = 1; $i <= 11; $i++) {
for ($i = 1; $i <= 11; $i++) {
echo "<td>"._var($psdetails,'data'.$i)."</td>" ;
}
$row = ['','',''];
echo '</tr>' ;
}
if ($nvme && $nvme_section == "lbaheading") {
echo '<table id="disk_capabilities_table3" class="share_status small"><thead>' ;
echo '<table id="disk_capabilities_table3" class="unraid"><thead>' ;
$nvme_section = "lbadetail";
preg_match('/^(?P<data1>.\S+)\s+(?P<data2>\S+)\s+(?P<data3>\S+)\s+(?P<data4>\S+)\s+(?P<data5>\S+)$/',$line, $lbaheadings);
for ($i = 1; $i <= 5; $i++) {
for ($i = 1; $i <= 5; $i++) {
echo "<td>"._var($lbaheadings,'data'.$i)."</td>" ;
}
$row = ['','',''];
@@ -170,7 +201,7 @@ case "capabilities":
$nvme_section = "lbadetail";
preg_match('/^(?P<data1>.\S+)\s+(?P<data2>\S\s+)\s+(?P<data3>\S+)\s+(?P<data4>\S\s+)\s+(?P<data5>\S+)$/',$line, $lbadetails);
echo '<tr>' ;
for ($i = 1; $i <= 5; $i++) {
for ($i = 1; $i <= 5; $i++) {
echo "<td>"._var($lbadetails,'data'.$i)."</td>" ;
}
$row = ['','',''];
@@ -236,7 +267,7 @@ case "update":
if ($progress) {
if ($transport == 'nvme') echo "<span class='big'><i class='fa fa-spinner fa-pulse'></i> "._('self-test in progress').", ".(substr($progress,0,-1))."% "._('complete')."</span>"; else echo "<span class='big'><i class='fa fa-spinner fa-pulse'></i> "._('self-test in progress').", ".(100-substr($progress,0,-1))."% "._('complete')."</span>";
break;
}
}
} else {
$progress = exec("smartctl -n standby -c $type ".escapeshellarg("/dev/$port")."|grep -Pom1 '\d+%'");
if ($progress) {

View File

@@ -73,7 +73,7 @@ case 't1load':
} else {
$supporturl = $module['supporturl'];
$pluginname = $module['plugin'];
$supporthtml = "<span id='link$modname'><a href='$supporturl' target='_blank'><i title='"._("Support page $pluginname")."' class='fa fa-phone-square'></i></a></span>";
$supporthtml = "<span id='link$modname'><a href='$supporturl' target='_blank'><i title='"._("Support page")." $pluginname' class='fa fa-phone-square'></i></a></span>";
}
}
if (!empty($module["version"])) $version = " (".$module["version"].")"; else $version = "";

View File

@@ -3,10 +3,11 @@
function SysDriverslog($m, $type='NOTICE') {
if ($type == 'DEBUG') return;
$m = str_replace(["\n",'"'],[" ","'"],print_r($m,true));
exec("logger -t sysDrivers -- \"$m\"");
my_logger("$m", 'sysDrivers');
}
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Wrappers.php";
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/SysDriversHelpers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";

Some files were not shown because too many files have changed in this diff Show More