Compare commits

...

18 Commits

Author SHA1 Message Date
Zack Spear
533927c692 fix: duplicate error message with no keyfile 2024-03-27 00:26:16 +09:00
Zack Spear
985ed5bad4 chore: lint 2024-03-26 19:45:00 +09:00
Zack Spear
21123f39a6 refactor: config error messages 2024-03-26 19:27:13 +09:00
Zack Spear
fd6f4eccf4 feat: server config enum message w/ ineligible support 2024-03-26 19:16:11 +09:00
Zack Spear
2ce62bf789 test: serverState local components data tweaks 2024-03-26 16:52:22 +09:00
Eli Bosley
b33c86c99c chore(release): 3.5.3 2024-03-25 09:22:14 -04:00
Zack Spear
cd0248e4c9 refactor: upgrade action button for unleashed to lifetime (#859) 2024-03-20 10:27:33 -04:00
Zack Spear
ecb3ed5003 fix: regDevs usage to allow more flexibility for STARTER (#860)
* fix: regDevs usage to allow more flexibility for STARTER

* fix: lint and type-check
2024-03-20 08:50:02 -04:00
Zack Spear
0569339a41 refactor(upc): remove UpdateDNS requests 2024-03-12 16:36:04 -07:00
ljm42
3e9faead43 Replace UpdateDNS.php with a stub (#857)
* This new stub file makes no network calls and always returns success
* It is meant to be backwards compatible with older releases of Unraid that expect the script to exist
2024-03-12 15:57:17 -04:00
Eli Bosley
6e700b2385 chore(release): 3.5.2 2024-03-06 10:20:09 -05:00
renovate[bot]
464fc4993c fix(deps): update dependency vue-i18n to v9.10.1 (#813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:08:22 -05:00
renovate[bot]
4316c72809 chore(deps): update dependency terser to v5.28.1 (#802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:08:05 -05:00
renovate[bot]
ce0cebe09c chore(deps): update dependency node to v18.19.1 (#801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:07:51 -05:00
renovate[bot]
23b90a0d56 fix(deps): update dependency wretch to v2.8.0 (#814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:07:38 -05:00
Zack Spear
3f8b3536b5 refactor: ENOKEYFILE messaging + button order (#856) 2024-03-05 15:07:20 -05:00
Zack Spear
0dcf785b45 fix: update os check modal button conditionals 2024-02-29 14:49:20 -08:00
Zack Spear
8cf4aff622 fix: update os check modal ineligible date format 2024-02-29 14:16:09 -08:00
20 changed files with 248 additions and 572 deletions

View File

@@ -1 +1 @@
18.17.1
18.19.1

View File

@@ -2,6 +2,23 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [3.5.3](https://github.com/unraid/api/compare/v3.5.2...v3.5.3) (2024-03-25)
### Bug Fixes
* regDevs usage to allow more flexibility for STARTER ([#860](https://github.com/unraid/api/issues/860)) ([92a9600](https://github.com/unraid/api/commit/92a9600f3a242c5f263f1672eab81054b9cf4fae))
### [3.5.2](https://github.com/unraid/api/compare/v3.5.1...v3.5.2) (2024-03-06)
### Bug Fixes
* **deps:** update dependency vue-i18n to v9.10.1 ([#813](https://github.com/unraid/api/issues/813)) ([69b599c](https://github.com/unraid/api/commit/69b599c5ed8d44864201a32b4d952427d454dc74))
* **deps:** update dependency wretch to v2.8.0 ([#814](https://github.com/unraid/api/issues/814)) ([66900b4](https://github.com/unraid/api/commit/66900b495b82b923264897d38b1529a22b10aa1c))
* update os check modal button conditionals ([282a836](https://github.com/unraid/api/commit/282a83625f417ccefe090b65cc6b73a084727a87))
* update os check modal ineligible date format ([83083de](https://github.com/unraid/api/commit/83083de1e698f73a35635ae6047dcf49fd4b8114))
### [3.5.1](https://github.com/unraid/api/compare/v3.5.0...v3.5.1) (2024-02-29)

View File

@@ -1,7 +1,7 @@
###########################################################
# Development/Build Image
###########################################################
FROM node:18.17.1-bookworm-slim As development
FROM node:18.19.1-bookworm-slim As development
# Install build tools and dependencies
RUN apt-get update -y && apt-get install -y \

4
api/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@unraid/api",
"version": "3.5.1",
"version": "3.5.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@unraid/api",
"version": "3.5.1",
"version": "3.5.3",
"license": "UNLICENSED",
"dependencies": {
"@apollo/client": "^3.8.9",

View File

@@ -1,6 +1,6 @@
{
"name": "@unraid/api",
"version": "3.5.1",
"version": "3.5.3",
"main": "dist/index.js",
"bin": "dist/unraid-api.cjs",
"type": "module",

View File

@@ -140,7 +140,7 @@ function registerServer(button) {
button.form.submit();
});
<?else:?>
// give the unraid-api time to call rc.nginx and UpdateDNS before refreshing the page
// give the unraid-api time to call rc.nginx before refreshing the page
const delay = 4000;
setTimeout(function() {
button.form.submit();

View File

@@ -47,6 +47,7 @@ class ServerState
private $connectPluginVersion;
private $configErrorEnum = [
"error" => 'UNKNOWN_ERROR',
"ineligible" => 'INELIGIBLE',
"invalid" => 'INVALID',
"nokeyserver" => 'NO_KEY_SERVER',
"withdrawn" => 'WITHDRAWN',
@@ -241,7 +242,7 @@ class ServerState
"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,
@@ -270,7 +271,7 @@ class ServerState
"osVersionBranch" => $this->osVersionBranch,
"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'], ENT_HTML5, 'UTF-8') ?? '',

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,
@@ -11,429 +11,12 @@
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
// add translations
$_SERVER['REQUEST_URI'] = 'settings';
require_once "$docroot/webGui/include/Translations.php";
require_once "$docroot/webGui/include/Helpers.php";
function host_lookup_ip($host) {
$result = @dns_get_record($host, DNS_A);
$ip = ($result) ? $result[0]['ip']??'' : '';
return($ip);
}
function rebindDisabled() {
global $isLegacyCert;
$rebindtesturl = $isLegacyCert ? "rebindtest.unraid.net" : "rebindtest.myunraid.net";
// DNS Rebind Protection - this checks the server but clients could still have issues
$validResponse = array("192.168.42.42", "fd42");
$response = host_lookup_ip($rebindtesturl);
return in_array(explode('::',$response)[0], $validResponse);
}
function format_port($port) {
return ($port != 80 && $port != 443) ? ':'.$port : '';
}
function anonymize_host($host) {
global $anon;
if ($anon) {
$host = preg_replace('/.*\.myunraid\.net/', '*.hash.myunraid.net', $host);
$host = preg_replace('/.*\.unraid\.net/', 'hash.unraid.net', $host);
}
return $host;
}
function anonymize_ip($ip) {
global $anon;
if ($anon && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
$ip = "[redacted]";
}
return $ip;
}
function generate_internal_host($host, $ip) {
if (strpos($host,'.myunraid.net') !== false) {
$host = str_replace('*', str_replace('.', '-', $ip), $host);
}
return $host;
}
function generate_external_host($host, $ip) {
if (strpos($host,'.myunraid.net') !== false) {
$host = str_replace('*', str_replace('.', '-', $ip), $host);
} elseif (strpos($host,'.unraid.net') !== false) {
$host = "www.".$host;
}
return $host;
}
function verbose_output($httpcode, $result) {
global $cli, $verbose, $anon, $plgversion, $post, $var, $isRegistered, $myservers, $reloadNginx, $nginx, $isLegacyCert;
global $remoteaccess;
global $icon_warn, $icon_ok;
if (!$cli || !$verbose) return;
if ($anon) echo "(Output is anonymized, use '-vv' to see full details)".PHP_EOL;
echo "Unraid OS {$var['version']}".((strpos($plgversion, "base-") === false) ? " with My Servers plugin version {$plgversion}" : '').PHP_EOL;
echo ($isRegistered) ? "{$icon_ok}Signed in to Unraid.net as {$myservers['remote']['username']}".PHP_EOL : "{$icon_warn}Not signed in to Unraid.net".PHP_EOL ;
echo "Use SSL is {$nginx['NGINX_USESSL']}".PHP_EOL;
echo (rebindDisabled()) ? "{$icon_ok}Rebind protection is disabled" : "{$icon_warn}Rebind protection is enabled";
echo " for ".($isLegacyCert ? "unraid.net" : "myunraid.net").PHP_EOL;
if ($post) {
$wanip = trim(@file_get_contents("https://wanip4.unraid.net/"));
// check the data
$certhostname = $nginx['NGINX_CERTNAME'];
if ($certhostname) {
// $certhostname is $nginx['NGINX_CERTNAME'] (certificate_bundle.pem)
$certhostip = host_lookup_ip(generate_internal_host($certhostname, $post['internalip']));
$certhosterr = ($certhostip != $post['internalip']);
}
if ($post['internalhostname'] != $certhostname) {
// $post['internalhostname'] is $nginx['NGINX_LANMDNS'] (no cert, or Server_unraid_bundle.pem) || $nginx['NGINX_CERTNAME'] (certificate_bundle.pem)
$internalhostip = host_lookup_ip(generate_internal_host($post['internalhostname'], $post['internalip']));
$internalhosterr = ($internalhostip != $post['internalip']);
}
if (!empty($post['externalhostname'])) {
// $post['externalhostname'] is $nginx['NGINX_CERTNAME'] (certificate_bundle.pem)
$externalhostip = host_lookup_ip(generate_external_host($post['externalhostname'], $wanip));
$externalhosterr = ($externalhostip != $wanip);
}
// anonymize data. no caclulations can be done with this data beyond this point.
if ($anon) {
if (!empty($certhostip)) $certhostip = anonymize_ip($certhostip);
if (!empty($certhostname)) $certhostname = anonymize_host($certhostname);
if (!empty($internalhostip)) $internalhostip = anonymize_ip($internalhostip);
if (!empty($externalhostip)) $externalhostip = anonymize_ip($externalhostip);
if (!empty($wanip)) $wanip = anonymize_ip($wanip);
if (!empty($post['internalip'])) $post['internalip'] = anonymize_ip($post['internalip']);
if (!empty($post['internalhostname'])) $post['internalhostname'] = anonymize_host($post['internalhostname']);
if (!empty($post['externalhostname'])) $post['externalhostname'] = anonymize_host($post['externalhostname']);
if (!empty($post['externalport'])) $post['externalport'] = "[redacted]";
}
// always anonymize the keyfile
if (!empty($post['keyfile'])) $post['keyfile'] = "[redacted]";
// output notes
if (!empty($post['internalprotocol']) && !empty($post['internalhostname']) && !empty($post['internalport'])) {
$localurl = $post['internalprotocol']."://".generate_internal_host($post['internalhostname'], $post['internalip']).format_port($post['internalport']);
echo 'Local Access url: '.$localurl.PHP_EOL;
if ($internalhostip) {
// $internalhostip will not be defined for .local domains, ok to skip
echo ($internalhosterr) ? $icon_warn : $icon_ok;
echo generate_internal_host($post['internalhostname'], $post['internalip'])." resolves to {$internalhostip}";
echo ($internalhosterr) ? ", it should resolve to {$post['internalip']}" : "";
echo PHP_EOL;
}
if ($certhostname) {
echo ($certhosterr) ? $icon_warn : $icon_ok;
echo generate_internal_host($certhostname, $post['internalip']).' ';
echo ($certhostip) ? "resolves to {$certhostip}" : "does not resolve to an IP address";
echo ($certhosterr) ? ", it should resolve to {$post['internalip']}" : "";
echo PHP_EOL;
}
if ($remoteaccess == 'yes' && !empty($post['externalprotocol']) && !empty($post['externalhostname']) && !empty($post['externalport'])) {
$remoteurl = $post['externalprotocol']."://".generate_external_host($post['externalhostname'], $wanip).format_port($post['externalport']);
echo 'Remote Access url: '.$remoteurl.PHP_EOL;
echo ($externalhosterr) ? $icon_warn : $icon_ok;
echo generate_external_host($post['externalhostname'], $wanip).' ';
echo ($externalhosterr) ? "does not resolve to an IP address" : "resolves to {$externalhostip}";
echo PHP_EOL;
}
if ($reloadNginx) {
echo "IP address changes were detected, nginx was reloaded".PHP_EOL;
}
}
// output post data
echo PHP_EOL.'Request:'.PHP_EOL;
echo @json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
}
if ($result) {
echo "Response (HTTP $httpcode):".PHP_EOL;
$mutatedResult = is_array($result) ? json_encode($result) : $result;
echo @json_encode(@json_decode($mutatedResult, true), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
}
}
/**
* @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
*/
function response_complete($httpcode, $result, $cli_success_msg='') {
global $cli, $verbose;
$mutatedResult = is_array($result) ? json_encode($result) : $result;
if ($cli) {
if ($verbose) verbose_output($httpcode, $result);
$json = @json_decode($mutatedResult,true);
if (!empty($json['error'])) {
echo 'Error: '.$json['error'].PHP_EOL;
exit(1);
}
exit($cli_success_msg.PHP_EOL);
}
header('Content-Type: application/json');
http_response_code($httpcode);
exit((string)$mutatedResult);
}
// This is a stub, does nothing but return success
$cli = php_sapi_name()=='cli';
$verbose = $anon = false;
if ($cli && ($argc > 1) && $argv[1] == "-v") {
$verbose = true;
$anon = true;
}
if ($cli && ($argc > 1) && $argv[1] == "-vv") {
$verbose = true;
}
$var = parse_ini_file('/var/local/emhttp/var.ini');
$nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
$is69 = version_compare($var['version'],"6.9.9","<");
$reloadNginx = false;
$dnserr = false;
$icon_warn = "⚠️ ";
$icon_ok = "✅ ";
$myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg';
$myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : [];
// ensure some vars are defined here so we don't have to test them later
if (empty($myservers['remote']['apikey'])) {
$myservers['remote']['apikey'] = "";
}
if (empty($myservers['remote']['wanaccess'])) {
$myservers['remote']['wanaccess'] = "no";
}
if (empty($myservers['remote']['wanport'])) {
$myservers['remote']['wanport'] = 443;
}
// remoteaccess, externalport
if ($cli) {
$remoteaccess = (empty($nginx['NGINX_WANFQDN'])) ? 'no' : 'yes';
$externalport = $myservers['remote']['wanport'];
} else {
$remoteaccess = $_POST['remoteaccess']??'no';
$externalport = intval($_POST['externalport']??443);
if ($remoteaccess != 'yes') {
$remoteaccess = 'no';
}
if ($externalport < 1 || $externalport > 65535) {
$externalport = 443;
}
if ($myservers['remote']['wanaccess'] != $remoteaccess) {
// update the wanaccess ini value
$orig = file_exists($myservers_flash_cfg_path) ? parse_ini_file($myservers_flash_cfg_path,true) : [];
if (!$orig) {
$orig = ['remote' => $myservers['remote']];
}
$orig['remote']['wanaccess'] = $remoteaccess;
$text = '';
foreach ($orig as $section => $block) {
$pairs = "";
foreach ($block as $key => $value) if (strlen($value)) $pairs .= "$key=\"$value\"\n";
if ($pairs) $text .= "[$section]\n".$pairs;
}
if ($text) file_put_contents($myservers_flash_cfg_path, $text);
// need nginx reload
$reloadNginx = true;
}
exit("success".PHP_EOL);
}
$isRegistered = !empty($myservers['remote']['username']);
// protocols, hostnames, ports
$internalprotocol = 'http';
$internalport = $nginx['NGINX_PORT'];
$internalhostname = $nginx['NGINX_LANMDNS'];
$externalprotocol = 'https';
// keyserver will expand *.hash.myunraid.net or add www to hash.unraid.net as needed
$externalhostname = $nginx['NGINX_CERTNAME'];
$isLegacyCert = preg_match('/.*\.unraid\.net$/', $nginx['NGINX_CERTNAME']);
$isWildcardCert = preg_match('/.*\.myunraid\.net$/', $nginx['NGINX_CERTNAME']);
$internalip = $nginx['NGINX_LANIP'];
if ($nginx['NGINX_USESSL']=='yes') {
// When NGINX_USESSL is 'yes' in 6.9, it could be using either Server_unraid_bundle.pem or certificate_bundle.pem
// When NGINX_USESSL is 'yes' in 6.10, it is is using Server_unraid_bundle.pem
$internalprotocol = 'https';
$internalport = $nginx['NGINX_PORTSSL'];
if ($is69 && $nginx['NGINX_CERTNAME']) {
// this is from certificate_bundle.pem
$internalhostname = $nginx['NGINX_CERTNAME'];
}
}
if ($nginx['NGINX_USESSL']=='auto') {
// NGINX_USESSL cannot be 'auto' in 6.9, it is either 'yes' or 'no'
// When NGINX_USESSL is 'auto' in 6.10, it is using certificate_bundle.pem
$internalprotocol = 'https';
$internalport = $nginx['NGINX_PORTSSL'];
// keyserver will expand *.hash.myunraid.net as needed
$internalhostname = $nginx['NGINX_CERTNAME'];
}
// My Servers version
$plgversion = 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-'.$var['version'] );
// only proceed when when signed in or when legacy unraid.net SSL certificate exists
if (!$isRegistered && !$isLegacyCert) {
response_complete(406, array('error' => _('Nothing to do')));
}
// keyfile
$keyfile = empty($var['regFILE']) ? false : @file_get_contents($var['regFILE']);
if ($keyfile === false) {
response_complete(406, array('error' => _('Registration key required')));
}
$keyfile = @base64_encode($keyfile);
// build post array
$post = [
'keyfile' => $keyfile,
'plgversion' => $plgversion
];
if ($isLegacyCert) {
// sign in not required to maintain local ddns for unraid.net cert
// enable local ddns regardless of use_ssl value
$post['internalip'] = $internalip;
// if host.unraid.net does not resolve to the internalip and DNS Rebind Protection is disabled, disable caching
if (host_lookup_ip(generate_internal_host($nginx['NGINX_CERTNAME'], $post['internalip'])) != $post['internalip'] && rebindDisabled()) $dnserr = true;
}
if ($isRegistered) {
// if signed in, send data needed to maintain My Servers Dashboard
$post['internalhostname'] = $internalhostname;
$post['internalport'] = $internalport;
$post['internalprotocol'] = $internalprotocol;
$post['remoteaccess'] = $remoteaccess;
$post['servercomment'] = $var['COMMENT'];
$post['servername'] = $var['NAME'];
if ($isWildcardCert) {
// keyserver needs the internalip to generate the local access url
$post['internalip'] = $internalip;
}
if ($remoteaccess == 'yes') {
// include wanip in the cache file so we can track if it changes
$post['_wanip'] = trim(@file_get_contents("https://wanip4.unraid.net/"));
$post['externalhostname'] = $externalhostname;
$post['externalport'] = $externalport;
$post['externalprotocol'] = $externalprotocol;
// if wanip.hash.myunraid.net or www.hash.unraid.net does not resolve to the wanip, disable caching
if (host_lookup_ip(generate_external_host($post['externalhostname'], $post['_wanip'])) != $post['_wanip']) $dnserr = true;
}
}
// Include unraid-api report
$unraidreport = [];
if (file_exists('/usr/local/sbin/unraid-api')) {
$jsonString = trim(@exec("/usr/local/sbin/unraid-api report --json 2>/dev/null"));
$unraidreport = @json_decode($jsonString, true);
if ($unraidreport === false) {
$post['unraidreport'] = $jsonString;
} else {
// remove fields we don't need to submit
unset($unraidreport['servers']);
}
} elseif (strpos($plgversion, "base-") === false) {
// The plugin is installed but the api doesn't exist. This is a failed install. Generate basic troubleshooting data.
if (file_exists('/boot/config/plugins/dynamix.my.servers/env')) {
@extract(parse_ini_file('/boot/config/plugins/dynamix.my.servers/env',true));
}
if (empty($env)) {
$env = "production";
}
$unraidreport['os']['version'] = $var['version'];
$unraidreport['api']['version'] = "failed install";
$unraidreport['api']['status'] = "missing";
$unraidreport['api']['environment'] = $env;
$unraidreport['relay']['status'] = "disconnected";
$unraidreport['minigraph']['status'] = "disconnected";
if ($isRegistered) {
$unraidreport['myServers']['status'] = "authenticated";
$unraidreport['myServers']['myServersUsername'] = $myservers['remote']['username'];
} else {
$unraidreport['myServers']['status'] = "signed out";
}
$unraidreport['apiKey'] = (empty($myservers['remote']['apikey'])) ? "invalid" : "exists";
}
if (!empty($unraidreport)) {
// include unraid-api crash logs
$crashLog = '/var/log/unraid-api/crash.json';
$crashAge = 0;
if (file_exists($crashLog)) {
$crashTime = filemtime($crashLog);
$crashAge = time() - $crashTime; // age of crashLog in seconds
$crashDetails = @json_decode(@file_get_contents($crashLog), true);
if (empty($crashDetails['apiVersion']) && $crashAge < 30*60) {
// found a recent crash log without an apiVersion, assume was created by current version of api
$crashDetails['apiVersion'] = $unraidreport['api']['version'];
// overwrite the crash log so it will always have the apiVersion
file_put_contents($crashLog, json_encode($crashDetails, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
// reset to original timestamp so crashAge remains accurate
touch($crashLog, $crashTime);
}
$unraidreport['crashAge'] = $crashAge;
$unraidreport['crashLogs'] = $crashDetails;
}
// add flash backup status
$flashbackup_ini = '/var/local/emhttp/flashbackup.ini';
$flashbackup_status = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashbackup_ini) : [];
if (empty($flashbackup_status['activated'])) {
$flashbackup_status['activated'] = "";
}
if (empty($flashbackup_status['error'])) {
$flashbackup_status['error'] = "";
}
$unraidreport['flashbackup']['activated'] = ($flashbackup_status['activated']) ? "yes" : "no";
$unraidreport['flashbackup']['error'] = ($flashbackup_status['error']) ? $flashbackup_status['error'] : "no";
// add unraidreport to payload
$post['unraidreport'] = json_encode($unraidreport);
// if the api is stopped and there are no crashLogs, or any crashLogs are more than maxCrashAge, start the api
$maxCrashAge = 1*60*60; // 1 hour
if ($unraidreport['api']['status'] == 'stopped' && (empty($unraidreport['crashLogs']) || $crashAge > $maxCrashAge)) {
exec("echo \"/usr/local/sbin/unraid-api start\" | at -M now >/dev/null 2>&1");
}
}
// if remoteaccess is enabled in 6.10.0-rc3+ and WANIP has changed since nginx started, reload nginx
if (isset($post['_wanip']) && ($post['_wanip'] != $nginx['NGINX_WANIP']) && version_compare($var['version'],"6.10.0-rc2",">")) $reloadNginx = true;
// if remoteaccess is currently disabled (perhaps because a wanip was not available when nginx was started)
// BUT the system is configured to have it enabled AND a wanip is now available
// then reload nginx
if ($remoteaccess == 'no' && $nginx['NGINX_WANACCESS'] == 'yes' && !empty(trim(@file_get_contents("https://wanip4.unraid.net/")))) $reloadNginx = true;
if ($reloadNginx) {
exec("/etc/rc.d/rc.nginx reload &>/dev/null");
}
// maxage is 36 hours
$maxage = 36*60*60;
if ($dnserr || $verbose) $maxage = 0;
$datafile = "/tmp/UpdateDNS.txt";
$datafiletmp = "/tmp/UpdateDNS.txt.new";
$dataprev = @file_get_contents($datafile) ?: '';
$datanew = implode("\n",$post)."\n";
if ($datanew == $dataprev && (time()-filemtime($datafile) < $maxage)) {
response_complete(204, null, _('No change to report'));
}
file_put_contents($datafiletmp,$datanew);
rename($datafiletmp, $datafile);
// do not submit the wanip, it will be captured from the submission if needed for remote access
unset($post['_wanip']);
// report necessary server details to limetech for DNS updates
$ch = curl_init('https://keys.lime-technology.com/account/server/register');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ( ($result === false) || ($httpcode != "200") ) {
// delete cache file to retry submission on next run
@unlink($datafile);
response_complete($httpcode ?? "500", array('error' => $error));
}
response_complete($httpcode, $result, _('success'));
header('Content-Type: application/json');
http_response_code(204);
exit(0);
?>

View File

@@ -1 +1 @@
18.17.1
18.19.1

View File

@@ -24,8 +24,32 @@ import type {
// return result;
// }
// ENOKEYFILE
// TRIAL
// BASIC
// PLUS
// PRO
// STARTER
// UNLEASHED
// LIFETIME
// EEXPIRED
// EGUID
// EGUID1
// ETRIAL
// ENOKEYFILE2
// ENOKEYFILE1
// ENOFLASH
// EBLACKLISTED
// EBLACKLISTED1
// EBLACKLISTED2
// ENOCONN
// '1111-1111-5GDB-123412341234' Starter.key = TkJCrVyXMLWWGKZF6TCEvf0C86UYI9KfUDSOm7JoFP19tOMTMgLKcJ6QIOt9_9Psg_t0yF-ANmzSgZzCo94ljXoPm4BESFByR0K7nyY9KVvU8szLEUcBUT3xC2adxLrAXFNxiPeK-mZqt34n16uETKYvLKL_Sr5_JziG5L5lJFBqYZCPmfLMiguFo1vp0xL8pnBH7q8bYoBnePrAcAVb9mAGxFVPEInSPkMBfC67JLHz7XY1Y_K5bYIq3go9XPtLltJ53_U4BQiMHooXUBJCKXodpqoGxq0eV0IhNEYdauAhnTsG90qmGZig0hZalQ0soouc4JZEMiYEcZbn9mBxPg
const staticGuid = '1111-1111-5GDB-123412341234';
const state: ServerState = 'STARTER';
const currentFlashGuid = '1111-1111-SDDD-TEST1234ZACK '; // this is the flash drive that's been booted from
const regGuid = '1111-1111-SDDD-TEST1234ZACK '; // this guid is registered in key server
const keyfileBase64 = ''; // @todo raycast download key to base64
// const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server
// const newGuid = `1234-1234-${makeid(4)}-123412341234`; // this is a new USB, not registered
@@ -42,24 +66,7 @@ const oneHourFromNow = Date.now() + 60 * 60 * 1000; // 1 hour from now
let expireTime = 0;
let regExp: number | undefined;
// ENOKEYFILE
// TRIAL
// BASIC
// PLUS
// PRO
// EEXPIRED
// EGUID
// EGUID1
// ETRIAL
// ENOKEYFILE2
// ENOKEYFILE1
// ENOFLASH
// EBLACKLISTED
// EBLACKLISTED1
// EBLACKLISTED2
// ENOCONN
const state: ServerState = 'STARTER';
let regDev = 0;
let regDevs = 0;
let regTy = '';
switch (state) {
// @ts-ignore
@@ -73,15 +80,15 @@ switch (state) {
regTy = 'Trial';
// @ts-ignore
case 'BASIC':
regDev = 6;
regDevs = 6;
// @ts-ignore
case 'PLUS':
regDev = 12;
regDevs = 12;
// @ts-ignore
case 'PRO':
// @ts-ignore
case 'STARTER':
regDev = 4;
regDevs = 6;
// regExp = oneHourFromNow;
// regExp = oneDayFromNow;
regExp = ninetyDaysAgo;
@@ -96,15 +103,15 @@ switch (state) {
// regExp = 1696363920000; // nori.local's expiration
// @ts-ignore
case 'LIFETIME':
if (regDev === 0) { regDev = 99999; }
if (regDevs === 0) { regDevs = -1; }
if (regTy === '') { regTy = state.charAt(0).toUpperCase() + state.substring(1).toLowerCase(); } // title case
break;
}
const connectPluginInstalled = 'dynamix.unraid.net.staging.plg';
// const connectPluginInstalled = '';
// const connectPluginInstalled = 'dynamix.unraid.net.staging.plg';
const connectPluginInstalled = '';
const osVersion = '6.12.5';
const osVersion = '6.12.8';
const osVersionBranch = 'stable';
// const parsedRegExp = regExp ? dayjs(regExp).format('YYYY-MM-DD') : undefined;
@@ -133,21 +140,21 @@ export const serverState: Server = {
apiKey: 'unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810',
avatar: 'https://source.unsplash.com/300x300/?portrait',
config: {
// error: 'INVALID',
valid: true,
error: null,
valid: false,
},
connectPluginInstalled,
description: 'DevServer9000',
deviceCount: 3,
deviceCount: 4,
expireTime,
flashBackupActivated: !!connectPluginInstalled,
flashProduct: 'SanDisk_3.2Gen1',
flashVendor: 'USB',
guid: staticGuid,
guid: currentFlashGuid,
// "guid": "0781-5583-8355-81071A2B0211",
inIframe: false,
// keyfile: 'DUMMY_KEYFILE',
keyfile: 'TkJCrVyXMLWWGKZF6TCEvf0C86UYI9KfUDSOm7JoFP19tOMTMgLKcJ6QIOt9_9Psg_t0yF-ANmzSgZzCo94ljXoPm4BESFByR0K7nyY9KVvU8szLEUcBUT3xC2adxLrAXFNxiPeK-mZqt34n16uETKYvLKL_Sr5_JziG5L5lJFBqYZCPmfLMiguFo1vp0xL8pnBH7q8bYoBnePrAcAVb9mAGxFVPEInSPkMBfC67JLHz7XY1Y_K5bYIq3go9XPtLltJ53_U4BQiMHooXUBJCKXodpqoGxq0eV0IhNEYdauAhnTsG90qmGZig0hZalQ0soouc4JZEMiYEcZbn9mBxPg',
keyfile: keyfileBase64,
lanIp: '192.168.254.36',
license: '',
locale: 'en_US', // en_US, ja
@@ -161,7 +168,7 @@ export const serverState: Server = {
regTo: 'Zack Spear',
regTy,
regExp,
// "regGuid": "0781-5583-8355-81071A2B0211",
regGuid,
site: 'http://localhost:4321',
state,
theme: {
@@ -173,15 +180,15 @@ export const serverState: Server = {
name: 'white',
textColor: ''
},
updateOsResponse: {
version: '6.12.6',
name: 'Unraid 6.12.6',
date: '2023-12-13',
isNewer: true,
isEligible: false,
changelog: 'https://docs.unraid.net/unraid-os/release-notes/6.12.6/',
sha256: '2f5debaf80549029cf6dfab0db59180e7e3391c059e6521aace7971419c9c4bf',
},
// updateOsResponse: {
// version: '6.12.6',
// name: 'Unraid 6.12.6',
// date: '2023-12-13',
// isNewer: true,
// isEligible: false,
// changelog: 'https://docs.unraid.net/unraid-os/release-notes/6.12.6/',
// sha256: '2f5debaf80549029cf6dfab0db59180e7e3391c059e6521aace7971419c9c4bf',
// },
uptime,
username: 'zspearmint',
wanFQDN: ''

View File

@@ -47,6 +47,7 @@ const {
flashProduct,
keyActions,
keyfile,
computedRegDevs,
regGuid,
regTm,
regTo,
@@ -56,6 +57,7 @@ const {
state,
stateData,
stateDataError,
tooManyDevices,
} = storeToRefs(serverStore);
const formattedRegTm = ref<any>();
@@ -76,24 +78,6 @@ onBeforeMount(() => {
setFormattedRegTm();
});
const devicesAvailable = computed((): number => {
switch (regTy.value) {
case 'Starter':
return 4;
case 'Basic':
return 6;
case 'Plus':
return 12;
case 'Unleashed':
case 'Lifetime':
case 'Pro':
case 'Trial':
return 9999;
default:
return 0;
}
});
const showTrialExpiration = computed((): boolean => state.value === 'TRIAL' || state.value === 'EEXPIRED');
const showUpdateEligibility = computed((): boolean => !!(regExp.value));
const keyInstalled = computed((): boolean => !!(!stateDataError.value && state.value !== 'ENOKEYFILE'));
@@ -169,11 +153,11 @@ const items = computed((): RegistrationItemProps[] => {
: []),
...(keyInstalled.value
? [{
error: deviceCount.value > devicesAvailable.value,
error: tooManyDevices.value,
label: t('Attached Storage Devices'),
text: deviceCount.value > devicesAvailable.value
? t('{0} out of {1} allowed devices upgrade your key to support more devices', [deviceCount.value, devicesAvailable.value > 12 ? t('unlimited') : devicesAvailable.value])
: t('{0} out of {1} devices', [deviceCount.value, devicesAvailable.value > 12 ? t('unlimited') : devicesAvailable.value]),
text: tooManyDevices.value
? t('{0} out of {1} allowed devices upgrade your key to support more devices', [deviceCount.value, computedRegDevs.value])
: t('{0} out of {1} devices', [deviceCount.value, computedRegDevs.value === -1 ? t('unlimited') : computedRegDevs.value]),
}]
: []),
...(showTransferStatus.value

View File

@@ -49,9 +49,20 @@ const {
checkForUpdatesLoading,
} = storeToRefs(updateOsStore);
const {
outputDateTimeFormatted: formattedRegExp,
} = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
/**
* regExp may not have a value until we get a response from the refreshServerState action
* So we need to watch for this value to be able to format it based on the user's date time preferences.
*/
const formattedRegExp = ref<any>();
const setFormattedRegExp = () => { // ran in watch on regExp and onBeforeMount
if (!regExp.value) { return; }
const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
formattedRegExp.value = outputDateTimeFormatted.value;
};
watch(regExp, (_newV) => {
setFormattedRegExp();
});
const ignoreThisRelease = ref(false);
// if we had a release ignored and now we don't set ignoreThisRelease to false
@@ -136,7 +147,8 @@ const actionButtons = computed((): ButtonProps[] | null => {
const buttons: ButtonProps[] = [];
// update available but not stable branch - should link out to account update callback
if (availableRequiresAuth.value) {
// if availableWithRenewal.value is true, then we need to renew the license before we can update so don't show the verify button
if (availableRequiresAuth.value && !availableWithRenewal.value) {
buttons.push({
click: async () => await accountStore.updateOs(),
icon: IdentificationIcon,
@@ -147,7 +159,7 @@ const actionButtons = computed((): ButtonProps[] | null => {
}
// update available - open changelog to commence update
if (available.value) {
if (available.value && updateOsResponse.value?.changelog) {
buttons.push({
btnStyle: availableWithRenewal.value
? 'outline'
@@ -208,6 +220,7 @@ onBeforeMount(() => {
if (availableReleaseDate.value) {
setUserFormattedReleaseDate();
}
setFormattedRegExp();
});
const modalWidth = computed(() => {

View File

@@ -20,7 +20,10 @@ const showExpireTime = computed(() => (state.value === 'TRIAL' || state.value ==
<div class="flex flex-col gap-y-24px w-full min-w-300px md:min-w-[500px] max-w-xl p-16px">
<header>
<h2 class="text-24px text-center font-semibold" v-html="t(stateData.heading)" />
<div class="flex flex-col gap-y-8px" v-html="t(stateData.message)" />
<div
class="text-center prose text-16px leading-relaxed whitespace-normal opacity-75 gap-y-8px"
v-html="t(stateData.message)"
/>
<UpcUptimeExpire
v-if="showExpireTime"
class="text-center opacity-75 mt-12px"

View File

@@ -33,6 +33,22 @@ const timeFormatOptions: TimeFormatOption[] = [
];
/**
* the provided ref may not have a value until we get a response from the refreshServerState action
* So we need to watch for this value to be able to format it based on the user's date time preferences.
* @example below is how to use this composable
* const formattedRegExp = ref<any>();
* const setFormattedRegExp = () => { // ran in watch on regExp and onBeforeMount
* if (!regExp.value) { return; }
* const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
* formattedRegExp.value = outputDateTimeFormatted.value;
* };
* watch(regExp, (_newV) => {
* setFormattedRegExp();
* });
* onBeforeMount(() => {
* setFormattedRegExp();
* });
*
* @param format provided by Unraid server's state.php and set in the server store
* @param t translations
* @param hideMinutesSeconds true will hide minutes and seconds from the output

View File

@@ -242,6 +242,7 @@ export type Config = {
};
export enum ConfigErrorState {
Ineligible = 'INELIGIBLE',
Invalid = 'INVALID',
NoKeyServer = 'NO_KEY_SERVER',
UnknownError = 'UNKNOWN_ERROR',

View File

@@ -21,16 +21,6 @@ export const WebguiInstallKey = request.url('/webGui/include/InstallKey.php');
* @param {string} username
*/
export const WebguiUpdate = request.url('/update.php');
/**
* @name WebguiUpdateDns
* @dataForm formUrl
* @description Used after Sign In to ensure URLs will work correctly
* @note this request is delayed by 500ms to allow server to process key install fully
* @todo potentially remove delay
* @param csrf_token
* @type POST
*/
export const WebguiUpdateDns = request.url('/webGui/include/UpdateDNS.php');
/**
* @name WebguiState
* @description used to get current state of server via PHP rather than unraid-api

46
web/package-lock.json generated
View File

@@ -2984,12 +2984,12 @@
"dev": true
},
"node_modules/@intlify/core-base": {
"version": "9.6.5",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.6.5.tgz",
"integrity": "sha512-LzbGXiZkMWPIHnHI0g6q554S87Cmh2mmCmjytK/3pDQfjI84l+dgGoeQuKj02q7EbULRuUUgYVZVqAwEUawXGg==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.10.1.tgz",
"integrity": "sha512-0+Wtjj04GIyglh5KKiNjRwgjpHrhqqGZhaKY/QVjjogWKZq5WHROrTi84pNVsRN18QynyPmjtsVUWqFKPQ45xQ==",
"dependencies": {
"@intlify/message-compiler": "9.6.5",
"@intlify/shared": "9.6.5"
"@intlify/message-compiler": "9.10.1",
"@intlify/shared": "9.10.1"
},
"engines": {
"node": ">= 16"
@@ -2999,11 +2999,11 @@
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.6.5",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.6.5.tgz",
"integrity": "sha512-WeJ499thIj0p7JaIO1V3JaJbqdqfBykS5R8fElFs5hNeotHtPAMBs4IiA+8/KGFkAbjJusgFefCq6ajP7F7+4Q==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.10.1.tgz",
"integrity": "sha512-b68UTmRhgZfswJZI7VAgW6BXZK5JOpoi5swMLGr4j6ss2XbFY13kiw+Hu+xYAfulMPSapcHzdWHnq21VGnMCnA==",
"dependencies": {
"@intlify/shared": "9.6.5",
"@intlify/shared": "9.10.1",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -3014,9 +3014,9 @@
}
},
"node_modules/@intlify/shared": {
"version": "9.6.5",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.6.5.tgz",
"integrity": "sha512-gD7Ey47Xi4h/t6P+S04ymMSoA3wVRxGqjxuIMglwRO8POki9h164Epu2N8wk/GHXM/hR6ZGcsx2HArCCENjqSQ==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.10.1.tgz",
"integrity": "sha512-liyH3UMoglHBUn70iCYcy9CQlInx/lp50W2aeSxqqrvmG+LDj/Jj7tBJhBoQL4fECkldGhbmW0g2ommHfL6Wmw==",
"engines": {
"node": ">= 16"
},
@@ -18353,9 +18353,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/terser": {
"version": "5.24.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz",
"integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==",
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz",
"integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
@@ -19666,12 +19666,12 @@
}
},
"node_modules/vue-i18n": {
"version": "9.6.5",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.6.5.tgz",
"integrity": "sha512-dpUEjKHg7pEsaS7ZPPxp1CflaR7bGmsvZJEhnszHPKl9OTNyno5j/DvMtMSo41kpddq4felLA7GK2prjpnXVlw==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.10.1.tgz",
"integrity": "sha512-37HVJQZ/pZaRXGzFmmMomM1u1k7kndv3xCBPYHKEVfv5W3UVK67U/TpBug71ILYLNmjHLHdvTUPRF81pFT5fFg==",
"dependencies": {
"@intlify/core-base": "9.6.5",
"@intlify/shared": "9.6.5",
"@intlify/core-base": "9.10.1",
"@intlify/shared": "9.10.1",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
@@ -20099,9 +20099,9 @@
"dev": true
},
"node_modules/wretch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/wretch/-/wretch-2.7.0.tgz",
"integrity": "sha512-IOjqi9SlQ8FEWp1X3KJ74wLNqpDVBoJIJvC7ZDHxPhzriNJd84+7RAhFBTl2sHiqnzBhzfqs1sznaB0Ik/3Ngw==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/wretch/-/wretch-2.8.0.tgz",
"integrity": "sha512-MNYh+lukFfFgcmVpvxv0KsQt2LtAPvKFfLNcLGpCMiHTD3ndSoQ3/k5fGOCPI1J7CgN1mYAbp9mhPFFUGTzUeg==",
"engines": {
"node": ">=14"
}

View File

@@ -1,8 +1,6 @@
import { defineStore, createPinia, setActivePinia } from 'pinia';
import { delay } from 'wretch/middlewares';
import { WebguiInstallKey, WebguiUpdateDns } from '~/composables/services/webgui';
import { WebguiInstallKey } from '~/composables/services/webgui';
import { useErrorsStore } from '~/store/errors';
import { useServerStore } from '~/store/server';
import type { ExternalKeyActions } from '~/store/callback';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
@@ -12,7 +10,6 @@ setActivePinia(createPinia());
export const useInstallKeyStore = defineStore('installKey', () => {
const errorsStore = useErrorsStore();
const serverStore = useServerStore();
const keyInstallStatus = ref<'failed' | 'installing' | 'ready' | 'success'>('ready');
@@ -45,18 +42,6 @@ export const useInstallKeyStore = defineStore('installKey', () => {
console.log('[install] WebguiInstallKey installResponse', installResponse);
keyInstallStatus.value = 'success';
try {
const updateDnsResponse = await WebguiUpdateDns
.middlewares([
delay(1500)
])
.formUrl({ csrf_token: serverStore.csrf })
.post();
console.log('[install] WebguiUpdateDns updateDnsResponse', updateDnsResponse);
} catch (error) {
console.error('[install] WebguiUpdateDns error', error);
}
} catch (error) {
console.error('[install] WebguiInstallKey error', error);
let errorMessage = 'Unknown error';

View File

@@ -5,6 +5,7 @@ import dayjs from 'dayjs';
import { defineStore, createPinia, setActivePinia } from 'pinia';
import prerelease from 'semver/functions/prerelease';
import {
ArrowPathIcon,
ArrowRightOnRectangleIcon,
CogIcon,
GlobeAltIcon,
@@ -17,7 +18,11 @@ import { useQuery } from '@vue/apollo-composable';
import { SERVER_CLOUD_FRAGMENT, SERVER_STATE_QUERY } from './server.fragment';
import { useFragment } from '~/composables/gql/fragment-masking';
import { WebguiState, WebguiUpdateIgnore } from '~/composables/services/webgui';
import { WEBGUI_SETTINGS_MANAGMENT_ACCESS } from '~/helpers/urls';
import {
WEBGUI_SETTINGS_MANAGMENT_ACCESS,
WEBGUI_TOOLS_REGISTRATION,
WEBGUI_TOOLS_UPDATE,
} from '~/helpers/urls';
import { useAccountStore } from '~/store/account';
import { useErrorsStore, type Error } from '~/store/errors';
import { usePurchaseStore } from '~/store/purchase';
@@ -95,7 +100,27 @@ export const useServerStore = defineStore('server', () => {
const rebootType = ref<'thirdPartyDriversDownloading' | 'downgrade' | 'update' | ''>('');
const rebootVersion = ref<string | undefined>();
const registered = ref<boolean>();
const regDev = ref<number>(0);
const regDevs = ref<number>(0); // use computedRegDevs to ensure it includes Basic, Plus, and Pro
const computedRegDevs = computed(() => {
if (regDevs.value > 0) {
return regDevs.value;
}
switch (regTy.value) {
case 'Starter':
case 'Basic':
return 6;
case 'Plus':
return 12;
case 'Unleashed':
case 'Lifetime':
case 'Pro':
case 'Trial':
return -1; // unlimited
default:
return 0;
}
});
const regGen = ref<number>(0);
const regGuid = ref<string>('');
const regTm = ref<number>(0);
@@ -164,7 +189,7 @@ export const useServerStore = defineStore('server', () => {
osVersion: osVersion.value,
osVersionBranch: osVersionBranch.value,
registered: registered.value,
regDev: regDev.value,
regDevs: computedRegDevs.value,
regGen: regGen.value,
regGuid: regGuid.value,
regExp: regExp.value,
@@ -415,12 +440,12 @@ export const useServerStore = defineStore('server', () => {
return {
actions: [
...(!registered.value && connectPluginInstalled.value ? [signInAction.value] : []),
...([purchaseAction.value, redeemAction.value, trialStartAction.value, recoverAction.value]),
...([trialStartAction.value, purchaseAction.value, redeemAction.value, recoverAction.value]),
...(registered.value && connectPluginInstalled.value ? [signOutAction.value] : []),
],
humanReadable: 'No Keyfile',
heading: 'Let\'s Unleash your Hardware!',
message: '<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>',
heading: 'Let\'s Unleash Your Hardware',
message: '<p>Choose an option below, then use our <a href="https://unraid.net/getting-started" target="_blank" rel="noreffer noopener">Getting Started Guide</a> to configure your array in less than 15 minutes.</p>',
};
case 'TRIAL':
return {
@@ -487,6 +512,7 @@ export const useServerStore = defineStore('server', () => {
actions: [
...(!registered.value && connectPluginInstalled.value ? [signInAction.value] : []),
...(regUpdatesExpired.value ? [renewAction.value] : []),
...(state.value === 'UNLEASHED' ? [upgradeAction.value] : []),
...(registered.value && connectPluginInstalled.value ? [signOutAction.value] : []),
],
humanReadable: state.value === 'PRO'
@@ -662,24 +688,73 @@ export const useServerStore = defineStore('server', () => {
});
const trialExtensionEligible = computed(() => !regGen.value || regGen.value < 2);
const tooManyDevices = computed((): Error | undefined => {
if ((deviceCount.value !== 0 && regDev.value !== 0 && deviceCount.value > regDev.value) ||
(!config.value?.valid && config.value?.error === 'INVALID')) {
return {
heading: 'Too Many Devices',
level: 'error',
message: 'You have exceeded the number of devices allowed for your license. Please remove a device before adding another.',
ref: 'tooManyDevices',
type: 'server',
};
const serverConfigError = computed((): Error | undefined => {
if (!config.value?.valid && config.value?.error) {
switch (config.value?.error) {
// case 'UNKNOWN_ERROR':
// return {
// heading: 'Unknown Error',
// level: 'error',
// message: 'An unknown internal error occurred.',
// ref: 'configError',
// type: 'server',
// };
case 'INELIGIBLE':
return {
heading: 'Ineligible for OS Version',
level: 'error',
message: 'Your License Key does not support this OS Version. OS build date greater than key expiration.',
actions: [{
href: WEBGUI_TOOLS_REGISTRATION.toString(),
icon: CogIcon,
text: 'Learn More at Tools > Registration',
}],
ref: 'configError',
type: 'server',
};
case 'INVALID':
return {
heading: 'Too Many Devices',
level: 'error',
message: 'You have exceeded the number of devices allowed for your license. Please remove a device before starting the array or upgrade your key to support more devices.',
ref: 'configError',
type: 'server',
};
case 'NO_KEY_SERVER':
return {
heading: 'Check Network Connection',
level: 'error',
message: 'Unable to validate your trial key. Please check your network connection.',
ref: 'configError',
type: 'server',
};
case 'WITHDRAWN':
return {
heading: 'OS Version Withdrawn',
level: 'error',
message: 'This OS release should not be run. OS Update Required.',
actions: [{
href: WEBGUI_TOOLS_UPDATE.toString(),
icon: ArrowPathIcon,
text: 'Check for Update',
}],
ref: 'configError',
type: 'server',
};
}
return undefined;
}
return undefined;
});
watch(tooManyDevices, (newVal, oldVal) => {
watch(serverConfigError, (newVal, oldVal) => {
if (oldVal && oldVal.ref) { errorsStore.removeErrorByRef(oldVal.ref); }
if (newVal) { errorsStore.setError(newVal); }
});
const tooManyDevices = computed((): boolean => {
return ((deviceCount.value !== 0 && computedRegDevs.value > 0 && deviceCount.value > computedRegDevs.value) ||
(!config.value?.valid && config.value?.error === 'INVALID'));
});
const pluginInstallFailed = computed((): Error | undefined => {
if (connectPluginInstalled.value && connectPluginInstalled.value.includes('_installFailed')) {
return {
@@ -776,7 +851,7 @@ export const useServerStore = defineStore('server', () => {
const serverErrors = computed(() => {
return [
stateDataError.value,
tooManyDevices.value,
serverConfigError.value,
pluginInstallFailed.value,
deprecatedUnraidSSL.value,
cloudError.value,
@@ -1022,7 +1097,7 @@ export const useServerStore = defineStore('server', () => {
rebootType,
rebootVersion,
registered,
regDev,
computedRegDevs,
regGen,
regGuid,
regTm,
@@ -1056,6 +1131,7 @@ export const useServerStore = defineStore('server', () => {
stateDataError,
serverErrors,
tooManyDevices,
serverConfigError,
// actions
setServer,
setUpdateOsResponse,

View File

@@ -91,7 +91,7 @@ export interface Server {
rebootType?: ServerRebootType;
rebootVersion?: string;
registered?: boolean;
regDev?: number;
regDevs?: number;
regGen?: number;
regGuid?: string;
regTm?: number;