commit fe4b286ce1ee9259a74df0e8ea323207421f3a5a Author: yuancheng Date: Sun Jul 19 18:01:05 2026 +0800 main diff --git a/admin/admin.php b/admin/admin.php new file mode 100644 index 0000000..d191786 --- /dev/null +++ b/admin/admin.php @@ -0,0 +1,237 @@ +" link never ""` + * (or `unlink`) against the running ircDDBGateway. + * + * Hit directly via /admin/admin.php (the form action self-posts); + * not inline-included by any current parent page. The historical + * "Loaded inline by /admin/index.php" docstring claim was stale — + * there is no `include 'admin.php'` site in the codebase today. + * Reachable only by direct navigation; admin/configure.php + * configures the *password* for the same `remotecontrold` daemon + * but doesn't issue link/unlink commands. + * + * Service-status row pgreps for the six daemons (DStarRepeater, + * MMDVMHost, ircDDBgateway, timeserver, pistar-watchdog, + * pistar-keeper) and renders a green/red dot per service. + * + * Input validation: preg_match whitelist on Module / RefName / + * Letter / Link verb (uppercase A-Z and digits only). That guards + * against shell-quote escape but does not substitute for CSRF — + * a forged POST with valid-looking values could otherwise trigger + * link/unlink under the operator's basic-auth session. csrf_verify() + * below closes that gap before any sudo runs. + */ + +require_once($_SERVER['DOCUMENT_ROOT'] . '/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'] . '/config/csrf.php'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships and csrf_field() at line ~98 has a token to +// emit) and rejects forged POSTs cleanly with 403 before the +// `sudo remotecontrold "" link|unlink ...` state-change +// in the POST handler runs. +csrf_verify(); + +?> +Service Status + + + + + + + + + + + + + + + + + +
DStarRepeaterDStarRepeaterMMDVMHostDStarRepeaterircDDBgatewayircDDBgatewaytimeservertimeserverpistar-watchdogpistar-watchdogpistar-keeperpistar-keeper
"; } else { echo ""; } ?>"; } else { echo ""; } ?>"; } else { echo ""; } ?>"; } else { echo ""; } ?>"; } else { echo ""; } ?>"; } else { echo ""; } ?>
+
+ +Reflector Connector\n"; + echo "\n\n\n
Command OutputCommand Output
"; + // remotecontrold output is text from the daemon; escape on + // display defence-in-depth (the input fields above were + // already preg_match-whitelist-validated). + echo htmlspecialchars((string)exec($linkCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n"; + } + if (($_POST["Link"] ?? '') == "UNLINK") { + echo "Reflector Connector\n"; + echo "\n\n\n
Command OutputCommand Output
"; + echo htmlspecialchars((string)exec($unlinkCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n"; + } + } + +unset($_POST); +echo ''; + +else: ?> +Reflector Connector +
+ + + + + + + + + + + + + + +
Radio ModuleRadio ModuleReflectorReflectorLink / Un-LinkLink / Un-LinkActionAction
+ + + + + + + + + +
+
+ + +\n"; + echo "PiStar-Keeper Logbook\n"; + echo "\n"; + echo " \n"; + echo " \n"; + echo " \n"; + + exec ("tail -n 5 /var/pistar-keeper/pistar-keeper.log", $lines); + $counter = 0; + foreach ($lines as $line) { + echo "\n"; + $counter++; + } + + echo "
PiStar-Keeper Log Entries (UTC)PiStar-Keeper Log Entries (UTC)
".$lines[$counter]."
\n"; + } diff --git a/admin/calibration.php b/admin/calibration.php new file mode 100644 index 0000000..76ebec1 --- /dev/null +++ b/admin/calibration.php @@ -0,0 +1,384 @@ + /dev/null 2>&1'); + system('nc -ulp 33273 | sudo -i script -qfc "/usr/local/sbin/pistar-mmdvmcal" /tmp/pi-star_mmdvmcal.log > /dev/null 2>&1 &'); + } + else if (($_GET['action'] === 'saveoffset')) { + if (isset($_GET['param']) && strlen($_GET['param'])) { + system('sudo mount -o remount,rw /'); + system('sudo sed -i "/RXOffset=/c\\RXOffset='.intval($_GET['param']).'" /etc/mmdvmhost'); + system('sudo sed -i "/TXOffset=/c\\TXOffset='.intval($_GET['param']).'" /etc/mmdvmhost'); + } + } + exit(); + } + + if (isset($_GET['cmd']) && strlen($_GET['cmd'])) { + $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + socket_bind($sock, '127.0.0.1', 33272) || exit(); + socket_sendto($sock, $_GET['cmd'], strlen($_GET['cmd']), 0, '127.0.0.1', 33273); + if (isset($_GET['param']) && strlen($_GET['param'])) { + usleep(500*1000); + socket_sendto($sock, $_GET['param']."\n", strlen($_GET['param'])+1, 0, '127.0.0.1', 33273); + } + if ($_GET['cmd'] === 'q') { + sleep(1); + socket_sendto($sock, "\n", 1, 0, '127.0.0.1', 33273); //send something to kill the pipe, also \n may be useful if something went wrong and mmdvmcal is waiting some param input + } + socket_close($sock); + exit(); + } + + // Sanity Check Passed. + header('Cache-Control: no-cache'); + session_start(); + + if (!isset($_GET['ajax'])) { + //unset($_SESSION['mmdvmcal_offset']); + if (file_exists('/tmp/pi-star_mmdvmcal.log')) { + $_SESSION['mmdvmcal_offset'] = filesize('/tmp/pi-star_mmdvmcal.log'); + } else { + $_SESSION['mmdvmcal_offset'] = 0; + } + } + + if (isset($_GET['ajax'])) { + //session_start(); + if (!file_exists('/tmp/pi-star_mmdvmcal.log')) { + exit(); + } + + $handle = fopen('/tmp/pi-star_mmdvmcal.log', 'rb'); + if (isset($_SESSION['mmdvmcal_offset'])) { + fseek($handle, 0, SEEK_END); + if ($_SESSION['mmdvmcal_offset'] > ftell($handle)) //log rotated/truncated + $_SESSION['mmdvmcal_offset'] = 0; //continue at beginning of the new log + $data = stream_get_contents($handle, -1, $_SESSION['mmdvmcal_offset']); + $_SESSION['mmdvmcal_offset'] += strlen($data); + echo nl2br($data); + } + else { + fseek($handle, 0, SEEK_END); + $_SESSION['mmdvmcal_offset'] = ftell($handle); + } + exit(); + } + + $RXFrequency = intval(exec('grep "RXFrequency" /etc/mmdvmhost | awk -F "=" \'{print $2}\'')); + $RXOffset = intval(exec('grep "RXOffset" /etc/mmdvmhost | awk -F "=" \'{print $2}\'')); + +?> + + + + + + + + + + + + + + + CDN - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - 校准";?> + + + + + + + +
+
+
CDN: / Dashboard:
+

CDN -

+

+ | + | + | + | + +

+
+
+

校准工具

+ +
+
+

运行

+
+
+
+ +
+

模式

+
+
+
+
+
+
+ +
+

频率

+
基准频率
Hz
+
频率
Hz
+
偏移量
+ + + +
+
步进
+
+
+ +
+

实时统计

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 当前总计
帧数:  
比特数:  
错误数:  
误码率:  
秒数: +  
+
+
+ +
+
+ +
+ +
+ +
+
+ + + + diff --git a/admin/callsign_lookup.php b/admin/callsign_lookup.php new file mode 100644 index 0000000..5338566 --- /dev/null +++ b/admin/callsign_lookup.php @@ -0,0 +1,27 @@ + false); + +$csvFile = '/usr/local/etc/NXDN.csv'; +if ($callsign !== '' && is_readable($csvFile)) { + if (($fh = fopen($csvFile, 'r')) !== false) { + fgetcsv($fh); // skip header row + while (($row = fgetcsv($fh)) !== false) { + if (isset($row[1]) && strtoupper($row[1]) === $callsign) { + $result['found'] = true; + $result['city'] = isset($row[3]) ? $row[3] : ''; + $result['state'] = isset($row[4]) ? $row[4] : ''; + $result['country'] = isset($row[5]) ? $row[5] : ''; + break; + } + } + fclose($fh); + } +} + +echo json_encode($result); diff --git a/admin/change_password_required.php b/admin/change_password_required.php new file mode 100644 index 0000000..403d474 --- /dev/null +++ b/admin/change_password_required.php @@ -0,0 +1,266 @@ + 256; + $isStillDefault = hash_equals('raspberry', $rawPassword); + // hash_equals on user-supplied vs user-supplied isn't a security + // boundary (no secret on either side), but it's the right idiom + // for password compares — and guards against any future caller + // mistaking the result for an authentication signal. + $confirmMatches = hash_equals($rawPassword, $rawPasswordConfirm); + + if ($rawPassword === '' || $hasIllegalChar || $tooLong) { + $passwordRejected = true; + $rejectReason = 'invalid'; + error_log('Pi-Star change_password_required.php: adminPassword rejected ' + . '(empty, contains NUL/CR/LF, or > 256 bytes)'); + } elseif ($isStillDefault) { + $passwordRejected = true; + $rejectReason = 'default'; + error_log('Pi-Star change_password_required.php: adminPassword rejected (still the default)'); + } elseif (!$confirmMatches) { + $passwordRejected = true; + $rejectReason = 'mismatch'; + error_log('Pi-Star change_password_required.php: adminPassword rejected ' + . '(confirmation does not match)'); + } else { + // Pi-Star runs with `/` mounted read-only by default. chpasswd + // and htpasswd both write to files on the rootfs (/etc/shadow + // and /var/www/.htpasswd respectively); without a remount-rw + // they fail with PAM "Authentication token manipulation error" + // and the symptom looks like every password is rejected. The + // matching configure.php POST handler does this at line ~348. + // Re-seal the rootfs at the bottom of this block on every + // exit path (success, PAM rejection, htpasswd failure). + system('sudo mount -o remount,rw /'); + + $descriptors = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ); + + // 1. Linux user via chpasswd. Hardcoded command; payload on stdin only. + $cpExit = 1; + $cpStderr = ''; + $proc = proc_open('sudo -n /usr/sbin/chpasswd', $descriptors, $pipes); + if (is_resource($proc)) { + fwrite($pipes[0], 'pi-star:' . $rawPassword . "\n"); + fclose($pipes[0]); + stream_get_contents($pipes[1]); fclose($pipes[1]); + $cpStderr = stream_get_contents($pipes[2]); fclose($pipes[2]); + $cpExit = proc_close($proc); + } else { + error_log('Pi-Star change_password_required.php: failed to spawn chpasswd'); + } + + if ($cpExit !== 0) { + // PAM rejection (libpwquality, history, length, etc.). + $passwordRejected = true; + $rejectReason = 'pam'; + error_log('Pi-Star change_password_required.php: chpasswd exit=' + . $cpExit . '; stderr=' . trim($cpStderr)); + } else { + // 2. Apache basic-auth file. /var/www/.htpasswd is owned by + // www-data, so no sudo needed. + $htExit = 1; + $htStderr = ''; + $proc = proc_open('htpasswd -i /var/www/.htpasswd pi-star', + $descriptors, $pipes); + if (is_resource($proc)) { + fwrite($pipes[0], $rawPassword); + fclose($pipes[0]); + stream_get_contents($pipes[1]); fclose($pipes[1]); + $htStderr = stream_get_contents($pipes[2]); fclose($pipes[2]); + $htExit = proc_close($proc); + if ($htExit !== 0) { + error_log('Pi-Star change_password_required.php: htpasswd exit=' + . $htExit . ' (Linux password updated); stderr=' + . trim($htStderr)); + } + } else { + error_log('Pi-Star change_password_required.php: failed to spawn htpasswd'); + } + // Tighten /var/www/.htpasswd from htpasswd's default 644 + // to 600. Owner stays www-data so nginx (running as + // www-data) keeps reading for basic-auth checks; other + // local users no longer see the bcrypt hash. + // + // Surface chmod failure into the error log. The realistic + // failure modes are (a) htpasswd spawn failed and the + // file is in some unexpected state, or (b) a future + // refactor moves this call outside the rootfs-rw window + // (it sits inside one today — see commit aa999d49). + // Either way, the file silently staying mode 644 would + // be worth knowing about. + if (@chmod('/var/www/.htpasswd', 0600) === false) { + error_log('Pi-Star change_password_required.php: chmod 0600 /var/www/.htpasswd failed (file may stay mode 644 until next save)'); + } + // chpasswd succeeded; treat as success even if htpasswd + // failed (operator can retry — same behaviour as + // configure.php). The browser will re-prompt for the new + // credentials on the next request. + $passwordChanged = true; + } + + // Re-seal the rootfs on every exit path out of the writable + // block. sync first so the shadow / htpasswd writes land on + // disk before the remount makes the FS read-only again. + system('sudo sync && sudo sync && sudo sync && sudo mount -o remount,ro /'); + } +} +?> + + + + + + + + + + + + +Pi-Star — Change Default Password + + + +
+
+

Pi-Star — Default Password Change Required

+
+
+ + + + +
Working...
Password changed. Your browser will re-prompt for the new credentials shortly.
+ + +

You are connecting from outside the local network and your dashboard is still using the + factory default password. Set a new password to continue.

+Passwords do not match. Please enter the same value in both fields.'; + break; + case 'default': + $msg = 'Password rejected. The new password must be different from the factory default.'; + break; + case 'pam': + $msg = 'Password rejected by the system. It must satisfy the system password-quality rules (length, complexity, history).'; + break; + case 'invalid': + default: + $msg = 'Password rejected. It must not contain NUL / CR / LF bytes and must be at most 256 bytes long.'; + break; + } +?> +

+ +
+ + + + + + + + + + + + + +
+ +
+ +
+ + +
+
+ +
+ +
+ + diff --git a/admin/config b/admin/config new file mode 100644 index 0000000..4088526 --- /dev/null +++ b/admin/config @@ -0,0 +1 @@ +../config/ \ No newline at end of file diff --git a/admin/config_backup.php b/admin/config_backup.php new file mode 100644 index 0000000..9c85dc4 --- /dev/null +++ b/admin/config_backup.php @@ -0,0 +1,617 @@ + + * basename => [path, mode, owner, group] + */ +function backup_files() +{ + // Each entry is [path, mode, owner, group]. The mode/owner/group + // tuple is what the restore loop passes to `sudo install -m -o + // -g`, and MUST match the corresponding /etc/sudoers.d/pistar- + // dashboard PISTAR_DASH_INSTALL line for that destination — or + // sudo will reject the install and the file will silently fail + // to restore. Pre-2026-05-01 the restore loop used a hardcoded + // `install -m 644 -o root -g root` for every entry, which + // worked under the old NOPASSWD: ALL sudoers but breaks under + // the path-scoped allowlist for: the .key files (require 600 + // www-data:www-data so the dashboard's per-page sudo-less + // parse_ini_file() reads still work — see fulledit_bmapikey.php + // for the full rationale), wpa_supplicant.conf (600 root:root), + // and the three system files (dhcpcd.conf, hosts, hostname) + // which have no install entry in the deployed sudoers at all + // and rely on a re-spun image to gain them. + return array( + // Network / WiFi + 'dhcpcd.conf' => array('/etc/dhcpcd.conf', 644, 'root', 'root'), + 'wpa_supplicant.conf' => array('/etc/wpa_supplicant/wpa_supplicant.conf', 600, 'root', 'root'), + // Gateway daemon configs (flat key=value INI-ish) + 'ircddbgateway' => array('/etc/ircddbgateway', 644, 'root', 'root'), + 'mmdvmhost' => array('/etc/mmdvmhost', 644, 'root', 'root'), + 'dstarrepeater' => array('/etc/dstarrepeater', 644, 'root', 'root'), + 'dapnetgateway' => array('/etc/dapnetgateway', 644, 'root', 'root'), + 'p25gateway' => array('/etc/p25gateway', 644, 'root', 'root'), + 'm17gateway' => array('/etc/m17gateway', 644, 'root', 'root'), + 'ysfgateway' => array('/etc/ysfgateway', 644, 'root', 'root'), + 'ysf2dmr' => array('/etc/ysf2dmr', 644, 'root', 'root'), + 'dgidgateway' => array('/etc/dgidgateway', 644, 'root', 'root'), + 'nxdngateway' => array('/etc/nxdngateway', 644, 'root', 'root'), + 'dmrgateway' => array('/etc/dmrgateway', 644, 'root', 'root'), + 'mobilegps' => array('/etc/mobilegps', 644, 'root', 'root'), + 'starnetserver' => array('/etc/starnetserver', 644, 'root', 'root'), + 'timeserver' => array('/etc/timeserver', 644, 'root', 'root'), + // Mode markers (operator has at most one of these) + 'dstar-radio.mmdvmhost' => array('/etc/dstar-radio.mmdvmhost', 644, 'root', 'root'), + 'dstar-radio.dstarrepeater' => array('/etc/dstar-radio.dstarrepeater', 644, 'root', 'root'), + // Pi-Star service / dashboard config + 'pistar-remote' => array('/etc/pistar-remote', 644, 'root', 'root'), + 'hosts' => array('/etc/hosts', 644, 'root', 'root'), + 'hostname' => array('/etc/hostname', 644, 'root', 'root'), + // .key files: 600 www-data so unprivileged dashboard reads + // (banner_warnings.inc, bm_links.php, bm_manager.php — all + // parse_ini_file() without sudo) still work post-restore. + 'bmapi.key' => array('/etc/bmapi.key', 600, 'www-data', 'www-data'), + 'dapnetapi.key' => array('/etc/dapnetapi.key', 600, 'www-data', 'www-data'), + 'pistar-css.ini' => array('/etc/pistar-css.ini', 644, 'root', 'root'), + 'RSSI.dat' => array('/usr/local/etc/RSSI.dat', 644, 'root', 'root'), + 'ircddblocal.php' => array('/var/www/dashboard/config/ircddblocal.php', 644, 'root', 'root'), + 'config.php' => array('/var/www/dashboard/config/config.php', 644, 'root', 'root'), + ); +} + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before the download / restore handlers run. +// +// CSRF protection here makes C1's zip-slip / restore-pipeline RCE +// harder to exploit (attacker can no longer trigger restore via a +// cross-site click); the underlying bugs in the restore handler +// remain — they are tracked separately as the C1/C2 work-on-hold. +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('config/language.php'); +// Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +// Load the Version Info +require_once('config/version.php'); +// Sanity Check that this file has been opened correctly +if ($_SERVER["PHP_SELF"] == "/admin/config_backup.php") { + // Sanity Check Passed. + header('Cache-Control: no-cache'); + // session_start() is no longer called here — csrf_verify() at + // the top of the file already started the session via + // csrf_session_start(). A second session_start() would emit a + // "session is already active" NOTICE on PHP 8.x. +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['backup_restore'];?> + + + + +
+
+
Pi-Star: /
+

Pi-Star

+

+ | + | + | + | + +

+
+
+'."\n"; + + if ( $_POST["action"] === "download" ) { + echo "".$lang['backup_restore']."\n"; + + $output = "Finding config files to be backed up\n"; + $backupDir = "/tmp/config_backup"; + $backupZip = "/tmp/config_backup.zip"; + $hostNameInfo = exec('cat /etc/hostname'); + + $output .= shell_exec("sudo rm -rf " . escapeshellarg($backupZip) . " 2>&1"); + $output .= shell_exec("sudo rm -rf " . escapeshellarg($backupDir) . " 2>&1"); + $output .= shell_exec("sudo mkdir " . escapeshellarg($backupDir) . " 2>&1"); + + // Iterate the canonical backup map so the backup and the + // restore allowlist cannot drift from each other. dhcpcd.conf + // is special-cased (only included when the operator has a + // static IP configured) — everything else is unconditional; + // missing source files are silently skipped (the cp returns + // an error to stderr that nobody reads, same as the legacy + // behaviour). + foreach (backup_files() as $basename => $meta) { + // $meta is [path, mode, owner, group]. The backup half + // only needs the path — mode/owner/group are consumed + // on the restore-half install command. + $srcpath = $meta[0]; + if ($basename === 'dhcpcd.conf') { + // Only back up dhcpcd.conf if the operator has set a + // static IP — otherwise restoring would clobber + // working DHCP config on the target device. + if (!shell_exec('cat /etc/dhcpcd.conf | grep "static ip_address" | grep -v "#"')) { + continue; + } + } + if (file_exists($srcpath)) { + $output .= shell_exec( + "sudo cp " . escapeshellarg($srcpath) . " " + . escapeshellarg($backupDir . '/' . $basename) . " 2>&1" + ); + } + } + + $output .= "Compressing backup files\n"; + $output .= shell_exec("sudo zip -j " . escapeshellarg($backupZip) . " " . escapeshellarg($backupDir) . "/* 2>&1"); + $output .= "Starting download\n"; + + echo "
"
+             . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + + if (file_exists($backupZip)) { + $utc_time = gmdate('Y-m-d H:i:s'); + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('Y-M-d'); + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + if ($hostNameInfo != "pi-star") { + header('Content-Disposition: attachment; filename="'.basename("Pi-Star_Config_".$hostNameInfo."_".$local_time.".zip").'"'); + } + else { + header('Content-Disposition: attachment; filename="'.basename("Pi-Star_Config_$local_time.zip").'"'); + } + header('Content-Transfer-Encoding: binary'); + header('Expires: 0'); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + header('Content-Length: ' . filesize($backupZip)); + ob_clean(); + flush(); + readfile($backupZip); + exit; + } + + }; + if ( $_POST["action"] === "restore" ) { + echo "Config Restore\n"; + $output = "Uploading your Config data\n"; + + // Wrap the whole restore in a do/while(false) so an early + // bail-out (upload validation failure, ZIP parse error, + // unsafe entry name, etc.) can break cleanly out of the + // sequence without an `if/else` pyramid. PHP 7.0+ idiom. + do { + + // Hardened restore pipeline. See the file-level docblock for + // the security model. Constants here: + // - $target_dir is hardcoded; the operator-supplied filename + // never touches a path concat. + // - $upload_path is also hardcoded; we move the uploaded + // temp file to a known name before any further processing. + // - $max_zip_bytes caps the upload; a real Pi-Star backup + // is ~25 KB, nginx already caps the body at 512 KB, so + // 256 KB is generous. + $target_dir = '/tmp/config_restore'; + $upload_path = '/tmp/config_restore_upload.zip'; + $max_zip_bytes = 256 * 1024; + $allowlist = backup_files(); + + shell_exec("sudo rm -rf " . escapeshellarg($target_dir) . " 2>&1"); + shell_exec("rm -f " . escapeshellarg($upload_path) . " 2>&1"); + shell_exec("mkdir -p " . escapeshellarg($target_dir) . " 2>&1"); + + // ----- Upload validation ------------------------------------- + $upload_ok = false; + $err_msg = ''; + if (!isset($_FILES['fileToUpload']) || + $_FILES['fileToUpload']['error'] !== UPLOAD_ERR_OK) { + $err_msg = 'No file uploaded, or upload failed.'; + } elseif ($_FILES['fileToUpload']['size'] <= 0 || + $_FILES['fileToUpload']['size'] > $max_zip_bytes) { + $err_msg = 'Upload size out of range (expected up to ' + . round($max_zip_bytes / 1024) . ' KB).'; + } else { + $tmp_name = $_FILES['fileToUpload']['tmp_name']; + // finfo_file() reads the magic bytes — much harder for an + // attacker to fake than the client-supplied $_FILES['type']. + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $magic = $finfo ? finfo_file($finfo, $tmp_name) : ''; + if ($finfo) finfo_close($finfo); + if ($magic !== 'application/zip') { + $err_msg = 'Uploaded file is not a ZIP archive (detected ' + . htmlspecialchars($magic, ENT_QUOTES, 'UTF-8') . ').'; + } elseif (!move_uploaded_file($tmp_name, $upload_path)) { + $err_msg = 'Could not stage upload to ' . $upload_path . '.'; + } else { + $upload_ok = true; + } + } + + if (!$upload_ok) { + $output .= $err_msg . "\n"; + echo "
"
+                 . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + // Don't emit here — the outer code does it once + // after the if-action blocks finish. The "Go Back" button + // is rendered AFTER the table closes (see end of file). + break; + } + + // ----- ZIP entry validation --------------------------------- + // Open the archive and decide which entries to extract BEFORE + // any extraction happens. An entry is admitted iff: + // - its name appears verbatim as a key in $allowlist + // (basename → target map), AND + // - its name contains no `/`, no `\`, and does not start + // with `.` (defence in depth — the backup writer uses + // `zip -j` so directory components are never legitimate). + // Entries outside the allowlist are silently skipped — that's + // the documented behaviour for forward-compat with future + // Pi-Star versions adding new files. + $zip = new ZipArchive(); + if ($zip->open($upload_path) !== true) { + $output .= "Could not open the uploaded ZIP.\n"; + @unlink($upload_path); + echo "
"
+                 . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + break; + } + + $admit = array(); + for ($i = 0; $i < $zip->numFiles; $i++) { + $name = $zip->getNameIndex($i); + if ($name === false || $name === '') { + continue; + } + if (strpos($name, '/') !== false || + strpos($name, '\\') !== false || + strpos($name, "\0") !== false || + $name[0] === '.') { + // Path-traversal / hidden / NUL-injection attempt. + // Reject the whole archive — this is well outside any + // Pi-Star-generated backup shape. break 2 exits the + // for() AND the surrounding do/while(false) early- + // exit block in one hop. + $zip->close(); + @unlink($upload_path); + $output .= "Refusing ZIP — entry '" + . htmlspecialchars(substr($name, 0, 64), ENT_QUOTES, 'UTF-8') + . "' has an unsafe name.\n"; + error_log("config_backup: rejected ZIP with unsafe entry '$name'"); + echo "
"
+                     . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + break 2; + } + if (isset($allowlist[$name])) { + $admit[$name] = $i; + } + // else: silently ignored — unknown filename. + } + + // Extract only the admitted entries into $target_dir. We use + // ZipArchive::extractTo() with an explicit entry list so + // ANYTHING outside that list is left in the archive and + // never written to disk. + $entries_to_extract = array_keys($admit); + if (empty($entries_to_extract)) { + $zip->close(); + @unlink($upload_path); + $output .= "ZIP contained no recognised Pi-Star config files.\n"; + echo "
"
+                 . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + break; + } + if (!$zip->extractTo($target_dir, $entries_to_extract)) { + $zip->close(); + @unlink($upload_path); + $output .= "Failed to extract ZIP entries.\n"; + echo "
"
+                 . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + break; + } + $zip->close(); + @unlink($upload_path); + $output .= "Your .zip file was uploaded and unpacked (" + . count($entries_to_extract) . " files).\n"; + + // ----- Service stop ---------------------------------------- + $output .= "Stopping Services.\n"; + $stop_services = array( + 'cron.service', 'dstarrepeater.service', 'mmdvmhost.service', + 'ircddbgateway.service', 'timeserver.service', + 'pistar-watchdog.service', 'pistar-remote.service', + 'ysfgateway.service', 'ysf2dmr.service', 'p25gateway.service', + 'nxdngateway.service', 'm17gateway.service', + 'dapnetgateway.service', 'mobilegps.service', + ); + foreach ($stop_services as $svc) { + shell_exec('sudo systemctl stop ' . escapeshellarg($svc) . ' 2>&1'); + } + + shell_exec('sudo mount -o remount,rw / 2>&1'); + + // ----- Per-file install ------------------------------------ + // Replace the previous blind `sudo mv -v -f /tmp/.../* /etc/` + // with a per-file `sudo install -m 644 -o root -g root` driven + // by the canonical map. install is atomic on same-fs (rename) + // and falls back to safe copy across filesystems. The mode and + // owner are forced regardless of how the file arrived in the + // archive. + $output .= "Writing new Config\n"; + + // Tear down stale dstar-radio.* markers first — only one of + // the two can be in use, and the restored archive may carry + // a different mode than the device currently has. Use + // `rm -rf` (not `rm -f`) so the call matches the + // PISTAR_DASH_MARKERS sudoers entry — different argv tokens + // yield different sudoers matches, and only `-rf` is + // allowlisted. + shell_exec('sudo rm -rf /etc/dstar-radio.* 2>&1'); + + $installed = 0; + foreach ($admit as $basename => $_idx) { + $src = $target_dir . '/' . $basename; + $meta = $allowlist[$basename]; + $target = $meta[0]; + $mode = (string)$meta[1]; + $owner = $meta[2]; + $group = $meta[3]; + if (!file_exists($src)) { + // Should be impossible after a successful extractTo, + // but guard anyway — extraction can fail per-entry on + // unusual archives without throwing. + continue; + } + // Per-file install args from backup_files() metadata — + // mode/owner/group must match the deployed sudoers + // PISTAR_DASH_INSTALL line for $target or sudo will + // reject. The hardcoded `install -m 644 -o root -g root` + // here used to silently fail on .key files (require + // 600 www-data:www-data) and wpa_supplicant.conf + // (600 root:root) under the path-scoped sudoers. + $cmd = 'sudo install -m ' . $mode + . ' -o ' . escapeshellarg($owner) + . ' -g ' . escapeshellarg($group) . ' ' + . escapeshellarg($src) . ' ' + . escapeshellarg($target); + $rc = 0; $cmd_out = array(); + exec($cmd . ' 2>&1', $cmd_out, $rc); + if ($rc === 0) { + $installed++; + } else { + $output .= " install failed for $basename: " + . implode(' / ', $cmd_out) . "\n"; + } + } + $output .= "Installed $installed file(s).\n"; + + // ----- Post-restore re-applies (data-side, not shell) ----- + + // Timezone: the restored config.php contains the operator's + // timezone in a date_default_timezone_set('…') call. We want + // the OS clock to match the dashboard's view. PRE-fix this + // path was a shell pipeline interpolating the grepped value; + // post-fix we extract via PHP-side parsing and validate + // strictly against PHP's own list of valid timezone IDs + // before passing through escapeshellarg(). + $cfg_path = '/var/www/dashboard/config/config.php'; + if (is_readable($cfg_path)) { + $cfg = file_get_contents($cfg_path); + if (preg_match("/date_default_timezone_set\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/", + $cfg, $m)) { + $tz = $m[1]; + if (in_array($tz, DateTimeZone::listIdentifiers(), true)) { + shell_exec('sudo timedatectl set-timezone ' + . escapeshellarg($tz) . ' 2>&1'); + } else { + error_log("config_backup: skipping unknown timezone '$tz' from restored config.php"); + } + } + } + + // ircDDBGateway Remote Control password: the restored + // /etc/ircddbgateway carries `remotePassword=...`; the + // /root/.Remote Control sibling file (mode 600 root:root, + // not in the backup set) needs the same value or the + // remotecontrold tool can't reach the daemon. + // + // Pre-fix this was a shell-interpolated `sudo sed -i ...`. + // Post-fix: read via PHP-side fopen/fgets (the file is mode + // 644 root:root after we just installed it, so www-data can + // read it back), validate, then route through the helper's + // privileged-flat editor — same primitive the C6.7 fix uses + // and also what the configure.php confPassword handler now + // uses. No shell ever sees the value. + $rp_target = '/etc/ircddbgateway'; + if (is_readable($rp_target)) { + $rp = ''; + foreach (file($rp_target, FILE_IGNORE_NEW_LINES) as $line) { + if (strpos($line, 'remotePassword=') === 0) { + $rp = substr($line, strlen('remotePassword=')); + break; + } + } + // ircDDBGateway accepts arbitrary printable bytes for the + // password; the helper's NUL/CR/LF guard is the floor. + if ($rp !== '' && !preg_match('/[\x00\r\n]/', $rp)) { + config_writer_stage_privileged_flat( + '/root/.Remote Control', 'password', $rp + ); + config_writer_commit(false); + } + } + + shell_exec('sudo mount -o remount,ro / 2>&1'); + + // ----- Service start --------------------------------------- + $output .= "Starting Services.\n"; + $start_services = array( + 'dstarrepeater.service', 'mmdvmhost.service', + 'ircddbgateway.service', 'timeserver.service', + 'pistar-watchdog.service', 'pistar-remote.service', + ); + foreach ($start_services as $svc) { + shell_exec('sudo systemctl start ' . escapeshellarg($svc) . ' 2>&1'); + } + if (substr(exec('grep "pistar-upnp.service" /etc/crontab | cut -c 1'), 0, 1) !== '#') { + shell_exec('sudo systemctl start pistar-upnp.service 2>&1'); + } + $start_services_after_upnp = array( + 'ysfgateway.service', 'ysf2dmr.service', 'p25gateway.service', + 'nxdngateway.service', 'm17gateway.service', + 'dapnetgateway.service', 'mobilegps.service', 'cron.service', + ); + foreach ($start_services_after_upnp as $svc) { + shell_exec('sudo systemctl start ' . escapeshellarg($svc) . ' 2>&1'); + } + + // Cleanup: remove the staged extraction dir so subsequent + // restores don't see leftovers. + shell_exec("sudo rm -rf " . escapeshellarg($target_dir) . " 2>&1"); + + $output .= "Configuration Restore Complete.\n"; + echo "
"
+             . htmlspecialchars($output, ENT_QUOTES, 'UTF-8') . "
\n"; + + } while (false); // end do/while(false) early-exit block + }; + + echo "\n"; + } else { ?> +
" method="post" enctype="multipart/form-data"> + + + + + + + + + + + + +
Download Configuration
+ +
Restore Configuration
+
+ +
+
+ WARNING:
+ Editing the files outside of Pi-Star *could* have un-desireable side effects.
+
+ This backup and restore tool, will backup your config files to a Zip file, and allow you to restore them later
+ either to this Pi-Star or another one.
+
    +
  • System Passwords / Dashboard passwords are NOT backed up / restored.
  • +
  • Wireless Configuration IS backed up and restored
  • +
+
+
+ +
+ +
+ + + 1000) { $cpuTempC = round($cpuTempCRaw / 1000, 1); } else { $cpuTempC = round($cpuTempCRaw, 1); } +$cpuTempF = round(+$cpuTempC * 9 / 5 + 32, 1); +if ($cpuTempC < 50) { $cpuTempHTML = "".$cpuTempC."°C / ".$cpuTempF."°F\n"; } +if ($cpuTempC >= 50) { $cpuTempHTML = "".$cpuTempC."°C / ".$cpuTempF."°F\n"; } +if ($cpuTempC >= 69) { $cpuTempHTML = "".$cpuTempC."°C / ".$cpuTempF."°F\n"; } + +// Pull in some config +require_once('config/version.php'); +require_once('config/ircddblocal.php'); +require_once('config/language.php'); +$cpuLoad = sys_getloadavg(); + +// Load the pistar-release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); + +// Load the dstarrepeater config file +$configdstar = array(); +if ($configdstarfile = fopen('/etc/dstarrepeater','r')) { + while ($line1 = fgets($configdstarfile)) { + if (strpos($line1, '=') !== false) { + list($key1,$value1) = preg_split('/=/',$line1); + $value1 = trim(str_replace('"','',$value1)); + if (strlen($value1) > 0) + $configdstar[$key1] = $value1; + } + } + fclose($configdstarfile); +} + +// Load the ircDDBGateway config file +$configs = array(); +if ($configfile = fopen($gatewayConfigPath,'r')) { + while ($line = fgets($configfile)) { + if (strpos($line, '=') !== false) { + list($key,$value) = preg_split('/=/',$line); + $value = trim(str_replace('"','',$value)); + if ($key != 'ircddbPassword' && strlen($value) > 0) + $configs[$key] = $value; + } + } + fclose($configfile); +} + +// Load the mmdvmhost config file +$mmdvmConfigFile = '/etc/mmdvmhost'; +$configmmdvm = parse_ini_file($mmdvmConfigFile, true); + +// Load the ysfgateway config file +$ysfgatewayConfigFile = '/etc/ysfgateway'; +$configysfgateway = parse_ini_file($ysfgatewayConfigFile, true); + +// Load the ysf2dmr config file +if (file_exists('/etc/ysf2dmr')) { + $ysf2dmrConfigFile = '/etc/ysf2dmr'; + if (fopen($ysf2dmrConfigFile,'r')) { $configysf2dmr = parse_ini_file($ysf2dmrConfigFile, true); } +} + +// Load the ysf2nxdn config file +if (file_exists('/etc/ysf2nxdn')) { + $ysf2nxdnConfigFile = '/etc/ysf2nxdn'; + if (fopen($ysf2nxdnConfigFile,'r')) { $configysf2nxdn = parse_ini_file($ysf2nxdnConfigFile, true); } +} + +// Load the ysf2p25 config file +if (file_exists('/etc/ysf2p25')) { + $ysf2p25ConfigFile = '/etc/ysf2p25'; + if (fopen($ysf2p25ConfigFile,'r')) { $configysf2p25 = parse_ini_file($ysf2p25ConfigFile, true); } +} + +// Load the dgidgateway config file +if (file_exists('/etc/dgidgateway')) { + $dgidgatewayConfigFile = '/etc/dgidgateway'; + if (fopen($dgidgatewayConfigFile,'r')) { $configdgidgateway = parse_ini_file($dgidgatewayConfigFile, true); } +} + +// Load the dmr2ysf config file +if (file_exists('/etc/dmr2ysf')) { + $dmr2ysfConfigFile = '/etc/dmr2ysf'; + if (fopen($dmr2ysfConfigFile,'r')) { $configdmr2ysf = parse_ini_file($dmr2ysfConfigFile, true); } +} + +// Load the dmr2nxdn config file +if (file_exists('/etc/dmr2nxdn')) { + $dmr2nxdnConfigFile = '/etc/dmr2nxdn'; + if (fopen($dmr2nxdnConfigFile,'r')) { $configdmr2nxdn = parse_ini_file($dmr2nxdnConfigFile, true); } +} + +// Load the p25gateway config file +if (file_exists('/etc/p25gateway')) { + $p25gatewayConfigFile = '/etc/p25gateway'; + if (fopen($p25gatewayConfigFile,'r')) { $configp25gateway = parse_ini_file($p25gatewayConfigFile, true); } +} + +// Load the nxdngateway config file +if (file_exists('/etc/nxdngateway')) { + $nxdngatewayConfigFile = '/etc/nxdngateway'; + if (fopen($nxdngatewayConfigFile,'r')) { $confignxdngateway = parse_ini_file($nxdngatewayConfigFile, true); } +} + +// Load the m17gateway config file +if (file_exists('/etc/m17gateway')) { + $m17gatewayConfigFile = '/etc/m17gateway'; + if (fopen($m17gatewayConfigFile,'r')) { $configm17gateway = parse_ini_file($m17gatewayConfigFile, true); } +} + +// Load the nxdn2dmr config file +if (file_exists('/etc/nxdn2dmr')) { + $nxdn2dmrConfigFile = '/etc/nxdn2dmr'; + if (fopen($nxdn2dmrConfigFile,'r')) { $confignxdn2dmr = parse_ini_file($nxdn2dmrConfigFile, true); } +} + +// DAPNet Gateway config +if (file_exists('/etc/dapnetgateway')) { + $configDAPNetConfigFile = '/etc/dapnetgateway'; + if (fopen($configDAPNetConfigFile,'r')) { $configdapnetgw = parse_ini_file($configDAPNetConfigFile, true); } +} + +// APRS Gateway config +if (file_exists('/etc/aprsgateway')) { + $configAPRSconfigFile = '/etc/aprsgateway'; + if (fopen($configAPRSconfigFile,'r')) { $configaprsgw = parse_ini_file($configAPRSconfigFile, true); } +} + +// Load the dmrgateway config file +$dmrGatewayConfigFile = '/etc/dmrgateway'; +if (fopen($dmrGatewayConfigFile,'r')) { $configdmrgateway = parse_ini_file($dmrGatewayConfigFile, true); } + +// Load the modem config information +if (file_exists('/etc/dstar-radio.dstarrepeater')) { + $modemConfigFileDStarRepeater = '/etc/dstar-radio.dstarrepeater'; + if (fopen($modemConfigFileDStarRepeater,'r')) { $configModem = parse_ini_file($modemConfigFileDStarRepeater, true); } +} + +if (file_exists('/etc/dstar-radio.mmdvmhost')) { + $modemConfigFileMMDVMHost = '/etc/dstar-radio.mmdvmhost'; + if (fopen($modemConfigFileMMDVMHost,'r')) { $configModem = parse_ini_file($modemConfigFileMMDVMHost, true); } +} + +function aprspass ($callsign) { + $stophere = strpos($callsign, '-'); + if ($stophere) $callsign = substr($callsign, 0, $stophere); + $realcall = strtoupper(substr($callsign, 0, 10)); + // initialize hash + $hash = 0x73e2; + $i = 0; + $len = strlen($realcall); + // hash callsign two bytes at a time + while ($i < $len) { + $hash ^= ord(substr($realcall, $i, 1))<<8; + $hash ^= ord(substr($realcall, $i + 1, 1)); + $i += 2; + } + // mask off the high bit so number is always positive + return $hash & 0x7fff; +} + +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev=$version; +$MYCALL=strtoupper($configs['gatewayCallsign']); +?> + + + + + + + + + \n"; ?> + + + + + + + + + <?php echo "$MYCALL"." - ".$lang['digital_voice']." ".$lang['dashboard']." - ".$lang['configuration'];?> + + + + + += 7 ) && (!isset($configmmdvm['DMR']['WhiteList'])) ) { +?> +
+ + + + +
Alert: You are running a hotspot in public mode without an access list for DMR, this setup *could* participate in network loops!
+
+ +
+ + + + +
Alert: You have a BM API v1 Key, click here for the announcement: BM API Keys - Announcement.
+
+ +
+
+
CDN: /
+

CDN

+

+ | + | + 专家模式 | + 校准 | + | + | + +

+
+
+ +

+ +
+\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
处理中…
正在应用配置更改…
\n"; + echo "
\n"; + echo ''; + echo "
\n
\n"; + echo "\n\n\n"; + die(); + } + + // AutoAP PSK Change + if (empty($_POST['autoapPsk']) != TRUE ) { + $rollAutoApPsk = 'sudo sed -i "/wpa_passphrase=/c\\wpa_passphrase='.$_POST['autoapPsk'].'" /etc/hostapd/hostapd.conf'; + system($rollAutoApPsk); + $rollAutoApWPA = 'sudo sed -i "/wpa=/c\\wpa=2" /etc/hostapd/hostapd.conf'; + system($rollAutoApWPA); + unset($_POST); + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
处理中…
正在应用配置更改…
\n"; + echo "
\n"; + echo ''; + echo "
\n\n"; + echo "\n\n\n"; + die(); + } + + // Stop Cron (occasionally remounts root as RO - would be bad if it did this at the wrong time....) + system('sudo systemctl stop cron.service > /dev/null 2>/dev/null &'); //Cron + + // Stop the DV Services + system('sudo systemctl stop dstarrepeater.service > /dev/null 2>/dev/null &'); // D-Star Radio Service + system('sudo systemctl stop mmdvmhost.service > /dev/null 2>/dev/null &'); // MMDVMHost Radio Service + system('sudo systemctl stop ircddbgateway.service > /dev/null 2>/dev/null &'); // ircDDBGateway Service + system('sudo systemctl stop timeserver.service > /dev/null 2>/dev/null &'); // Time Server Service + system('sudo systemctl stop pistar-watchdog.service > /dev/null 2>/dev/null &'); // PiStar-Watchdog Service + system('sudo systemctl stop pistar-remote.service > /dev/null 2>/dev/null &'); // PiStar-Remote Service + system('sudo systemctl stop ysfgateway.service > /dev/null 2>/dev/null &'); // YSFGateway + system('sudo systemctl stop ysf2dmr.service > /dev/null 2>/dev/null &'); // YSF2DMR + system('sudo systemctl stop ysf2nxdn.service > /dev/null 2>/dev/null &'); // YSF2NXDN + system('sudo systemctl stop ysf2p25.service > /dev/null 2>/dev/null &'); // YSF2P25 + system('sudo systemctl stop nxdn2dmr.service > /dev/null 2>/dev/null &'); // NXDN2DMR + system('sudo systemctl stop ysfparrot.service > /dev/null 2>/dev/null &'); // YSFParrot + system('sudo systemctl stop p25gateway.service > /dev/null 2>/dev/null &'); // P25Gateway + system('sudo systemctl stop p25parrot.service > /dev/null 2>/dev/null &'); // P25Parrot + system('sudo systemctl stop nxdngateway.service > /dev/null 2>/dev/null &'); // NXDNGateway + system('sudo systemctl stop nxdnparrot.service > /dev/null 2>/dev/null &'); // NXDNParrot + system('sudo systemctl stop m17gateway.service > /dev/null 2>/dev/null &'); // M17Gateway + system('sudo systemctl stop dmr2ysf.service > /dev/null 2>/dev/null &'); // DMR2YSF + system('sudo systemctl stop dmr2nxdn.service > /dev/null 2>/dev/null &'); // DMR2YSF + system('sudo systemctl stop dmrgateway.service > /dev/null 2>/dev/null &'); // DMRGateway + system('sudo systemctl stop dapnetgateway.service > /dev/null 2>/dev/null &'); // DAPNetGateway + system('sudo systemctl stop aprsgateway.service > /dev/null 2>/dev/null &'); // APRSGateway + + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
处理中…
正在停止服务并应用配置更改…
\n"; + echo "
\n"; + + // Let the services actualy stop + sleep(1); + + // Factory Reset Handler Here + if (empty($_POST['factoryReset']) != TRUE ) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
Factory Reset Config
Loading fresh configuration file(s)...
\n"; + echo "
\n"; + unset($_POST); + + // Over-write the config files with the clean copies + exec('sudo unzip -o /usr/local/bin/config_clean.zip -d /etc/'); + exec('sudo rm -rf /etc/dstar-radio.*'); + exec('sudo rm -rf /etc/pistar-css.ini'); + exec('sudo git --work-tree=/usr/local/sbin --git-dir=/usr/local/sbin/.git update-index --assume-unchanged pistar-upnp.service'); + exec('sudo git --work-tree=/usr/local/sbin --git-dir=/usr/local/sbin/.git reset --hard origin/master'); + exec('sudo git --work-tree=/usr/local/bin --git-dir=/usr/local/bin/.git reset --hard origin/master'); + exec('sudo git --work-tree=/var/www/dashboard --git-dir=/var/www/dashboard/.git reset --hard origin/master'); + echo ''; + // Make the root filesystem read-only + system('sudo sync && sudo sync && sudo sync && sudo mount -o remount,ro /'); + echo "
\n\n"; + echo "\n\n\n"; + die(); + } + + // Handle the case where the config is not read correctly + if (count($configmmdvm) <= 18) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to read source configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + echo "
\n\n"; + echo "\n\n\n"; + die(); + } + + // Change Radio Control Software + if (empty($_POST['controllerSoft']) != TRUE ) { + system('sudo rm -rf /etc/dstar-radio.*'); + if (escapeshellcmd($_POST['controllerSoft']) == 'DSTAR') { system('sudo touch /etc/dstar-radio.dstarrepeater'); } + if (escapeshellcmd($_POST['controllerSoft']) == 'MMDVM') { system('sudo touch /etc/dstar-radio.mmdvmhost'); } + } + + // HostAP + if (empty($_POST['autoAP']) != TRUE ) { + if (escapeshellcmd($_POST['autoAP']) == 'OFF') { system('sudo touch /etc/hostap.off'); } + if (escapeshellcmd($_POST['autoAP']) == 'ON') { system('sudo rm -rf /etc/hostap.off'); } + } + + // Change Dashboard Language + if (empty($_POST['dashboardLanguage']) != TRUE ) { + $rollDashLang = 'sudo sed -i "/pistarLanguage=/c\\$pistarLanguage=\''.escapeshellcmd($_POST['dashboardLanguage']).'\';" /var/www/dashboard/config/language.php'; + system($rollDashLang); + } + + // Set the ircDDBGAteway Remote Password and Port + if (empty($_POST['confPassword']) != TRUE ) { + $rollConfPassword0 = 'sudo sed -i "/remotePassword=/c\\remotePassword='.escapeshellcmd($_POST['confPassword']).'" /etc/ircddbgateway'; + $rollConfPassword1 = 'sudo sed -i "/password=/c\\password='.escapeshellcmd($_POST['confPassword']).'" /root/.Remote\ Control'; + $rollConfRemotePort = 'sudo sed -i "/port=/c\\port='.$configs['remotePort'].'" /root/.Remote\ Control'; + system($rollConfPassword0); + system($rollConfPassword1); + system($rollConfRemotePort); + } + + // Set the ircDDBGateway Defaut Reflector + if (empty($_POST['confDefRef']) != TRUE ) { + if (stristr(strtoupper(escapeshellcmd($_POST['confDefRef'])), strtoupper(escapeshellcmd($_POST['confCallsign']))) != TRUE ) { + if (strlen($_POST['confDefRef']) != 7) { + $targetRef = strtoupper(escapeshellcmd(str_pad($_POST['confDefRef'], 7, " "))); + } else { + $targetRef = strtoupper(escapeshellcmd($_POST['confDefRef'])); + } + $rollconfDefRef = 'sudo sed -i "/reflector1=/c\\reflector1='.$targetRef.escapeshellcmd($_POST['confDefRefLtr']).'" /etc/ircddbgateway'; + system($rollconfDefRef); + } + } + + // Set the ircDDBGAteway Defaut Reflector Autostart + if (empty($_POST['confDefRefAuto']) != TRUE ) { + if (escapeshellcmd($_POST['confDefRefAuto']) == 'ON') { + $rollconfDefRefAuto = 'sudo sed -i "/atStartup1=/c\\atStartup1=1" /etc/ircddbgateway'; + } + if (escapeshellcmd($_POST['confDefRefAuto']) == 'OFF') { + $rollconfDefRefAuto = 'sudo sed -i "/atStartup1=/c\\atStartup1=0" /etc/ircddbgateway'; + } + system($rollconfDefRefAuto); + } + + // Set random (working) CCS host. + if ($configs['ccsEnabled'] == "1") { + $activeCCS = array("CCS701"=>"CCS701","CCS702"=>"CCS702","CCS704"=>"CCS704"); + shuffle($activeCCS); + $rollCCS = 'sudo sed -i "/ccsHost=/c\\ccsHost='.$activeCCS[0].'" /etc/ircddbgateway'; + system($rollCCS); + } + + // Set the Latitude + if (empty($_POST['confLatitude']) != TRUE ) { + $newConfLatitude = preg_replace('/[^0-9\.\-]/', '', $_POST['confLatitude']); + $rollConfLat0 = 'sudo sed -i "/latitude=/c\\latitude='.$newConfLatitude.'" /etc/ircddbgateway'; + $rollConfLat1 = 'sudo sed -i "/latitude1=/c\\latitude1='.$newConfLatitude.'" /etc/ircddbgateway'; + $configmmdvm['Info']['Latitude'] = $newConfLatitude; + $configysfgateway['Info']['Latitude'] = $newConfLatitude; + $configysf2dmr['Info']['Latitude'] = $newConfLatitude; + $configysf2nxdn['Info']['Latitude'] = $newConfLatitude; + $configysf2p25['Info']['Latitude'] = $newConfLatitude; + if (isset($configdgidgateway)) { $configdgidgateway['Info']['Latitude'] = $newConfLatitude; } + $configdmrgateway['Info']['Latitude'] = $newConfLatitude; + $confignxdngateway['Info']['Latitude'] = $newConfLatitude; + if (isset($configm17gateway['Info']['Latitude'])) { $configm17gateway['Info']['Latitude'] = $newConfLatitude; } + system($rollConfLat0); + system($rollConfLat1); + } + + // Set the Longitude + if (empty($_POST['confLongitude']) != TRUE ) { + $newConfLongitude = preg_replace('/[^0-9\.\-]/', '', $_POST['confLongitude']); + $rollConfLon0 = 'sudo sed -i "/longitude=/c\\longitude='.$newConfLongitude.'" /etc/ircddbgateway'; + $rollConfLon1 = 'sudo sed -i "/longitude1=/c\\longitude1='.$newConfLongitude.'" /etc/ircddbgateway'; + $configmmdvm['Info']['Longitude'] = $newConfLongitude; + $configysfgateway['Info']['Longitude'] = $newConfLongitude; + $configysf2dmr['Info']['Longitude'] = $newConfLongitude; + $configysf2nxdn['Info']['Longitude'] = $newConfLongitude; + $configysf2p25['Info']['Longitude'] = $newConfLongitude; + if (isset($configdgidgateway)) { $configdgidgateway['Info']['Longitude'] = $newConfLongitude; } + $configdmrgateway['Info']['Longitude'] = $newConfLongitude; + $confignxdngateway['Info']['Longitude'] = $newConfLongitude; + if (isset($configm17gateway['Info']['Longitude'])) { $configm17gateway['Info']['Longitude'] = $newConfLongitude; } + system($rollConfLon0); + system($rollConfLon1); + } + + // Set the Town + if (empty($_POST['confDesc1']) != TRUE ) { + $newConfDesc1 = preg_replace('/[^A-Za-z0-9\.\s\,\-\x{4e00}-\x{9fff}]/u', '', $_POST['confDesc1']); + $rollDesc1 = 'sudo sed -i "/description1=/c\\description1='.$newConfDesc1.'" /etc/ircddbgateway'; + $rollDesc11 = 'sudo sed -i "/description1_1=/c\\description1_1='.$newConfDesc1.'" /etc/ircddbgateway'; + $configmmdvm['Info']['Location'] = '"'.$newConfDesc1.'"'; + $configdmrgateway['Info']['Location'] = '"'.$newConfDesc1.'"'; + $configysf2dmr['Info']['Location'] = '"'.$newConfDesc1.'"'; + $configysf2nxdn['Info']['Location'] = '"'.$newConfDesc1.'"'; + $configysf2p25['Info']['Location'] = '"'.$newConfDesc1.'"'; + $confignxdngateway['Info']['Name'] = '"'.$newConfDesc1.'"'; + if (isset($configm17gateway['Info']['Name'])) { $configm17gateway['Info']['Name'] = '"'.$newConfDesc1.'"'; } + system($rollDesc1); + system($rollDesc11); + } + + // Set the Country + if (empty($_POST['confDesc2']) != TRUE ) { + $newConfDesc2 = preg_replace('/[^A-Za-z0-9\.\s\,\-\x{4e00}-\x{9fff}]/u', '', $_POST['confDesc2']); + $rollDesc2 = 'sudo sed -i "/description2=/c\\description2='.$newConfDesc2.'" /etc/ircddbgateway'; + $rollDesc22 = 'sudo sed -i "/description1_2=/c\\description1_2='.$newConfDesc2.'" /etc/ircddbgateway'; + $configmmdvm['Info']['Description'] = '"'.$newConfDesc2.'"'; + $configdmrgateway['Info']['Description'] = '"'.$newConfDesc2.'"'; + $configysfgateway['Info']['Description'] = '"'.$newConfDesc2.'"'; + $confignxdngateway['Info']['Description'] = '"'.$newConfDesc2.'"'; + if (isset($configm17gateway['Info']['Description'])) { $configm17gateway['Info']['Description'] = '"'.$newConfDesc2.'"'; } + if (isset($configdgidgateway)) { $configdgidgateway['Info']['Description'] = '"'.$newConfDesc2.'"'; } + system($rollDesc2); + system($rollDesc22); + } + + // Set the URL + if (empty($_POST['confURL']) != TRUE ) { + $newConfURL = strtolower(preg_replace('/[^A-Za-z0-9\.\s\,\-\/\:]/', '', $_POST['confURL'])); + if (escapeshellcmd($_POST['urlAuto']) == 'auto') { $txtURL = "https://www.qrz.com/db/".strtoupper(escapeshellcmd($_POST['confCallsign'])); } + if (escapeshellcmd($_POST['urlAuto']) == 'man') { $txtURL = $newConfURL; } + if (escapeshellcmd($_POST['urlAuto']) == 'auto') { $rollURL0 = 'sudo sed -i "/url=/c\\url=https://www.qrz.com/db/'.strtoupper(escapeshellcmd($_POST['confCallsign'])).'" /etc/ircddbgateway'; } + if (escapeshellcmd($_POST['urlAuto']) == 'man') { $rollURL0 = 'sudo sed -i "/url=/c\\url='.$newConfURL.'" /etc/ircddbgateway'; } + $configmmdvm['Info']['URL'] = $txtURL; + $configysf2dmr['Info']['URL'] = $txtURL; + $configysf2nxdn['Info']['URL'] = $txtURL; + $configysf2p25['Info']['URL'] = $txtURL; + $configdmrgateway['Info']['URL'] = $txtURL; + system($rollURL0); + } + + // Set the APRS Host for ircDDBGateway + if (empty($_POST['selectedAPRSHost']) != TRUE ) { + $rollAPRSHost = 'sudo sed -i "/aprsHostname=/c\\aprsHostname='.escapeshellcmd($_POST['selectedAPRSHost']).'" /etc/ircddbgateway'; + system($rollAPRSHost); + $configysfgateway['aprs.fi']['Server'] = escapeshellcmd($_POST['selectedAPRSHost']); + $configysf2dmr['aprs.fi']['Server'] = escapeshellcmd($_POST['selectedAPRSHost']); + $configysf2nxdn['aprs.fi']['Server'] = escapeshellcmd($_POST['selectedAPRSHost']); + $configysf2p25['aprs.fi']['Server'] = escapeshellcmd($_POST['selectedAPRSHost']); + $configysf2dmr['aprs.fi']['Enable'] = "0"; + $configysf2nxdn['aprs.fi']['Enable'] = "0"; + $configysf2p25['aprs.fi']['Enable'] = "0"; + if ($configPistarRelease['Pi-Star']['Version'] >= "4.1.4") { + $rollAPRSGatewayHost = 'sudo sed -i "/Server=/c\\Server='.escapeshellcmd($_POST['selectedAPRSHost']).'" /etc/aprsgateway'; + system($rollAPRSGatewayHost); + } + } + + // Set ircDDBGateway and TimeServer language + if (empty($_POST['ircDDBGatewayAnnounceLanguage']) != TRUE) { + $ircDDBGatewayAnnounceLanguageArr = explode(',', escapeshellcmd($_POST['ircDDBGatewayAnnounceLanguage'])); + $rollIrcDDBGatewayLang = 'sudo sed -i "/language=/c\\language='.escapeshellcmd($ircDDBGatewayAnnounceLanguageArr[0]).'" /etc/ircddbgateway'; + $rollTimeserverLang = 'sudo sed -i "/language=/c\\language='.escapeshellcmd($ircDDBGatewayAnnounceLanguageArr[1]).'" /etc/timeserver'; + system($rollIrcDDBGatewayLang); + system($rollTimeserverLang); + } + + // Clear timeserver modules + $rollTimeserverBandA = 'sudo sed -i "/sendA=/c\\sendA=0" /etc/timeserver'; + $rollTimeserverBandB = 'sudo sed -i "/sendB=/c\\sendB=0" /etc/timeserver'; + $rollTimeserverBandC = 'sudo sed -i "/sendC=/c\\sendC=0" /etc/timeserver'; + $rollTimeserverBandD = 'sudo sed -i "/sendD=/c\\sendD=0" /etc/timeserver'; + $rollTimeserverBandE = 'sudo sed -i "/sendE=/c\\sendE=0" /etc/timeserver'; + system($rollTimeserverBandA); + system($rollTimeserverBandB); + system($rollTimeserverBandC); + system($rollTimeserverBandD); + system($rollTimeserverBandE); + + // Set the POCSAG Frequency + if (empty($_POST['pocsagFrequency']) != TRUE ) { + $newPocsagFREQ = preg_replace('/[^0-9\.]/', '', $_POST['pocsagFrequency']); + $newPocsagFREQ = number_format($newPocsagFREQ, 6, ".", ""); + $newPocsagFREQ = str_pad(str_replace(".", "", $newPocsagFREQ), 9, "0"); + $configmmdvm['POCSAG']['Frequency'] = $newPocsagFREQ; + } + + // Set the POCSAG AuthKey + if (empty($_POST['pocsagAuthKey']) != TRUE ) { + $configdapnetgw['DAPNET']['AuthKey'] = escapeshellcmd($_POST['pocsagAuthKey']); + } + + // Set the POCSAG Callsign + if (empty($_POST['pocsagCallsign']) != TRUE ) { + $configdapnetgw['General']['Callsign'] = strtoupper(escapeshellcmd($_POST['pocsagCallsign'])); + } + + // Set the POCSAG Whitelist + if (isset($configdapnetgw['General']['WhiteList'])) { unset($configdapnetgw['General']['WhiteList']); } + if (empty($_POST['pocsagWhitelist']) != TRUE ) { + $configdapnetgw['General']['WhiteList'] = preg_replace('/[^0-9\,]/', '', escapeshellcmd($_POST['pocsagWhitelist'])); + } + + // Set the POCSAG Blacklist + if (isset($configdapnetgw['General']['BlackList'])) { unset($configdapnetgw['General']['BlackList']); } + if (empty($_POST['pocsagBlacklist']) != TRUE ) { + $configdapnetgw['General']['BlackList'] = preg_replace('/[^0-9\,]/', '', escapeshellcmd($_POST['pocsagBlacklist'])); + } + + // Set the POCSAG Server + if (empty($_POST['pocsagServer']) != TRUE ) { + $configdapnetgw['DAPNET']['Address'] = escapeshellcmd($_POST['pocsagServer']); + } + + // Set the Frequency for Duplex + if (empty($_POST['confFREQtx']) != TRUE && empty($_POST['confFREQrx']) != TRUE ) { + if (empty($_POST['confHardware']) != TRUE ) { $confHardware = escapeshellcmd($_POST['confHardware']); } + $newConfFREQtx = preg_replace('/[^0-9\.]/', '', $_POST['confFREQtx']); + $newConfFREQrx = preg_replace('/[^0-9\.]/', '', $_POST['confFREQrx']); + $newFREQtx = number_format($newConfFREQtx, 6, ".", ""); + $newFREQrx = number_format($newConfFREQrx, 6, ".", ""); + $newFREQtx = str_pad(str_replace(".", "", $newFREQtx), 9, "0"); + //$newFREQtx = mb_strimwidth($newFREQtx, 0, 9); + $newFREQrx = str_pad(str_replace(".", "", $newFREQrx), 9, "0"); + //$newFREQrx = mb_strimwidth($newFREQrx, 0, 9); + //$newFREQirc = substr_replace($newFREQtx, '.', '3', 0); + //$newFREQirc = mb_strimwidth($newFREQirc, 0, 9); + $newFREQirc = number_format($newConfFREQtx, 5, ".", ""); + $newFREQOffset = ($newFREQrx - $newFREQtx)/1000000; + $newFREQOffset = number_format($newFREQOffset, 4, '.', ''); + $rollFREQirc = 'sudo sed -i "/frequency1=/c\\frequency1='.$newFREQirc.'" /etc/ircddbgateway'; + $rollFREQdvap = 'sudo sed -i "/dvapFrequency=/c\\dvapFrequency='.$newFREQrx.'" /etc/dstarrepeater'; + $rollFREQdvmegaRx = 'sudo sed -i "/dvmegaRXFrequency=/c\\dvmegaRXFrequency='.$newFREQrx.'" /etc/dstarrepeater'; + $rollFREQdvmegaTx = 'sudo sed -i "/dvmegaTXFrequency=/c\\dvmegaTXFrequency='.$newFREQtx.'" /etc/dstarrepeater'; + $rollModeDuplex = 'sudo sed -i "/mode=/c\\mode=0" /etc/dstarrepeater'; + $rollGatewayType = 'sudo sed -i "/gatewayType=/c\\gatewayType=0" /etc/ircddbgateway'; + $rollFREQOffset = 'sudo sed -i "/offset1=/c\\offset1='.$newFREQOffset.'" /etc/ircddbgateway'; + $configmmdvm['Info']['RXFrequency'] = $newFREQrx; + $configmmdvm['Info']['TXFrequency'] = $newFREQtx; + $configdmrgateway['Info']['RXFrequency'] = $newFREQrx; + $configdmrgateway['Info']['TXFrequency'] = $newFREQtx; + $configysfgateway['Info']['RXFrequency'] = $newFREQrx; + $configysfgateway['Info']['TXFrequency'] = $newFREQtx; + $configysfgateway['General']['Suffix'] = "RPT"; + $configysf2dmr['Info']['RXFrequency'] = $newFREQrx; + $configysf2dmr['Info']['TXFrequency'] = $newFREQrx; + $configysf2dmr['YSF Network']['Suffix'] = "RPT"; + $configysf2nxdn['Info']['RXFrequency'] = $newFREQrx; + $configysf2nxdn['Info']['TXFrequency'] = $newFREQtx; + $configysf2nxdn['YSF Network']['Suffix'] = "RPT"; + $configysf2p25['Info']['RXFrequency'] = $newFREQrx; + $configysf2p25['Info']['TXFrequency'] = $newFREQtx; + $configysf2p25['YSF Network']['Suffix'] = "RPT"; + if (isset($configdgidgateway)) { $configdgidgateway['Info']['RXFrequency'] = $newFREQrx; } + if (isset($configdgidgateway)) { $configdgidgateway['Info']['TXFrequency'] = $newFREQtx; } + if (isset($configdgidgateway)) { $configdgidgateway['General']['Suffix'] = "RPT"; } + $configdmr2ysf['YSF Network']['Suffix'] = "RPT"; + $confignxdngateway['Info']['RXFrequency'] = $newFREQrx; + $confignxdngateway['Info']['TXFrequency'] = $newFREQtx; + $confignxdngateway['General']['Suffix'] = "RPT"; + if (isset($configm17gateway['Info']['RXFrequency'])) { $configm17gateway['Info']['RXFrequency'] = $newFREQrx; } + if (isset($configm17gateway['Info']['TXFrequency'])) { $configm17gateway['Info']['TXFrequency'] = $newFREQtx; } + if (isset($configm17gateway['General']['Suffix'])) { $configm17gateway['General']['Suffix'] = "R"; } + + system($rollFREQirc); + system($rollFREQdvap); + system($rollFREQdvmegaRx); + system($rollFREQdvmegaTx); + system($rollModeDuplex); + system($rollGatewayType); + system($rollFREQOffset); + + // Set RPT1 and RPT2 + if (empty($_POST['confDStarModuleSuffix'])) { + if ($newFREQtx >= 1240000000 && $newFREQtx <= 1300000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."A"; + $confIRCrepeaterBand1 = "A"; + $configmmdvm['D-Star']['Module'] = "A"; + $rollTimeserverBand = 'sudo sed -i "/sendA=/c\\sendA=1" /etc/timeserver'; + system($rollTimeserverBand); + } + if ($newFREQtx >= 420000000 && $newFREQtx <= 450000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."B"; + $confIRCrepeaterBand1 = "B"; + $configmmdvm['D-Star']['Module'] = "B"; + $rollTimeserverBand = 'sudo sed -i "/sendB=/c\\sendB=1" /etc/timeserver'; + system($rollTimeserverBand); + } + if ($newFREQtx >= 218000000 && $newFREQtx <= 226000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."A"; + $confIRCrepeaterBand1 = "A"; + $configmmdvm['D-Star']['Module'] = "A"; + $rollTimeserverBand = 'sudo sed -i "/sendA=/c\\sendA=1" /etc/timeserver'; + system($rollTimeserverBand); + } + if ($newFREQtx >= 144000000 && $newFREQtx <= 148000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."C"; + $confIRCrepeaterBand1 = "C"; + $configmmdvm['D-Star']['Module'] = "C"; + $rollTimeserverBand = 'sudo sed -i "/sendC=/c\\sendC=1" /etc/timeserver'; + system($rollTimeserverBand); + } + } + else { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ").strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])); + $confIRCrepeaterBand1 = strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])); + $configmmdvm['D-Star']['Module'] = strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])); + $rollTimeserverBand = 'sudo sed -i "/send'.strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])).'=/c\\send'.strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])).'=1" /etc/timeserver'; + system($rollTimeserverBand); + } + + $newCallsignUpper = strtoupper(escapeshellcmd($_POST['confCallsign'])); + $confRPT2 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."G"; + + $confRPT1 = strtoupper($confRPT1); + $confRPT2 = strtoupper($confRPT2); + + $rollRPT1 = 'sudo sed -i "/callsign=/c\\callsign='.$confRPT1.'" /etc/dstarrepeater'; + $rollRPT2 = 'sudo sed -i "/gateway=/c\\gateway='.$confRPT2.'" /etc/dstarrepeater'; + $rollBEACONTEXT = 'sudo sed -i "/beaconText=/c\\beaconText='.$confRPT1.'" /etc/dstarrepeater'; + $rollIRCrepeaterBand1 = 'sudo sed -i "/repeaterBand1=/c\\repeaterBand1='.$confIRCrepeaterBand1.'" /etc/ircddbgateway'; + $rollIRCrepeaterCall1 = 'sudo sed -i "/repeaterCall1=/c\\repeaterCall1='.$newCallsignUpper.'" /etc/ircddbgateway'; + + system($rollRPT1); + system($rollRPT2); + system($rollBEACONTEXT); + system($rollIRCrepeaterBand1); + system($rollIRCrepeaterCall1); + } + + // Set the Frequency for Simplex + if (empty($_POST['confFREQ']) != TRUE ) { + if (empty($_POST['confHardware']) != TRUE ) { $confHardware = escapeshellcmd($_POST['confHardware']); } + $newConfFREQ = preg_replace('/[^0-9\.]/', '', $_POST['confFREQ']); + $newFREQ = number_format($newConfFREQ, 6, ".", ""); + $newFREQ = str_pad(str_replace(".", "", $newFREQ), 9, "0"); + $newFREQirc = number_format($newConfFREQ, 5, ".", ""); + //$newFREQ = mb_strimwidth($newFREQ, 0, 9); + //$newFREQirc = substr_replace($newFREQ, '.', '3', 0); + //$newFREQirc = mb_strimwidth($newFREQirc, 0, 9); + $newFREQOffset = "0.0000"; + $rollFREQirc = 'sudo sed -i "/frequency1=/c\\frequency1='.$newFREQirc.'" /etc/ircddbgateway'; + $rollFREQdvap = 'sudo sed -i "/dvapFrequency=/c\\dvapFrequency='.$newFREQ.'" /etc/dstarrepeater'; + $rollFREQdvmegaRx = 'sudo sed -i "/dvmegaRXFrequency=/c\\dvmegaRXFrequency='.$newFREQ.'" /etc/dstarrepeater'; + $rollFREQdvmegaTx = 'sudo sed -i "/dvmegaTXFrequency=/c\\dvmegaTXFrequency='.$newFREQ.'" /etc/dstarrepeater'; + $rollModeSimplex = 'sudo sed -i "/mode=/c\\mode=1" /etc/dstarrepeater'; + $rollGatewayType = 'sudo sed -i "/gatewayType=/c\\gatewayType=1" /etc/ircddbgateway'; + $rollFREQOffset = 'sudo sed -i "/offset1=/c\\offset1='.$newFREQOffset.'" /etc/ircddbgateway'; + $configmmdvm['Info']['RXFrequency'] = $newFREQ; + $configmmdvm['Info']['TXFrequency'] = $newFREQ; + $configdmrgateway['Info']['RXFrequency'] = $newFREQ; + $configdmrgateway['Info']['TXFrequency'] = $newFREQ; + $configysfgateway['Info']['RXFrequency'] = $newFREQ; + $configysfgateway['Info']['TXFrequency'] = $newFREQ; + $configysfgateway['General']['Suffix'] = "ND"; + $configysf2dmr['Info']['RXFrequency'] = $newFREQ; + $configysf2dmr['Info']['TXFrequency'] = $newFREQ; + $configysf2dmr['YSF Network']['Suffix'] = "ND"; + $configysf2nxdn['Info']['RXFrequency'] = $newFREQ; + $configysf2nxdn['Info']['TXFrequency'] = $newFREQ; + $configysf2nxdn['YSF Network']['Suffix'] = "ND"; + $configysf2p25['Info']['RXFrequency'] = $newFREQ; + $configysf2p25['Info']['TXFrequency'] = $newFREQ; + $configysf2p25['YSF Network']['Suffix'] = "ND"; + if (isset($configdgidgateway)) { $configdgidgateway['Info']['RXFrequency'] = $newFREQ; } + if (isset($configdgidgateway)) { $configdgidgateway['Info']['TXFrequency'] = $newFREQ; } + if (isset($configdgidgateway)) { $configdgidgateway['General']['Suffix'] = "ND"; } + $configdmr2ysf['YSF Network']['Suffix'] = "ND"; + $confignxdngateway['Info']['RXFrequency'] = $newFREQ; + $confignxdngateway['Info']['TXFrequency'] = $newFREQ; + $confignxdngateway['General']['Suffix'] = "ND"; + if (isset($configm17gateway['Info']['RXFrequency'])) { $configm17gateway['Info']['RXFrequency'] = $newFREQ; } + if (isset($configm17gateway['Info']['TXFrequency'])) { $configm17gateway['Info']['TXFrequency'] = $newFREQ; } + if (isset($configm17gateway['General']['Suffix'])) { $configm17gateway['General']['Suffix'] = "H"; } + + system($rollFREQirc); + system($rollFREQdvap); + system($rollFREQdvmegaRx); + system($rollFREQdvmegaTx); + system($rollModeSimplex); + system($rollGatewayType); + system($rollFREQOffset); + + // Set RPT1 and RPT2 + if (empty($_POST['confDStarModuleSuffix'])) { + if ($newFREQ >= 1240000000 && $newFREQ <= 1300000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."A"; + $confIRCrepeaterBand1 = "A"; + $configmmdvm['D-Star']['Module'] = "A"; + $rollTimeserverBand = 'sudo sed -i "/sendA=/c\\sendA=1" /etc/timeserver'; + system($rollTimeserverBand); + } + if ($newFREQ >= 420000000 && $newFREQ <= 450000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."B"; + $confIRCrepeaterBand1 = "B"; + $configmmdvm['D-Star']['Module'] = "B"; + $rollTimeserverBand = 'sudo sed -i "/sendB=/c\\sendB=1" /etc/timeserver'; + system($rollTimeserverBand); + } + if ($newFREQ >= 218000000 && $newFREQ <= 226000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."A"; + $confIRCrepeaterBand1 = "A"; + $configmmdvm['D-Star']['Module'] = "A"; + $rollTimeserverBand = 'sudo sed -i "/sendA=/c\\sendA=1" /etc/timeserver'; + system($rollTimeserverBand); + } + if ($newFREQ >= 144000000 && $newFREQ <= 148000000) { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."C"; + $confIRCrepeaterBand1 = "C"; + $configmmdvm['D-Star']['Module'] = "C"; + $rollTimeserverBand = 'sudo sed -i "/sendA=/c\\sendA=1" /etc/timeserver'; + system($rollTimeserverBand); + } + } + else { + $confRPT1 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ").strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])); + $confIRCrepeaterBand1 = strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])); + $configmmdvm['D-Star']['Module'] = strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])); + $rollTimeserverBand = 'sudo sed -i "/send'.strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])).'=/c\\send'.strtoupper(escapeshellcmd($_POST['confDStarModuleSuffix'])).'=1" /etc/timeserver'; + system($rollTimeserverBand); + } + + $newCallsignUpper = strtoupper(escapeshellcmd($_POST['confCallsign'])); + $confRPT2 = str_pad(escapeshellcmd($_POST['confCallsign']), 7, " ")."G"; + + $confRPT1 = strtoupper($confRPT1); + $confRPT2 = strtoupper($confRPT2); + + $rollRPT1 = 'sudo sed -i "/callsign=/c\\callsign='.$confRPT1.'" /etc/dstarrepeater'; + $rollRPT2 = 'sudo sed -i "/gateway=/c\\gateway='.$confRPT2.'" /etc/dstarrepeater'; + $rollBEACONTEXT = 'sudo sed -i "/beaconText=/c\\beaconText='.$confRPT1.'" /etc/dstarrepeater'; + $rollIRCrepeaterBand1 = 'sudo sed -i "/repeaterBand1=/c\\repeaterBand1='.$confIRCrepeaterBand1.'" /etc/ircddbgateway'; + $rollIRCrepeaterCall1 = 'sudo sed -i "/repeaterCall1=/c\\repeaterCall1='.$newCallsignUpper.'" /etc/ircddbgateway'; + + system($rollRPT1); + system($rollRPT2); + system($rollBEACONTEXT); + system($rollIRCrepeaterBand1); + system($rollIRCrepeaterCall1); + } + + // Set Callsign + if (empty($_POST['confCallsign']) != TRUE ) { + $newCallsignUpper = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $_POST['confCallsign'])); + // Removed the need for the r prefix - OpenQuad have fixed up the servers not to require it. + // if (preg_match("/^[0-9]/", $newCallsignUpper)) { $newCallsignUpperIRC = 'r'.$newCallsignUpper; } else { $newCallsignUpperIRC = $newCallsignUpper; } + $newCallsignUpperIRC = $newCallsignUpper; + + $rollGATECALL = 'sudo sed -i "/gatewayCallsign=/c\\gatewayCallsign='.$newCallsignUpper.'" /etc/ircddbgateway'; + $rollDPLUSLOGIN = 'sudo sed -i "/dplusLogin=/c\\dplusLogin='.str_pad($newCallsignUpper, 8, " ").'" /etc/ircddbgateway'; + $rollDASHBOARDcall = 'sudo sed -i "/callsign=/c\\$callsign=\''.$newCallsignUpper.'\';" /var/www/dashboard/config/ircddblocal.php'; + $rollTIMESERVERcall = 'sudo sed -i "/callsign=/c\\callsign='.$newCallsignUpper.'" /etc/timeserver'; + $rollSTARNETSERVERcall = 'sudo sed -i "/callsign=/c\\callsign='.$newCallsignUpper.'" /etc/starnetserver'; + $rollSTARNETSERVERirc = 'sudo sed -i "/ircddbUsername=/c\\ircddbUsername='.$newCallsignUpperIRC.'" /etc/starnetserver'; + + // Only roll ircDDBGateway Username if using OpenQuad + if (strpos($configs['ircddbHostname'], 'openquad.net') !== false) { + $rollIRCUSER = 'sudo sed -i "/ircddbUsername=/c\\ircddbUsername='.$newCallsignUpperIRC.'" /etc/ircddbgateway'; + system($rollIRCUSER); + } + + //if ( strlen($newCallsignUpper) < 6 ) { $configysfgateway['General']['Callsign'] = $newCallsignUpper."-1"; } + //else { $configysfgateway['General']['Callsign'] = $newCallsignUpper; } + $configysfgateway['General']['Callsign'] = $newCallsignUpper; + $configmmdvm['General']['Callsign'] = $newCallsignUpper; + $configysfgateway['aprs.fi']['Password'] = aprspass($newCallsignUpper); + $configysfgateway['aprs.fi']['Description'] = $newCallsignUpper."_Pi-Star"; + $configysf2dmr['aprs.fi']['Password'] = aprspass($newCallsignUpper); + $configysf2dmr['aprs.fi']['Description'] = $newCallsignUpper."_Pi-Star"; + $configysf2dmr['YSF Network']['Callsign'] = $newCallsignUpper; + $configysf2nxdn['aprs.fi']['Password'] = aprspass($newCallsignUpper); + $configysf2nxdn['aprs.fi']['Description'] = $newCallsignUpper."_Pi-Star"; + $configysf2nxdn['YSF Network']['Callsign'] = $newCallsignUpper; + $configysf2p25['aprs.fi']['Password'] = aprspass($newCallsignUpper); + $configysf2p25['aprs.fi']['Description'] = $newCallsignUpper."_Pi-Star"; + $configysf2p25['YSF Network']['Callsign'] = $newCallsignUpper; + $configdmr2ysf['YSF Network']['Callsign'] = $newCallsignUpper; + $configp25gateway['General']['Callsign'] = $newCallsignUpper; + $confignxdngateway['aprs.fi']['Description'] = $newCallsignUpper."_Pi-Star"; + $confignxdngateway['aprs.fi']['Password'] = aprspass($newCallsignUpper); + $confignxdngateway['General']['Callsign'] = $newCallsignUpper; + if (isset($configm17gateway['General']['Callsign'])) { $configm17gateway['General']['Callsign'] = $newCallsignUpper; } + $configysfgateway['Info']['Name'] = $newCallsignUpper."_Pi-Star"; + $configysf2dmr['Info']['Description'] = $newCallsignUpper."_Pi-Star"; + $configysf2nxdn['Info']['Description'] = $newCallsignUpper."_Pi-Star"; + $configysf2p25['Info']['Description'] = $newCallsignUpper."_Pi-Star"; + if (isset($configdgidgateway)) { $configdgidgateway['General']['Callsign'] = $newCallsignUpper; } + if (isset($configdgidgateway)) { $configdgidgateway['Info']['Description'] = $newCallsignUpper."_Pi-Star"; } + if ($configPistarRelease['Pi-Star']['Version'] >= "4.1.4") { + $rollAPRSGatewayCallsign = 'sudo sed -i "/Callsign=/c\\Callsign='.$newCallsignUpper.'" /etc/aprsgateway'; + system($rollAPRSGatewayCallsign); + $rollAPRSGatewayPassword = 'sudo sed -i "/Password=/c\\Password='.aprspass($newCallsignUpper).'" /etc/aprsgateway'; + system($rollAPRSGatewayPassword); + $rollircDDBGatewayAprsPort = 'sudo sed -i "/aprsPort=/c\\aprsPort=8673" /etc/ircddbgateway'; + system($rollircDDBGatewayAprsPort); + unset($configs['aprsPassword']); + $rollircDDBGatewayAprsPass = 'sudo sed -i "/aprsPassword/d" /etc/ircddbgateway'; + system($rollircDDBGatewayAprsPass); + if (empty($_POST['APRSGatewayEnable']) != TRUE ) { + if (escapeshellcmd($_POST['APRSGatewayEnable']) == 'ON' ) { $rollAPRSGatewayEnable = 'sudo sed -i "/Enabled=/c\\Enabled=1" /etc/aprsgateway'; } + if (escapeshellcmd($_POST['APRSGatewayEnable']) == 'OFF' ) { $rollAPRSGatewayEnable = 'sudo sed -i "/Enabled=/c\\Enabled=0" /etc/aprsgateway'; } + } + system($rollAPRSGatewayEnable); + } + + // If ircDDBGateway config supports APRS Password + if (isset($configs['aprsPassword'])) { + $rollircDDBGatewayAprsPassword = 'sudo sed -i "/aprsPassword=/c\\aprsPassword='.aprspass($newCallsignUpper).'" /etc/ircddbgateway'; + system($rollircDDBGatewayAprsPassword); + } + + system($rollGATECALL); + system($rollDPLUSLOGIN); + system($rollDASHBOARDcall); + system($rollTIMESERVERcall); + system($rollSTARNETSERVERcall); + system($rollSTARNETSERVERirc); + } + + // Set the ircDDB Callsign routing option + if (empty($_POST['confircddbEnabled']) != TRUE ) { + if (escapeshellcmd($_POST['confircddbEnabled']) == 'ON' ) { + $rollconfircddbEnabled = 'sudo sed -i "/rcddbEnabled=/c\\ircddbEnabled=1" /etc/ircddbgateway'; + } + if (escapeshellcmd($_POST['confircddbEnabled']) == 'OFF' ) { + $rollconfircddbEnabled = 'sudo sed -i "/rcddbEnabled=/c\\ircddbEnabled=0" /etc/ircddbgateway'; + } + if (isset($configs['ircddbHostname']) && $configs['ircddbHostname'] == "rr.openquad.net") { + $rollconfircddbEnabled = 'sudo sed -i "/rcddbEnabled=/c\\ircddbEnabled=0" /etc/ircddbgateway'; + $rollconfircddbHostname = 'sudo sed -i "/rcddbHostname=/c\\ircddbHostname=ircv4.openquad.net" /etc/ircddbgateway'; + system($rollconfircddbHostname); + } + system($rollconfircddbEnabled); + } + + // Set the P25 Startup Host + if (empty($_POST['p25StartupHost']) != TRUE ) { + $newP25StartupHost = strtoupper(escapeshellcmd($_POST['p25StartupHost'])); + if ($newP25StartupHost === "NONE") { + unset($configp25gateway['Network']['Startup']); + unset($configysf2p25['P25 Network']['StartupDstId']); + unset($configp25gateway['Network']['Static']); + } else { + $configp25gateway['Network']['Startup'] = $newP25StartupHost; + $configysf2p25['P25 Network']['StartupDstId'] = $newP25StartupHost; + } + } + + // Set P25 NAC + if (empty($_POST['p25nac']) != TRUE ) { + $p25nacNew = strtolower(escapeshellcmd($_POST['p25nac'])); + if (preg_match('/[a-f0-9]{3}/', $p25nacNew)) { + $configmmdvm['P25']['NAC'] = $p25nacNew; + } + } + + // Set the NXDN Startup Host + if (empty($_POST['nxdnStartupHost']) != TRUE ) { + $newNXDNStartupHost = strtoupper(escapeshellcmd($_POST['nxdnStartupHost'])); + if (file_exists('/etc/nxdngateway')) { + if ($newNXDNStartupHost === "NONE") { + if (isset($confignxdngateway['Network']['Startup'])) { unset($confignxdngateway['Network']['Startup']); } + if (isset($confignxdngateway['Network']['Static'])) { unset($confignxdngateway['Network']['Static']); } + } else { + $confignxdngateway['Network']['Startup'] = $newNXDNStartupHost; + } + } else { + $configmmdvm['NXDN Network']['GatewayAddress'] = $newNXDNStartupHost; + $configmmdvm['NXDN Network']['GatewayPort'] = "41007"; + } + $configysf2nxdn['NXDN Network']['StartupDstId'] = $newNXDNStartupHost; + } + + // Set NXDN RAN + if (empty($_POST['nxdnran']) != TRUE ) { + $nxdnranNew = strtolower(escapeshellcmd($_POST['nxdnran'])); + $nxdnranNew = preg_replace('/[^0-9]/', '', $nxdnranNew); + if (($nxdnranNew >= 1) && ($nxdnranNew <= 64)) { + $configmmdvm['NXDN']['RAN'] = $nxdnranNew; + } + } + + // Set the M17 Startup Host + if (empty($_POST['m17StartupHost']) != TRUE || empty($_POST['m17StartupRoom']) != TRUE) { + $newM17StartupHost = strtoupper(escapeshellcmd($_POST['m17StartupHost']))."_".strtoupper(escapeshellcmd($_POST['m17StartupRoom'])); + if (file_exists('/etc/m17gateway')) { + if (strtoupper(escapeshellcmd($_POST['m17StartupHost'])) === "NONE") { + if (isset($configm17gateway['Network']['Startup'])) { unset($configm17gateway['Network']['Startup']); } + } else { + $configm17gateway['Network']['Startup'] = $newM17StartupHost; + } + } + } + + // Set M17 CAN + if (isset($_POST['m17can']) && $_POST['m17can'] !== '') { + $m17canNew = strtolower(escapeshellcmd($_POST['m17can'])); + $m17canNew = preg_replace('/[^0-9]/', '', $m17canNew); + $configmmdvm['M17']['CAN'] = $m17canNew; + } + + // Set the YSF Startup Host + if (empty($_POST['ysfStartupHost']) != TRUE ) { + $newYSFStartupHostArr = explode(',', escapeshellcmd($_POST['ysfStartupHost'])); + //$newYSFStartupHost = strtoupper(escapeshellcmd($_POST['ysfStartupHost'])); + //if ($newYSFStartupHost == "NONE") { unset($configysfgateway['Network']['Startup']); } + //else { $configysfgateway['Network']['Startup'] = $newYSFStartupHost; } + if (isset($configysfgateway['FCS Network'])) { + if ($newYSFStartupHostArr[0] == "none") { + unset($configysfgateway['Network']['Startup']); + $configdmr2ysf['DMR Network']['DefaultDstTG'] = "9"; + } + else { + $configysfgateway['Network']['Startup'] = $newYSFStartupHostArr[1]; + if (substr( $newYSFStartupHostArr[0], 0, 3 ) !== "FCS") { + $configdmr2ysf['DMR Network']['DefaultDstTG'] = $newYSFStartupHostArr[0]; + } else { + $configdmr2ysf['DMR Network']['DefaultDstTG'] = "9"; + } + //$configdmr2ysf['DMR Network']['DefaultDstTG'] = str_replace("FCS", "1", $newYSFStartupHostArr[0]); + } + } else { + if ($newYSFStartupHostArr[0] == "none") { + unset($configysfgateway['Network']['Startup']); + $configdmr2ysf['DMR Network']['DefaultDstTG'] = "9"; + } + else { + $configysfgateway['Network']['Startup'] = $newYSFStartupHostArr[0]; + if (substr( $newYSFStartupHostArr[0], 0, 3 ) !== "FCS") { + $configdmr2ysf['DMR Network']['DefaultDstTG'] = $newYSFStartupHostArr[0]; + } else { + $configdmr2ysf['DMR Network']['DefaultDstTG'] = "9"; + } + //$configdmr2ysf['DMR Network']['DefaultDstTG'] = str_replace("FCS", "1", $newYSFStartupHostArr[0]); + } + } + } + + // Set YSFGateway to automatically pass through WiresX + if (empty($_POST['wiresXCommandPassthrough']) != TRUE ) { + if (escapeshellcmd($_POST['wiresXCommandPassthrough']) == 'ON' ) { $configysfgateway['General']['WiresXCommandPassthrough'] = "1"; } + if (escapeshellcmd($_POST['wiresXCommandPassthrough']) == 'OFF' ) { $configysfgateway['General']['WiresXCommandPassthrough'] = "0"; } + } + + // Remove hostfiles.ysfupper and use the new YSFGateway Feature + if (empty($_POST['confHostFilesYSFUpper']) != TRUE ) { + if (escapeshellcmd($_POST['confHostFilesYSFUpper']) == 'ON' ) { $configysfgateway['General']['WiresXMakeUpper'] = "1"; } + if (escapeshellcmd($_POST['confHostFilesYSFUpper']) == 'OFF' ) { $configysfgateway['General']['WiresXMakeUpper'] = "0"; } + if (file_exists('/etc/hostfiles.ysfupper')) { system('sudo rm -rf /etc/hostfiles.ysfupper'); } + } + + // Set the YSF2DMR Master + if (empty($_POST['ysf2dmrMasterHost']) != TRUE ) { + $ysf2dmrMasterHostArr = explode(',', escapeshellcmd($_POST['ysf2dmrMasterHost'])); + $configysf2dmr['DMR Network']['Address'] = $ysf2dmrMasterHostArr[0]; + $configysf2dmr['DMR Network']['Password'] = '"'.$ysf2dmrMasterHostArr[1].'"'; + $configysf2dmr['DMR Network']['Port'] = $ysf2dmrMasterHostArr[2]; + if (isset($_POST['bmHSSecurity'])) { + if (empty($_POST['bmHSSecurity']) != TRUE ) { + $configysf2dmr['DMR Network']['Password'] = '"'.$_POST['bmHSSecurity'].'"'; + $configModem['BrandMeister']['Password'] = '"'.$_POST['bmHSSecurity'].'"'; + } else { + unset ($configModem['BrandMeister']['Password']); + } + } + } + + // Set the YSF2DMR Starting TG + if (empty($_POST['ysf2dmrTg']) != TRUE ) { + $ysf2dmrStartupDstId = preg_replace('/[^0-9]/', '', $_POST['ysf2dmrTg']); + $configysf2dmr['DMR Network']['StartupDstId'] = $ysf2dmrStartupDstId; + } + + // Set the YSF2NXDN Master + if (empty($_POST['ysf2nxdnStartupDstId']) != TRUE ) { + $configysf2nxdn['NXDN Network']['StartupDstId'] = escapeshellcmd($_POST['ysf2nxdnStartupDstId']); + if (file_exists('/etc/nxdngateway')) { + if (escapeshellcmd($_POST['ysf2nxdnStartupDstId']) === "none") { + unset($confignxdngateway['Network']['Startup']); + unset($confignxdngateway['Network']['Static']); + } else { + $confignxdngateway['Network']['Startup'] = escapeshellcmd($_POST['ysf2nxdnStartupDstId']); + } + } + } + + // Set the YSF2NXDN Id + if (empty($_POST['ysf2nxdnId']) != TRUE ) { + $configysf2nxdn['NXDN Network']['Id'] = preg_replace('/[^0-9]/', '', $_POST['ysf2nxdnId']); + } + + // Set the YSF2P25 Master + if (empty($_POST['ysf2p25StartupDstId']) != TRUE ) { + $newYSF2P25StartupHost = strtoupper(escapeshellcmd($_POST['ysf2p25StartupDstId'])); + + if ($newYSF2P25StartupHost === "NONE") { + unset($configp25gateway['Network']['Startup']); + unset($configp25gateway['Network']['Static']); + unset($configysf2p25['P25 Network']['StartupDstId']); + } else { + $configp25gateway['Network']['Startup'] = $newYSF2P25StartupHost; + $configysf2p25['P25 Network']['StartupDstId'] = $newYSF2P25StartupHost; + } + } + + // Set the YSF2P25 P25Id + if (empty($_POST['ysf2p25Id']) != TRUE ) { + $configysf2p25['P25 Network']['Id'] = preg_replace('/[^0-9]/', '', $_POST['ysf2p25Id']); + } + + // Set Duplex + if (empty($_POST['trxMode']) != TRUE ) { + if ($configmmdvm['Info']['RXFrequency'] === $configmmdvm['Info']['TXFrequency'] && $_POST['trxMode'] == "DUPLEX" ) { + $configmmdvm['Info']['RXFrequency'] = $configmmdvm['Info']['TXFrequency'] - 1; + } + if ($configmmdvm['Info']['RXFrequency'] !== $configmmdvm['Info']['TXFrequency'] && $_POST['trxMode'] == "SIMPLEX" ) { + $configmmdvm['Info']['RXFrequency'] = $configmmdvm['Info']['TXFrequency']; + } + if ($_POST['trxMode'] == "DUPLEX") { + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['DMR Network']['Slot1'] = '1'; + $configmmdvm['DMR Network']['Slot2'] = '1'; + } + if ($_POST['trxMode'] == "SIMPLEX") { + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = '0'; + $configmmdvm['DMR Network']['Slot2'] = '1'; + } + } + + // Set DMR / CCS7 ID + if (empty($_POST['dmrId']) != TRUE ) { + $newPostDmrId = preg_replace('/[^0-9]/', '', $_POST['dmrId']); + $newPostDmrId = substr($newPostDmrId, 0, 7); + $configmmdvm['General']['Id'] = $newPostDmrId; + $configmmdvm['DMR']['Id'] = $newPostDmrId; + $configysfgateway['General']['Id'] = $newPostDmrId; + $configdmrgateway['XLX Network']['Id'] = $newPostDmrId; + $configdmr2ysf['DMR Network']['Id'] = $newPostDmrId; + $configdmr2nxdn['DMR Network']['Id'] = $newPostDmrId; + if (isset($configdgidgateway)) { $configdgidgateway['General']['Id'] = $newPostDmrId; } + } + + // Set DMR Extended ID + if (empty($_POST['dmrExtendedId']) != TRUE ) { + $newPostdmrExtendedId = preg_replace('/[^0-9]/', '', $_POST['dmrExtendedId']); + $configmmdvm['DMR']['Id'] = $configmmdvm['General']['Id'].$newPostdmrExtendedId; + } + + // Set BrandMeister Extended ID + if (empty($_POST['bmExtendedId']) != TRUE ) { + $newPostbmExtendedId = preg_replace('/[^0-9]/', '', $_POST['bmExtendedId']); + $configdmrgateway['DMR Network 1']['Id'] = $configmmdvm['General']['Id'].$newPostbmExtendedId; + } + + // Set DMR Plus Extended ID + if (empty($_POST['dmrPlusExtendedId']) != TRUE ) { + $newPostdmrPlusExtendedId = preg_replace('/[^0-9]/', '', $_POST['dmrPlusExtendedId']); + $configdmrgateway['DMR Network 2']['Id'] = $configmmdvm['General']['Id'].$newPostdmrPlusExtendedId; + } + + // Set YSF2DMR ID + if (empty($_POST['ysf2dmrId']) != TRUE ) { + $newPostYsf2DmrId = preg_replace('/[^0-9]/', '', $_POST['ysf2dmrId']); + $configysf2dmr['DMR Network']['Id'] = $newPostYsf2DmrId; + } + + // Set NXDN ID + if (empty($_POST['nxdnId']) != TRUE ) { + $newPostNxdnId = preg_replace('/[^0-9]/', '', $_POST['nxdnId']); + $configmmdvm['NXDN']['Id'] = $newPostNxdnId; + if ($configmmdvm['NXDN']['Id'] > 65535) { unset($configmmdvm['NXDN']['Id']); } + } + + // Set DMR Master Server + if (empty($_POST['dmrMasterHost']) != TRUE ) { + $dmrMasterHostArr = explode(',', escapeshellcmd($_POST['dmrMasterHost'])); + $configmmdvm['DMR Network']['Address'] = $dmrMasterHostArr[0]; + $configmmdvm['DMR Network']['RemoteAddress'] = $dmrMasterHostArr[0]; + $configmmdvm['DMR Network']['Password'] = '"'.$dmrMasterHostArr[1].'"'; + $configmmdvm['DMR Network']['Port'] = $dmrMasterHostArr[2]; + $configmmdvm['DMR Network']['RemotePort'] = $dmrMasterHostArr[2]; + if ($dmrMasterHostArr[0] == '127.0.0.1' && $dmrMasterHostArr[2] == '62031') { + // DMR Gateway + $configmmdvm['DMR Network']['Type'] = "Gateway"; + } else { + // Everything Else + $configmmdvm['DMR Network']['Type'] = "Direct"; + } + if ((isset($_POST['bmHSSecurity'])) && ((substr($dmrMasterHostArr[3], 0, 2) == "BM") || (substr($dmrMasterHostArr[3], 0, 3) == "CDN"))) { + if (empty($_POST['bmHSSecurity']) != TRUE ) { + $configModem['BrandMeister']['Password'] = '"'.$_POST['bmHSSecurity'].'"'; + if ($dmrMasterHostArr[0] != '127.0.0.1') { $configmmdvm['DMR Network']['Password'] = '"'.$_POST['bmHSSecurity'].'"'; } + } else { + unset ($configModem['BrandMeister']['Password']); + } + } + if ((isset($_POST['tgifHSSecurity'])) && substr($dmrMasterHostArr[3], 0, 4) == "TGIF") { + if (empty($_POST['tgifHSSecurity']) != TRUE ) { + $configModem['TGIF']['Password'] = '"'.$_POST['tgifHSSecurity'].'"'; + if ($dmrMasterHostArr[0] != '127.0.0.1') { $configmmdvm['DMR Network']['Password'] = '"'.$_POST['tgifHSSecurity'].'"'; } + } else { + unset ($configModem['TGIF']['Password']); + } + } + + if ((substr($dmrMasterHostArr[3], 0, 2) == "BM") || (substr($dmrMasterHostArr[3], 0, 3) == "CDN")) { + unset ($configmmdvm['DMR Network']['Options']); + unset ($configdmrgateway['DMR Network 2']['Options']); + unset ($configmmdvm['DMR Network']['Local']); + unset ($configmmdvm['DMR Network']['LocalPort']); + unset ($configysf2dmr['DMR Network']['Options']); + unset ($configysf2dmr['DMR Network']['Local']); + if (isset($configModem['BrandMeister']['Password'])) { + $configmmdvm['DMR Network']['Password'] = '"'.str_replace('"', "", $configModem['BrandMeister']['Password']).'"'; + } + } + + // DMR Gateway + if ($dmrMasterHostArr[0] == '127.0.0.1' && $dmrMasterHostArr[2] == '62031') { + unset ($configmmdvm['DMR Network']['Options']); + unset ($configdmrgateway['DMR Network 2']['Options']); + $configmmdvm['DMR Network']['Local'] = "62032"; + $configmmdvm['DMR Network']['LocalPort'] = "62032"; + unset ($configysf2dmr['DMR Network']['Options']); + $configysf2dmr['DMR Network']['Local'] = "62032"; + if (isset($configdmr2ysf['DMR Network']['LocalAddress'])) { + $configdmr2ysf['DMR Network']['LocalAddress'] = "127.0.0.1"; + } + if (isset($configdmr2nxdn['DMR Network']['LocalAddress'])) { + $configdmr2nxdn['DMR Network']['LocalAddress'] = "127.0.0.1"; + } + } + + // DMR2YSF + if ($dmrMasterHostArr[0] == '127.0.0.2' && $dmrMasterHostArr[2] == '62033') { + unset ($configmmdvm['DMR Network']['Options']); + $configmmdvm['DMR Network']['Local'] = "62034"; + $configmmdvm['DMR Network']['LocalPort'] = "62034"; + if (isset($configdmr2ysf['DMR Network']['LocalAddress'])) { + $configdmr2ysf['DMR Network']['LocalAddress'] = "127.0.0.2"; + } + } + + // DMR2NXDN + if ($dmrMasterHostArr[0] == '127.0.0.3' && $dmrMasterHostArr[2] == '62035') { + unset ($configmmdvm['DMR Network']['Options']); + $configmmdvm['DMR Network']['Local'] = "62036"; + $configmmdvm['DMR Network']['LocalPort'] = "62036"; + if (isset($configdmr2nxdn['DMR Network']['LocalAddress'])) { + $configdmr2nxdn['DMR Network']['LocalAddress'] = "127.0.0.3"; + } + } + + // TGIF + if (substr($dmrMasterHostArr[3], 0, 4) == "TGIF") { + unset ($configmmdvm['DMR Network']['Options']); + } + + // Set the DMR+ / HBLink Options= line + if ((substr($dmrMasterHostArr[3], 0, 4) == "DMR+") || (substr($dmrMasterHostArr[3], 0, 3) == "HB_") || (substr($dmrMasterHostArr[3], 0, 3) == "FD_") || (substr($dmrMasterHostArr[3], 0, 8) == "FreeDMR_") || (substr($dmrMasterHostArr[3], 0, 9) == "FreeSTAR_")) { + unset ($configmmdvm['DMR Network']['Local']); + unset ($configmmdvm['DMR Network']['LocalPort']); + unset ($configysf2dmr['DMR Network']['Local']); + if (empty($_POST['dmrNetworkOptions']) != TRUE ) { + $dmrOptionsLineStripped = str_replace('"', "", $_POST['dmrNetworkOptions']); + $configmmdvm['DMR Network']['Options'] = '"'.$dmrOptionsLineStripped.'"'; + $configdmrgateway['DMR Network 2']['Options'] = '"'.$dmrOptionsLineStripped.'"'; + } + else { + unset ($configmmdvm['DMR Network']['Options']); + unset ($configdmrgateway['DMR Network 2']['Options']); + unset ($configysf2dmr['DMR Network']['Options']); + } + } + } + if (empty($_POST['dmrMasterHost']) == TRUE ) { + unset ($configmmdvm['DMR Network']['Options']); + unset ($configdmrgateway['DMR Network 2']['Options']); + } + if (empty($_POST['dmrMasterHost1']) != TRUE ) { + $dmrMasterHostArr1 = explode(',', escapeshellcmd($_POST['dmrMasterHost1'])); + $configdmrgateway['DMR Network 1']['Address'] = $dmrMasterHostArr1[0]; + $configdmrgateway['DMR Network 1']['Password'] = '"'.$dmrMasterHostArr1[1].'"'; + if (empty($_POST['bmHSSecurity']) != TRUE ) { + $configdmrgateway['DMR Network 1']['Password'] = '"'.$_POST['bmHSSecurity'].'"'; + } + $configdmrgateway['DMR Network 1']['Port'] = $dmrMasterHostArr1[2]; + $configdmrgateway['DMR Network 1']['Name'] = $dmrMasterHostArr1[3]; + } + if (empty($_POST['dmrMasterHost2']) != TRUE ) { + $dmrMasterHostArr2 = explode(',', escapeshellcmd($_POST['dmrMasterHost2'])); + $configdmrgateway['DMR Network 2']['Address'] = $dmrMasterHostArr2[0]; + $configdmrgateway['DMR Network 2']['Password'] = '"'.$dmrMasterHostArr2[1].'"'; + $configdmrgateway['DMR Network 2']['Port'] = $dmrMasterHostArr2[2]; + $configdmrgateway['DMR Network 2']['Name'] = $dmrMasterHostArr2[3]; + if (empty($_POST['dmrNetworkOptions']) != TRUE ) { + $dmrOptionsLineStripped = str_replace('"', "", $_POST['dmrNetworkOptions']); + unset ($configmmdvm['DMR Network']['Options']); + $configdmrgateway['DMR Network 2']['Options'] = '"'.$dmrOptionsLineStripped.'"'; + } + else { + unset ($configdmrgateway['DMR Network 2']['Options']); + } + } + if (empty($_POST['dmrMasterHost3']) != TRUE ) { + $dmrMasterHostArr3 = explode(',', escapeshellcmd($_POST['dmrMasterHost3'])); + $configdmrgateway['XLX Network 1']['Address'] = $dmrMasterHostArr3[0]; + $configdmrgateway['XLX Network 1']['Password'] = '"'.$dmrMasterHostArr3[1].'"'; + $configdmrgateway['XLX Network 1']['Port'] = $dmrMasterHostArr3[2]; + $configdmrgateway['XLX Network 1']['Name'] = $dmrMasterHostArr3[3]; + $configdmrgateway['XLX Network']['Startup'] = substr($dmrMasterHostArr3[3], 4); + } + + // XLX StartUp TG + if (empty($_POST['dmrMasterHost3Startup']) != TRUE ) { + $dmrMasterHost3Startup = escapeshellcmd($_POST['dmrMasterHost3Startup']); + if ($dmrMasterHost3Startup != "None") { + $configdmrgateway['XLX Network 1']['Startup'] = $dmrMasterHost3Startup; + } + else { unset($configdmrgateway['XLX Network 1']['Startup']); } + } + + // XLX Module Override + if (empty($_POST['dmrMasterHost3StartupModule']) != TRUE ) { + $dmrMasterHost3StartupModule = escapeshellcmd($_POST['dmrMasterHost3StartupModule']); + if ($dmrMasterHost3StartupModule == "Default") { + unset($configdmrgateway['XLX Network']['Module']); + } else { + $configdmrgateway['XLX Network']['Module'] = $dmrMasterHost3StartupModule; + } + } + + // Set JutterBuffer Option + //if (empty($_POST['dmrDMRnetJitterBufer']) != TRUE ) { + // if (escapeshellcmd($_POST['dmrDMRnetJitterBufer']) == 'ON' ) { $configmmdvm['DMR Network']['JitterEnabled'] = "1"; $configysf2dmr['DMR Network']['JitterEnabled'] = "1"; } + // if (escapeshellcmd($_POST['dmrDMRnetJitterBufer']) == 'OFF' ) { $configmmdvm['DMR Network']['JitterEnabled'] = "0"; $configysf2dmr['DMR Network']['JitterEnabled'] = "0"; } + //} + unset($configmmdvm['DMR Network']['JitterEnabled']); + + // Set Talker Alias Option + if (empty($_POST['dmrEmbeddedLCOnly']) != TRUE ) { + if (escapeshellcmd($_POST['dmrEmbeddedLCOnly']) == 'ON' ) { $configmmdvm['DMR']['EmbeddedLCOnly'] = "1"; } + if (escapeshellcmd($_POST['dmrEmbeddedLCOnly']) == 'OFF' ) { $configmmdvm['DMR']['EmbeddedLCOnly'] = "0"; } + } + + // Set Dump TA Data Option for GPS support + if (empty($_POST['dmrDumpTAData']) != TRUE ) { + if (escapeshellcmd($_POST['dmrDumpTAData']) == 'ON' ) { $configmmdvm['DMR']['DumpTAData'] = "1"; } + if (escapeshellcmd($_POST['dmrDumpTAData']) == 'OFF' ) { $configmmdvm['DMR']['DumpTAData'] = "0"; } + } + + // Set the XLX DMRGateway Master On or Off + if (empty($_POST['dmrGatewayXlxEn']) != TRUE ) { + if (escapeshellcmd($_POST['dmrGatewayXlxEn']) == 'ON' ) { $configdmrgateway['XLX Network 1']['Enabled'] = "1"; $configdmrgateway['XLX Network']['Enabled'] = "1"; } + if (escapeshellcmd($_POST['dmrGatewayXlxEn']) == 'OFF' ) { $configdmrgateway['XLX Network 1']['Enabled'] = "0"; $configdmrgateway['XLX Network']['Enabled'] = "0"; } + } + + // Set the DMRGateway Network 2 On or Off + if (empty($_POST['dmrGatewayNet2En']) != TRUE ) { + if (escapeshellcmd($_POST['dmrGatewayNet2En']) == 'ON' ) { $configdmrgateway['DMR Network 2']['Enabled'] = "1"; } + if (escapeshellcmd($_POST['dmrGatewayNet2En']) == 'OFF' ) { $configdmrgateway['DMR Network 2']['Enabled'] = "0"; } + } + + // Set the DMRGateway Network 1 On or Off + if (empty($_POST['dmrGatewayNet1En']) != TRUE ) { + if (escapeshellcmd($_POST['dmrGatewayNet1En']) == 'ON' ) { $configdmrgateway['DMR Network 1']['Enabled'] = "1"; } + if (escapeshellcmd($_POST['dmrGatewayNet1En']) == 'OFF' ) { $configdmrgateway['DMR Network 1']['Enabled'] = "0"; } + } + + // Remove old settings + if (isset($configmmdvm['General']['ModeHang'])) { unset($configmmdvm['General']['ModeHang']); } + if (isset($configdmrgateway['General']['Timeout'])) { unset($configdmrgateway['General']['Timeout']); } + if (isset($configmmdvm['General']['RFModeHang'])) { $configmmdvm['General']['RFModeHang'] = 300; } + if (isset($configmmdvm['General']['NetModeHang'])) { $configmmdvm['General']['NetModeHang'] = 300; } + + // Set DMR Hang Timers + if (empty($_POST['dmrRfHangTime']) != TRUE ) { + $configmmdvm['DMR']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['dmrRfHangTime']); + $configdmrgateway['General']['RFTimeout'] = preg_replace('/[^0-9]/', '', $_POST['dmrRfHangTime']); + } + if (empty($_POST['dmrNetHangTime']) != TRUE ) { + $configmmdvm['DMR Network']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['dmrNetHangTime']); + $configdmrgateway['General']['NetTimeout'] = preg_replace('/[^0-9]/', '', $_POST['dmrNetHangTime']); + } + // Set D-Star Hang Timers + if (empty($_POST['dstarRfHangTime']) != TRUE ) { + $configmmdvm['D-Star']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['dstarRfHangTime']); + } + if (empty($_POST['dstarNetHangTime']) != TRUE ) { + $configmmdvm['D-Star Network']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['dstarNetHangTime']); + } + // Set YSF Hang Timers + if (empty($_POST['ysfRfHangTime']) != TRUE ) { + $configmmdvm['System Fusion']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['ysfRfHangTime']); + if (isset($configdgidgateway)) { $configdgidgateway['General']['RFHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfRfHangTime']); } + if (isset($configdgidgateway)) { $configdgidgateway['YSF Network']['RFHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfRfHangTime']); } + if (isset($configdgidgateway)) { $configdgidgateway['FCS Network']['RFHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfRfHangTime']); } + if (isset($configdgidgateway)) { $configdgidgateway['IMRS Network']['RFHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfRfHangTime']); } + } + if (empty($_POST['ysfNetHangTime']) != TRUE ) { + $configmmdvm['System Fusion Network']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['ysfNetHangTime']); + if (isset($configdgidgateway)) { $configdgidgateway['General']['NetHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfNetHangTime']); } + if (isset($configdgidgateway)) { $configdgidgateway['YSF Network']['NetHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfNetHangTime']); } + if (isset($configdgidgateway)) { $configdgidgateway['FCS Network']['NetHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfNetHangTime']); } + if (isset($configdgidgateway)) { $configdgidgateway['IMRS Network']['NetHangTime'] = preg_replace('/[^0-9]/', '', $_POST['ysfNetHangTime']); } + } + // Set P25 Hang Timers + if (empty($_POST['p25RfHangTime']) != TRUE ) { + $configmmdvm['P25']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['p25RfHangTime']); + $configp25gateway['Network']['RFHangTime'] = "0"; + } + if (empty($_POST['p25NetHangTime']) != TRUE ) { + $configmmdvm['P25 Network']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['p25NetHangTime']); + $configp25gateway['Network']['NetHangTime'] = "0"; + } + // Set NXDN Hang Timers + if (empty($_POST['nxdnRfHangTime']) != TRUE ) { + $configmmdvm['NXDN']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['nxdnRfHangTime']); + $confignxdngateway['Network']['RFHangTime'] = "0"; + } + if (empty($_POST['nxdnNetHangTime']) != TRUE ) { + $configmmdvm['NXDN Network']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['nxdnNetHangTime']); + $confignxdngateway['Network']['NetHangTime'] = "0"; + } + // Set M17 Hang Timers + if (empty($_POST['m17RfHangTime']) != TRUE ) { + $configmmdvm['M17']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['m17RfHangTime']); + } + if (empty($_POST['m17NetHangTime']) != TRUE ) { + $configmmdvm['M17 Network']['ModeHang'] = preg_replace('/[^0-9]/', '', $_POST['m17NetHangTime']); + if (isset($configm17gateway['Network']['HangTime'])) { $configm17gateway['Network']['HangTime'] = preg_replace('/[^0-9]/', '', $_POST['m17NetHangTime']); } + } + + // Set the hardware type + if (empty($_POST['confHardware']) != TRUE ) { + $confHardware = escapeshellcmd($_POST['confHardware']); + $configModem['Modem']['Hardware'] = $confHardware; + // Set the Start delay + $rollDstarRepeaterStartDelay = 'sudo sed -i "/OnStartupSec=/c\\OnStartupSec=30" /lib/systemd/system/dstarrepeater.timer'; + $rollMMDVMHostStartDelay = 'sudo sed -i "/OnStartupSec=/c\\OnStartupSec=30" /lib/systemd/system/mmdvmhost.timer'; + // Turn on RPT1 Validation in DStarRepeater + $rollRpt1Validation = 'sudo sed -i "/rpt1Validation=/c\\rpt1Validation=1" /etc/dstarrepeater'; + // Set Standard IP/Port for DStarRepeater/MMDVMHost + $rollRepeaterAddress1 = 'sudo sed -i "/repeaterAddress1=/c\\repeaterAddress1=127.0.0.1" /etc/ircddbgateway'; + $rollRepeaterPort1 = 'sudo sed -i "/repeaterPort1=/c\\repeaterPort1=20011" /etc/ircddbgateway'; + + if ( $confHardware == 'idrp2c' ) { + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=1" /etc/ircddbgateway'; + $rollRepeaterAddress1 = 'sudo sed -i "/repeaterAddress1=/c\\repeaterAddress1=172.16.0.1" /etc/ircddbgateway'; + $rollRepeaterPort1 = 'sudo sed -i "/repeaterPort1=/c\\repeaterPort1=20000" /etc/ircddbgateway'; + system($rollRepeaterType1); + $testNeworkConfig = exec('grep "eth0:1" /etc/network/interfaces | wc -l'); + if (substr($testNeworkConfig, 0, 1) === '0') { + system('sudo sed -i "$ a\ \\nauto eth0:1\\nallow-hotplug eth0:1\\niface eth0:1 inet static\\n address 172.16.0.20\\n netmask 255.255.255.0" /etc/network/interfaces'); + } + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'icomTerminalAuto' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=Icom Access Point\/Terminal Mode" /etc/dstarrepeater'; + $rollIcomPort = 'sudo sed -i "/icomPort=/c\\icomPort=/dev/icom_ta" /etc/dstarrepeater'; + $rollRpt1Validation = 'sudo sed -i "/rpt1Validation=/c\\rpt1Validation=0" /etc/dstarrepeater'; + system($rollModemType); + system($rollIcomPort); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'dvmpis' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyAMA0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=2" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmpid' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyAMA0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=3" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmuadu' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyUSB0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=3" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmuada' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyACM0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=3" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmbss' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyUSB0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=2" /etc/dstarrepeater'; + $rollDstarRepeaterStartDelay = 'sudo sed -i "/OnStartupSec=/c\\OnStartupSec=60" /lib/systemd/system/dstarrepeater.timer'; + $rollMMDVMHostStartDelay = 'sudo sed -i "/OnStartupSec=/c\\OnStartupSec=60" /lib/systemd/system/mmdvmhost.timer'; + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmbsd' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyUSB0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=3" /etc/dstarrepeater'; + $rollDstarRepeaterStartDelay = 'sudo sed -i "/OnStartupSec=/c\\OnStartupSec=60" /lib/systemd/system/dstarrepeater.timer'; + $rollMMDVMHostStartDelay = 'sudo sed -i "/OnStartupSec=/c\\OnStartupSec=60" /lib/systemd/system/mmdvmhost.timer'; + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmuagmsku' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyUSB0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=0" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmuagmska' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyACM0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=0" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvrptr1' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DV-RPTR V1" /etc/dstarrepeater'; + $rollDVRPTRPort = 'sudo sed -i "/dvrptr1Port=/c\\dvrptr1Port=/dev/ttyACM0" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVRPTRPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvrptr2' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DV-RPTR V2" /etc/dstarrepeater'; + $rollDVRPTRPort = 'sudo sed -i "/dvrptr1Port=/c\\dvrptr1Port=/dev/ttyACM0" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVRPTRPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvrptr3' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DV-RPTR V3" /etc/dstarrepeater'; + $rollDVRPTRPort = 'sudo sed -i "/dvrptr1Port=/c\\dvrptr1Port=/dev/ttyACM0" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollDVRPTRPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'gmsk_modem' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=GMSK Modem" /etc/dstarrepeater'; + system($rollModemType); + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvap' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVAP" /etc/dstarrepeater'; + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'zumspotlibre' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'zumspotusb' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'lsusb' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'zumspotgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'zumspotdualgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'zumspotduplexgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'zumradiopiusb' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'zumradiopigpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollMMDVMPort = 'sudo sed -i "/mmdvmPort=/c\\mmdvmPort=/dev/ttyAMA0" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollMMDVMPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'zum' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollMMDVMPort = 'sudo sed -i "/mmdvmPort=/c\\mmdvmPort=/dev/ttyACM0" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollMMDVMPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'stm32dvm' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollMMDVMPort = 'sudo sed -i "/mmdvmPort=/c\\mmdvmPort=/dev/ttyAMA0" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollMMDVMPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'stm32usb' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollMMDVMPort = 'sudo sed -i "/mmdvmPort=/c\\mmdvmPort=/dev/ttyUSB0" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollMMDVMPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'f4mgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollMMDVMPort = 'sudo sed -i "/mmdvmPort=/c\\mmdvmPort=/dev/ttyAMA0" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollMMDVMPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'f4mf7m' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'mmdvmhshat' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'lshshatgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'mmdvmhshatambe' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttySC0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mmdvmhsdualbandgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'sbhsdualbandgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'genesyshat' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'genesysdualhat' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mmdvmhsdualhatgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'lshsdualhatgpio' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mmdvmhsdualhatusb' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mmdvmrpthat' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'mmdvmmdohat' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mmdvmvyehat' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mmdvmvyehatdual' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 1; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mnnano-spot' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'mnnano-teensy' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollMMDVMPort = 'sudo sed -i "/mmdvmPort=/c\\mmdvmPort=/dev/ttyUSB0" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollMMDVMPort); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyUSB0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'nanodv' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'nanodvusb' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + } + + if ( $confHardware == 'dvmpicast' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyAMA0" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=2" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + $configmmdvm['Modem']['Port'] = "/dev/ttyAMA0"; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmpicasths' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyS2" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=3" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + $configmmdvm['Modem']['Port'] = "/dev/ttyS2"; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'dvmpicasthd' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=DVMEGA" /etc/dstarrepeater'; + $rollDVMegaPort = 'sudo sed -i "/dvmegaPort=/c\\dvmegaPort=/dev/ttyS2" /etc/dstarrepeater'; + $rollDVMegaVariant = 'sudo sed -i "/dvmegaVariant=/c\\dvmegaVariant=3" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + $configmmdvm['Modem']['Port'] = "/dev/ttyS2"; + system($rollModemType); + system($rollDVMegaPort); + system($rollDVMegaVariant); + system($rollRepeaterType1); + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + if ( $confHardware == 'opengd77' ) { + $rollModemType = 'sudo sed -i "/modemType=/c\\modemType=MMDVM" /etc/dstarrepeater'; + $rollRepeaterType1 = 'sudo sed -i "/repeaterType1=/c\\repeaterType1=0" /etc/ircddbgateway'; + system($rollModemType); + system($rollRepeaterType1); + $configmmdvm['Modem']['Port'] = "/dev/ttyACM0"; + $configmmdvm['General']['Duplex'] = 0; + $configmmdvm['DMR Network']['Slot1'] = 0; + $configmmdvm['Modem']['Protocol'] = "uart"; + $configmmdvm['Modem']['UARTPort'] = $configmmdvm['Modem']['Port']; + if (isset($configmmdvm['FM']['Enable'])) { $configmmdvm['FM']['Enable'] = "0"; } + } + + // Set the Service start delay + system($rollDstarRepeaterStartDelay); + system($rollMMDVMHostStartDelay); + // Turn on RPT1 validation on DStarRepeater + system($rollRpt1Validation); + // Set Standard IP/Port for ircDDBGateway + system($rollRepeaterAddress1); + system($rollRepeaterPort1); + } + + // Set the Dashboard Public + if (empty($_POST['dashAccess']) != TRUE ) { + $publicDashboard = 'sudo sed -i \'/$ipVar 80 80/c\\\t\t$DAEMON -u ${igdURL} -e ${hostVar}_Dash -a $ipVar 80 80 TCP > /dev/null 2>&1\' /usr/local/sbin/pistar-upnp.service'; + $privateDashboard = 'sudo sed -i \'/$ipVar 80 80/ s/^#*/#/\' /usr/local/sbin/pistar-upnp.service'; + + if (escapeshellcmd($_POST['dashAccess']) == 'PUB' ) { system($publicDashboard); } + if (escapeshellcmd($_POST['dashAccess']) == 'PRV' ) { system($privateDashboard); } + } + + // Set the ircDDBGateway Remote Public + if (empty($_POST['ircRCAccess']) != TRUE ) { + $publicRCirc = 'sudo sed -i \'/$ipVar 10022 10022/c\\\t\t\t$DAEMON -u ${igdURL} -e ${hostVar}_Remote -a $ipVar 10022 10022 UDP > /dev/null 2>&1\' /usr/local/sbin/pistar-upnp.service'; + $privateRCirc = 'sudo sed -i \'/$ipVar 10022 10022/ s/^#*/#/\' /usr/local/sbin/pistar-upnp.service'; + + if (escapeshellcmd($_POST['ircRCAccess']) == 'PUB' ) { system($publicRCirc); } + if (escapeshellcmd($_POST['ircRCAccess']) == 'PRV' ) { system($privateRCirc); } + } + + // Set SSH Access Public + if (empty($_POST['sshAccess']) != TRUE ) { + $publicSSH = 'sudo sed -i \'/$ipVar 22 22/c\\\t\t$DAEMON -u ${igdURL} -e ${hostVar}_SSH -a $ipVar 22 22 TCP > /dev/null 2>&1\' /usr/local/sbin/pistar-upnp.service'; + $privateSSH = 'sudo sed -i \'/$ipVar 22 22/ s/^#*/#/\' /usr/local/sbin/pistar-upnp.service'; + + if (escapeshellcmd($_POST['sshAccess']) == 'PUB' ) { system($publicSSH); } + if (escapeshellcmd($_POST['sshAccess']) == 'PRV' ) { system($privateSSH); } + } + + // Set uPNP On or Off + if (empty($_POST['uPNP']) != TRUE ) { + $uPNPon = 'sudo sed -i \'/pistar-upnp.service/c\\*/5 *\t* * *\troot\t/usr/local/sbin/pistar-upnp.service start > /dev/null 2>&1 &\' /etc/crontab'; + $uPNPoff = 'sudo sed -i \'/pistar-upnp.service/ s/^#*/#/\' /etc/crontab'; + $uPNPsvcOn = 'sudo systemctl enable pistar-upnp.timer'; + $uPNPsvcOff = 'sudo systemctl disable pistar-upnp.timer'; + $uPNPsvcStart = '(sudo systemctl stop pistar-upnp.service && sudo systemctl start pistar-upnp.service) > /dev/null 2>&1 &'; + $uPNPsvcStop = '(sudo systemctl stop pistar-upnp.service) > /dev/null 2>&1 &'; + + if (escapeshellcmd($_POST['uPNP']) == 'ON' ) { system($uPNPon); system($uPNPsvcOn); system($uPNPsvcStart); } + if (escapeshellcmd($_POST['uPNP']) == 'OFF' ) { system($uPNPoff); system($uPNPsvcStop); system($uPNPsvcOff); } + } + + // D-Star Time Announce + if (empty($_POST['confTimeAnnounce']) != TRUE ) { + if (escapeshellcmd($_POST['confTimeAnnounce']) == 'ON' ) { system('sudo rm -rf /etc/timeserver.dissable'); } + if (escapeshellcmd($_POST['confTimeAnnounce']) == 'OFF' ) { system('sudo touch /etc/timeserver.dissable'); } + } + + // Set MMDVMHost DMR Mode + if (empty($_POST['MMDVMModeDMR']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeDMR']) == 'ON' ) { $configmmdvm['DMR']['Enable'] = "1"; $configmmdvm['DMR Network']['Enable'] = "1"; $configysf2dmr['Enabled']['Enabled'] = "0";} + if (escapeshellcmd($_POST['MMDVMModeDMR']) == 'OFF' ) { $configmmdvm['DMR']['Enable'] = "0"; $configmmdvm['DMR Network']['Enable'] = "0"; } + } + + // Set MMDVMHost D-Star Mode + if (empty($_POST['MMDVMModeDSTAR']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeDSTAR']) == 'ON' ) { $configmmdvm['D-Star']['Enable'] = "1"; $configmmdvm['D-Star Network']['Enable'] = "1"; } + if (escapeshellcmd($_POST['MMDVMModeDSTAR']) == 'OFF' ) { $configmmdvm['D-Star']['Enable'] = "0"; $configmmdvm['D-Star Network']['Enable'] = "0"; } + } + + // Set MMDVMHost Fusion Mode + if (empty($_POST['MMDVMModeFUSION']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeFUSION']) == 'ON' ) { $configmmdvm['System Fusion']['Enable'] = "1"; $configmmdvm['System Fusion Network']['Enable'] = "1"; $configdmr2ysf['Enabled']['Enabled'] = "0"; } + if (escapeshellcmd($_POST['MMDVMModeFUSION']) == 'OFF' ) { $configmmdvm['System Fusion']['Enable'] = "0"; $configmmdvm['System Fusion Network']['Enable'] = "0"; } + } + + // Set MMDVMHost P25 Mode + if (empty($_POST['MMDVMModeP25']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeP25']) == 'ON' ) { $configmmdvm['P25']['Enable'] = "1"; $configmmdvm['P25 Network']['Enable'] = "1"; $configysf2p25['Enabled']['Enabled'] = "0"; } + if (escapeshellcmd($_POST['MMDVMModeP25']) == 'OFF' ) { $configmmdvm['P25']['Enable'] = "0"; $configmmdvm['P25 Network']['Enable'] = "0"; } + } + + // Set MMDVMHost NXDN Mode + if (empty($_POST['MMDVMModeNXDN']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeNXDN']) == 'ON' ) { $configmmdvm['NXDN']['Enable'] = "1"; $configmmdvm['NXDN Network']['Enable'] = "1"; $configysf2nxdn['Enabled']['Enabled'] = "0"; } + if (escapeshellcmd($_POST['MMDVMModeNXDN']) == 'OFF' ) { $configmmdvm['NXDN']['Enable'] = "0"; $configmmdvm['NXDN Network']['Enable'] = "0"; } + } + + // Set MMDVMHost M17 Mode + if (empty($_POST['MMDVMModeM17']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeM17']) == 'ON' ) { $configmmdvm['M17']['Enable'] = "1"; $configmmdvm['M17 Network']['Enable'] = "1"; } + if (escapeshellcmd($_POST['MMDVMModeM17']) == 'OFF' ) { $configmmdvm['M17']['Enable'] = "0"; $configmmdvm['M17 Network']['Enable'] = "0"; } + } + + // Set YSF2DMR Mode + if (empty($_POST['MMDVMModeYSF2DMR']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeYSF2DMR']) == 'ON' ) { $configysf2dmr['Enabled']['Enabled'] = "1"; } + if (escapeshellcmd($_POST['MMDVMModeYSF2DMR']) == 'OFF' ) { $configysf2dmr['Enabled']['Enabled'] = "0"; } + } + + // Set YSF2NXDN Mode + if (empty($_POST['MMDVMModeYSF2NXDN']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeYSF2NXDN']) == 'ON' ) { $configysf2nxdn['Enabled']['Enabled'] = "1"; $configmmdvm['NXDN']['Enable'] = "0"; $configmmdvm['NXDN Network']['Enable'] = "0";} + if (escapeshellcmd($_POST['MMDVMModeYSF2NXDN']) == 'OFF' ) { $configysf2nxdn['Enabled']['Enabled'] = "0"; } + } + + // Set YSF2P25 Mode + if (empty($_POST['MMDVMModeYSF2P25']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeYSF2P25']) == 'ON' ) { $configysf2p25['Enabled']['Enabled'] = "1"; $configmmdvm['P25']['Enable'] = "0"; $configmmdvm['P25 Network']['Enable'] = "0"; } + if (escapeshellcmd($_POST['MMDVMModeYSF2P25']) == 'OFF' ) { $configysf2p25['Enabled']['Enabled'] = "0"; } + if (escapeshellcmd($_POST['MMDVMModeFUSION']) == 'OFF' ) { $configysf2p25['Enabled']['Enabled'] = "0"; } + } + + // Set DMR2YSF Mode + if (empty($_POST['MMDVMModeDMR2YSF']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeDMR2YSF']) == 'ON' ) { + $configdmr2ysf['Enabled']['Enabled'] = "1"; + unset($configdmrgateway['DMR Network 3']); + $configdmrgateway['DMR Network 3']['Enabled'] = "0"; + $configdmrgateway['DMR Network 3']['Name'] = "DMR2YSF_Cross-over"; + $configdmrgateway['DMR Network 3']['Id'] = $configdmrgateway['DMR Network 2']['Id']; + $configdmrgateway['DMR Network 3']['Address'] = "127.0.0.1"; + $configdmrgateway['DMR Network 3']['Port'] = "62033"; + $configdmrgateway['DMR Network 3']['Local'] = "62034"; + $configdmrgateway['DMR Network 3']['TGRewrite0'] = "2,7000001,2,1,999998"; + $configdmrgateway['DMR Network 3']['SrcRewrite0'] = "2,1,2,7000001,999998"; + $configdmrgateway['DMR Network 3']['PCRewrite0'] = "2,7000001,2,1,999998"; + $configdmrgateway['DMR Network 3']['Password'] = '"'."PASSWORD".'"'; + $configdmrgateway['DMR Network 3']['Location'] = "0"; + $configdmrgateway['DMR Network 3']['Debug'] = "0"; + $configmmdvm['System Fusion']['Enable'] = "0"; + $configmmdvm['System Fusion Network']['Enable'] = "0"; + } + if (escapeshellcmd($_POST['MMDVMModeDMR2YSF']) == 'OFF' ) { + $configdmr2ysf['Enabled']['Enabled'] = "0"; + $configdmrgateway['DMR Network 3']['Enabled'] = "0"; + } + } + + // Set DMR2NXDN Mode + if (empty($_POST['MMDVMModeDMR2NXDN']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeDMR2NXDN']) == 'ON' ) { + if (empty($_POST['MMDVMModeDMR2YSF']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeDMR2YSF']) == 'ON' ) { + $configdmr2ysf['Enabled']['Enabled'] = "0"; + } + } + if (empty($_POST['MMDVMModeYSF2NXDN']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModeYSF2NXDN']) == 'ON' ) { + $configysf2nxdn['Enabled']['Enabled'] = "0"; + } + } + $configdmr2nxdn['Enabled']['Enabled'] = "1"; + unset($configdmrgateway['DMR Network 3']); + $configdmrgateway['DMR Network 3']['Enabled'] = "0"; + $configdmrgateway['DMR Network 3']['Name'] = "DMR2NXDN_Cross-over"; + $configdmrgateway['DMR Network 3']['Id'] = $configdmrgateway['DMR Network 2']['Id']; + $configdmrgateway['DMR Network 3']['Address'] = "127.0.0.1"; + $configdmrgateway['DMR Network 3']['Port'] = "62035"; + $configdmrgateway['DMR Network 3']['Local'] = "62036"; + $configdmrgateway['DMR Network 3']['TGRewrite0'] = "2,7000001,2,1,999998"; + $configdmrgateway['DMR Network 3']['SrcRewrite0'] = "2,1,2,7000001,999998"; + $configdmrgateway['DMR Network 3']['PCRewrite0'] = "2,7000001,2,1,999998"; + $configdmrgateway['DMR Network 3']['Password'] = '"'."PASSWORD".'"'; + $configdmrgateway['DMR Network 3']['Location'] = "0"; + $configdmrgateway['DMR Network 3']['Debug'] = "0"; + $configmmdvm['NXDN']['Enable'] = "0"; + $configmmdvm['NXDN Network']['Enable'] = "0"; + } + if (escapeshellcmd($_POST['MMDVMModeDMR2NXDN']) == 'OFF' ) { + $configdmr2nxdn['Enabled']['Enabled'] = "0"; + $configdmrgateway['DMR Network 3']['Enabled'] = "0"; + } + } + + // Work out if DMR Network 3 should be ON or not + if (empty($_POST['MMDVMModeDMR2YSF']) != TRUE || empty($_POST['MMDVMModeDMR2NXDN']) != TRUE) { + if (escapeshellcmd($_POST['MMDVMModeDMR2YSF']) == 'ON' || escapeshellcmd($_POST['MMDVMModeDMR2NXDN']) == 'ON') { + $configdmrgateway['DMR Network 3']['Enabled'] = "1"; + } else { + $configdmrgateway['DMR Network 3']['Enabled'] = "0"; + } + } + + // Set POCSAG Mode + if (empty($_POST['MMDVMModePOCSAG']) != TRUE ) { + if (escapeshellcmd($_POST['MMDVMModePOCSAG']) == 'ON' ) { $configmmdvm['POCSAG']['Enable'] = "1"; $configmmdvm['POCSAG Network']['Enable'] = "1"; } + if (escapeshellcmd($_POST['MMDVMModePOCSAG']) == 'OFF' ) { $configmmdvm['POCSAG']['Enable'] = "0"; $configmmdvm['POCSAG Network']['Enable'] = "0"; } + } + + // Set the MMDVMHost Display Type + if (empty($_POST['mmdvmDisplayType']) != TRUE ) { + if (substr($_POST['mmdvmDisplayType'] , 0, 4 ) === "OLED") { + $configmmdvm['General']['Display'] = "OLED"; + $configmmdvm['OLED']['Type'] = substr($_POST['mmdvmDisplayType'] , 4, 1 ); + if ($configmmdvm['OLED']['Type'] == "6") { $configmmdvm['OLED']['Scroll'] = "0"; } + } + else { + $configmmdvm['General']['Display'] = escapeshellcmd($_POST['mmdvmDisplayType']); + } + } + + // Set the MMDVMHost Display Type + if (empty($_POST['mmdvmDisplayPort']) != TRUE ) { + if (($_POST['mmdvmDisplayPort'] == "None") || ($_POST['mmdvmDisplayPort'] == "modem")) { + $configmmdvm['Nextion']['Port'] = $_POST['mmdvmDisplayPort']; + } else { + $configmmdvm['Nextion']['Port'] = "/dev/".$_POST['mmdvmDisplayPort']; + } + } + + // Set the Nextion Display Layout + if (empty($_POST['mmdvmNextionDisplayType']) != TRUE ) { + if (escapeshellcmd($_POST['mmdvmNextionDisplayType']) == "G4KLX") { $configmmdvm['Nextion']['ScreenLayout'] = "0"; } + if (escapeshellcmd($_POST['mmdvmNextionDisplayType']) == "ON7LDSL2") { $configmmdvm['Nextion']['ScreenLayout'] = "2"; } + if (escapeshellcmd($_POST['mmdvmNextionDisplayType']) == "ON7LDSL3") { $configmmdvm['Nextion']['ScreenLayout'] = "3"; } + if (escapeshellcmd($_POST['mmdvmNextionDisplayType']) == "ON7LDSL3HS") { $configmmdvm['Nextion']['ScreenLayout'] = "4"; } + } + + // Set MMDVMHost DMR Colour Code + if (empty($_POST['dmrColorCode']) != TRUE ) { + $configmmdvm['DMR']['ColorCode'] = escapeshellcmd($_POST['dmrColorCode']); + } + + // Set MMDVMHost DMR Access List + if (isset($configmmdvm['DMR']['WhiteList'])) { unset($configmmdvm['DMR']['WhiteList']); } + if (empty($_POST['confDMRWhiteList']) != TRUE ) { + $configmmdvm['DMR']['WhiteList'] = escapeshellcmd(preg_replace('/[^0-9\,]/', '', $_POST['confDMRWhiteList'])); + } + + // Set Node Lock Status + if (empty($_POST['nodeMode']) != TRUE ) { + if (escapeshellcmd($_POST['nodeMode']) == 'prv' ) { + $configmmdvm['DMR']['SelfOnly'] = 1; + $configmmdvm['D-Star']['SelfOnly'] = 1; + $configmmdvm['System Fusion']['SelfOnly'] = 1; + $configmmdvm['P25']['SelfOnly'] = 1; + $configmmdvm['NXDN']['SelfOnly'] = 1; + system('sudo sed -i "/restriction=/c\\restriction=1" /etc/dstarrepeater'); + } + if (escapeshellcmd($_POST['nodeMode']) == 'pub' ) { + $configmmdvm['DMR']['SelfOnly'] = 0; + $configmmdvm['D-Star']['SelfOnly'] = 0; + $configmmdvm['System Fusion']['SelfOnly'] = 0; + $configmmdvm['P25']['SelfOnly'] = 0; + $configmmdvm['NXDN']['SelfOnly'] = 0; + system('sudo sed -i "/restriction=/c\\restriction=0" /etc/dstarrepeater'); + } + } + + // Set the Hostname + if (empty($_POST['confHostame']) != TRUE ) { + $newHostnameLower = strtolower(preg_replace('/[^A-Za-z0-9\-]/', '', $_POST['confHostame'])); + $currHostname = exec('cat /etc/hostname'); + $rollHostname = 'sudo sed -i "s/'.$currHostname.'/'.$newHostnameLower.'/" /etc/hostname'; + $rollHosts = 'sudo sed -i "s/'.$currHostname.'/'.$newHostnameLower.'/" /etc/hosts'; + $rollMotd = 'sudo sed -i "s/'.$currHostname.'/'.$newHostnameLower.'/" /etc/motd'; + system($rollHostname); + system($rollHosts); + system($rollMotd); + if (file_exists('/etc/hostapd/hostapd.conf')) { + // Update the Hotspot name to the Hostname + $rollApSsid = 'sudo sed -i "/^ssid=/c\\ssid='.$newHostnameLower.'" /etc/hostapd/hostapd.conf'; + system($rollApSsid); + } + } + + // Add missing values to DMRGateway + if (!isset($configdmrgateway['Info']['Enabled'])) { $configdmrgateway['Info']['Enabled'] = "0"; } + if (!isset($configdmrgateway['Info']['Power'])) { $configdmrgateway['Info']['Power'] = $configmmdvm['Info']['Power']; } + if (!isset($configdmrgateway['Info']['Height'])) { $configdmrgateway['Info']['Height'] = $configmmdvm['Info']['Height']; } + if (!isset($configdmrgateway['XLX Network']['Enabled'])) { $configdmrgateway['XLX Network']['Enabled'] = "0"; } + if (!isset($configdmrgateway['XLX Network']['File'])) { $configdmrgateway['XLX Network']['File'] = "/usr/local/etc/XLXHosts.txt"; } + if (!isset($configdmrgateway['XLX Network']['Port'])) { $configdmrgateway['XLX Network']['Port'] = "62030"; } + if (!isset($configdmrgateway['XLX Network']['Password'])) { $configdmrgateway['XLX Network']['Password'] = "passw0rd"; } + if (!isset($configdmrgateway['XLX Network']['ReloadTime'])) { $configdmrgateway['XLX Network']['ReloadTime'] = "60"; } + if (!isset($configdmrgateway['XLX Network']['Slot'])) { $configdmrgateway['XLX Network']['Slot'] = "2"; } + if (!isset($configdmrgateway['XLX Network']['TG'])) { $configdmrgateway['XLX Network']['TG'] = "6"; } + if (!isset($configdmrgateway['XLX Network']['Base'])) { $configdmrgateway['XLX Network']['Base'] = "64000"; } + if (!isset($configdmrgateway['XLX Network']['Startup'])) { $configdmrgateway['XLX Network']['Startup'] = "950"; } + if (!isset($configdmrgateway['XLX Network']['Relink'])) { $configdmrgateway['XLX Network']['Relink'] = "60"; } + if (!isset($configdmrgateway['XLX Network']['Debug'])) { $configdmrgateway['XLX Network']['Debug'] = "0"; } + if (!isset($configdmrgateway['DMR Network 3']['Enabled'])) { $configdmrgateway['DMR Network 3']['Enabled'] = "0"; } + if (!isset($configdmrgateway['DMR Network 3']['Name'])) { $configdmrgateway['DMR Network 3']['Name'] = "HBLink"; } + if (!isset($configdmrgateway['DMR Network 3']['Address'])) { $configdmrgateway['DMR Network 3']['Address'] = "1.2.3.4"; } + if (!isset($configdmrgateway['DMR Network 3']['Port'])) { $configdmrgateway['DMR Network 3']['Port'] = "5555"; } + if (!isset($configdmrgateway['DMR Network 3']['TGRewrite0'])) { $configdmrgateway['DMR Network 3']['TGRewrite0'] = "2,11,2,11,1"; } + if (!isset($configdmrgateway['DMR Network 3']['Password'])) { $configdmrgateway['DMR Network 3']['Password'] = "PASSWORD"; } + if (!isset($configdmrgateway['DMR Network 3']['Location'])) { $configdmrgateway['DMR Network 3']['Location'] = "0"; } + if (!isset($configdmrgateway['DMR Network 3']['Debug'])) { $configdmrgateway['DMR Network 3']['Debug'] = "0"; } + if (!isset($configdmrgateway['XLX Network']['UserControl'])) { $configdmrgateway['XLX Network']['UserControl'] = "1"; } + $dmrGatewayVer = exec("DMRGateway -v | awk {'print $3'} | cut -c 1-8"); + if ($dmrGatewayVer > 20210130) { + if (!isset($configdmrgateway['DMR Network 1']['Location'])) { $configdmrgateway['DMR Network 1']['Location'] = "1"; } + if (!isset($configdmrgateway['DMR Network 2']['Location'])) { $configdmrgateway['DMR Network 2']['Location'] = "0"; } + if (!isset($configdmrgateway['DMR Network 3']['Location'])) { $configdmrgateway['DMR Network 3']['Location'] = "0"; } + if (isset($configdmrgateway['DMR Network 4'])) { + if (!isset($configdmrgateway['DMR Network 4']['Location'])) { $configdmrgateway['DMR Network 4']['Location'] = "0"; } + } + if (isset($configdmrgateway['DMR Network 5'])) { + if (!isset($configdmrgateway['DMR Network 5']['Location'])) { $configdmrgateway['DMR Network 5']['Location'] = "0"; } + } + if (isset($configdmrgateway['DMR Network 6'])) { + if (!isset($configdmrgateway['DMR Network 6']['Location'])) { $configdmrgateway['DMR Network 6']['Location'] = "0"; } + } + if (!isset($configdmrgateway['GPSD'])) { + $configdmrgateway['GPSD']['Enable'] = "0"; + $configdmrgateway['GPSD']['Address'] = "127.0.0.1"; + $configdmrgateway['GPSD']['Port'] = "2947"; + } + if (!isset($configdmrgateway['APRS'])) { + $configdmrgateway['APRS']['Enable'] = "1"; + $configdmrgateway['APRS']['Address'] = "127.0.0.1"; + $configdmrgateway['APRS']['Port'] = "8673"; + $configdmrgateway['APRS']['Description'] = "APRS for DMRGateway"; + $configdmrgateway['APRS']['Suffix'] = "DMR"; + } + if (!isset($configdmrgateway['Dynamic TG Control'])) { + $configdmrgateway['Dynamic TG Control']['Enabled'] = "1"; + $configdmrgateway['Dynamic TG Control']['Port'] = "3769"; + } + } + // DMRGateway can break the lines with quotes in, when DMRGateway is off... + if ( isset($configdmrgateway['Info']['Location']) && substr($configdmrgateway['Info']['Location'], 0, 1) !== '"' ) { $configdmrgateway['Info']['Location'] = '"'.$configdmrgateway['Info']['Location'].'"'; } + if ( isset($configdmrgateway['Info']['Description']) && substr($configdmrgateway['Info']['Description'], 0, 1) !== '"' ) { $configdmrgateway['Info']['Description'] = '"'.$configdmrgateway['Info']['Description'].'"'; } + if ( isset($configdmrgateway['DMR Network 1']['Password']) && substr($configdmrgateway['DMR Network 1']['Password'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 1']['Password'] = '"'.$configdmrgateway['DMR Network 1']['Password'].'"'; } + if ( isset($configdmrgateway['DMR Network 1']['Options']) && substr($configdmrgateway['DMR Network 1']['Options'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 1']['Options'] = '"'.$configdmrgateway['DMR Network 1']['Options'].'"'; } + if ( isset($configdmrgateway['DMR Network 2']['Password']) && substr($configdmrgateway['DMR Network 2']['Password'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 2']['Password'] = '"'.$configdmrgateway['DMR Network 2']['Password'].'"'; } + if ( isset($configdmrgateway['DMR Network 2']['Options']) && substr($configdmrgateway['DMR Network 2']['Options'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 2']['Options'] = '"'.$configdmrgateway['DMR Network 2']['Options'].'"'; } + if ( isset($configdmrgateway['DMR Network 3']['Password']) && substr($configdmrgateway['DMR Network 3']['Password'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 3']['Password'] = '"'.$configdmrgateway['DMR Network 3']['Password'].'"'; } + if ( isset($configdmrgateway['DMR Network 3']['Options']) && substr($configdmrgateway['DMR Network 3']['Options'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 3']['Options'] = '"'.$configdmrgateway['DMR Network 3']['Options'].'"'; } + if ( isset($configdmrgateway['DMR Network 4']['Password']) && substr($configdmrgateway['DMR Network 4']['Password'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 4']['Password'] = '"'.$configdmrgateway['DMR Network 4']['Password'].'"'; } + if ( isset($configdmrgateway['DMR Network 4']['Options']) && substr($configdmrgateway['DMR Network 4']['Options'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 4']['Options'] = '"'.$configdmrgateway['DMR Network 4']['Options'].'"'; } + if ( isset($configdmrgateway['DMR Network 5']['Password']) && substr($configdmrgateway['DMR Network 5']['Password'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 5']['Password'] = '"'.$configdmrgateway['DMR Network 5']['Password'].'"'; } + if ( isset($configdmrgateway['DMR Network 5']['Options']) && substr($configdmrgateway['DMR Network 5']['Options'], 0, 1) !== '"' ) { $configdmrgateway['DMR Network 5']['Options'] = '"'.$configdmrgateway['DMR Network 5']['Options'].'"'; } + + // Add missing options to MMDVMHost + if (!isset($configmmdvm['Modem']['RFLevel'])) { $configmmdvm['Modem']['RFLevel'] = "100"; } + if (!isset($configmmdvm['Modem']['RXDCOffset'])) { $configmmdvm['Modem']['RXDCOffset'] = "0"; } + if (!isset($configmmdvm['Modem']['TXDCOffset'])) { $configmmdvm['Modem']['TXDCOffset'] = "0"; } + if (!isset($configmmdvm['Modem']['CWIdTXLevel'])) { $configmmdvm['Modem']['CWIdTXLevel'] = "50"; } + if (!isset($configmmdvm['Modem']['NXDNTXLevel'])) { $configmmdvm['Modem']['NXDNTXLevel'] = "50"; } + if (!isset($configmmdvm['Modem']['M17TXLevel'])) { $configmmdvm['Modem']['M17TXLevel'] = "50"; } + if (!isset($configmmdvm['Modem']['POCSAGTXLevel'])) { $configmmdvm['Modem']['POCSAGTXLevel'] = "50"; } + if (!isset($configmmdvm['Modem']['FMTXLevel'])) { $configmmdvm['Modem']['FMTXLevel'] = "50"; } + if (!isset($configmmdvm['Modem']['AX25TXLevel'])) { $configmmdvm['Modem']['AX25TXLevel'] = "50"; } + if (!isset($configmmdvm['Modem']['UseCOSAsLockout'])) { $configmmdvm['Modem']['UseCOSAsLockout'] = "0"; } + if (!isset($configmmdvm['Modem']['UARTSpeed'])) { $configmmdvm['Modem']['UARTSpeed'] = "115200"; } + if (!isset($configmmdvm['Transparent Data']['SendFrameType'])) { $configmmdvm['Transparent Data']['SendFrameType'] = "0"; } + if (!isset($configmmdvm['D-Star']['AckReply'])) { $configmmdvm['D-Star']['AckReply'] = "1"; } + if (!isset($configmmdvm['D-Star']['AckTime'])) { $configmmdvm['D-Star']['AckTime'] = "750"; } + if (!isset($configmmdvm['D-Star']['AckMessage'])) { $configmmdvm['D-Star']['AckMessage'] = "0"; } + if (!isset($configmmdvm['D-Star']['RemoteGateway'])) { $configmmdvm['D-Star']['RemoteGateway'] = "0"; } + if (!isset($configmmdvm['DMR']['BeaconInterval'])) { $configmmdvm['DMR']['BeaconInterval'] = "60"; } + if (!isset($configmmdvm['DMR']['BeaconDuration'])) { $configmmdvm['DMR']['BeaconDuration'] = "3"; } + if (!isset($configmmdvm['DMR']['OVCM'])) { $configmmdvm['DMR']['OVCM'] = "0"; } + if (!isset($configmmdvm['DMR Network']['Type'])) { $configmmdvm['DMR Network']['Type'] = "Direct"; } + if (!isset($configmmdvm['P25']['RemoteGateway'])) { $configmmdvm['P25']['RemoteGateway'] = "0"; } + if (!isset($configmmdvm['P25']['TXHang'])) { $configmmdvm['P25']['TXHang'] = "5"; } + if (!isset($configmmdvm['OLED']['Scroll'])) { $configmmdvm['OLED']['Scroll'] = "0"; } + if (!isset($configmmdvm['NXDN']['Enable'])) { $configmmdvm['NXDN']['Enable'] = "0"; } + if (!isset($configmmdvm['NXDN']['RAN'])) { $configmmdvm['NXDN']['RAN'] = "1"; } + if (!isset($configmmdvm['NXDN']['SelfOnly'])) { $configmmdvm['NXDN']['SelfOnly'] = "1"; } + if (!isset($configmmdvm['NXDN']['RemoteGateway'])) { $configmmdvm['NXDN']['RemoteGateway'] = "0"; } + if (!isset($configmmdvm['NXDN']['TXHang'])) { $configmmdvm['NXDN']['TXHang'] = "5"; } + if (!isset($configmmdvm['M17']['Enable'])) { $configmmdvm['M17']['Enable'] = "0"; } + if (!isset($configmmdvm['M17']['CAN'])) { $configmmdvm['M17']['CAN'] = "0"; } + if (!isset($configmmdvm['M17']['SelfOnly'])) { $configmmdvm['M17']['SelfOnly'] = "1"; } + if (!isset($configmmdvm['M17']['TXHang'])) { $configmmdvm['M17']['TXHang'] = "5"; } + if (!isset($configmmdvm['M17']['Enable'])) { $configmmdvm['M17']['Enable'] = "0"; } + if (!isset($configmmdvm['M17']['ModeHang'])) { $configmmdvm['M17']['ModeHang'] = "20"; } + if (!isset($configmmdvm['AX.25']['Enable'])) { $configmmdvm['AX.25']['Enable'] = "0"; } + if (!isset($configmmdvm['AX.25']['TXDelay'])) { $configmmdvm['AX.25']['TXDelay'] = "300"; } + if (!isset($configmmdvm['AX.25']['RXTwist'])) { $configmmdvm['AX.25']['RXTwist'] = "6"; } + if (!isset($configmmdvm['AX.25']['SlotTime'])) { $configmmdvm['AX.25']['SlotTime'] = "30"; } + if (!isset($configmmdvm['AX.25']['PPersist'])) { $configmmdvm['AX.25']['PPersist'] = "128"; } + if (!isset($configmmdvm['AX.25']['Trace'])) { $configmmdvm['AX.25']['Trace'] = "0"; } + if (!isset($configmmdvm['NXDN Network']['Enable'])) { $configmmdvm['NXDN Network']['Enable'] = "0"; } + if (!isset($configmmdvm['NXDN Network']['LocalPort'])) { $configmmdvm['NXDN Network']['LocalPort'] = "3300"; } + if (!isset($configmmdvm['NXDN Network']['GatewayAddress'])) { $configmmdvm['NXDN Network']['GatewayAddress'] = "127.0.0.1"; } + if (!isset($configmmdvm['NXDN Network']['GatewayPort'])) { $configmmdvm['NXDN Network']['GatewayPort'] = "4300"; } + if (!isset($configmmdvm['NXDN Network']['Protocol'])) { $configmmdvm['NXDN Network']['Protocol'] = "Icom"; } + if (!isset($configmmdvm['NXDN Network']['Debug'])) { $configmmdvm['NXDN Network']['Debug'] = "0"; } + if (!isset($configmmdvm['M17 Network']['Enable'])) { $configmmdvm['M17 Network']['Enable'] = "0"; } + if (!isset($configmmdvm['M17 Network']['LocalAddress'])) { $configmmdvm['M17 Network']['LocalAddress'] = "127.0.0.1"; } + if (!isset($configmmdvm['M17 Network']['LocalPort'])) { $configmmdvm['M17 Network']['LocalPort'] = "17011"; } + if (!isset($configmmdvm['M17 Network']['GatewayAddress'])) { $configmmdvm['M17 Network']['GatewayAddress'] = "127.0.0.1"; } + if (!isset($configmmdvm['M17 Network']['GatewayPort'])) { $configmmdvm['M17 Network']['GatewayPort'] = "17010"; } + if (!isset($configmmdvm['M17 Network']['ModeHang'])) { $configmmdvm['M17 Network']['ModeHang'] = "20"; } + if (!isset($configmmdvm['M17 Network']['Debug'])) { $configmmdvm['M17 Network']['Debug'] = "0"; } + if (!isset($configmmdvm['AX.25 Network']['Enable'])) { $configmmdvm['AX.25 Network']['Enable'] = "0"; } + if (!isset($configmmdvm['AX.25 Network']['Port'])) { $configmmdvm['AX.25 Network']['Port'] = "/dev/ttyp7"; } + if (!isset($configmmdvm['AX.25 Network']['Speed'])) { $configmmdvm['AX.25 Network']['Speed'] = "9600"; } + if (!isset($configmmdvm['AX.25 Network']['Debug'])) { $configmmdvm['AX.25 Network']['Debug'] = "0"; } + if (!isset($configmmdvm['NXDN Id Lookup']['File'])) { $configmmdvm['NXDN Id Lookup']['File'] = "/usr/local/etc/NXDN.csv"; } + if (!isset($configmmdvm['NXDN Id Lookup']['Time'])) { $configmmdvm['NXDN Id Lookup']['Time'] = "24"; } + if (!isset($configmmdvm['System Fusion']['TXHang'])) { $configmmdvm['System Fusion']['TXHang'] = "3"; } + if (!isset($configmmdvm['Lock File']['Enable'])) { $configmmdvm['Lock File']['Enable'] = "0"; } + if (!isset($configmmdvm['Lock File']['File'])) { $configmmdvm['Lock File']['File'] = "/tmp/MMDVMHost.lock"; } + if (!isset($configmmdvm['Mobile GPS']['Enable'])) { $configmmdvm['Mobile GPS']['Enable'] = "0"; } + if (!isset($configmmdvm['Mobile GPS']['Address'])) { $configmmdvm['Mobile GPS']['Address'] = "127.0.0.1"; } + if (!isset($configmmdvm['Mobile GPS']['Port'])) { $configmmdvm['Mobile GPS']['Port'] = "7834"; } + if (!isset($configmmdvm['OLED']['Rotate'])) { $configmmdvm['OLED']['Rotate'] = "0"; } + if (!isset($configmmdvm['OLED']['Cast'])) { $configmmdvm['OLED']['Cast'] = "0"; } + if (!isset($configmmdvm['OLED']['LogoScreensaver'])) { $configmmdvm['OLED']['LogoScreensaver'] = "0"; } + if (!isset($configmmdvm['Remote Control']['Enable'])) { $configmmdvm['Remote Control']['Enable'] = "0"; } + if (!isset($configmmdvm['Remote Control']['Port'])) { $configmmdvm['Remote Control']['Port'] = "7642"; } + if (!isset($configmmdvm['Remote Control']['Address'])) { $configmmdvm['Remote Control']['Address'] = "127.0.0.1"; } + if (isset($configmmdvm['TFT Serial']['Port'])) { $configmmdvm['TFT Serial']['Port'] = "/dev/ttyAMA0"; } + if (isset($configmmdvm['Nextion']['Port'])) { + if ( $configmmdvm['Nextion']['Port'] == "/dev/modem" ) { $configmmdvm['Nextion']['Port'] = "modem"; } + } + if (!isset($configmmdvm['NextionDriver'])) { + $configmmdvm['NextionDriver']['Port'] = "modem"; + $configmmdvm['NextionDriver']['LogLevel'] = "2"; + $configmmdvm['NextionDriver']['DataFilesPath'] = "/usr/local/etc/"; + $configmmdvm['NextionDriver']['GroupsFile'] = "nextionGroups.txt"; + $configmmdvm['NextionDriver']['GroupsFileSrc'] = "https://www.pistar.uk/downloads/groups.txt"; + $configmmdvm['NextionDriver']['DMRidFile'] = "nextionUsers.csv"; + $configmmdvm['NextionDriver']['DMRidFileSrc'] = "https://www.pistar.uk/downloads/nextionUsers.csv"; + $configmmdvm['NextionDriver']['DMRidDelimiter'] = ","; + $configmmdvm['NextionDriver']['DMRidId'] = "1"; + $configmmdvm['NextionDriver']['DMRidCall'] = "2"; + $configmmdvm['NextionDriver']['DMRidName'] = "3"; + $configmmdvm['NextionDriver']['DMRidX1'] = "5"; + $configmmdvm['NextionDriver']['DMRidX2'] = "6"; + $configmmdvm['NextionDriver']['DMRidX3'] = "7"; + $configmmdvm['NextionDriver']['RemoveDim'] = "0"; + $configmmdvm['NextionDriver']['SleepWhenInactive'] = "600"; + $configmmdvm['NextionDriver']['ShowModesStatus'] = "0"; + $configmmdvm['NextionDriver']['WaitForLan'] = "1"; + $configmmdvm['Transparent Data']['Enable'] = "1"; + $configmmdvm['Transparent Data']['SendFrameType'] = "1"; + $configmmdvm['Nextion']['Port'] = "/dev/ttyNextionDriver"; + } + if (isset($configmmdvm['NextionDriver'])) { + if ($configmmdvm['Nextion']['Port'] == $configmmdvm['Modem']['Port']) { $configmmdvm['Nextion']['Port'] = "/dev/ttyNextionDriver"; } + if ($configmmdvm['Nextion']['Port'] == $configmmdvm['Modem']['UARTPort']) { $configmmdvm['Nextion']['Port'] = "/dev/ttyNextionDriver"; } + } + if (!isset($configmmdvm['FM'])) { + $configmmdvm['FM']['Enable'] = "0"; + $configmmdvm['FM']['Callsign'] = $newCallsignUpper; + $configmmdvm['FM']['CallsignSpeed'] = "20"; + $configmmdvm['FM']['CallsignFrequency'] = "1000"; + $configmmdvm['FM']['CallsignTime'] = "10"; + $configmmdvm['FM']['CallsignHoldoff'] = "0"; + $configmmdvm['FM']['CallsignHighLevel'] = "50"; + $configmmdvm['FM']['CallsignLowLevel'] = "20"; + $configmmdvm['FM']['CallsignAtStart'] = "1"; + $configmmdvm['FM']['CallsignAtEnd'] = "1"; + $configmmdvm['FM']['CallsignAtLatch'] = "0"; + $configmmdvm['FM']['RFAck'] = "K"; + $configmmdvm['FM']['ExtAck'] = "N"; + $configmmdvm['FM']['AckSpeed'] = "20"; + $configmmdvm['FM']['AckFrequency'] = "1750"; + $configmmdvm['FM']['AckMinTime'] = "4"; + $configmmdvm['FM']['AckDelay'] = "1000"; + $configmmdvm['FM']['AckLevel'] = "50"; + $configmmdvm['FM']['Timeout'] = "180"; + $configmmdvm['FM']['TimeoutLevel'] = "80"; + $configmmdvm['FM']['CTCSSFrequency'] = "94.8"; + $configmmdvm['FM']['CTCSSHighThreshold'] = "30"; + $configmmdvm['FM']['CTCSSLowThreshold'] = "20"; + $configmmdvm['FM']['CTCSSLevel'] = "20"; + $configmmdvm['FM']['KerchunkTime'] = "0"; + $configmmdvm['FM']['HangTime'] = "7"; + $configmmdvm['FM']['AccessMode'] = "1"; + $configmmdvm['FM']['LinkMode'] = "0"; + $configmmdvm['FM']['COSInvert'] = "0"; + $configmmdvm['FM']['NoiseSquelch'] = "0"; + $configmmdvm['FM']['SquelchHighThreshold'] = "30"; + $configmmdvm['FM']['SquelchLowThreshold'] = "20"; + $configmmdvm['FM']['RFAudioBoost'] = "1"; + $configmmdvm['FM']['MaxDevLevel'] = "90"; + $configmmdvm['FM']['ExtAudioBoost'] = "1"; + $configmmdvm['FM']['ModeHang'] = "20"; + } + + // Stop ircDDBGateway from trying to lookup rr.openquad.net (the hard coded default) all the time + if ( (!isset($configs['ircddbHostname2'])) && (!isset($configs['ircddbEnabled2'])) ) { + $fix2ndIRCHost = "sudo sed -i '/^ircddbEnabled=/a ircddbEnabled2=0' /etc/ircddbgateway"; + system($fix2ndIRCHost); + } + + // Add missing options to DMR2YSF + if (!isset($configdmr2ysf['YSF Network']['FCSRooms'])) { $configdmr2ysf['YSF Network']['FCSRooms'] = "/usr/local/etc/FCSHosts.txt"; } + if (!isset($configdmr2ysf['DMR Network']['DefaultDstTG'])) { $configdmr2ysf['DMR Network']['DefaultDstTG'] = "9"; } + if (!isset($configdmr2ysf['DMR Network']['TGUnlink'])) { $configdmr2ysf['DMR Network']['TGUnlink'] = "4000"; } + if (!isset($configdmr2ysf['DMR Network']['TGListFile'])) { $configdmr2ysf['DMR Network']['TGListFile'] = "/usr/local/etc/TGList_YSF.txt"; } + $configdmr2ysf['Log']['DisplayLevel'] = "0"; + $configdmr2ysf['Log']['FileLevel'] = "2"; + if (!isset($configdmr2ysf['YSF Network']['DT1'])) { $configdmr2ysf['YSF Network']['DT1'] = "1,34,97,95,43,3,17,0,0,0"; } + if (!isset($configdmr2ysf['YSF Network']['DT2'])) { $configdmr2ysf['YSF Network']['DT2'] = "0,0,0,0,108,32,28,32,3,8"; } + if (!isset($configdmr2ysf['YSF Network']['Debug'])) { $configdmr2ysf['YSF Network']['Debug'] = "0"; } + + // Add missing options to YSFGateway + if (!isset($configysfgateway['General']['WiresXMakeUpper'])) { $configysfgateway['General']['WiresXMakeUpper'] = "1"; } + if (!isset($configysfgateway['Network']['Revert'])) { $configysfgateway['Network']['Revert'] = "0"; } + if (!isset($configysfgateway['Network']['Port'])) { $configysfgateway['Network']['Port'] = "42000"; } + if (isset($configysfgateway['Network']['YSF2DMRAddress'])) { unset($configysfgateway['Network']['YSF2DMRAddress']); } + if (isset($configysfgateway['Network']['YSF2DMRPort'])) { unset($configysfgateway['Network']['YSF2DMRPort']); } + unset($configysfgateway['Network']['DataPort']); + unset($configysfgateway['Network']['StatusPort']); + if (!isset($configysfgateway['Mobile GPS']['Enable'])) { $configysfgateway['Mobile GPS']['Enable'] = "0"; } + if (!isset($configysfgateway['Mobile GPS']['Address'])) { $configysfgateway['Mobile GPS']['Address'] = "127.0.0.1"; } + if (!isset($configysfgateway['Mobile GPS']['Port'])) { $configysfgateway['Mobile GPS']['Port'] = "7834"; } + + // Add missing options to YSF2DMR + if (!isset($configysf2dmr['Info']['Power'])) { $configysf2dmr['Info']['Power'] = "1"; } + if (!isset($configysf2dmr['Info']['Height'])) { $configysf2dmr['Info']['Height'] = "0"; } + if (!isset($configysf2dmr['YSF Network']['DstAddress'])) { $configysf2dmr['YSF Network']['DstAddress'] = "127.0.0.1"; } + if (!isset($configysf2dmr['YSF Network']['DstPort'])) { $configysf2dmr['YSF Network']['DstPort'] = "42000"; } + if (!isset($configysf2dmr['YSF Network']['LocalAddress'])) { $configysf2dmr['YSF Network']['LocalAddress'] = "127.0.0.1"; } + if (!isset($configysf2dmr['YSF Network']['LocalPort'])) { $configysf2dmr['YSF Network']['LocalPort'] = "42013"; } + if (!isset($configysf2dmr['YSF Network']['Daemon'])) { $configysf2dmr['YSF Network']['Daemon'] = "1"; } + if (!isset($configysf2dmr['YSF Network']['EnableWiresX'])) { $configysf2dmr['YSF Network']['EnableWiresX'] = "1"; } + if (!isset($configysf2dmr['DMR Network']['StartupDstId'])) { $configysf2dmr['DMR Network']['StartupDstId'] = "31672"; } + if (!isset($configysf2dmr['DMR Network']['StartupPC'])) { $configysf2dmr['DMR Network']['StartupPC'] = "0"; } + if (!isset($configysf2dmr['DMR Network']['Jitter'])) { $configysf2dmr['DMR Network']['Jitter'] = "500"; } + if (!isset($configysf2dmr['DMR Network']['EnableUnlink'])) { $configysf2dmr['DMR Network']['EnableUnlink'] = "1"; } + if (!isset($configysf2dmr['DMR Network']['TGUnlink'])) { $configysf2dmr['DMR Network']['TGUnlink'] = "4000"; } + if (!isset($configysf2dmr['DMR Network']['PCUnlink'])) { $configysf2dmr['DMR Network']['PCUnlink'] = "0"; } + if (!isset($configysf2dmr['DMR Network']['Debug'])) { $configysf2dmr['DMR Network']['Debug'] = "0"; } + if ( (!isset($configysf2dmr['DMR Network']['TGListFile'])) && (file_exists('/usr/local/etc/TGList_BM.txt')) ) { $configysf2dmr['DMR Network']['TGListFile'] = "/usr/local/etc/TGList_BM.txt"; } + if (!isset($configysf2dmr['DMR Id Lookup']['File'])) { $configysf2dmr['DMR Id Lookup']['File'] = "/usr/local/etc/DMRIds.dat"; } + if (!isset($configysf2dmr['DMR Id Lookup']['Time'])) { $configysf2dmr['DMR Id Lookup']['Time'] = "24"; } + if (!isset($configysf2dmr['DMR Id Lookup']['DropUnknown'])) { $configysf2dmr['DMR Id Lookup']['DropUnknown'] = "0"; } + if (!isset($configysf2dmr['Log']['DisplayLevel'])) { $configysf2dmr['Log']['DisplayLevel'] = "1"; } + if (!isset($configysf2dmr['Log']['FileLevel'])) { $configysf2dmr['Log']['FileLevel'] = "2"; } + if (!isset($configysf2dmr['Log']['FilePath'])) { $configysf2dmr['Log']['FilePath'] = "/var/log/pi-star"; } + if (!isset($configysf2dmr['Log']['FileRoot'])) { $configysf2dmr['Log']['FileRoot'] = "YSF2DMR"; } + if (!isset($configysf2dmr['aprs.fi']['Enable'])) { $configysf2dmr['aprs.fi']['Enable'] = "0"; } + if (!isset($configysf2dmr['aprs.fi']['Port'])) { $configysf2dmr['aprs.fi']['Port'] = "14580"; } + if (!isset($configysf2dmr['aprs.fi']['Refresh'])) { $configysf2dmr['aprs.fi']['Refresh'] = "240"; } + if (!isset($configysf2dmr['Enabled']['Enabled'])) { $configysf2dmr['Enabled']['Enabled'] = "0"; } + unset($configysf2dmr['Info']['Enabled']); + unset($configysf2dmr['DMR Network']['JitterEnabled']); + $configysf2dmr['Log']['DisplayLevel'] = "0"; + $configysf2dmr['Log']['FileLevel'] = "0"; + if (!isset($configysf2dmr['aprs.fi']['Enable'])) { $configysf2dmr['aprs.fi']['Enable'] = "0"; } + if (!isset($configysf2dmr['YSF Network']['WiresXMakeUpper'])) { $configysf2dmr['YSF Network']['WiresXMakeUpper'] = "1"; } + if (!isset($configysf2dmr['YSF Network']['DT1'])) { $configysf2dmr['YSF Network']['DT1'] = "1,34,97,95,43,3,17,0,0,0"; } + if (!isset($configysf2dmr['YSF Network']['DT2'])) { $configysf2dmr['YSF Network']['DT2'] = "0,0,0,0,108,32,28,32,3,8"; } + + // Add missing options to YSF2NXDN + $configysf2nxdn['YSF Network']['LocalPort'] = $configysfgateway['YSF Network']['YSF2NXDNPort']; + $configysf2nxdn['YSF Network']['DstPort'] = $configysfgateway['YSF Network']['Port']; + $configysf2nxdn['YSF Network']['Daemon'] = "1"; + $configysf2nxdn['YSF Network']['EnableWiresX'] = "1"; + if (!isset($configysf2nxdn['Enabled']['Enabled'])) { $configysf2nxdn['Enabled']['Enabled'] = "0"; } + $configysf2nxdn['NXDN Id Lookup']['File'] = "/usr/local/etc/NXDN.csv"; + $configysf2nxdn['NXDN Network']['TGListFile'] = "/usr/local/etc/TGList_NXDN.txt"; + $configysf2nxdn['Log']['DisplayLevel'] = "0"; + $configysf2nxdn['Log']['FileLevel'] = "0"; + $configysf2nxdn['Log']['FilePath'] = "/var/log/pi-star"; + $configysf2nxdn['Log']['FileRoot'] = "YSF2NXDN"; + if (!isset($configysf2nxdn['aprs.fi']['Enable'])) { $configysf2nxdn['aprs.fi']['Enable'] = "0"; } + if (!isset($configysf2nxdn['YSF Network']['WiresXMakeUpper'])) { $configysf2nxdn['YSF Network']['WiresXMakeUpper'] = "1"; } + if (!isset($configysf2nxdn['YSF Network']['DT1'])) { $configysf2nxdn['YSF Network']['DT1'] = "1,34,97,95,43,3,17,0,0,0"; } + if (!isset($configysf2nxdn['YSF Network']['DT2'])) { $configysf2nxdn['YSF Network']['DT2'] = "0,0,0,0,108,32,28,32,3,8"; } + + // Add missing options to YSF2P25 + $configysf2p25['YSF Network']['LocalPort'] = $configysfgateway['YSF Network']['YSF2P25Port']; + $configysf2p25['YSF Network']['DstPort'] = $configysfgateway['YSF Network']['Port']; + $configysf2p25['YSF Network']['Daemon'] = "1"; + $configysf2p25['YSF Network']['EnableWiresX'] = "1"; + if (!isset($configysf2p25['Enabled']['Enabled'])) { $configysf2p25['Enabled']['Enabled'] = "0"; } + $configysf2p25['DMR Id Lookup']['File'] = "/usr/local/etc/DMRIds.dat"; + $configysf2p25['P25 Network']['TGListFile'] = "/usr/local/etc/TGList_P25.txt"; + $configysf2p25['Log']['DisplayLevel'] = "0"; + $configysf2p25['Log']['FileLevel'] = "0"; + $configysf2p25['Log']['FilePath'] = "/var/log/pi-star"; + $configysf2p25['Log']['FileRoot'] = "YSF2P25"; + if (isset($configysf2p25['aprs.fi'])) { unset($configysf2p25['aprs.fi']); } + if (!isset($configysf2p25['YSF Network']['WiresXMakeUpper'])) { $configysf2p25['YSF Network']['WiresXMakeUpper'] = "1"; } + if (!isset($configysf2p25['YSF Network']['DT1'])) { $configysf2p25['YSF Network']['DT1'] = "1,34,97,95,43,3,17,0,0,0"; } + if (!isset($configysf2p25['YSF Network']['DT2'])) { $configysf2p25['YSF Network']['DT2'] = "0,0,0,0,108,32,28,32,3,8"; } + + // Defaults for DGIdGateway + if (isset($configdgidgateway)) { + $configdgidgateway['General']['LocalPort'] = $configmmdvm['System Fusion Network']['GatewayPort']; + $configdgidgateway['General']['RptPort'] = $configmmdvm['System Fusion Network']['LocalPort']; + $configdgidgateway['Log']['DisplayLevel'] = 1; + $configdgidgateway['Log']['FileLevel'] = 1; + $configdgidgateway['Log']['FilePath'] = "/var/log/pi-star"; + $configdgidgateway['Log']['FileRoot'] = "DGIdGateway"; + $configdgidgateway['Log']['FileRotate'] = 0; + $configdgidgateway['YSF Network']['Hosts'] = "/usr/local/etc/YSFHosts.txt"; + } + + // Clean up for NXDN Gateway + if (file_exists('/etc/nxdngateway')) { + if (isset($confignxdngateway['Network']['HostsFile'])) { + $confignxdngateway['Network']['HostsFile1'] = $confignxdngateway['Network']['HostsFile']; + $confignxdngateway['Network']['HostsFile2'] = "/usr/local/etc/NXDNHostsLocal.txt"; + unset($confignxdngateway['Network']['HostsFile']); + if (!file_exists('/usr/local/etc/NXDNHostsLocal.txt')) { exec('sudo touch /usr/local/etc/NXDNHostsLocal.txt'); } + } + $configmmdvm['NXDN Network']['LocalAddress'] = "127.0.0.1"; + $configmmdvm['NXDN Network']['LocalPort'] = "14021"; + $configmmdvm['NXDN Network']['GatewayAddress'] = "127.0.0.1"; + $configmmdvm['NXDN Network']['GatewayPort'] = "14020"; + if(isset($configmmdvm['NXDN']['SelfOnly'])) { + $nxdnSelfOnlyTmp = $configmmdvm['NXDN']['SelfOnly']; + unset($configmmdvm['NXDN']['SelfOnly']); + $configmmdvm['NXDN']['SelfOnly'] = $nxdnSelfOnlyTmp; + } + if(isset($configmmdvm['NXDN']['ModeHang'])) { + $nxdnRfModeHangTmp = $configmmdvm['NXDN']['ModeHang']; + unset($configmmdvm['NXDN']['ModeHang']); + $configmmdvm['NXDN']['ModeHang'] = $nxdnRfModeHangTmp; + } + if(isset($configmmdvm['NXDN Network']['ModeHang'])) { + $nxdnNetModeHangTmp = $configmmdvm['NXDN Network']['ModeHang']; + unset($configmmdvm['NXDN Network']['ModeHang']); + $configmmdvm['NXDN Network']['ModeHang'] = $nxdnNetModeHangTmp; + } + // Add in all the APRS stuff + if(!isset($confignxdngateway['Info']['Power'])) { $confignxdngateway['Info']['Power'] = "1"; } + if(!isset($confignxdngateway['Info']['Height'])) { $confignxdngateway['Info']['Height'] = "0"; } + if(!isset($confignxdngateway['aprs.fi']['Enable'])) { $confignxdngateway['aprs.fi']['Enable'] = "0"; } + if(!isset($confignxdngateway['aprs.fi']['Server'])) { $confignxdngateway['aprs.fi']['Server'] = "euro.aprs2.net"; } + if(!isset($confignxdngateway['aprs.fi']['Port'])) { $confignxdngateway['aprs.fi']['Port'] = "14580"; } + if(!isset($confignxdngateway['aprs.fi']['Password'])) { $confignxdngateway['aprs.fi']['Password'] = "9999"; } + if(!isset($confignxdngateway['aprs.fi']['Description'])) { $confignxdngateway['aprs.fi']['Description'] = "APRS for NXDN Gateway"; } + if(!isset($confignxdngateway['aprs.fi']['Suffix'])) { $confignxdngateway['aprs.fi']['Suffix'] = "N"; } + } + + // Clean up legacy options + $dmrGatewayVer = exec("DMRGateway -v | awk {'print $3'} | cut -c 1-8"); + if ($dmrGatewayVer > 20170924) { + unset($configdmrgateway['XLX Network 1']); + unset($configdmrgateway['XLX Network 2']); + } + + // Add P25Gateway Options + $p25GatewayVer = exec("P25Gateway -v | awk {'print $3'} | cut -c 1-8"); + if ($p25GatewayVer > 20200502) { + if (!isset($configp25gateway['Remote Commands']['Enable'])) { $configp25gateway['Remote Commands']['Enable'] = "1"; } + if (!isset($configp25gateway['Remote Commands']['Port'])) { $configp25gateway['Remote Commands']['Port'] = "6074"; } + } + if ($p25GatewayVer > 20210201) { + if (isset($configp25gateway['General']['Announcements'])) { unset($configp25gateway['General']['Announcements']); } + if (!isset($configp25gateway['Log']['DisplayLevel'])) { $configp25gateway['Log']['DisplayLevel'] = "1"; } + if (!isset($configp25gateway['Log']['FileLevel'])) { $configp25gateway['Log']['FileLevel'] = "1"; } + if (!isset($configp25gateway['Network']['P252DMRAddress'])) { $configp25gateway['Network']['P252DMRAddress'] = "127.0.0.1"; } + if (!isset($configp25gateway['Network']['P252DMRPort'])) { $configp25gateway['Network']['P252DMRPort'] = "42012"; } + if (isset($configp25gateway['Network']['Startup'])) { + $configp25gateway['Network']['Static'] = $configp25gateway['Network']['Startup']; + unset($configp25gateway['Network']['Startup']); + } + } + + // Add NXDNGateway Options + $nxdnGatewayVer = exec("NXDNGateway -v | awk {'print $3'} | cut -c 1-8"); + if ($nxdnGatewayVer > 20200502) { + if (!isset($confignxdngateway['Remote Commands']['Enable'])) { $confignxdngateway['Remote Commands']['Enable'] = "1"; } + if (!isset($confignxdngateway['Remote Commands']['Port'])) { $confignxdngateway['Remote Commands']['Port'] = "6075"; } + } + if ($nxdnGatewayVer > 20210131) { + if (isset($confignxdngateway['aprs.fi'])) { unset($confignxdngateway['aprs.fi']); } + if (isset($confignxdngateway['Mobile GPS'])) { unset($confignxdngateway['Mobile GPS']); } + if (!isset($confignxdngateway['General']['RptProtocol'])) { $confignxdngateway['General']['RptProtocol'] = "Icom"; } + if (!isset($confignxdngateway['Log']['DisplayLevel'])) { $confignxdngateway['Log']['DisplayLevel'] = "1"; } + if (!isset($confignxdngateway['Log']['FileLevel'])) { $confignxdngateway['Log']['FileLevel'] = "1"; } + if (!isset($confignxdngateway['APRS']['Enable'])) { $confignxdngateway['APRS']['Enable'] = "1"; } + if (!isset($confignxdngateway['APRS']['Address'])) { $confignxdngateway['APRS']['Address'] = "127.0.0.1"; } + if (!isset($confignxdngateway['APRS']['Port'])) { $confignxdngateway['APRS']['Port'] = "8673"; } + if (!isset($confignxdngateway['APRS']['Suffix'])) { $confignxdngateway['APRS']['Suffix'] = "N"; } + if (!isset($confignxdngateway['APRS']['Description'])) { $confignxdngateway['APRS']['Description'] = $configysfgateway['Info']['Name']."_".$configysfgateway['General']['Suffix']; } + if (isset($confignxdngateway['APRS']['Description'])) { $confignxdngateway['APRS']['Description'] = $configysfgateway['Info']['Name']."_".$configysfgateway['General']['Suffix']; } + if (!isset($confignxdngateway['GPSD']['Enable'])) { $confignxdngateway['GPSD']['Enable'] = "0"; } + if (!isset($confignxdngateway['GPSD']['Address'])) { $confignxdngateway['GPSD']['Address'] = "127.0.0.1"; } + if (!isset($confignxdngateway['GPSD']['Port'])) { $confignxdngateway['GPSD']['Port'] = "2947"; } + if (isset($confignxdngateway['Network']['Startup'])) { + $confignxdngateway['Network']['Static'] = $confignxdngateway['Network']['Startup']; + unset($confignxdngateway['Network']['Startup']); + } + } + + // Migrate YSFGateway Config + $ysfGatewayVer = exec("YSFGateway -v | awk {'print $3'} | cut -c 1-8"); + if ($ysfGatewayVer > 20180303) { + if (isset($configysfgateway['Network']['Startup'])) { $ysfTmpStartup = $configysfgateway['Network']['Startup']; } + if (!isset($configysfgateway['aprs.fi']['Enable'])) { $configysfgateway['aprs.fi']['Enable'] = "1"; } + //unset($configysfgateway['Network']); + if (isset($ysfTmpStartup)) { $configysfgateway['Network']['Startup'] = $ysfTmpStartup; } + if (!isset($configysfgateway['Network']['InactivityTimeout'])) { $configysfgateway['Network']['InactivityTimeout'] = "0"; } + if (!isset($configysfgateway['Network']['Revert'])) { $configysfgateway['Network']['Revert'] = "0"; } + $configysfgateway['Network']['Debug'] = "0"; + $configysfgateway['YSF Network']['Enable'] = "1"; + $configysfgateway['YSF Network']['Port'] = "42000"; + $configysfgateway['YSF Network']['Hosts'] = "/usr/local/etc/YSFHosts.txt"; + $configysfgateway['YSF Network']['ReloadTime'] = "60"; + $configysfgateway['YSF Network']['ParrotAddress'] = "127.0.0.1"; + $configysfgateway['YSF Network']['ParrotPort'] = "42012"; + $configysfgateway['YSF Network']['YSF2DMRAddress'] = "127.0.0.1"; + $configysfgateway['YSF Network']['YSF2DMRPort'] = "42013"; + $configysfgateway['YSF Network']['YSF2NXDNAddress'] = "127.0.0.1"; + $configysfgateway['YSF Network']['YSF2NXDNPort'] = "42014"; + $configysfgateway['YSF Network']['YSF2P25Address'] = "127.0.0.1"; + $configysfgateway['YSF Network']['YSF2P25Port'] = "42015"; + $configysfgateway['FCS Network']['Enable'] = "1"; + $configysfgateway['FCS Network']['Port'] = "42001"; + $configysfgateway['FCS Network']['Rooms'] = "/usr/local/etc/FCSHosts.txt"; + } + if ($ysfGatewayVer > 20200502) { + if (!isset($configysfgateway['Remote Commands']['Enable'])) { $configysfgateway['Remote Commands']['Enable'] = "1"; } + if (!isset($configysfgateway['Remote Commands']['Port'])) { $configysfgateway['Remote Commands']['Port'] = "6073"; } + } + if ($ysfGatewayVer > 20200907) { + if (!isset($configysfgateway['General']['Debug'])) { $configysfgateway['General']['Debug'] = "0"; } + if (!isset($configysfgateway['GPSD']['Enable'])) { $configysfgateway['GPSD']['Enable'] = "0"; } + if (!isset($configysfgateway['GPSD']['Address'])) { $configysfgateway['GPSD']['Address'] = "127.0.0.1"; } + if (!isset($configysfgateway['GPSD']['Port'])) { $configysfgateway['GPSD']['Port'] = "2947"; } + if (!isset($configysfgateway['APRS']['Enable'])) { $configysfgateway['APRS']['Enable'] = "0"; } + if (!isset($configysfgateway['APRS']['Address'])) { $configysfgateway['APRS']['Address'] = "127.0.0.1"; } + if (!isset($configysfgateway['APRS']['Port'])) { $configysfgateway['APRS']['Port'] = "8673"; } + if (!isset($configysfgateway['APRS']['Description'])) { $configysfgateway['APRS']['Description'] = $configysfgateway['Info']['Name']."_".$configysfgateway['General']['Suffix']; } + if (isset($configysfgateway['APRS']['Description'])) { $configysfgateway['APRS']['Description'] = $configysfgateway['Info']['Name']."_".$configysfgateway['General']['Suffix']; } + if (!isset($configysfgateway['APRS']['Suffix'])) { $configysfgateway['APRS']['Suffix'] = "Y"; } + if (isset($configysfgateway['Mobile GPS'])) { unset($configysfgateway['Mobile GPS']); } + if (isset($configysfgateway['aprs.fi'])) { unset($configysfgateway['aprs.fi']); } + } + if ($ysfGatewayVer > 20210130) { + if (isset($configysfgateway['APRS']['Enable'])) { $configysfgateway['APRS']['Enable'] = "1"; } + } + + // Add the DAPNet Config + if (!isset($configdapnetgw['General']['Callsign'])) { $configdapnetgw['General']['Callsign'] = "M1ABC"; } + if (!isset($configdapnetgw['General']['RptAddress'])) { $configdapnetgw['General']['RptAddress'] = "127.0.0.1"; } + if (!isset($configdapnetgw['General']['RptPort'])) { $configdapnetgw['General']['RptPort'] = "3800"; } + if (!isset($configdapnetgw['General']['LocalAddress'])) { $configdapnetgw['General']['LocalAddress'] = "127.0.0.1"; } + if (!isset($configdapnetgw['General']['LocalPort'])) { $configdapnetgw['General']['LocalPort'] = "4800"; } + if (!isset($configdapnetgw['General']['Daemon'])) { $configdapnetgw['General']['Daemon'] = "0"; } + if (!isset($configdapnetgw['Log']['DisplayLevel'])) { $configdapnetgw['Log']['DisplayLevel'] = "0"; } + if (!isset($configdapnetgw['Log']['FileLevel'])) { $configdapnetgw['Log']['FileLevel'] = "2"; } + if (!isset($configdapnetgw['Log']['FilePath'])) { $configdapnetgw['Log']['FilePath'] = "/var/log/pi-star"; } + if (!isset($configdapnetgw['Log']['FileRoot'])) { $configdapnetgw['Log']['FileRoot'] = "DAPNETGateway"; } + if (!isset($configdapnetgw['DAPNET']['Address'])) { $configdapnetgw['DAPNET']['Address'] = "dapnet.afu.rwth-aachen.de"; } + if (!isset($configdapnetgw['DAPNET']['Port'])) { $configdapnetgw['DAPNET']['Port'] = "43434"; } + if (!isset($configdapnetgw['DAPNET']['AuthKey'])) { $configdapnetgw['DAPNET']['AuthKey'] = "TOPSECRET"; } + if (!isset($configdapnetgw['DAPNET']['SuppressTimeWhenBusy'])) { $configdapnetgw['DAPNET']['SuppressTimeWhenBusy'] = "1"; } + if (!isset($configdapnetgw['DAPNET']['Debug'])) { $configdapnetgw['DAPNET']['Debug'] = "0"; } + if (!isset($configmmdvm['POCSAG']['Enable'])) { $configmmdvm['POCSAG']['Enable'] = "0"; } + if (!isset($configmmdvm['POCSAG']['Frequency'])) { $configmmdvm['POCSAG']['Frequency'] = "439987500"; } + if (!isset($configmmdvm['POCSAG Network']['Enable'])) { $configmmdvm['POCSAG Network']['Enable'] = "0"; } + if (!isset($configmmdvm['POCSAG Network']['LocalAddress'])) { $configmmdvm['POCSAG Network']['LocalAddress'] = "127.0.0.1"; } + if (!isset($configmmdvm['POCSAG Network']['LocalPort'])) { $configmmdvm['POCSAG Network']['LocalPort'] = "3800"; } + if (!isset($configmmdvm['POCSAG Network']['GatewayAddress'])) { $configmmdvm['POCSAG Network']['GatewayAddress'] = "127.0.0.1"; } + if (!isset($configmmdvm['POCSAG Network']['GatewayPort'])) { $configmmdvm['POCSAG Network']['GatewayPort'] = "4800"; } + if (!isset($configmmdvm['POCSAG Network']['ModeHang'])) { $configmmdvm['POCSAG Network']['ModeHang'] = "5"; } + if (!isset($configmmdvm['POCSAG Network']['Debug'])) { $configmmdvm['POCSAG Network']['Debug'] = "0"; } + if (isset($configmmdvm['POCSAG Network']['ModeHang'])) { $configmmdvm['POCSAG Network']['ModeHang'] = "5"; } + + // Fix Demon mode on M17Gateway + if (isset($configm17gateway['General']['Daemon'])) { $configm17gateway['General']['Daemon'] = "1"; } + + // MobileGPS Setup + if (file_exists('/etc/mobilegps')) { + if (empty($_POST['mobilegps_enable']) != TRUE ) { + // Add missing lines to MMDVMHost Config + if (!isset($configmmdvm['Mobile GPS']['Enable'])) { $configmmdvm['Mobile GPS']['Enable'] = "0"; } + if (!isset($configmmdvm['Mobile GPS']['Address'])) { $configmmdvm['Mobile GPS']['Address'] = "127.0.0.1"; } + if (!isset($configmmdvm['Mobile GPS']['Port'])) { $configmmdvm['Mobile GPS']['Port'] = "7834"; } + // Add missing lines to YSFGateway Config + if (!isset($configysfgateway['GPSD'])) { + if (!isset($configysfgateway['Mobile GPS']['Enable'])) { $configysfgateway['Mobile GPS']['Enable'] = "0"; } + if (!isset($configysfgateway['Mobile GPS']['Address'])) { $configysfgateway['Mobile GPS']['Address'] = "127.0.0.1"; } + if (!isset($configysfgateway['Mobile GPS']['Port'])) { $configysfgateway['Mobile GPS']['Port'] = "7834"; } + } + // Add missing lines to NXDNGateway Config + //if (!isset($confignxdngateway['Mobile GPS']['Enable'])) { $confignxdngateway['Mobile GPS']['Enable'] = "0"; } + //if (!isset($confignxdngateway['Mobile GPS']['Address'])) { $confignxdngateway['Mobile GPS']['Address'] = "127.0.0.1"; } + //if (!isset($confignxdngateway['Mobile GPS']['Port'])) { $confignxdngateway['Mobile GPS']['Port'] = "7834"; } + + // Clean up MobilGPS config + system('sudo sed -i "/Daemon=/c\\Daemon=0" /etc/mobilegps'); + system('sudo sed -i "/Debug=/c\\Debug=0" /etc/mobilegps'); + system('sudo sed -i "/DisplayLevel=/c\\DisplayLevel=0" /etc/mobilegps'); + system('sudo sed -i "/FileLevel=/c\\FileLevel=1" /etc/mobilegps'); + system('sudo sed -i "/FilePath=/c\\FilePath=/var/log/pi-star" /etc/mobilegps'); + + // Enable or Disable MobileGPS + if (escapeshellcmd($_POST['mobilegps_enable']) == 'ON' ) { + $configmmdvm['Mobile GPS']['Enable'] = "1"; + if (isset($configysfgateway['Mobile GPS']['Enable'])) { $configysfgateway['Mobile GPS']['Enable'] = "1"; } + if (isset($confignxdngateway['Mobile GPS']['Enable'])) { $confignxdngateway['Mobile GPS']['Enable'] = "1"; } + system('sudo sed -i "/Enabled=/c\\Enabled=1" /etc/mobilegps'); + } else { + $configmmdvm['Mobile GPS']['Enable'] = "0"; + if (isset($configysfgateway['Mobile GPS']['Enable'])) { $configysfgateway['Mobile GPS']['Enable'] = "0"; } + if (isset($confignxdngateway['Mobile GPS']['Enable'])) { $confignxdngateway['Mobile GPS']['Enable'] = "0"; } + system('sudo sed -i "/Enabled=/c\\Enabled=0" /etc/mobilegps'); + } + } + if (empty($_POST['mobilegps_port']) != TRUE ) { + $newMobileGPSport = preg_replace('/[^a-z0-9]/i', '', $_POST['mobilegps_port']); + system('sudo sed -i "/Port=\/dev/c\\Port=/dev/'.$newMobileGPSport.'" /etc/mobilegps'); + } + if (empty($_POST['mobilegps_speed']) != TRUE ) { + $newMobileGPSspeed = preg_replace('/[^0-9]/', '', $_POST['mobilegps_speed']); + system('sudo sed -i "/Speed=/c\\Speed='.$newMobileGPSspeed.'" /etc/mobilegps'); + } + } + + // Create the hostfiles.nodextra file if required + if (empty($_POST['confHostFilesNoDExtra']) != TRUE ) { + if (escapeshellcmd($_POST['confHostFilesNoDExtra']) == 'ON' ) { + if (!file_exists('/etc/hostfiles.nodextra')) { system('sudo touch /etc/hostfiles.nodextra'); } + } + if (escapeshellcmd($_POST['confHostFilesNoDExtra']) == 'OFF' ) { + if (file_exists('/etc/hostfiles.nodextra')) { system('sudo rm -rf /etc/hostfiles.nodextra'); } + } + } + + // Continue Page Output + echo "
"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
完成…
更改已应用,正在启动服务…
\n"; + echo "
\n"; + + // MMDVMHost config file wrangling + $mmdvmContent = ""; + foreach($configmmdvm as $mmdvmSection=>$mmdvmValues) { + // UnBreak special cases + $mmdvmSection = str_replace("_", " ", $mmdvmSection); + $mmdvmContent .= "[".$mmdvmSection."]\n"; + // append the values + foreach($mmdvmValues as $mmdvmKey=>$mmdvmValue) { + $mmdvmContent .= $mmdvmKey."=".$mmdvmValue."\n"; + } + $mmdvmContent .= "\n"; + } + + if (!$handleMMDVMHostConfig = fopen('/tmp/bW1kdm1ob3N0DQo.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/bW1kdm1ob3N0DQo.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleMMDVMHostConfig, $mmdvmContent); + fclose($handleMMDVMHostConfig); + if (intval(exec('cat /tmp/bW1kdm1ob3N0DQo.tmp | wc -l')) > 140 ) { + exec('sudo mv /tmp/bW1kdm1ob3N0DQo.tmp /etc/mmdvmhost'); // Move the file back + exec('sudo chmod 644 /etc/mmdvmhost'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/mmdvmhost'); // Set the owner + } + } + + // ysfgateway config file wrangling + $ysfgwContent = ""; + foreach($configysfgateway as $ysfgwSection=>$ysfgwValues) { + // UnBreak special cases + $ysfgwSection = str_replace("_", " ", $ysfgwSection); + $ysfgwContent .= "[".$ysfgwSection."]\n"; + // append the values + foreach($ysfgwValues as $ysfgwKey=>$ysfgwValue) { + $ysfgwContent .= $ysfgwKey."=".$ysfgwValue."\n"; + } + $ysfgwContent .= "\n"; + } + + if (!$handleYSFGWconfig = fopen('/tmp/eXNmZ2F0ZXdheQ.tmp', 'w')) { + return false; + } + + if (!is_writable('/tmp/eXNmZ2F0ZXdheQ.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleYSFGWconfig, $ysfgwContent); + fclose($handleYSFGWconfig); + if (intval(exec('cat /tmp/eXNmZ2F0ZXdheQ.tmp | wc -l')) > 35 ) { + exec('sudo mv /tmp/eXNmZ2F0ZXdheQ.tmp /etc/ysfgateway'); // Move the file back + exec('sudo chmod 644 /etc/ysfgateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/ysfgateway'); // Set the owner + } + } + + // NXDNGateway config file wrangling + $nxdngwContent = ""; + foreach($confignxdngateway as $nxdngwSection=>$nxdngwValues) { + // UnBreak special cases + $nxdngwSection = str_replace("_", " ", $nxdngwSection); + $nxdngwContent .= "[".$nxdngwSection."]\n"; + // append the values + foreach($nxdngwValues as $nxdngwKey=>$nxdngwValue) { + $nxdngwContent .= $nxdngwKey."=".$nxdngwValue."\n"; + } + $nxdngwContent .= "\n"; + } + + if (!$handleNXDNGWconfig = fopen('/tmp/kXKwkDKy793HF5.tmp', 'w')) { + return false; + } + + if (!is_writable('/tmp/kXKwkDKy793HF5.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleNXDNGWconfig, $nxdngwContent); + fclose($handleNXDNGWconfig); + if ( (intval(exec('cat /tmp/kXKwkDKy793HF5.tmp | wc -l')) > 30 ) && (file_exists('/etc/nxdngateway')) ) { + exec('sudo mv /tmp/kXKwkDKy793HF5.tmp /etc/nxdngateway'); // Move the file back + exec('sudo chmod 644 /etc/nxdngateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/nxdngateway'); // Set the owner + } + } + + // P25Gateway config file wrangling + $p25gwContent = ""; + foreach($configp25gateway as $p25gwSection=>$p25gwValues) { + // UnBreak special cases + $p25gwSection = str_replace("_", " ", $p25gwSection); + $p25gwContent .= "[".$p25gwSection."]\n"; + // append the values + foreach($p25gwValues as $p25gwKey=>$p25gwValue) { + $p25gwContent .= $p25gwKey."=".$p25gwValue."\n"; + } + $p25gwContent .= "\n"; + } + + if (!$handleP25GWconfig = fopen('/tmp/sJSySkheSgrelJX.tmp', 'w')) { + return false; + } + + if (!is_writable('/tmp/sJSySkheSgrelJX.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleP25GWconfig, $p25gwContent); + fclose($handleP25GWconfig); + if ( (intval(exec('cat /tmp/sJSySkheSgrelJX.tmp | wc -l')) > 30 ) && (file_exists('/etc/p25gateway')) ) { + exec('sudo mv /tmp/sJSySkheSgrelJX.tmp /etc/p25gateway'); // Move the file back + exec('sudo chmod 644 /etc/p25gateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/p25gateway'); // Set the owner + } + } + + // M17Gateway config file wrangling + $m17gwContent = ""; + foreach($configm17gateway as $m17gwSection=>$m17gwValues) { + // UnBreak special cases + $m17gwSection = str_replace("_", " ", $m17gwSection); + $m17gwContent .= "[".$m17gwSection."]\n"; + // append the values + foreach($m17gwValues as $m17gwKey=>$m17gwValue) { + $m17gwContent .= $m17gwKey."=".$m17gwValue."\n"; + } + $m17gwContent .= "\n"; + } + + if (!$handleM17GWconfig = fopen('/tmp/spNcSdRUEmySTo9.tmp', 'w')) { + return false; + } + + if (!is_writable('/tmp/spNcSdRUEmySTo9.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleM17GWconfig, $m17gwContent); + fclose($handleM17GWconfig); + if ( (intval(exec('cat /tmp/spNcSdRUEmySTo9.tmp | wc -l')) > 30 ) && (file_exists('/etc/m17gateway')) ) { + exec('sudo mv /tmp/spNcSdRUEmySTo9.tmp /etc/m17gateway'); // Move the file back + exec('sudo chmod 644 /etc/m17gateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/m17gateway'); // Set the owner + } + } + + // ysf2dmr config file wrangling + $ysf2dmrContent = ""; + foreach($configysf2dmr as $ysf2dmrSection=>$ysf2dmrValues) { + // UnBreak special cases + $ysf2dmrSection = str_replace("_", " ", $ysf2dmrSection); + $ysf2dmrContent .= "[".$ysf2dmrSection."]\n"; + // append the values + foreach($ysf2dmrValues as $ysf2dmrKey=>$ysf2dmrValue) { + $ysf2dmrContent .= $ysf2dmrKey."=".$ysf2dmrValue."\n"; + } + $ysf2dmrContent .= "\n"; + } + + if (!$handleYSF2DMRconfig = fopen('/tmp/dsWGR34tHRrSFFGA.tmp', 'w')) { + return false; + } + + if (!is_writable('/tmp/dsWGR34tHRrSFFGA.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleYSF2DMRconfig, $ysf2dmrContent); + fclose($handleYSF2DMRconfig); + if (intval(exec('cat /tmp/dsWGR34tHRrSFFGA.tmp | wc -l')) > 35 ) { + exec('sudo mv /tmp/dsWGR34tHRrSFFGA.tmp /etc/ysf2dmr'); // Move the file back + exec('sudo chmod 644 /etc/ysf2dmr'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/ysf2dmr'); // Set the owner + } + } + + // ysf2nxdn config file wrangling + $ysf2nxdnContent = ""; + foreach($configysf2nxdn as $ysf2nxdnSection=>$ysf2nxdnValues) { + // UnBreak special cases + $ysf2nxdnSection = str_replace("_", " ", $ysf2nxdnSection); + $ysf2nxdnContent .= "[".$ysf2nxdnSection."]\n"; + // append the values + foreach($ysf2nxdnValues as $ysf2nxdnKey=>$ysf2nxdnValue) { + $ysf2nxdnContent .= $ysf2nxdnKey."=".$ysf2nxdnValue."\n"; + } + $ysf2nxdnContent .= "\n"; + } + if (!$handleYSF2NXDNconfig = fopen('/tmp/dsWGR34tHRrSFFGb.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/dsWGR34tHRrSFFGb.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleYSF2NXDNconfig, $ysf2nxdnContent); + fclose($handleYSF2NXDNconfig); + if (intval(exec('cat /tmp/dsWGR34tHRrSFFGb.tmp | wc -l')) > 35 ) { + exec('sudo mv /tmp/dsWGR34tHRrSFFGb.tmp /etc/ysf2nxdn'); // Move the file back + exec('sudo chmod 644 /etc/ysf2nxdn'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/ysf2nxdn'); // Set the owner + } + } + + // ysf2p25 config file wrangling + $ysf2p25Content = ""; + foreach($configysf2p25 as $ysf2p25Section=>$ysf2p25Values) { + // UnBreak special cases + $ysf2p25Section = str_replace("_", " ", $ysf2p25Section); + $ysf2p25Content .= "[".$ysf2p25Section."]\n"; + // append the values + foreach($ysf2p25Values as $ysf2p25Key=>$ysf2p25Value) { + $ysf2p25Content .= $ysf2p25Key."=".$ysf2p25Value."\n"; + } + $ysf2p25Content .= "\n"; + } + if (!$handleYSF2P25config = fopen('/tmp/dsWGR34tHRrSFFGc.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/dsWGR34tHRrSFFGc.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleYSF2P25config, $ysf2p25Content); + fclose($handleYSF2P25config); + if (intval(exec('cat /tmp/dsWGR34tHRrSFFGc.tmp | wc -l')) > 25 ) { + exec('sudo mv /tmp/dsWGR34tHRrSFFGc.tmp /etc/ysf2p25'); // Move the file back + exec('sudo chmod 644 /etc/ysf2p25'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/ysf2p25'); // Set the owner + } + } + + // dgidgateway config file wrangling + if (isset($configdgidgateway)) { + $dgidgatewayContent = ""; + foreach($configdgidgateway as $dgidgatewaySection=>$dgidgatewayValues) { + // UnBreak special cases + $dgidgatewaySection = str_replace("_", " ", $dgidgatewaySection); + $dgidgatewayContent .= "[".$dgidgatewaySection."]\n"; + // append the values + foreach($dgidgatewayValues as $dgidgatewayKey=>$dgidgatewayValue) { + $dgidgatewayContent .= $dgidgatewayKey."=".$dgidgatewayValue."\n"; + } + $dgidgatewayContent .= "\n"; + } + if (!$handleDGIdGatewayConfig = fopen('/tmp/cu0G4tG3CA45Z9B.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/cu0G4tG3CA45Z9B.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleDGIdGatewayConfig, $dgidgatewayContent); + fclose($handleDGIdGatewayConfig); + if (intval(exec('cat /tmp/cu0G4tG3CA45Z9B.tmp | wc -l')) > 25 ) { + exec('sudo mv /tmp/cu0G4tG3CA45Z9B.tmp /etc/dgidgateway'); // Move the file back + exec('sudo chmod 644 /etc/dgidgateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/dgidgateway'); // Set the owner + } + } + } + + // dmr2ysf config file wrangling + $dmr2ysfContent = ""; + foreach($configdmr2ysf as $dmr2ysfSection=>$dmr2ysfValues) { + // UnBreak special cases + $dmr2ysfSection = str_replace("_", " ", $dmr2ysfSection); + $dmr2ysfContent .= "[".$dmr2ysfSection."]\n"; + // append the values + foreach($dmr2ysfValues as $dmr2ysfKey=>$dmr2ysfValue) { + $dmr2ysfContent .= $dmr2ysfKey."=".$dmr2ysfValue."\n"; + } + $dmr2ysfContent .= "\n"; + } + if (!$handleDMR2YSFconfig = fopen('/tmp/dhJSgdy7755HGc.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/dhJSgdy7755HGc.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleDMR2YSFconfig, $dmr2ysfContent); + fclose($handleDMR2YSFconfig); + if (intval(exec('cat /tmp/dhJSgdy7755HGc.tmp | wc -l')) > 25 ) { + exec('sudo mv /tmp/dhJSgdy7755HGc.tmp /etc/dmr2ysf'); // Move the file back + exec('sudo chmod 644 /etc/dmr2ysf'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/dmr2ysf'); // Set the owner + } + } + + // dmr2nxdn config file wrangling + $dmr2nxdnContent = ""; + foreach($configdmr2nxdn as $dmr2nxdnSection=>$dmr2nxdnValues) { + // UnBreak special cases + $dmr2nxdnSection = str_replace("_", " ", $dmr2nxdnSection); + $dmr2nxdnContent .= "[".$dmr2nxdnSection."]\n"; + // append the values + foreach($dmr2nxdnValues as $dmr2nxdnKey=>$dmr2nxdnValue) { + $dmr2nxdnContent .= $dmr2nxdnKey."=".$dmr2nxdnValue."\n"; + } + $dmr2nxdnContent .= "\n"; + } + if (!$handleDMR2NXDNconfig = fopen('/tmp/nthfheS55HGc.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/nthfheS55HGc.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleDMR2NXDNconfig, $dmr2nxdnContent); + fclose($handleDMR2NXDNconfig); + if (intval(exec('cat /tmp/nthfheS55HGc.tmp | wc -l')) > 25 ) { + exec('sudo mv /tmp/nthfheS55HGc.tmp /etc/dmr2nxdn'); // Move the file back + exec('sudo chmod 644 /etc/dmr2nxdn'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/dmr2nxdn'); // Set the owner + } + } + + // DAPNet Gateway Config file wragling + $dapnetContent = ""; + foreach($configdapnetgw as $dapnetSection=>$dapnetValues) { + // UnBreak special cases + $dapnetSection = str_replace("_", " ", $dapnetSection); + $dapnetContent .= "[".$dapnetSection."]\n"; + // append the values + foreach($dapnetValues as $dapnetKey=>$dapnetValue) { + $dapnetContent .= $dapnetKey."=".$dapnetValue."\n"; + } + $dapnetContent .= "\n"; + } + if (!$handledapnetconfig = fopen('/tmp/lsHWie734HS.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/lsHWie734HS.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handledapnetconfig, $dapnetContent); + fclose($handledapnetconfig); + if (intval(exec('cat /tmp/lsHWie734HS.tmp | wc -l')) > 19 ) { + exec('sudo mv /tmp/lsHWie734HS.tmp /etc/dapnetgateway'); // Move the file back + exec('sudo chmod 644 /etc/dapnetgateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/dapnetgateway'); // Set the owner + } + } + + // dmrgateway config file wrangling + $dmrgwContent = ""; + foreach($configdmrgateway as $dmrgwSection=>$dmrgwValues) { + // UnBreak special cases + $dmrgwSection = str_replace("_", " ", $dmrgwSection); + $dmrgwContent .= "[".$dmrgwSection."]\n"; + // append the values + foreach($dmrgwValues as $dmrgwKey=>$dmrgwValue) { + $dmrgwContent .= $dmrgwKey."=".$dmrgwValue."\n"; + } + $dmrgwContent .= "\n"; + } + if (!$handledmrGWconfig = fopen('/tmp/k4jhdd34jeFr8f.tmp', 'w')) { + return false; + } + if (!is_writable('/tmp/k4jhdd34jeFr8f.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handledmrGWconfig, $dmrgwContent); + fclose($handledmrGWconfig); + if (fopen($dmrGatewayConfigFile,'r')) { + if (intval(exec('cat /tmp/k4jhdd34jeFr8f.tmp | wc -l')) > 55 ) { + exec('sudo mv /tmp/k4jhdd34jeFr8f.tmp /etc/dmrgateway'); // Move the file back + exec('sudo chmod 644 /etc/dmrgateway'); // Set the correct runtime permissions + exec('sudo chown root:root /etc/dmrgateway'); // Set the owner + } + } + } + + // modem config file wrangling + $configModemContent = ""; + if (isset($configModem)) { + foreach($configModem as $configModemSection=>$configModemValues) { + // UnBreak special cases + $configModemSection = str_replace("_", " ", $configModemSection); + $configModemContent .= "[".$configModemSection."]\n"; + // append the values + foreach($configModemValues as $modemKey=>$modemValue) { + if ($modemKey == "Password") { $configModemContent .= $modemKey."=".'"'.str_replace('"', "", $modemValue).'"'."\n"; } + else { $configModemContent .= $modemKey."=".$modemValue."\n"; } + } + $configModemContent .= "\n"; + } + + if (!$handleModemConfig = fopen('/tmp/sja7hFRkw4euG7.tmp', 'w')) { + return false; + } + + if (!is_writable('/tmp/sja7hFRkw4euG7.tmp')) { + echo "
\n"; + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
ERROR
Unable to write configuration file(s)...
Please wait a few seconds and retry...
\n"; + echo "
\n"; + unset($_POST); + echo ''; + die(); + } + else { + $success = fwrite($handleModemConfig, $configModemContent); + fclose($handleModemConfig); + if (file_exists('/etc/dstar-radio.dstarrepeater')) { + if (fopen($modemConfigFileDStarRepeater,'r')) { + exec('sudo mv /tmp/sja7hFRkw4euG7.tmp '.$modemConfigFileDStarRepeater); // Move the file back + exec('sudo chmod 644 $modemConfigFileDStarRepeater'); // Set the correct runtime permissions + exec('sudo chown root:root $modemConfigFileDStarRepeater'); // Set the owner + } + } + if (file_exists('/etc/dstar-radio.mmdvmhost')) { + if (fopen($modemConfigFileMMDVMHost,'r')) { + exec('sudo mv /tmp/sja7hFRkw4euG7.tmp '.$modemConfigFileMMDVMHost); // Move the file back + exec('sudo chmod 644 $modemConfigFileMMDVMHost'); // Set the correct runtime permissions + exec('sudo chown root:root $modemConfigFileMMDVMHost'); // Set the owner + } + } + } + } + + // Start the DV Services + system('sudo systemctl daemon-reload > /dev/null 2>/dev/null &'); // Restart Systemd to account for any service changes + system('sudo systemctl start dstarrepeater.service > /dev/null 2>/dev/null &'); // D-Star Radio Service + system('sudo systemctl start mmdvmhost.service > /dev/null 2>/dev/null &'); // MMDVMHost Radio Service + system('sudo systemctl start ircddbgateway.service > /dev/null 2>/dev/null &'); // ircDDBGateway Service + system('sudo systemctl start timeserver.service > /dev/null 2>/dev/null &'); // Time Server Service + system('sudo systemctl start pistar-watchdog.service > /dev/null 2>/dev/null &'); // PiStar-Watchdog Service + system('sudo systemctl start pistar-remote.service > /dev/null 2>/dev/null &'); // PiStar-Remote Service + system('sudo systemctl start ysf2dmr.service > /dev/null 2>/dev/null &'); // YSF2DMR + system('sudo systemctl start ysf2nxdn.service > /dev/null 2>/dev/null &'); // YSF2NXDN + system('sudo systemctl start ysf2p25.service > /dev/null 2>/dev/null &'); // YSF2P25 + system('sudo systemctl start nxdn2dmr.service > /dev/null 2>/dev/null &'); // NXDN2DMR + system('sudo systemctl start ysfgateway.service > /dev/null 2>/dev/null &'); // YSFGateway + system('sudo systemctl start ysfparrot.service > /dev/null 2>/dev/null &'); // YSFParrot + system('sudo systemctl start p25gateway.service > /dev/null 2>/dev/null &'); // P25Gateway + system('sudo systemctl start p25parrot.service > /dev/null 2>/dev/null &'); // P25Parrot + system('sudo systemctl start nxdngateway.service > /dev/null 2>/dev/null &'); // NXDNGateway + system('sudo systemctl start nxdnparrot.service > /dev/null 2>/dev/null &'); // NXDNParrot + system('sudo systemctl start m17gateway.service > /dev/null 2>/dev/null &'); // M17Gateway + system('sudo systemctl start dmr2ysf.service > /dev/null 2>/dev/null &'); // DMR2YSF + system('sudo systemctl start dmr2nxdn.service > /dev/null 2>/dev/null &'); // DMR2NXDN + system('sudo systemctl start dmrgateway.service > /dev/null 2>/dev/null &'); // DMRGateway + system('sudo systemctl start dapnetgateway.service > /dev/null 2>/dev/null &'); // DAPNetGateway + system('sudo systemctl start aprsgateway.service > /dev/null 2>/dev/null &'); // APRSGateway + + // Set the system timezone + if (empty($_POST['systemTimezone']) != TRUE ) { + $rollTimeZone = 'sudo timedatectl set-timezone '.escapeshellcmd($_POST['systemTimezone']); + system($rollTimeZone); + system('echo "'.escapeshellcmd($_POST['systemTimezone']).'" | sudo tee /etc/timezone > /dev/null'); + $rollTimeZoneConfig = 'sudo sed -i "/date_default_timezone_set/c\\date_default_timezone_set(\''.escapeshellcmd($_POST['systemTimezone']).'\')\;" /var/www/dashboard/config/config.php'; + system($rollTimeZoneConfig); + } + + // Start Cron (occasionally remounts root as RO - would be bad if it did this at the wrong time....) + system('sudo systemctl start cron.service > /dev/null 2>/dev/null &'); //Cron + + unset($_POST); + echo ''; + + // Make the root filesystem read-only + system('sudo sync && sudo sync && sudo sync && sudo mount -o remount,ro /'); + +else: + // Output the HTML Form here + if ((file_exists('/etc/dstar-radio.mmdvmhost') || file_exists('/etc/dstar-radio.dstarrepeater')) && (!isset($configModem['Modem']['Hardware']) || !$configModem['Modem']['Hardware'])) { echo "\n"; } + if (strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome') !== false) { + $toggleDMRCheckboxCr = 'onclick="toggleDMRCheckbox()"'; + $toggleDSTARCheckboxCr = 'onclick="toggleDSTARCheckbox()"'; + $toggleYSFCheckboxCr = 'onclick="toggleYSFCheckbox()"'; + $toggleP25CheckboxCr = 'onclick="toggleP25Checkbox()"'; + $toggleNXDNCheckboxCr = 'onclick="toggleNXDNCheckbox()"'; + $toggleM17CheckboxCr = 'onclick="toggleM17Checkbox()"'; + $toggleYSF2DMRCheckboxCr = 'onclick="toggleYSF2DMRCheckbox()"'; + $toggleYSF2NXDNCheckboxCr = 'onclick="toggleYSF2NXDNCheckbox()"'; + $toggleYSF2P25CheckboxCr = 'onclick="toggleYSF2P25Checkbox()"'; + $toggleDMR2YSFCheckboxCr = 'onclick="toggleDMR2YSFCheckbox()"'; + $toggleDMR2NXDNCheckboxCr = 'onclick="toggleDMR2NXDNCheckbox()"'; + $togglePOCSAGCheckboxCr = 'onclick="togglePOCSAGCheckbox()"'; + $toggleAPRSGatewayCheckboxCr = 'onclick="toggleAPRSGatewayCheckbox()"'; + $toggleDmrGatewayNet1EnCheckboxCr = 'onclick="toggleDmrGatewayNet1EnCheckbox()"'; + $toggleDmrGatewayNet2EnCheckboxCr = 'onclick="toggleDmrGatewayNet2EnCheckbox()"'; + $toggleDmrGatewayXlxEnCheckboxCr = 'onclick="toggleDmrGatewayXlxEnCheckbox()"'; + $toggleDmrEmbeddedLCOnlyCr = 'onclick="toggleDmrEmbeddedLCOnly()"'; + $toggleDmrDumpTADataCr = 'onclick="toggleDmrDumpTAData()"'; + $toggleHostFilesYSFUpperCr = 'onclick="toggleHostFilesYSFUpper()"'; + $toggleWiresXCommandPassthroughCr = 'onclick="toggleWiresXCommandPassthrough()"'; + $toggleDstarTimeAnnounceCr = 'onclick="toggleDstarTimeAnnounce()"'; + $toggleDstarDplusHostfilesCr = 'onclick="toggleDstarDplusHostfiles()"'; + $toggleMobilegps_enableCr = 'onclick="toggleMobilegps_enable()"'; + $toggleircddbEnabledCr = 'onclick="toggleircddbEnabled()"'; + } else { + $toggleDMRCheckboxCr = ""; + $toggleDSTARCheckboxCr = ""; + $toggleYSFCheckboxCr = ""; + $toggleP25CheckboxCr = ""; + $toggleNXDNCheckboxCr = ""; + $toggleM17CheckboxCr = ""; + $toggleYSF2DMRCheckboxCr = ""; + $toggleYSF2NXDNCheckboxCr = ""; + $toggleYSF2P25CheckboxCr = ""; + $toggleDMR2YSFCheckboxCr = ""; + $toggleDMR2NXDNCheckboxCr = ""; + $togglePOCSAGCheckboxCr = ""; + $toggleAPRSGatewayCheckboxCr = ""; + $toggleDmrGatewayNet1EnCheckboxCr = ""; + $toggleDmrGatewayNet2EnCheckboxCr = ""; + $toggleDmrGatewayXlxEnCheckboxCr = ""; + $toggleDmrEmbeddedLCOnlyCr = ""; + $toggleDmrDumpTADataCr = ""; + $toggleHostFilesYSFUpperCr = ""; + $toggleWiresXCommandPassthroughCr = ""; + $toggleDstarTimeAnnounceCr = ""; + $toggleDstarDplusHostfilesCr = ""; + $toggleMobilegps_enableCr = ""; + $toggleircddbEnabledCr = ""; + } +?> +
" method="post"> +
+
+ +
" method="post"> +

+
+ \n"; + } + elseif (file_exists('/etc/dstar-radio.dstarrepeater')) { + echo "
DStarRepeater MMDVMHost (DV-Mega Minimum Firmware 3.07 Required)
\n"; + } + else { // Not set - default to MMDVMHost + echo "
DStarRepeater MMDVMHost (DV-Mega Minimum Firmware 3.07 Required)
\n"; + } + ?> +
+
+ + Simplex Node Duplex Repeater (or Half-Duplex on Hotspots)
\n"; + } + else { + echo "
Simplex Node Duplex Repeater (or Half-Duplex on Hotspots)
\n"; + } + ?> + + +
+ + + + + + + + + + + + + +

+
+ \n"; + } + else { + echo "
\n"; + } + ?> +
RF Hangtime: " /> + Net Hangtime: " /> +
+
+ \n"; + } + else { + echo "
\n"; + } + ?> +
RF Hangtime: " /> + Net Hangtime: " /> +
+ + \n"; + } + else { + echo "
\n"; + } + ?> +
RF Hangtime: " /> + Net Hangtime: " /> +
+ + \n"; + } + else { + echo "
\n"; + } + ?> +
RF Hangtime: " /> + Net Hangtime: " /> +
+ + \n"; + } + else { + echo "
\n"; + } + ?> +
RF Hangtime: " /> + Net Hangtime: " /> +
+ + \n"; + } + else { + echo "
\n"; + } + ?> +
RF Hangtime: " /> + Net Hangtime: " /> +
+ + \n"; + } + else { + echo "
\n"; + } + ?> + + + \n"; + } + else { + echo "
\n"; + } + ?> + + + + \n"; + } + else { + echo "
\n"; + } + ?> + + + + \n"; + } + else { + echo "
\n"; + } + ?> +
Uses 7 prefix on DMRGateway
+ + + + \n"; + } + else { + echo "
\n"; + } + ?> +
Uses 7 prefix on DMRGateway
+ + + + \n"; + } + else { + echo "
\n"; + } + ?> +
POCSAG Paging Features
+ + +
+ +
+ Port: + Nextion Layout: +
+ + +
+ +

+ +
+ + + + + + + +\n"; + echo " \n"; + echo "
MHz
\n"; + echo "
\n"; + } + else { + echo " \n"; + echo " \n"; + } +?> + + +
+ +
+
+
+ +
+
+ + +
+ +
+
+ + + + + +\n"; + echo "
".$lang['aprs_host']." Enable:APRS Host EnableEnabling this feature will make your location public.
\n"; + if ( $configaprsgw['Enabled']['Enabled'] == 1 ) { + echo "
\n"; + } else { + echo "
\n"; + } +} ?> +
+ +
+
+
+ +
+
+'."\n"; + echo '
'.$lang['dash_lang'].':Dashboard LanguageSet the language for the dashboard.
'."\n"; + echo '
'."\n"; + } +?> + +
+ +

+ + + + + + +
+
+ +
+
+ +
+ +
+ +
+ +
+ strlen($configmmdvm['General']['Id'])) { + $brandMeisterESSID = substr($configdmrgateway['DMR Network 1']['Id'], -2); + } else { + $brandMeisterESSID = "None"; + } + } else { + if (isset($configmmdvm['General']['Id'])) { + if (strlen($configmmdvm['General']['Id']) == 9) { + $brandMeisterESSID = substr($configmmdvm['General']['Id'], -2); + } else { + $brandMeisterESSID = "None"; + } + } else { + $brandMeisterESSID = "None"; + } + } + + if (isset($configmmdvm['General']['Id'])) { if ($configmmdvm['General']['Id'] !== "1234567") { echo substr($configmmdvm['General']['Id'], 0, 7); } } + echo "\n"; +?> +
+
+ +
+
\n"; } + else { echo "
\n"; } ?> +
+
+ +
+ +
+ +
+ +
+ strlen($configmmdvm['General']['Id'])) { + $dmrPlusESSID = substr($configdmrgateway['DMR Network 2']['Id'], -2); + } else { + $dmrPlusESSID = "None"; + } + } else { + if (isset($configmmdvm['General']['Id'])) { + if (strlen($configmmdvm['General']['Id']) == 9) { + $dmrPlusESSID = substr($configmmdvm['General']['Id'], -2); + } else { + $dmrPlusESSID = "None"; + } + } else { + $dmrPlusESSID = "None"; + } + } + + if (isset($configmmdvm['General']['Id'])) { if ($configmmdvm['General']['Id'] !== "1234567") { echo substr($configmmdvm['General']['Id'], 0, 7); } } + echo "\n"; +?> +
+
+ +
+
\n"; } + else { echo "
\n"; } ?> +
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
\n"; } + else if ((isset($configdmrgateway['XLX Network']['Enabled'])) && ($configdmrgateway['XLX Network']['Enabled'] == 1)) { echo "
\n"; } + else { echo "
\n"; } ?> +
+ +
Hotspot Security:Custom PasswordOverride the Password for your DMR Host with your own custom password, make sure you already configured this with your chosen DMR Host too. Empty the field to use the default.
+
+ +
+ + '."\n"; + } else { + echo '
'.$lang['bm_network'].':BrandMeister DashboardsDirect links to your BrandMeister Dashboards
+
+ Device Information | + Edit Device (BrandMeister Selfcare) +
+ '."\n"; + } + } + if (substr($dmrMasterNow, 0, 8) == "FreeDMR_") { + echo ' + '."\n";} + if ((substr($dmrMasterNow, 0, 4) == "DMR+") || (substr($dmrMasterNow, 0, 3) == "HB_") || (substr($dmrMasterNow, 0, 3) == "FD_") || (substr($dmrMasterNow, 0, 9) == "FreeSTAR_")) { + echo ' '."\n";} + if (substr($dmrMasterNow, 0, 4) == "TGIF") { + echo ' +
'."\n";} +?> + + +
+ +
strlen($configmmdvm['General']['Id'])) { + $essidLen = strlen($configmmdvm['DMR']['Id']) - strlen($configmmdvm['General']['Id']); + $dmrESSID = substr($configmmdvm['DMR']['Id'], - $essidLen); + } else { + $dmrESSID = "None"; + } + } else { + $dmrESSID = "None"; + } + + if (isset($configmmdvm['General']['Id'])) { if ($configmmdvm['General']['Id'] !== "1234567") { echo substr($configmmdvm['General']['Id'], 0, 7); } } + echo "\n"; +?> +
+
+ + +
+
+
+
+ +
+
\n"; } + else { echo "
\n"; } ?> +
+ + +
+ + + +

+ + + +
+
+ +
+
+
+ + +
+ +
+ +
+
+ />Startup + />Manual
+
+
+ +
+
+
+ +
\n"; + } + else { + echo "
\n"; + } + ?> +
+ \n"; + } + else { + echo "
\n"; + } + ?> +
Connect ircDDB for call routing
+ + \n"; + } + else { + echo "
\n"; + } + ?> +
Note: Update Required if changed
+ + +
+ + + + +

+
+
+ +
+
+ \n"; + } + else { + echo "
\n"; + } + } else { + echo "
\n"; + } + ?> +
Note: Update Required if changed
+
+ \n"; + } + else { + echo "
\n"; + } + } else { + echo "
\n"; + } + ?> + + +
+ +
+ strlen($configmmdvm['General']['Id'])) { + $essidYSF2DMRLen = strlen($configysf2dmr['DMR Network']['Id']) - strlen($configmmdvm['General']['Id']); + $ysf2dmrESSID = substr($configysf2dmr['DMR Network']['Id'], -$essidYSF2DMRLen); + } else { + $ysf2dmrESSID = "None"; + } + } else { + $ysf2dmrESSID = "None"; + } + + if (isset($configmmdvm['General']['Id'])) { if ($configmmdvm['General']['Id'] !== "1234567") { echo substr($configmmdvm['General']['Id'], 0, 7); } } + if (isset($configmmdvm['General']['Id'])) { $ysf2dmrIdBase = substr($configmmdvm['General']['Id'], 0, 7); } else { $ysf2dmrIdBase = "1234567"; } + echo "\n"; +?> +
+
+ +
+
+ + + + + +
+ +
+
+ + + +
+ +
+
+ + + + + + + +
+ + +

+
+
+ +
+
+ + + +
+
+ + + +

+
+
+ +
+
+ + + +
+
+ + + +

+
+
+ +
+
+
+ +
+
+ + + +

+ +
+ + + + +

+
+ \n"; + } + else { + echo "
\n"; + } + } else { + echo "
\n"; + } + ?> +
+
+ +
+
+ + +
+ + +

+ + \n"; + } + else { + echo "
Private Public
\n"; + } + ?> + + \n"; + } + else { + echo "
Private Public
\n"; + } + ?> + + + \n"; + } + else { + echo "
On Off
\n"; + } + ?> +
Note: Reboot Required if changed
+ + + \n"; + } + else { + echo "
On Off
\n"; + } + ?> + + +
+
+ + +

'.$lang['wifi_config'].'

+
+ +
+
+
+
+
Auto AP SSID: '.php_uname('n').'
+
+ +
+
+
+
+
';} ?> + +
+

+
" method="post"> +
+
+
: cdn
+
+ +
+
+
+
WARNING: This changes the password for this admin page only, not the "cdn" SSH account
+
+
+ +
+ + + + + + + + +
+
+ + + + + + + diff --git a/admin/css b/admin/css new file mode 100644 index 0000000..d2d7c52 --- /dev/null +++ b/admin/css @@ -0,0 +1 @@ +../css \ No newline at end of file diff --git a/admin/download_modem_log.php b/admin/download_modem_log.php new file mode 100644 index 0000000..835c6d9 --- /dev/null +++ b/admin/download_modem_log.php @@ -0,0 +1,63 @@ +.tmp + chown www-data + * + chmod 600 (so PHP can edit the temp). + * 2. fopen('w') and fwrite the rebuilt INI text into the temp. + * 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g + * root temp -> /etc/dapnetgateway + sudo mount -o remount,ro / to + * seal the rootfs again. (See edit_mmdvmhost.php for the full + * rationale on the install vs cp+chmod+chown migration.) + * 4. sudo systemctl restart dapnetgateway.service to pick up the change. + * + * Admin-only access; the dashboard's Apache basic-auth gate is the + * sole protection. The validation is what's in the form (none, in + * effect — operator-typed values are written raw). Treat with care. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + +// doesn't persist between requests. @-suppression handles +// the case where a sudo mv (e.g. fulledit_bmapikey) already +// consumed the staging file before script end. +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + +// after the form submit +if($_POST) { + $data = $_POST; + //update ini file, call function + update_ini_file($data, $filepath); +} + +// this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + // parse the ini file to get the sections + // parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + $content .= $key."=".$value."\n"; + } + $content .= "\n"; + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // L-5: atomic install replaces the prior cp + chmod + chown + // triplet (rejected by the tightened sudoers — see + // edit_mmdvmhost.php for the full rationale). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/dapnetgateway'); + exec('sudo mount -o remount,ro /'); + + // Reload the affected daemon + exec('sudo systemctl restart dapnetgateway.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_dashboard.php b/admin/expert/edit_dashboard.php new file mode 100644 index 0000000..6153da3 --- /dev/null +++ b/admin/expert/edit_dashboard.php @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + + +
+ +
+ +\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
Factory Reset NOT performed
Server-side confirmation did not match. Factory reset cancelled.
\n"; + unset($_POST); + } else { + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
Factory Reset Config
Loading fresh configuration file(s)...
\n"; + unset($_POST); + //Reset the config + exec('sudo mount -o remount,rw /'); // Make rootfs writable + exec('sudo rm -rf /etc/pistar-css.ini'); // Delete the Config + exec('sudo mount -o remount,ro /'); // Make rootfs read-only + echo ''; + die(); + } + } else { + //update ini file, call function + update_ini_file($data, $filepath); + } +} + + //this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + //parse the ini file to get the sections + //parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $section = str_replace("BannerH2", "BannerH1", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if ($value == '') { + $content .= $key."=none\n"; + } + else { + $content .= $key."=".$value."\n"; + } + } + $content .= "\n"; + } + + //write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // Atomic install — see the matching block earlier in this + // file for the rationale. Single sudo call, content + mode + // + owner all set together; no transient state on disk. + // $filepath here is the function parameter, which is the + // same per-request tempnam path created at file-top (A3-3). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/pistar-css.ini'); + exec('sudo mount -o remount,ro /'); + + return $success; + } + +//parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); +if (isset($parsed_ini['Lookup']['popupWidth'])) { unset($parsed_ini['Lookup']['popupWidth']); } +if (isset($parsed_ini['Lookup']['popupHeight'])) { unset($parsed_ini['Lookup']['popupHeight']); } + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // Same hardening as edit_mmdvmhost.php (#23): escape every + // INI section / key / value before HTML interpolation. The + // save handler writes POST bytes verbatim so legitimate + // values (including any with `"` or `<`) round-trip + // byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + // keep the section as hidden text so we can update once the form submitted + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + if ( $section == "Lookup" && $key == "Service" ) { + echo "\n"; + } else { + echo "\n"; + } + } + echo "
$sectionHtml
$keyHtml\n"; + echo " \n"; + echo "
$keyHtml
\n"; + echo ''."\n"; + echo "

\n"; + } +echo "
"; +echo "
\n"; +echo 'if you took it all too far and now it makes you feel sick, click below to reset the changes made on this page, this will ONLY reset the CSS settings above and will not change any other settings or configuration.'."\n"; +echo '
'."\n"; +echo csrf_field_html()."\n"; +echo '
'."\n"; +// Server-side confirmation. JS factoryReset() prompts for the +// magic word and only populates this input on a match. The +// handler requires factoryResetConfirm === 'RESET' before doing +// the wipe — closes the "valid CSRF token + accidental replay" +// attack class. +echo '
'."\n"; +echo '
'."\n"; +echo ''."\n"; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_dmrgateway.php b/admin/expert/edit_dmrgateway.php new file mode 100644 index 0000000..973ad7a --- /dev/null +++ b/admin/expert/edit_dmrgateway.php @@ -0,0 +1,181 @@ +.tmp + chown www-data + * + chmod 600 (so PHP can edit the temp). + * 2. fopen('w') and fwrite the rebuilt INI text into the temp. + * 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g + * root temp -> /etc/dmrgateway + sudo mount -o remount,ro / to + * seal the rootfs again. (See edit_mmdvmhost.php for the full + * rationale on the install vs cp+chmod+chown migration.) + * 4. sudo systemctl restart dmrgateway.service to pick up the change. + * + * Admin-only access; the dashboard's Apache basic-auth gate is the + * sole protection. The validation is what's in the form (none, in + * effect — operator-typed values are written raw). Treat with care. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + +// doesn't persist between requests. @-suppression handles +// the case where a sudo mv (e.g. fulledit_bmapikey) already +// consumed the staging file before script end. +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + +//after the form submit +if($_POST) { + $data = $_POST; + //update ini file, call function + update_ini_file($data, $filepath); +} + +//this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + //parse the ini file to get the sections + //parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if (($section == "DMR Network 1" || $section == "DMR Network 2") && $key == "Password" && $value) { + $value = str_replace('"', "", $value); + $content .= $key."=\"".$value."\"\n"; + } elseif (($section == "DMR Network 1" || $section == "DMR Network 2") && $key == "Options" && $value) { + $value = str_replace('"', "", $value); + $content .= $key."=\"".$value."\"\n"; + } else { + $content .= $key."=".$value."\n"; + } + } + $content .= "\n"; + } + + //write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // L-5: atomic install replaces the prior cp + chmod + chown + // triplet (rejected by the tightened sudoers — see + // edit_mmdvmhost.php for the full rationale). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/dmrgateway'); + exec('sudo mount -o remount,ro /'); + + // Reload the affected daemon + exec('sudo systemctl restart dmrgateway.service'); // Reload the daemon + return $success; + } + +//parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_dstarrepeater.php b/admin/expert/edit_dstarrepeater.php new file mode 100644 index 0000000..be76944 --- /dev/null +++ b/admin/expert/edit_dstarrepeater.php @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if ($value == '') { + $content .= $key."= \n"; + } + else { + $content .= $key."=".$value."\n"; + } + } + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // /etc/dstarrepeater is a FLAT key=value file on disk — the + // synthetic [dstarrepeater] header is injected only for + // parse_ini_file()'s benefit. Strip it via PHP and install + // the cleaned content directly (L-7: drops sudo sed -i; + // L-5: collapses cp + chmod + chown into one atomic install). + // See edit_ircddbgateway.php for the full rationale. + $etcContent = preg_replace('/^\[dstarrepeater\]\r?\n/m', '', $content); + // A3-3: per-request random staging — see edit_ircddbgateway.php + $etcStaging = tempnam('/tmp', 'pistar-edit-etc-'); + file_put_contents($etcStaging, $etcContent); + + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($etcStaging) . ' /etc/dstarrepeater'); + exec('sudo mount -o remount,ro /'); + @unlink($etcStaging); + + // Reload the affected daemon + exec('sudo systemctl restart dstarrepeater.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_ircddbgateway.php b/admin/expert/edit_ircddbgateway.php new file mode 100644 index 0000000..7f58b0d --- /dev/null +++ b/admin/expert/edit_ircddbgateway.php @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if ($value == '') { + $content .= $key."= \n"; + } + else { + $content .= $key."=".$value."\n"; + } + } + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // /etc/ircddbgateway is a FLAT key=value file on disk — the + // synthetic [ircddbgateway] header was injected at line ~75 + // only to satisfy parse_ini_file()'s section model. The /tmp + // staging file keeps the header (so the form re-render via + // parse_ini_file at the bottom of this script still finds + // sections); the on-disk version must not. Strip via PHP's + // preg_replace and install the cleaned content directly — + // drops the prior `sudo sed -i` from this code path (L-7). + $etcContent = preg_replace('/^\[ircddbgateway\]\r?\n/m', '', $content); + // A3-3: per-request random staging file rather than a + // predictable hardcoded /tmp/.tmp path. tempnam() also + // creates the file mode 600 — and since this is a freshly + // created file (not yet referenced anywhere), there's no + // race for an attacker to swap in a symlink before our write. + $etcStaging = tempnam('/tmp', 'pistar-edit-etc-'); + file_put_contents($etcStaging, $etcContent); + + // Atomic install: content + mode + owner set in one syscall + // sequence (B5 / L-5 pattern). Replaces the prior cp + + // chmod + chown trio so an interrupted RW window can't leave + // /etc/ircddbgateway in a transient state. + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($etcStaging) . ' /etc/ircddbgateway'); + exec('sudo mount -o remount,ro /'); + @unlink($etcStaging); + + // Reload the affected daemon + exec('sudo systemctl restart ircddbgateway.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_mmdvmhost.php b/admin/expert/edit_mmdvmhost.php new file mode 100644 index 0000000..3355164 --- /dev/null +++ b/admin/expert/edit_mmdvmhost.php @@ -0,0 +1,216 @@ +.tmp + chown www-data + * + chmod 600 (so PHP can edit the temp). + * 2. fopen('w') and fwrite the rebuilt INI text into the temp. + * 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g + * root temp -> /etc/mmdvmhost + sudo mount -o remount,ro / to + * seal the rootfs again. (L-5: collapses the prior cp + chmod + + * chown triplet into one atomic call — same idiom used by + * configure.php's gateway-save block. The triplet would also + * be rejected by the tightened /etc/sudoers.d/pistar-dashboard + * because it allowlists install but not bare cp+chmod+chown + * against /etc/.) + * 4. sudo systemctl restart mmdvmhost.service to pick up the change. + * + * Admin-only access; the dashboard's Apache basic-auth gate is the + * sole protection. The validation is what's in the form (none, in + * effect — operator-typed values are written raw). Treat with care. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + +// doesn't persist between requests. @-suppression handles +// the case where a sudo mv (e.g. fulledit_bmapikey) already +// consumed the staging file before script end. +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + +//after the form submit +if($_POST) { + $data = $_POST; + //update ini file, call function + update_ini_file($data, $filepath); +} + +//this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + //parse the ini file to get the sections + //parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if ($section == "DMR Network" && $key == "Password" && $value) { + $value = str_replace('"', "", $value); + $content .= $key."=\"".$value."\"\n"; + } + elseif ($section == "DMR Network" && $key == "Options" && $value) { + $value = str_replace('"', "", $value); + $content .= $key."=\"".$value."\"\n"; + } + elseif ($section == "DMR Network" && $key == "Options" && !$value) { + $content .= $key."= \n"; + } + elseif ($value == '') { + $content .= $key."=none\n"; + } + else { + $content .= $key."=".$value."\n"; + } + } + $content .= "\n"; + } + + //write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // Updates complete - install the working file back to the + // proper location. L-5: atomic install replaces the prior + // cp + chmod + chown triplet (which the tightened + // /etc/sudoers.d/pistar-dashboard rejects on the bare + // chmod/chown against /etc/ — only the install pattern + // is allowlisted there). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/mmdvmhost'); + exec('sudo mount -o remount,ro /'); + + // Reload the affected daemon + exec('sudo systemctl restart mmdvmhost.service'); // Reload the daemon + return $success; + } + +//parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // INI section / key / value all come from /etc/mmdvmhost. + // Operator-trusted in principle, but the M-3 audit class + // means a value with a literal `"` or `<` (legitimate INI + // content — e.g. "Options=foo=1,bar" or a callsign with a + // space) breaks out of `value="…"` attributes and renders + // as HTML. + // + // htmlspecialchars(ENT_QUOTES) preserves round-trip safety: + // on display: `"` -> `"`, `<` -> `<` etc. + // browser decode (form parse): `"` -> `"` again. + // POST: the raw byte gets back to the save handler. + // save handler at line ~108: writes `key=value` to INI + // verbatim — no further escaping or unescaping. + // So values like `Options="foo=1,bar"` go in, get displayed + // as `Options="foo=1,bar"`, the browser still shows + // `"foo=1,bar"` in the input field, and re-saving produces + // a byte-identical INI line. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + // keep the section as hidden text so we can update once the form submitted + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + if (($key == "Options") || ($value)) { + echo "\n"; + } + elseif (($key == "Display") && ($value == '')) { + echo "\n"; + } + else { + echo "\n"; + } + } + echo "
$sectionHtml
$keyHtml
$keyHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo "
"; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_nxdngateway.php b/admin/expert/edit_nxdngateway.php new file mode 100644 index 0000000..60b2a9b --- /dev/null +++ b/admin/expert/edit_nxdngateway.php @@ -0,0 +1,174 @@ +.tmp + chown www-data + * + chmod 600 (so PHP can edit the temp). + * 2. fopen('w') and fwrite the rebuilt INI text into the temp. + * 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g + * root temp -> /etc/nxdngateway + sudo mount -o remount,ro / to + * seal the rootfs again. (See edit_mmdvmhost.php for the full + * rationale on the install vs cp+chmod+chown migration.) + * 4. sudo systemctl restart nxdngateway.service to pick up the change. + * + * Admin-only access; the dashboard's Apache basic-auth gate is the + * sole protection. The validation is what's in the form (none, in + * effect — operator-typed values are written raw). Treat with care. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + +// doesn't persist between requests. @-suppression handles +// the case where a sudo mv (e.g. fulledit_bmapikey) already +// consumed the staging file before script end. +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + +// after the form submit +if($_POST) { + $data = $_POST; + //update ini file, call function + update_ini_file($data, $filepath); +} + +// this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + // parse the ini file to get the sections + // parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + if (strpos($section, 'aprs') !== false) { $section = str_replace("_", ".", $section); } + else { $section = str_replace("_", " ", $section); $section = str_replace(".", " ", $section); } + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + $content .= $key."=".$value."\n"; + } + $content .= "\n"; + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // L-5: atomic install replaces the prior cp + chmod + chown + // triplet (rejected by the tightened sudoers — see + // edit_mmdvmhost.php for the full rationale). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/nxdngateway'); + exec('sudo mount -o remount,ro /'); + + // Reload the affected daemon + exec('sudo systemctl restart nxdngateway.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_p25gateway.php b/admin/expert/edit_p25gateway.php new file mode 100644 index 0000000..cb25861 --- /dev/null +++ b/admin/expert/edit_p25gateway.php @@ -0,0 +1,173 @@ +.tmp + chown www-data + * + chmod 600 (so PHP can edit the temp). + * 2. fopen('w') and fwrite the rebuilt INI text into the temp. + * 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g + * root temp -> /etc/p25gateway + sudo mount -o remount,ro / to + * seal the rootfs again. (See edit_mmdvmhost.php for the full + * rationale on the install vs cp+chmod+chown migration.) + * 4. sudo systemctl restart p25gateway.service to pick up the change. + * + * Admin-only access; the dashboard's Apache basic-auth gate is the + * sole protection. The validation is what's in the form (none, in + * effect — operator-typed values are written raw). Treat with care. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + +// doesn't persist between requests. @-suppression handles +// the case where a sudo mv (e.g. fulledit_bmapikey) already +// consumed the staging file before script end. +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + +// after the form submit +if($_POST) { + $data = $_POST; + //update ini file, call function + update_ini_file($data, $filepath); +} + +// this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + // parse the ini file to get the sections + // parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + $content .= $key."=".$value."\n"; + } + $content .= "\n"; + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // L-5: atomic install replaces the prior cp + chmod + chown + // triplet (rejected by the tightened sudoers — see + // edit_mmdvmhost.php for the full rationale). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/p25gateway'); + exec('sudo mount -o remount,ro /'); + + // Reload the affected daemon + exec('sudo systemctl restart p25gateway.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_starnetserver.php b/admin/expert/edit_starnetserver.php new file mode 100644 index 0000000..bd2d95b --- /dev/null +++ b/admin/expert/edit_starnetserver.php @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if ($value == '') { + $content .= $key."=".$value." \n"; + } + else { + $content .= $key."=".$value."\n"; + } + } + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // /etc/starnetserver is a FLAT key=value file on disk — the + // synthetic [starnetserver] header is injected only for + // parse_ini_file()'s benefit. Strip it via PHP and install + // the cleaned content directly (L-7: drops sudo sed -i; + // L-5: collapses cp + chmod + chown into one atomic install). + // See edit_ircddbgateway.php for the full rationale. + $etcContent = preg_replace('/^\[starnetserver\]\r?\n/m', '', $content); + // A3-3: per-request random staging — see edit_ircddbgateway.php + $etcStaging = tempnam('/tmp', 'pistar-edit-etc-'); + file_put_contents($etcStaging, $etcContent); + + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($etcStaging) . ' /etc/starnetserver'); + exec('sudo mount -o remount,ro /'); + @unlink($etcStaging); + + // Reload the affected daemon so the saved edits take effect + // without a manual restart. Was commented out historically; + // restored so behaviour matches the other edit_*.php files. + exec('sudo systemctl restart starnetserver.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_timeserver.php b/admin/expert/edit_timeserver.php new file mode 100644 index 0000000..2cb10fd --- /dev/null +++ b/admin/expert/edit_timeserver.php @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + if ($value == '') { + $content .= $key."= \n"; + } + else { + $content .= $key."=".$value."\n"; + } + } + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // /etc/timeserver is a FLAT key=value file on disk — the + // synthetic [timeserver] header is injected only for + // parse_ini_file()'s benefit. Strip it via PHP and install + // the cleaned content directly (L-7: drops sudo sed -i; + // L-5: collapses cp + chmod + chown into one atomic install). + // See edit_ircddbgateway.php for the full rationale. + $etcContent = preg_replace('/^\[timeserver\]\r?\n/m', '', $content); + // A3-3: per-request random staging — see edit_ircddbgateway.php + $etcStaging = tempnam('/tmp', 'pistar-edit-etc-'); + file_put_contents($etcStaging, $etcContent); + + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($etcStaging) . ' /etc/timeserver'); + exec('sudo mount -o remount,ro /'); + @unlink($etcStaging); + + // Reload the affected daemon + exec('sudo systemctl restart timeserver.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/edit_ysfgateway.php b/admin/expert/edit_ysfgateway.php new file mode 100644 index 0000000..370a816 --- /dev/null +++ b/admin/expert/edit_ysfgateway.php @@ -0,0 +1,174 @@ +.tmp + chown www-data + * + chmod 600 (so PHP can edit the temp). + * 2. fopen('w') and fwrite the rebuilt INI text into the temp. + * 3. sudo mount -o remount,rw / + sudo install -m 644 -o root -g + * root temp -> /etc/ysfgateway + sudo mount -o remount,ro / to + * seal the rootfs again. (See edit_mmdvmhost.php for the full + * rationale on the install vs cp+chmod+chown migration.) + * 4. sudo systemctl restart ysfgateway.service to pick up the change. + * + * Admin-only access; the dashboard's Apache basic-auth gate is the + * sole protection. The validation is what's in the form (none, in + * effect — operator-typed values are written raw). Treat with care. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + +// doesn't persist between requests. @-suppression handles +// the case where a sudo mv (e.g. fulledit_bmapikey) already +// consumed the staging file before script end. +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + +// after the form submit +if($_POST) { + $data = $_POST; + //update ini file, call function + update_ini_file($data, $filepath); +} + +// this is the function going to update your ini file + function update_ini_file($data, $filepath) + { + $content = ""; + + // parse the ini file to get the sections + // parse the ini file using default parse_ini_file() PHP function + $parsed_ini = parse_ini_file($filepath, true); + + foreach($data as $section=>$values) { + // UnBreak special cases + if (strpos($section, 'aprs') !== false) { $section = str_replace("_", ".", $section); } + else { $section = str_replace("_", " ", $section); $section = str_replace(".", " ", $section); } + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + $content .= $key."=".$value."\n"; + } + $content .= "\n"; + } + + // write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // L-5: atomic install replaces the prior cp + chmod + chown + // triplet (rejected by the tightened sudoers — see + // edit_mmdvmhost.php for the full rationale). + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 644 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/ysfgateway'); + exec('sudo mount -o remount,ro /'); + + // Reload the affected daemon + exec('sudo systemctl restart ysfgateway.service'); // Reload the daemon + return $success; + } + +// parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // keep the section as hidden text so we can update once the form submitted + // INI section / key / value all come from the underlying + // /etc/ file. Same hardening as edit_mmdvmhost.php + // (#23): htmlspecialchars(ENT_QUOTES) on display so a value + // with a literal `"` or `<` (e.g. an Options string) can't + // break out of the `value="…"` attribute. The save handler + // writes the POST bytes verbatim, so legitimate quoted + // values round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + echo "\n"; + } + echo "
$sectionHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo ""; +?> +
+ + + +
+ + + diff --git a/admin/expert/fulledit_bmapikey.php b/admin/expert/fulledit_bmapikey.php new file mode 100644 index 0000000..a9c55aa --- /dev/null +++ b/admin/expert/fulledit_bmapikey.php @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + // Strip CR/LF from values before they reach the INI + // line. Same rationale as fulledit_dapnetapi.php: a + // newline inside $value would split into a fresh + // INI line and let an attacker inject extra keys. + $value = str_replace(array("\r", "\n"), "", (string)$value); + if ($value == '') { + $content .= $key."=none\n"; + } else { + $content .= $key."=".$value."\n"; + } + } + $content .= "\n"; + } + + //write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // Atomic install: mode + owner set in one syscall sequence. + // /etc/bmapi.key holds the BrandMeister API token — mode 600 + // keeps it readable only by www-data (the dashboard user). + // Owner left as www-data because banner_warnings.inc / bm_links.php / + // bm_manager.php read the file directly via parse_ini_file() + // without sudo — switching to root:root here would silently + // break those reads. (Tightening to root:root is a follow-up + // once the read sites move to a sudo-cat helper.) + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 600 -o www-data -g www-data ' + . escapeshellarg($filepath) . ' /etc/bmapi.key'); + exec('sudo mount -o remount,ro /'); + + return $success; + } + +//parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); +if (!isset($parsed_ini['key']['apikey'])) { $parsed_ini['key']['apikey'] = ""; } + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // INI section / key / value all come from /etc/bmapi.key. Same + // hardening as edit_mmdvmhost.php (#23) but the value lands + // inside a body — htmlspecialchars + // covers both attribute and body contexts safely. Browser + // decodes the named entities on form submit, so legitimate + // values containing `<`, `>`, `&`, `"` round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + // keep the section as hidden text so we can update once the form submitted + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + if (($key == "Options") || ($value)) { + echo "\n"; + } + elseif (($key == "Display") && ($value == '')) { + echo "\n"; + } + else { + echo "\n"; + } + } + echo "
$sectionHtml
$keyHtml
$keyHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo "
"; +?> +
+ + + +
+ + + diff --git a/admin/expert/fulledit_cron.php b/admin/expert/fulledit_cron.php new file mode 100644 index 0000000..91d3e67 --- /dev/null +++ b/admin/expert/fulledit_cron.php @@ -0,0 +1,376 @@ +.tmp, then sudo-copied back into /etc/crontab. + * No daemon restart (cron rereads its config automatically). + * + * Operator can edit any cron line, including the pistar-* timer hooks + * — read carefully before saving. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + */ +function pistar_cron_validate($content) +{ + $rules = array( + // Password modification commands. The leading character class + // matches start-of-line OR a typical token boundary (whitespace, + // path separator, shell metas, or `=` so we catch X=passwd). + // \b at the end stops "passwords" / "chpasswdtest" matching. + '#(?:^|[\s/;|&`$()=])(passwd|chpasswd|htpasswd)\b#i' + => 'invokes a password-modification command (passwd / chpasswd / htpasswd)', + // usermod -p sets a password hash directly, no PAM, no audit. + '#\busermod\b[^\n]*\s-p\b#i' + => 'usermod -p sets a password hash directly', + // Reference to any auth file. Even reading these from cron is + // unusual; writing is highly suspicious. Catches /etc/shadow, + // /etc/passwd, /etc/passwd-, and any .htpasswd path including + // /var/www/.htpasswd. + '#(/etc/shadow|/etc/passwd|\.htpasswd)\b#i' + => 'references an authentication file path', + // Netcat — block any invocation. Operators with a legitimate + // use should script it outside cron. Blocking outright is + // simpler than disambiguating safe vs. unsafe nc flags. + '#(?:^|[\s/;|&`$()=])(nc|ncat|netcat)\b#i' + => 'invokes nc / ncat / netcat', + // socat with EXEC: or SYSTEM: targets — process-spawning. + '#\bsocat\b[^\n]*\b(EXEC|SYSTEM):#i' + => 'socat with EXEC/SYSTEM token (spawns a process)', + // Bash builtin /dev/tcp / /dev/udp redirect — classic reverse- + // shell idiom (`bash -i >& /dev/tcp/h/p 0>&1`). No legitimate + // cron use. + '#/dev/(?:tcp|udp)/#' + => 'reads or writes /dev/tcp/* or /dev/udp/* (reverse-shell pattern)', + // Download-pipe-shell. Catches `curl ... | sh`, `wget ... | bash`, + // etc. The `[^|\n]*` between the fetch tool and the pipe stops + // a stray pipe later in a long line giving false positives. + '#\b(curl|wget|fetch)\b[^|\n]*\|\s*(?:bash|sh|zsh|ksh|dash|exec)\b#i' + => 'pipes downloaded content directly into a shell', + // python reverse-shell one-liners — heuristic on imports/calls + // commonly used in the published payloads. + '#\bpython[23]?\b[^\n]*-c[^\n]*(?:socket\.socket|os\.dup2)#i' + => 'python reverse-shell pattern', + // perl reverse-shell one-liner — `perl -e \'use Socket; ...\'` + '#\bperl\b[^\n]*-e[^\n]*\bSocket\b#i' + => 'perl reverse-shell pattern', + + // ====== Obfuscation / decoder patterns ====== + // No legitimate cron use for inline encoded payloads. Operators + // who need to encode/decode something should do it in a script, + // not in a cron line. Catches the common payload-wrapper idioms. + + // base64 in any form — the command, the python module, perl's + // decode_base64, etc. Substring match (no \b) so it also + // catches "decode_base64", "MIME::Base64", "base64.b64decode". + '#base64#i' + => 'references base64 (no legitimate cron use; payload-obfuscation idiom)', + // python's base64 module exposes b64decode / b64encode aliases + // that don't contain the literal "base64" substring. + '#\bb64(?:decode|encode)\b#i' + => 'python b64decode/b64encode (payload decoder)', + // python's bytes.fromhex(...) — turns a hex string into bytes + // for runtime execution. + '#\bfromhex\b#i' + => 'fromhex (hex payload decoder)', + // xxd dumps / reverses hex. Reverse mode (-r) is a payload + // decoder; forward mode is hex-dumping which has no cron use. + // Block the binary outright. + '#\bxxd\b#i' + => 'invokes xxd (hex dump / reverse-mode payload decoder)', + // Shell hex / unicode escape literals. `printf \'\x70\x61\x73\x73\'` + // spells "pass" at runtime — pure obfuscation in cron context. + '#\\\\x[0-9a-fA-F]{2}#' + => 'shell hex-escape literal (\\xNN — payload obfuscation)', + '#\\\\u[0-9a-fA-F]{4}#' + => 'shell unicode-escape literal (\\uNNNN — payload obfuscation)', + // eval interprets its argument as shell — opaque to anyone + // reading the cron line. + '#(?:^|[\s/;|&`$()=])eval\b#i' + => 'invokes eval (opaque shell-string execution)', + // openssl -d in any context (enc -d, base64 -d, ...) is a + // generic decoder. + '#\bopenssl\b[^\n]*\s-d\b#i' + => 'openssl with -d (decoder)', + ); + + $blockers = array(); + $lines = explode("\n", $content); + foreach ($lines as $i => $line) { + $trimmed = ltrim($line); + if ($trimmed === '' || $trimmed[0] === '#') { + continue; + } + foreach ($rules as $pattern => $reason) { + if (preg_match($pattern, $line)) { + $blockers[] = sprintf( + 'line %d (%s): %s', + $i + 1, + $reason, + substr(trim($line), 0, 100) + ); + break; // one diagnostic per line is enough + } + } + } + return $blockers; +} + +$cronBlockers = array(); +$saveError = false; +$saveOk = false; +$readError = false; +if(isset($_POST['data'])) { + // Normalise CRLF → LF before validation so a Windows browser's + // submission scans byte-identical to the on-disk form. + $rawData = str_replace("\r", "", (string)$_POST['data']); + $cronBlockers = pistar_cron_validate($rawData); + + if (!empty($cronBlockers)) { + // Validation failed — DO NOT touch /etc/crontab. Surface + // the diagnostics in the page and re-render the form + // with the operator's submitted content so they can + // fix in place without retyping. + $theData = $rawData; + error_log('Pi-Star fulledit_cron.php: rejected save with ' + . count($cronBlockers) . ' blocked line(s)'); + } else { + // A3-3 — see edit_ircddbgateway.php for the full TOCTOU + // rationale. Per-request random staging path defeats + // the predictable-name pre-create / symlink class. + $filepath = tempnam('/tmp', 'pistar-edit-'); + register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + $fh = fopen($filepath, 'w'); + if ($fh !== false) { + fwrite($fh, $rawData); + fclose($fh); + } + + // Write through the one sudoers-allowlisted primitive for + // this file: `sudo sed -i … /etc/crontab`. (The previous + // `install … /etc/crontab` had no matching sudoers rule and + // was silently rejected — saves looked successful but never + // touched disk.) The operator's bytes are poured over the + // file with sed's `1r ` + `d` idiom: read the staged + // temp file in on the first cycle, delete every original + // line, leaving an exact byte-for-byte copy. The operator's + // content never enters the sed script — only the temp file + // PATH does — so there is no sed/shell-injection surface. + // sed -i preserves the existing root:root 644 (verified on a + // live host), so no follow-up chown/chmod is needed. + // $sedOut is an unused placeholder (sed -i is silent on + // success); the save status we act on is the exit code $rc + // plus the read-back comparison below. + $rc = 0; + $sedOut = array(); + exec('sudo mount -o remount,rw /'); + exec('sudo sed -i -e ' . escapeshellarg('1r ' . $filepath) . ' -e d /etc/crontab', $sedOut, $rc); + exec('sudo mount -o remount,ro /'); // always re-protect the rootfs + + // Loud-failure check. The old code unconditionally rendered + // "as saved", which is exactly how the broken write hid for + // weeks. Read /etc/crontab back (world-readable) and compare + // byte-for-byte: a non-zero sed exit OR any mismatch means + // the save did not land. This also covers the degenerate + // empty-crontab corner, where `1r` never fires and the file + // would be left empty. + $onDisk = @file_get_contents('/etc/crontab'); + if ($rc !== 0 || $onDisk !== $rawData) { + $saveError = true; + error_log('Pi-Star fulledit_cron.php: crontab save FAILED (sed rc=' + . $rc . ', readback ' + . ($onDisk === $rawData ? 'matched' : 'MISMATCHED') . ')'); + } else { + $saveOk = true; + } + + // Re-render the submitted content either way so a failed + // save never discards the operator's edits. + $theData = $rawData; + } +} else { + // A3-3 — see edit_ircddbgateway.php for the full TOCTOU rationale. + $filepath = tempnam('/tmp', 'pistar-edit-'); + register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); + exec('sudo cp /etc/crontab ' . escapeshellarg($filepath)); + exec('sudo chown www-data:www-data ' . escapeshellarg($filepath)); + exec('sudo chmod 600 ' . escapeshellarg($filepath)); + $fh = fopen($filepath, 'r'); + if ($fh === false) { + // The privileged copy of /etc/crontab could not be staged + // (sudo cp/chown/chmod failed). Surface a read error and show + // an empty editor rather than a blank-but-editable textarea — + // saving from the latter would overwrite /etc/crontab with + // nothing. + $theData = ''; + $readError = true; + error_log('Pi-Star fulledit_cron.php: could not stage /etc/crontab for reading'); + } else { + $sz = filesize($filepath); + $theData = $sz > 0 ? fread($fh, $sz) : ''; + fclose($fh); + } +} + +?> + +
+Save rejected — the cron editor blocks lines that match common privilege-escalation patterns. +
    +' . htmlspecialchars($b, ENT_QUOTES, 'UTF-8') . "\n"; +} ?> +
+

Edit the offending line(s) and re-submit, or revert your changes. +If you have a legitimate need to schedule one of these patterns, use SSH and edit +/etc/crontab directly — the dashboard editor enforces these checks +to limit the damage of stolen dashboard credentials.

+
+ + +
+Save failed — /etc/crontab was not updated. +

The write was rejected or the file did not match after saving. Your edits are +preserved below — try again, or edit /etc/crontab directly over SSH.

+
+ +
+Crontab saved. +
+ + + +
+Could not read /etc/crontab. +

The current crontab could not be loaded, so the editor below is empty. +Do not save — doing so would overwrite /etc/crontab with nothing. Reload the page, or edit +/etc/crontab directly over SSH.

+
+ +
+ +
+ +
+ +
+ + + +
+ + + diff --git a/admin/expert/fulledit_dapnetapi.php b/admin/expert/fulledit_dapnetapi.php new file mode 100644 index 0000000..37ccefc --- /dev/null +++ b/admin/expert/fulledit_dapnetapi.php @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +$values) { + // UnBreak special cases + $section = str_replace("_", " ", $section); + $content .= "[".$section."]\n"; + //append the values + foreach($values as $key=>$value) { + // Strip CR/LF from values before they land in the INI + // file. The save handler writes `$key=$value\n` and a + // newline inside $value would split the value into a + // new INI line, allowing injection of arbitrary + // additional keys (e.g. `value=foo\nDEBUG=1`). On a + // single-operator device the practical risk is low + // but the sanitiser is one line. + $value = str_replace(array("\r", "\n"), "", (string)$value); + $content .= $key."=".$value."\n"; + } + $content .= "\n"; + } + + //write it into file + if (!$handle = fopen($filepath, 'w')) { + return false; + } + + $success = fwrite($handle, $content); + fclose($handle); + + // Atomic install: mode + owner set in one syscall sequence. + // /etc/dapnetapi.key holds DAPNET credentials — mode 600 keeps + // them readable only by www-data. Owner left as www-data + // because the dapnetgateway daemon reads via the dashboard + // (and dashboard reads it directly without sudo elsewhere); + // see fulledit_bmapikey.php for the same rationale. + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 600 -o www-data -g www-data ' + . escapeshellarg($filepath) . ' /etc/dapnetapi.key'); + exec('sudo mount -o remount,ro /'); + + return $success; + } + +//parse the ini file using default parse_ini_file() PHP function +$parsed_ini = parse_ini_file($filepath, true); + +echo '
'."\n"; +echo csrf_field_html()."\n"; + foreach($parsed_ini as $section=>$values) { + // Same hardening as edit_mmdvmhost.php (#23): escape every INI + // section / key / value before HTML interpolation. The save + // handler writes POST bytes verbatim so legitimate values + // (including any with `"` or `<`) round-trip byte-identically. + $sectionHtml = htmlspecialchars((string)$section, ENT_QUOTES, 'UTF-8'); + // keep the section as hidden text so we can update once the form submitted + echo "\n"; + echo "\n"; + echo "\n"; + // print all other values as input fields, so can edit. + // note the name='' attribute it has both section and key + foreach($values as $key=>$value) { + $keyHtml = htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8'); + $valueHtml = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + if (($key == "Options") || ($value)) { + echo "\n"; + } + elseif (($key == "Display") && ($value == '')) { + echo "\n"; + } + else { + echo "\n"; + } + } + echo "
$sectionHtml
$keyHtml
$keyHtml
$keyHtml
\n"; + echo ''."\n"; + echo "
\n"; + } +echo "
"; +?> +
+ + + +
+ + + diff --git a/admin/expert/fulledit_dmrgateway.php b/admin/expert/fulledit_dmrgateway.php new file mode 100644 index 0000000..2ffdb81 --- /dev/null +++ b/admin/expert/fulledit_dmrgateway.php @@ -0,0 +1,107 @@ +.tmp then sudo-copied back to /etc/dmrgateway. + * Restarts BOTH mmdvmhost.service AND dmrgateway.service after save. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +//Load the Version Info +require_once('../config/version.php'); +?> + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + + diff --git a/admin/expert/fulledit_pistar-remote.php b/admin/expert/fulledit_pistar-remote.php new file mode 100644 index 0000000..aef4a4d --- /dev/null +++ b/admin/expert/fulledit_pistar-remote.php @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + + diff --git a/admin/expert/fulledit_rssidat.php b/admin/expert/fulledit_rssidat.php new file mode 100644 index 0000000..696828a --- /dev/null +++ b/admin/expert/fulledit_rssidat.php @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + + diff --git a/admin/expert/fulledit_wpaconfig.php b/admin/expert/fulledit_wpaconfig.php new file mode 100644 index 0000000..93a923d --- /dev/null +++ b/admin/expert/fulledit_wpaconfig.php @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ .tmp as a symlink to a target +// they control reading and have our `sudo cp` follow it. +$filepath = tempnam('/tmp', 'pistar-edit-'); +register_shutdown_function(function() use ($filepath) { @unlink($filepath); }); +exec('sudo cp /etc/wpa_supplicant/wpa_supplicant.conf ' . escapeshellarg($filepath)); +exec('sudo chown www-data:www-data ' . escapeshellarg($filepath)); +exec('sudo chmod 600 ' . escapeshellarg($filepath)); + +if(isset($_POST['data'])) { + // Write submitted data into the staging file. + $fh = fopen($filepath, 'w'); + fwrite($fh, $_POST['data']); + fclose($fh); + // Atomic install: mode + owner set in one syscall sequence. + // wpa_supplicant.conf carries the WPA PSK in cleartext as + // `psk=…hex…` — mode 600 root:root keeps every other local + // user (and any sandbox-escape from another service) from + // reading it. wpa_supplicant runs as root, so the daemon's + // own access is unaffected. + exec('sudo mount -o remount,rw /'); + exec('sudo install -m 600 -o root -g root ' + . escapeshellarg($filepath) . ' /etc/wpa_supplicant/wpa_supplicant.conf'); + exec('sudo mount -o remount,ro /'); +} + +// Re-read for the form's textarea. +$fh = fopen($filepath, 'r'); +$theData = fread($fh, filesize($filepath)); +fclose($fh); + +?> +
+ +
+ +
+ +
+ + + +
+ + + diff --git a/admin/expert/header-menu.inc b/admin/expert/header-menu.inc new file mode 100644 index 0000000..8395a48 --- /dev/null +++ b/admin/expert/header-menu.inc @@ -0,0 +1,32 @@ +
+
CDN: / 仪表盘:
+

CDN 数字语音 - 专家编辑器

+

+ | + | + | + +

+

+ 快速编辑: + DStarRepeater | + ircDDBGateway | + TimeServer | + MMDVMHost | + DMR 网关 | + YSF 网关 | + P25 网关 | + NXDN 网关 | + DAPNET 网关
+ 完整编辑: + DMR 网关 | + PiStar-Remote | + WiFi | + BM API | + DAPNET API | + 系统 Cron 任务 | + RSSI 数据 +  工具: + 固件 +

+
diff --git a/admin/expert/index.php b/admin/expert/index.php new file mode 100644 index 0000000..b4a8022 --- /dev/null +++ b/admin/expert/index.php @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + Pi-Star - Digital Voice Dashboard - Expert Editor + + + + +
+ +
+ + + + +
Expert Editors
+

**WARNING**

+ Pi-Star Expert editors have been created to make editing some of the extra settings in the
+ config files more simple, allowing you to update some areas of the config files without the
+ need to login to your Pi over SSH.
+
+ Please keep in mind when making your edits here, that these config files can be updated by
+ the dashboard, and that your edits can be over-written. It is assumed that you already know
+ what you are doing editing the files by hand, and that you understand what parts of the files
+ are maintained by the dashboard.
+
+ With that warning in mind, you are free to make any changes you like, for help come to the Facebook
+ group (link at the bottom of the page) and ask for help if / when you need it.
+ 73 and enjoy your Pi-Star experiance.
+ Pi-Star UK Team.
+
+
+
+ + + +
+ + + + diff --git a/admin/expert/jitter_test.php b/admin/expert/jitter_test.php new file mode 100644 index 0000000..2f5df90 --- /dev/null +++ b/admin/expert/jitter_test.php @@ -0,0 +1,137 @@ +` on the + * device; output streams to /var/log/pi-star/pi-star_icmptest.log + * which this page tails via AJAX (jquery-timing $.repeat). + * + * Uses system()/exec() rather than the staged-write pattern — no + * file edits, just a privileged background process. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +// Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +// Load the Version Info +require_once('../config/version.php'); + +// Sanity Check that this file has been opened correctly +if ($_SERVER["PHP_SELF"] == "/admin/expert/jitter_test.php") { + + if (isset($_GET['group'])) { + if ($_GET['group'] == "brandmeister") { $target = "BM"; } + if ($_GET['group'] == "dmrplus") { $target = "DMR+"; } + if ($_GET['group'] == "hblink") { $target = "HB"; } + } else { $target = "DMR+"; } + + if (!isset($_GET['ajax'])) { + // truncate creates+clears the log file in one synchronous call — + // see admin/update.php for the full rationale. + system('sudo truncate -s 0 /var/log/pi-star/pi-star_icmptest.log'); + system('sudo /usr/local/sbin/pistar-jittertest '.$target.' > /dev/null 2>&1 &'); + } + + // Sanity Check Passed. + header('Cache-Control: no-cache'); + session_start(); + + if (!isset($_GET['ajax'])) { + //unset($_SESSION['update_offset']); + if (file_exists('/var/log/pi-star/pi-star_icmptest.log')) { + $_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_icmptest.log'); + } else { + $_SESSION['update_offset'] = 0; + } + } + + if (isset($_GET['ajax'])) { + //session_start(); + if (!file_exists('/var/log/pi-star/pi-star_icmptest.log')) { + exit(); + } + + $handle = fopen('/var/log/pi-star/pi-star_icmptest.log', 'rb'); + if (isset($_SESSION['update_offset'])) { + fseek($handle, 0, SEEK_END); + if ($_SESSION['update_offset'] > ftell($handle)) //log rotated/truncated + $_SESSION['update_offset'] = 0; //continue at beginning of the new log + $data = stream_get_contents($handle, -1, $_SESSION['update_offset']); + $_SESSION['update_offset'] += strlen($data); + echo "
$data
"; + } + else { + fseek($handle, 0, SEEK_END); + $_SESSION['update_offset'] = ftell($handle); + } + exit(); + } + +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?> + + + + + + + +
+ +
+ + + +
Test Running
Starting test, please wait...
+
+ +
+ + + +` to flash the selected one. Output streams + * to /var/log/pi-star/pi-star_modemflash.log; tailed via AJAX. + * + * The selected variant is run through escapeshellarg() before being + * passed to the upgrade script (defence-in-depth — the picklist is + * already constrained to script-reported names). + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/csrf.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// CSRF protection — see config/csrf.php for the full rationale. +// Must run BEFORE any output: bootstraps the session on GET (so +// Set-Cookie ships) and rejects forged POSTs cleanly with 403 +// before any state change (sed-i, fopen+fwrite, sudo cp, etc.). +csrf_verify(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +// Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +// Load the Version Info +require_once('../config/version.php'); + +// Force the Locale to the stock locale just while we run the update +setlocale(LC_ALL, "LC_CTYPE=en_GB.UTF-8;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=C;LC_PAPER=C;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=C;LC_IDENTIFICATION=C"); + +// Sanity Check that this file has been opened correctly +if ($_SERVER["PHP_SELF"] == "/admin/expert/modem_fw_upgrade.php") { + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['modem'])) { + // Whitelist-validate against the canonical modem list that + // pistar-modemupgrade itself reports. The script is the + // authoritative source of "what modem variants this device + // knows how to flash" — POSTed values that don't appear in + // that list are silently dropped (the operator just sees no + // flash happen). escapeshellarg() below is still defence- + // in-depth at the shell layer; this is the application-layer + // gate that stops the script from being invoked with + // attacker-controlled args at all. Mirrors the mmdvmcal + // command whitelist in admin/calibration.php. + $validModems = array_filter(array_map('trim', + explode("\n", (string)shell_exec('sudo /usr/local/sbin/pistar-modemupgrade list')) + )); + if (in_array($_POST['modem'], $validModems, true)) { + $selectedOption = $_POST['modem']; + } else { + error_log('Pi-Star modem_fw_upgrade.php: rejected modem=' . substr((string)$_POST['modem'], 0, 64)); + } + } + } + + if (!isset($_GET['ajax'])) { + // truncate creates+clears the log file in one synchronous call — + // see admin/update.php for the full rationale. + system('sudo truncate -s 0 /var/log/pi-star/pi-star_modemflash.log'); + // No `NP=1` env-var prefix: the path-scoped sudoers refuses + // env-var pass-through without a SETENV: tag, so `sudo NP=1 + // pistar-modemupgrade …` was rejected outright with "you are + // not allowed to set the following environment variables: NP" + // and the flash silently never started. The wrapper now + // auto-detects non-interactive mode via `[ -t 0 ]` (it's + // backgrounded here with no controlling terminal), so the + // explicit env var is no longer needed. + if (isset($selectedOption)) { system('sudo /usr/local/sbin/pistar-modemupgrade ' . escapeshellarg($selectedOption) . ' > /dev/null 2>&1 &'); } + } + + // passed sanity chk. + header('Cache-Control: no-cache'); + // csrf_verify() at the top of the file already started the + // session via csrf_session_start(). Calling session_start() a + // second time emits a PHP Notice on PHP 8.x ("Ignoring + // session_start() because a session is already active") that + // gets logged to nginx error.log on every page load. The + // existing $_SESSION['update_offset'] log-tail logic below + // works unchanged against the already-active session. + + if (!isset($_GET['ajax'])) { + if (file_exists('/var/log/pi-star/pi-star_modemflash.log')) { + $_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_modemflash.log'); + } else { + $_SESSION['update_offset'] = 0; + } + } + + if (isset($_GET['ajax'])) { + if (!file_exists('/var/log/pi-star/pi-star_modemflash.log')) { + exit(); + } + $handle = fopen('/var/log/pi-star/pi-star_modemflash.log', 'rb'); + if (isset($_SESSION['update_offset'])) { + fseek($handle, 0, SEEK_END); + if ($_SESSION['update_offset'] > ftell($handle)) //log rotated/truncated + $_SESSION['update_offset'] = 0; //continue at beginning of the new log + $data = stream_get_contents($handle, -1, $_SESSION['update_offset']); + $_SESSION['update_offset'] += strlen($data); + echo nl2br($data); + } + else { + fseek($handle, 0, SEEK_END); + $_SESSION['update_offset'] = ftell($handle); + } + exit(); + } + + // Get the firmware version + if (file_exists('/usr/local/bin/firmware/version.txt')) { + $versionData = parse_ini_file('/usr/local/bin/firmware/version.txt', true); + } + if (isset($versionData['Firmware']['mmdvm_hs_version'])) { + $mmdvm_hs_version = $versionData['Firmware']['mmdvm_hs_version']; + $dvmega_fw_version = $versionData['Firmware']['dvmega_version']; + $rpt_version = $versionData['Firmware']['rpt_version']; + $fw_ver_msg = "Latest firmware version(s): Hotspot:". $mmdvm_hs_version. " DV-Mega:".$dvmega_fw_version." Repeater:".$rpt_version."."; + } else { + $fw_ver_msg = "Unkown (failed to retrieve firmware version)."; + } +?> + + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - Modem FW ".$lang['update'];?> + + + + + + + +
+ +
+ + + + + +
Modem Firmware Upgrade Utility
+
+

Modem Firmware Upgrade Utility

+

This tool will attempt to upgrade your selected modem to the latest version available firmware version:
+

+

When ready, select your modem type below and click, "Upgrade Modem". Do not interrupt the process or
+ navigate away from the page while the process is running.

+

Please understand what you are doing, as well as the risks associated with flashing your modem.

+

(IMPORTANT: Please note, we are not firmware developers, and we offer no support for firmware.
+ We provide utilities to update the firmware. For firmware support, you will need to utilise other
+ support resources from the firmware developers/maintainers or the web.)

+
+ 'MMDVM_HS_Hat (14.7456MHz TCXO) GPIO', + 'hs_hat-12mhz' => 'MMDVM_HS_Hat (12.2880MHz TCXO) GPIO', + 'hs_dual_hat' => 'MMDVM_HS_Dual_Hat (14.7456MHz TCXO) GPIO', + 'hs_dual_hat-12mhz' => 'MMDVM_HS_Dual_Hat (12.2880MHz TCXO) GPIO', + 'zum_rpi' => 'ZUMSpot RPi boards/hotspots GPIO', + 'zum_rpi-duplex' => 'ZUMSpot RPi duplex GPIO board/hotspots', + 'zum_usb' => 'ZUMspot USB stick', + 'zum_libre' => 'ZUMspot Libre Kit or generic MMDVM_HS board', + 'skybridge' => 'SkyBridge board/hotspots (14.7456MHz TCXO) GPIO', + 'euronode' => 'DVMega-EuroNode hotspots (14.7456MHz TCXO) GPIO', + 'nanodv_npi' => 'NANO_DV NPi GPIO by BG4TGO', + 'nanodv_usb' => 'NANO_DV USB by BG4TG', + 'hs_hat_ambe' => 'HS_HAT_AMBE (14.7456MHz TCXO) GPIO', + 'hs_hat_lonestar-usb' => 'LoneStar LS MMDVM USB (14.7456MHz TCXO) USB', + 'hs_hat_generic' => 'MMDVM_HS_GENERIC (14.7456MHz TCXO) GPIO', + 'hs_hat_generic_duplex' => 'MMDVM_HS_GENERIC_DUPLEX (14.7456MHz TCXO) GPIO', + 'hs_hat_generic_duplex-usb' => 'MMDVM_HS_GENERIC_DUPLEX (14.7456MHz TCXO) USB', + 'hs_hat_nano_hotspot' => 'Nano_hotSPOT by BI7JTA (14.7456MHz TCXO) GPIO', + 'hs_hat_nano_hotspot-duplex' => 'Nano_hotSPOT Dual by VR2VYE & BI7JTA (14.7456MHz TCXO) GPIO', + 'dvmega_gpio' => 'DV-Mega Raspberry Pi Hat (Single or Dual Band) GPIO', + 'dvmega_usb_uno' => 'DV-Mega Arduino Uno Shield USB (ttyUSB0)', + 'dvmega_usb_mega' => 'DV-Mega Arduino Mega Shield USB (ttyUSB0)', + 'rpt_nucleo-64' => 'Repeater - Nucleo64 F446RE GPIO', + 'rpt_nucleo-144' => 'Repeater - Nucleo144 F767ZI GPIO', + 'rpt_mmdvm_hat-0.2' => 'Repeater - MMDVM_RPT_Hat 0.2 GPIO', + 'rpt_mmdvm_hat-0.3' => 'Repeater - MMDVM_RPT_Hat 0.3 GPIO', + 'rpt_zum-0.9' => 'Repeater - ZUM Radio MMDVM for Pi v0.9 GPIO', + 'rpt_zum-1.0' => 'Repeater - ZUM Radio MMDVM for Pi v1.0 GPIO', + 'rpt_builder_v3' => 'Repeater - Repeater Builder STM32_DVM_PiHat V3 GPIO', + 'rpt_builder_v4' => 'Repeater - Repeater Builder STM32_DVM_PiHat V4 GPIO', + 'rpt_builder_v5' => 'Repeater - Repeater Builder STM32_DVM_PiHat V5 GPIO', + //'mmdvm_pi-f7' => 'MMDVM Pi F7 Board 460800 baud (12.000MHz TCXO) GPIO', + //'mmdvm_pi-f4' => 'MMDVM Pi F4 Board 460800 baud (12.000MHz TCXO) GPIO', + ]; + + $output = shell_exec('sudo /usr/local/sbin/pistar-modemupgrade list'); + + if ($output !== null) { + // Split the output into an array of options + $options = explode("\n", trim($output)); + + // Create the select element + echo '

'; + echo csrf_field_html(); + echo ''; + echo ''; + echo ''; + echo '

'; + } else { + echo '

Error executing the command.

'; + } +?> + +
+
+ +
+ + + + + Modem Flash/Upgrade Output +
Starting FW Upgrade, please wait...
+ + + + + + +. Uses + * setSecurityHeadersAllowDifferentPorts() to relax the default same- + * origin frame restriction so the iframe can target a non-80 port on + * the same host. + * + * No file edits, no privileged calls — pure UI wrapper. + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeadersAllowDifferentPorts(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('../config/language.php'); +// Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +// Load the Version Info +require_once('../config/version.php'); + +if (file_exists('/etc/default/shellinabox')) { + $getPortCommand = "grep -m 1 'SHELLINABOX_PORT=' /etc/default/shellinabox | awk -F '=' '/SHELLINABOX_PORT=/ {print $2}'"; + $shellPort = exec($getPortCommand); +} + +// HTTP_HOST is client-controllable (any HTTP client can send any +// `Host:` header). Echoing it raw into HTML attributes — as the +// iframe `src` and the anchor `href` below do — gives a passing +// attacker a reflected XSS surface: a request with a crafted Host +// header would render an iframe pointing at attacker-controlled +// content inside this page. +// +// Two-layer defence: +// 1. Strip everything that isn't a hostname character. The set +// `[a-zA-Z0-9.\-\[\]:]` covers DNS names, IPv4 dotted-quad, +// IPv6 bracketed literals, and the optional `:port` suffix. +// Anything else (CRLF, `<`, `"`, `'`, semicolons, spaces, +// parens, slashes) is dropped — the regex is what closes +// the XSS / response-splitting class. +// 2. htmlspecialchars on top, so even if the regex were +// relaxed in the future the value still can't break out +// of the `"…"` HTML attribute it lands in. +$shellHost = preg_replace( + '/[^a-zA-Z0-9.\-\[\]:]/', + '', + isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '' +); +if ($shellHost === '') { + // Pathological case: HTTP_HOST contained nothing usable. Fall + // back to the LAN IP that nginx listens on rather than emitting + // a broken iframe `src=":port"`. + $shellHost = 'localhost'; +} +$shellHostHtml = htmlspecialchars($shellHost, ENT_QUOTES, 'UTF-8'); +$shellPortHtml = htmlspecialchars((string)(isset($shellPort) ? $shellPort : ''), ENT_QUOTES, 'UTF-8'); + +// Sanity Check that this file has been opened correctly +if ($_SERVER["PHP_SELF"] == "/admin/expert/ssh_access.php") { +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - SSH";?> + + + + + + +
+ +
+ + + +
SSH - Pi-Star
+ "; + } + else { + echo "SSH Feature not yet installed"; + } ?> +
+ Click here for fullscreen SSH client
\n"; } ?> +
+ +
+ + + +` ran as www-data, + // defeating the sudo on the truncation step). + system('sudo truncate -s 0 /var/log/pi-star/pi-star_upgrade.log'); + system('sudo /usr/local/sbin/pistar-upgrade > /dev/null 2>&1 &'); + } + + // Sanity Check Passed. + header('Cache-Control: no-cache'); + // session_start() is no longer called here — csrf_verify() at + // the top already started the session via csrf_session_start(). + // The existing $_SESSION['update_offset'] log-tail logic below + // works unchanged against the already-active session. + + // Initialize session offset only if upgrade has been confirmed + if (!isset($_GET['ajax']) && !empty($_POST) && isset($_POST['confirm_update'])) { + //unset($_SESSION['update_offset']); + if (file_exists('/var/log/pi-star/pi-star_upgrade.log')) { + $_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_upgrade.log'); + } else { + $_SESSION['update_offset'] = 0; + } + } + + if (isset($_GET['ajax'])) { + //session_start(); + if (!file_exists('/var/log/pi-star/pi-star_upgrade.log')) { + exit(); + } + + $handle = fopen('/var/log/pi-star/pi-star_upgrade.log', 'rb'); + if (isset($_SESSION['update_offset'])) { + fseek($handle, 0, SEEK_END); + if ($_SESSION['update_offset'] > ftell($handle)) //log rotated/truncated + $_SESSION['update_offset'] = 0; //continue at beginning of the new log + $data = stream_get_contents($handle, -1, $_SESSION['update_offset']); + $_SESSION['update_offset'] += strlen($data); + echo nl2br($data); + } + else { + fseek($handle, 0, SEEK_END); + $_SESSION['update_offset'] = ftell($handle); + } + exit(); + } + +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?> + + + + + + + + + +
+ +
+ + + + +
Upgrade Running
Starting upgrade, please wait...
+ +
" method="post"> + + + + + + + + +
Upgrade
+

+ This will upgrade your Pi-Star installation to the latest version.
+
+ The upgrade process may take several minutes to complete.
+ Please do not interrupt the process or power off your system during the upgrade. +

+ +
+
+ +
+ +
+ + + + ftell($handle)) //log rotated/truncated + $_SESSION['offset'] = 0; //continue at beginning of the new log + $data = stream_get_contents($handle, -1, $_SESSION['offset']); + $_SESSION['offset'] += strlen($data); + echo nl2br($data); + } + else { + fseek($handle, 0, SEEK_END); + $_SESSION['offset'] = ftell($handle); + } + exit(); + } + +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['live_logs'];?> + + + + + + + +
+
+
Pi-Star: /
+

Pi-Star

+

+ | + | + | + | + +

+
+
+ + + + +
Starting logging, please wait...
Download the log: here
+
+ +
+ + + + + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['power'];?> + + + + +
+
+
Pi-Star: /
+

Pi-Star

+

+ | + | + | + | + +

+
+
+ + + + '; + system('sudo sync && sudo sync && sudo sync && sudo mount -o remount,ro / > /dev/null &'); + exec('sudo reboot > /dev/null &'); + }; + if ( $_POST["action"] === "shutdown" ) { + echo ''; + system('sudo sync && sudo sync && sudo sync && sudo mount -o remount,ro / > /dev/null &'); + exec('sudo shutdown -h now > /dev/null &'); + }; + ?> +


Reboot command has been sent to your Pi, +
please wait up to 90 secs for it to reboot.
+
You will be re-directed back to the +
dashboard automatically in 90 seconds.


+ +


Shutdown command has been sent to your Pi, +
please wait 30 secs for it to fully shutdown
before removing the power.


+ +
" method="post" onsubmit="return confirm('Are you sure?');"> + + + + + + + + + +
+ Reboot
+ +
+ Shutdown
+ +
+
+ +
+ +
+ + + with `-v` to print version + * banners (MMDVMHost, DMRGateway, DMR2YSF/NXDN, YSFGateway, + * DGIdGateway, YSF2DMR/P25/NXDN, P25Gateway, NXDNGateway, + * M17Gateway, DAPNETGateway). + */ +require_once($_SERVER['DOCUMENT_ROOT'].'/config/security_headers.php'); +require_once($_SERVER['DOCUMENT_ROOT'].'/config/banner_warnings.inc'); +setSecurityHeaders(); + +// Layer 2 of the default-password protection — see config/banner_warnings.inc. +// MUST run BEFORE any output so header('Location: ...') works. +pistar_warnings_enforce_redirect(); + +// Load the language support +require_once('config/language.php'); +// Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +// Load the Version Info +require_once('config/version.php'); + +// Retrieve server information +$system = system_information(); + +function system_information() +{ + @list($system, $host, $kernel) = preg_split('/[\s,]+/', php_uname('a'), 5); + $meminfo = false; + if (@is_readable('/proc/meminfo')) { + $data = explode("\n", file_get_contents("/proc/meminfo")); + $meminfo = array(); + foreach ($data as $line) { + if (strpos($line, ':') !== false) { + list($key, $val) = explode(":", $line); + $meminfo[$key] = 1024 * floatval( trim( str_replace( ' kB', '', $val ) ) ); + } + } + } + return array('date' => date('Y-m-d H:i:s T'), + 'mem_info' => $meminfo, + 'partitions' => disk_list() + ); +} + +function disk_list() +{ + $partitions = array(); + // Fetch partition information from df command + // I would have used disk_free_space() and disk_total_space() here but + // there appears to be no way to get a list of partitions in PHP? + $output = array(); + @exec('df --block-size=1', $output); + foreach($output as $line) { + $columns = array(); + foreach(explode(' ', $line) as $column) { + $column = trim($column); + if($column != '') $columns[] = $column; + } + + // Only process 6 column rows + // (This has the bonus of ignoring the first row which is 7) + if(count($columns) == 6) { + $partition = $columns[5]; + $partitions[$partition]['Temporary']['bool'] = in_array($columns[0], array('tmpfs', 'devtmpfs')); + $partitions[$partition]['Partition']['text'] = $partition; + $partitions[$partition]['FileSystem']['text'] = $columns[0]; + if(is_numeric($columns[1]) && is_numeric($columns[2]) && is_numeric($columns[3])) { + $partitions[$partition]['Size']['value'] = $columns[1]; + $partitions[$partition]['Free']['value'] = $columns[3]; + $partitions[$partition]['Used']['value'] = $columns[2]; + } + else { + // Fallback if we don't get numerical values + $partitions[$partition]['Size']['text'] = $columns[1]; + $partitions[$partition]['Used']['text'] = $columns[2]; + $partitions[$partition]['Free']['text'] = $columns[3]; + } + } + } + return $partitions; +} + +function formatSize( $bytes ) +{ + $types = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ); + for( $i = 0; $bytes >= 1024 && $i < ( count( $types ) -1 ); $bytes /= 1024, $i++ ); + return( round( $bytes, 2 ) . " " . $types[$i] ); + } + +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?> + + + + + + + +
+
+
Pi-Star: / Dashboard:
+

Pi-Star -

+

+ | + | + | + | + +

+
+
+ + +\n"; + $sysRamUsed = $system['mem_info']['MemTotal'] - $system['mem_info']['MemFree'] - $system['mem_info']['Buffers'] - $system['mem_info']['Cached']; + $sysRamPercent = sprintf('%.2f',($sysRamUsed / $system['mem_info']['MemTotal']) * 100); + echo " \n"; +} +// Filesystem Information +if (count($system['partitions']) > 0) { + echo " \n"; + foreach($system['partitions'] as $fs) { + if ($fs['Used']['value'] > 0 && $fs['FileSystem']['text']!= "none" && $fs['FileSystem']['text']!= "udev") { + $diskFree = $fs['Free']['value']; + $diskTotal = $fs['Size']['value']; + $diskUsed = $fs['Used']['value']; + $diskPercent = sprintf('%.2f',($diskUsed / $diskTotal) * 100); + + echo " \n"; + } + } +} +// Binary Information +echo " \n"; +if (is_executable('/usr/local/bin/MMDVMHost')) { + $MMDVMHost_Ver = exec('/usr/local/bin/MMDVMHost -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/DMRGateway')) { + $DMRGateway_Ver = exec('/usr/local/bin/DMRGateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/DMR2YSF')) { + $DMR2YSF_Ver = exec('/usr/local/bin/DMR2YSF -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/DMR2NXDN')) { + $DMR2NXDN_Ver = exec('/usr/local/bin/DMR2NXDN -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/YSFGateway')) { + $YSFGateway_Ver = exec('/usr/local/bin/YSFGateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/DGIdGateway')) { + $DGIdGateway_Ver = exec('/usr/local/bin/DGIdGateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/YSF2DMR')) { + $YSF2DMR_Ver = exec('/usr/local/bin/YSF2DMR -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/YSF2P25')) { + $YSF2P25_Ver = exec('/usr/local/bin/YSF2P25 -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/YSF2NXDN')) { + $YSF2NXDN_Ver = exec('/usr/local/bin/YSF2NXDN -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/P25Gateway')) { + $P25Gateway_Ver = exec('/usr/local/bin/P25Gateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/NXDNGateway')) { + $NXDNGateway_Ver = exec('/usr/local/bin/NXDNGateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/M17Gateway')) { + $M17Gateway_Ver = exec('/usr/local/bin/M17Gateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +if (is_executable('/usr/local/bin/DAPNETGateway')) { + $DAPNETGateway_Ver = exec('/usr/local/bin/DAPNETGateway -v | cut -d\' \' -f 3-'); + echo " \n"; +} +?> +
Pi-Star System Information
MemoryStats
RAM
Used ".$sysRamPercent."%
"; + echo " Total: ".formatSize($system['mem_info']['MemTotal'])." Used: ".formatSize($sysRamUsed)." Free: ".formatSize($system['mem_info']['MemTotal'] - $sysRamUsed)."
MountStats
".$fs['Partition']['text']."
Used ".$diskPercent."%
"; + echo " Total: ".formatSize($diskTotal)." Used: ".formatSize($diskUsed)." Free: ".formatSize($diskFree)."
BinaryVersion
MMDVMHost".$MMDVMHost_Ver."
DMRGateway".$DMRGateway_Ver."
DMR2YSF".$DMR2YSF_Ver."
DMR2NXDN".$DMR2NXDN_Ver."
YSFGateway".$YSFGateway_Ver."
DGIdGateway".$DGIdGateway_Ver."
YSF2DMR".$YSF2DMR_Ver."
YSF2P25".$YSF2P25_Ver."
YSF2NXDN".$YSF2NXDN_Ver."
P25Gateway".$P25Gateway_Ver."
NXDNGateway".$NXDNGateway_Ver."
M17Gateway".$M17Gateway_Ver."
DAPNETGateway".$DAPNETGateway_Ver."
+
+ +
+ + + diff --git a/admin/update.php b/admin/update.php new file mode 100644 index 0000000..4638207 --- /dev/null +++ b/admin/update.php @@ -0,0 +1,210 @@ + log` pair + // tried to do. The earlier echo was buggy (the `>` redirect was + // run as www-data, not under sudo, so it could only ever truncate + // a www-data-writable file — a silent no-op when the log was last + // touched as root). Run synchronously, BEFORE the backgrounded + // pistar-update spawn, so the script's first writes always land + // in a freshly cleared file regardless of `&` scheduling. + system('sudo truncate -s 0 /var/log/pi-star/pi-star_update.log'); + system('sudo /usr/local/sbin/pistar-update > /dev/null 2>&1 &'); + } + + // Sanity Check Passed. + header('Cache-Control: no-cache'); + // session_start() is no longer called here — csrf_verify() at + // the top of the file already started the session via + // csrf_session_start(). A second session_start() would emit a + // "session is already active" NOTICE on PHP 8.x. The existing + // $_SESSION['update_offset'] log-tail logic below works + // unchanged against the already-active session. + + // Initialize session offset only if update has been confirmed + if (!isset($_GET['ajax']) && !empty($_POST) && isset($_POST['confirm_update'])) { + //unset($_SESSION['update_offset']); + if (file_exists('/var/log/pi-star/pi-star_update.log')) { + $_SESSION['update_offset'] = filesize('/var/log/pi-star/pi-star_update.log'); + } else { + $_SESSION['update_offset'] = 0; + } + } + + if (isset($_GET['ajax'])) { + //session_start(); + if (!file_exists('/var/log/pi-star/pi-star_update.log')) { + exit(); + } + + $handle = fopen('/var/log/pi-star/pi-star_update.log', 'rb'); + if (isset($_SESSION['update_offset'])) { + fseek($handle, 0, SEEK_END); + if ($_SESSION['update_offset'] > ftell($handle)) //log rotated/truncated + $_SESSION['update_offset'] = 0; //continue at beginning of the new log + $data = stream_get_contents($handle, -1, $_SESSION['update_offset']); + $_SESSION['update_offset'] += strlen($data); + echo nl2br($data); + } + else { + fseek($handle, 0, SEEK_END); + $_SESSION['update_offset'] = ftell($handle); + } + exit(); + } + +?> + + + + + + + + + + + + + + + Pi-Star - <?php echo $lang['digital_voice']." ".$lang['dashboard']." - ".$lang['update'];?> + + + + + + + + + +
+
+
+
+ Pi-Star: / Dashboard: +
+

Pi-Star -

+ +
+
+
+
+ + + + +
Update Running
Starting update, please wait...
+ +
" method="post"> + + + + + + + + +
+

+ This will update your Pi-Star installation to the latest version.
+
+ The update process may take several minutes to complete.
+ Please do not interrupt the process or power off your system during the update. +

+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + +CDN - 数字语音仪表盘 - WiFi 配置 + +'."\n"; +switch($page) { + case "wlan0_info": + //Declare a pile of variables + $strIPAddress = NULL; + $strNetMask = NULL; + $strRxPackets = NULL; + $strRxBytes = NULL; + $strTxPackets = NULL; + $strTxBytes = NULL; + $strSSID = NULL; + $strBSSID = NULL; + $strBitrate = NULL; + $strTxPower = NULL; + $strLinkQuality = NULL; + $strSignalLevel = NULL; + $strWifiFreq = NULL; + $strWifiChan = NULL; + + exec('ifconfig wlan0',$return); + exec('iwconfig wlan0',$return); + exec('iw dev wlan0 link',$return); + $strWlan0 = implode(" ",$return); + $strWlan0 = preg_replace('/\s\s+/', ' ', $strWlan0); + if (strpos($strWlan0,'HWaddr') !== false) { + preg_match('/HWaddr ([0-9a-f:]+)/i',$strWlan0,$result); + $strHWAddress = $result[1]; + } + if (strpos($strWlan0,'ether') !== false) { + preg_match('/ether ([0-9a-f:]+)/i',$strWlan0,$result); + $strHWAddress = $result[1]; + } + if(strpos($strWlan0, "UP") !== false && strpos($strWlan0, "RUNNING") !== false) { + $strStatus = '已启用'; + //Cant get these unless we are connected :) + if (strpos($strWlan0,'inet addr:') !== false) { + preg_match('/inet addr:([0-9.]+)/i',$strWlan0,$result); + $strIPAddress = $result[1]; + } else { + preg_match('/inet ([0-9.]+)/i',$strWlan0,$result); + $strIPAddress = $result[1]; + } + if (strpos($strWlan0,'Mask:') !== false) { + preg_match('/Mask:([0-9.]+)/i',$strWlan0,$result); + $strNetMask = $result[1]; + } else { + preg_match('/netmask ([0-9.]+)/i',$strWlan0,$result); + $strNetMask = $result[1]; + } + preg_match('/RX packets.(\d+)/',$strWlan0,$result); + $strRxPackets = $result[1]; + preg_match('/TX packets.(\d+)/',$strWlan0,$result); + $strTxPackets = $result[1]; + if (strpos($strWlan0,'RX bytes') !== false) { + preg_match('/RX [B|b]ytes:(\d+ \(\d+.\d+ [K|M|G]iB\))/i',$strWlan0,$result); + $strRxBytes = $result[1]; + } else { + preg_match('/RX packets \d+ bytes (\d+ \(\d+.\d+ [K|M|G]iB\))/i',$strWlan0,$result); + $strRxBytes = $result[1]; + } + if (strpos($strWlan0,'TX bytes') !== false) { + preg_match('/TX [B|b]ytes:(\d+ \(\d+.\d+ [K|M|G]iB\))/i',$strWlan0,$result); + $strTxBytes = $result[1]; + } else { + preg_match('/TX packets \d+ bytes (\d+ \(\d+.\d+ [K|M|G]iB\))/i',$strWlan0,$result); + $strTxBytes = $result[1]; + } + //preg_match('/TX Bytes:(\d+ \(\d+.\d+ [K|M|G]iB\))/i',$strWlan0,$result); + //$strTxBytes = $result[1]; + if (preg_match('/Access Point: ([0-9a-f:]+)/i',$strWlan0,$result)) { + $strBSSID = $result[1]; } + if (preg_match('/Connected to\ ([0-9a-f:]+)/i',$strWlan0,$result)) { + $strBSSID = $result[1]; } + if (preg_match('/Bit Rate([=:0-9\.]+ Mb\/s)/i',$strWlan0,$result)) { + $strBitrate = str_replace(':', '', str_replace('=', '', $result[1])); } + if (preg_match('/tx bitrate:\ ([0-9\.]+ Mbit\/s)/i',$strWlan0,$result)) { + $strBitrate = str_replace(':', '', str_replace('=', '', $result[1])); } + if (preg_match('/Tx-Power=([0-9]+ dBm)/i',$strWlan0,$result)) { + $strTxPower = $result[1]; } + if (preg_match('/ESSID:\"([a-zA-Z0-9-_.\s]+)\"/i',$strWlan0,$result)) { + $strSSID = str_replace('"','',$result[1]); } + if (preg_match('/SSID:\ ([a-zA-Z0-9-_.\s]+)/i',$strWlan0,$result)) { + $strSSID = str_replace(' freq','',$result[1]); } + if (preg_match('/Link Quality=([0-9]+\/[0-9]+)/i',$strWlan0,$result)) { + $strLinkQuality = $result[1]; + if (strpos($strLinkQuality, "/")) { + $arrLinkQuality = explode("/", $strLinkQuality); + $strLinkQuality = number_format(($arrLinkQuality[0] / $arrLinkQuality[1]) * 100)." %"; + } + } + if (preg_match('/Signal Level=(-[0-9]+ dBm)/i',$strWlan0,$result)) { + $strSignalLevel = $result[1]; } + if (preg_match('/Signal Level=([0-9]+\/[0-9]+)/i',$strWlan0,$result)) { + $strSignalLevel = $result[1]; } + if (preg_match('/signal:\ (-[0-9]+ dBm)/i',$strWlan0,$result)) { + $strSignalLevel = $result[1]; } + if (preg_match('/Frequency:([0-9.]+ GHz)/i',$strWlan0,$result)) { + $strWifiFreq = $result[1]; + $strWifiChan = str_replace(" GHz", "", $strWifiFreq); + $strWifiChan = str_replace(".", "", $strWifiChan); + $strWifiChan = ConvertToChannel(str_replace(".", "", $strWifiChan)); } + } + else { + $strStatus = '未启用'; + } + if(isset($_POST['ifdown_wlan0'])) { + exec('ifconfig wlan0 | grep -i running | wc -l',$test); + if($test[0] == 1) { + exec('sudo ifdown wlan0',$return); + } + else { + echo '接口已经是关闭状态'; + } + } + elseif(isset($_POST['ifup_wlan0'])) { + exec('ifconfig wlan0 | grep -i running | wc -l',$test); + if($test[0] == 0) { + exec('sudo ifup wlan0',$return); + } + else { + echo '接口已经是开启状态'; + } + } + elseif(isset($_POST['reset_wlan0'])) { + exec('sudo wpa_cli reconfigure wlan0 && sudo ifdown wlan0 && sleep 3 && sudo ifup wlan0 && sudo wpa_cli scan',$test); + echo ''; + } + + echo ' +
+
+ + + + + + +
+
无线信息与统计
+
接口信息
+      接口名称 : wlan0
+    接口状态 : ' . $strStatus . '
+          IP 地址 : ' . $strIPAddress . '
+         子网掩码 : ' . $strNetMask . '
+         MAC 地址 : ' . $strHWAddress . '
+
+
接口统计
+    接收数据包 : ' . $strRxPackets . '
+      接收字节数 : ' . $strRxBytes . '
+ 发送数据包 : ' . $strTxPackets . '
+   发送字节数 : ' . $strTxBytes . '
+
+
+
+
无线信息
+   已连接到 : ' . $strSSID . '
+ 接入点 MAC 地址 : ' . $strBSSID . '
+
+        传输速率 : ' . $strBitrate . '
+   信号强度 : ' . $strSignalLevel . '
+
'; +if ($strTxPower) { echo ' 发射功率 : ' . $strTxPower .'
'."\n"; } else { echo "
\n"; } +if ($strLinkQuality) { echo '   链路质量 : ' . $strLinkQuality . '
'."\n"; } else { echo "
\n"; } +if (($strWifiFreq) && ($strWifiChan) && ($strWifiChan != "Invalid Channel")) { + echo '   信道信息 : ' . $strWifiChan . ' (' . $strWifiFreq . ')
'."\n"; +} else { + echo "
\n"; +} +if (file_exists('/etc/wpa_supplicant/wpa_supplicant.conf')) { + exec('sudo grep "country" /etc/wpa_supplicant/wpa_supplicant.conf', $wifiCountryArr); + } +if (isset($wifiCountryArr[0])) { + $wifiCountry = explode("=", $wifiCountryArr[0]); + if (isset($wifiCountry[1])) { + echo '   WiFi 国家代码 : '.$wifiCountry[1]."
\n"; + } + } +echo '
+
+
+
+
+
信息来源:ifconfig 和 iwconfig
'; + break; + + case "wpa_conf": + exec('sudo cat /etc/wpa_supplicant/wpa_supplicant.conf',$return); + $ssid = array(); + $psk = array(); + foreach($return as $a) { + if(preg_match('/country=/i',$a)) { + $wifiCountryArr = explode("=",$a); + $wifiCountry = $wifiCountryArr[1]; + } + + // Make sure we only put ONE SSID and matching PSK into the arrays + if ( ( isset($curssidplain) || isset($curssidalt) ) && ( isset($curpskplain) || isset($curpskalt) ) ) { + if (isset($curssidplain)) { $ssid[] = $curssidplain; unset($curssidplain); unset($curssidalt); } + if (isset($curssidalt)) { $ssid[] = $curssidalt; unset($curssidplain); unset($curssidalt); } + if (isset($curpskplain)) { $psk[] = $curpskplain; unset($curpskplain); unset($curpskalt); } + if (isset($curpskalt)) { $psk[] = $curpskalt; unset($curpskplain); unset($curpskalt); } + } + + // Handle the case of the old file format, and the new... + if(preg_match('/\#SSID=/i',$a) && !preg_match('/scan_ssid/i',$a)) { + $arrssid = explode("=",$a); + //$ssid[] = str_replace('"','',$arrssid[1]); + $curssidplain = str_replace('"','',$arrssid[1]); + } + elseif(preg_match('/SSID="/i',$a) && !preg_match('/scan_ssid/i',$a)) { + $arrssid = explode("=",$a); + //$ssid[] = str_replace('"','',$arrssid[1]); + if (!isset($curssidplain)) { $curssidalt = str_replace('"','',$arrssid[1]); } + } + if (isset($curssidplain) || isset($curssidalt)) { + if(preg_match('/\#psk="/i',$a)) { + $arrpsk = explode("=",$a); + //$psk[] = str_replace('"','',$arrpsk[1]); + $curpskplain = str_replace('"','',$arrpsk[1]); + } + elseif(preg_match('/psk=/i',$a)) { + $arrpsk = explode("=",$a); + //$psk[] = str_replace('"','',$arrpsk[1]); + if (!isset($curpskplain)) { $curpskalt = str_replace('"','',$arrpsk[1]); } + } + } + } + $numSSIDs = count($ssid); + $output = '
+
+ +
'."\n"; + if (!isset($wifiCountry)) { $wifiCountry = "JP"; } + $output .= 'WiFi 法规域(国家代码):
'."\n"; + + for($ssids = 0; $ssids < $numSSIDs; $ssids++) { + $output .= '
网络 '.$ssids."\n"; + $output .= '
'."\n"; + $output .= 'SSID :
'."\n"; + $output .= 'PSK :

'."\n"; + } + $output .= '
'."\n"; + $output .= '
'."\n"; + $output .= ''."\n"; + $output .= ''."\n"; + $output .= ''."\n"; + $output .= '
'."\n"; + $output .= '
'."\n"; + + + echo $output; + echo ''; + + if(isset($_POST['SaveWPAPSKSettings'])) { + $config = "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\nap_scan=1\nfast_reauth=1\ncountry=".$_POST['wifiCountryCode']."\n\n"; + $networks = $_POST['Networks']; + + //Reworked WiFi Starts Here + for($x = 0; $x < $networks; $x++) { + //$network = ''; + $ssid = $_POST['ssid'.$x]; + $psk = $_POST['psk'.$x]; + $priority = 100 - $x; + if ($ssid == "*" && !$psk) { $config .= "network={\n\t#ssid=\"$ssid\"\n\t#psk=\"\"\n\tkey_mgmt=NONE\n\tid_str=\"$x\"\n\tpriority=$priority\n\tscan_ssid=1\n}\n\n"; } + elseif ($ssid && !$psk) { $config .= "network={\n\tssid=\"$ssid\"\n\t#psk=\"\"\n\tkey_mgmt=NONE\n\tid_str=\"$x\"\n\tpriority=$priority\n\tscan_ssid=1\n}\n\n"; } + elseif ($ssid && $psk) { + $pskSalted = hash_pbkdf2("sha1",$psk, $ssid, 4096, 64); + $ssidHex = bin2hex("$ssid"); + $config .= "network={\n\t#ssid=\"$ssid\"\n\tssid=$ssidHex\n\t#psk=\"$psk\"\n\tpsk=$pskSalted\n\tid_str=\"$x\"\n\tpriority=$priority\n\tscan_ssid=1\n}\n\n"; + } + } + file_put_contents('/tmp/wifidata', $config); + system('sudo mount -o remount,rw / && sudo cp -f /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant.conf && sudo sync && sudo sync && sudo sync && sudo mount -o remount,ro /'); + echo "WiFi 设置已成功更新\n"; + // If Auto AP is on, dont restart the WiFi Card + if (!file_exists('/sys/class/net/wlan0_ap')) { + exec('sudo ip link set wlan0 down && sleep 3 && sudo ip link set wlan0 up'); + } + echo ""; + + } elseif(isset($_POST['Scan'])) { + $return = ''; + exec('ifconfig wlan0 | grep -i running | wc -l',$test); + exec('sudo wpa_cli scan -i wlan0',$return); + sleep(8); + exec('sudo wpa_cli scan_results -i wlan0',$return); + unset($return['0']); // This is a better way to clean up; + unset($return['1']); // This is a better way to clean up; + echo "
\n"; + echo "
\n"; + echo "发现的网络 :
\n"; + echo "\n"; + echo ""; + foreach($return as $network) { + $arrNetwork = preg_split("/[\t]+/",$network); + $bssid = $arrNetwork[0]; + $channel = ConvertToChannel($arrNetwork[1]); + $signal = $arrNetwork[2] . " dBm"; + $security = ConvertToSecurity($arrNetwork[3]); + $ssid = $arrNetwork[4]; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''."\n"; + + } + echo "
连接SSID信道信号加密方式
'.$ssid.''.$channel.''.$signal.''.$security.'
\n"; + echo "
\n"; + } + + break; +} + + +echo ' +
.
+ +'; +?> diff --git a/admin/wifi/functions.js b/admin/wifi/functions.js new file mode 100644 index 0000000..f22755d --- /dev/null +++ b/admin/wifi/functions.js @@ -0,0 +1,69 @@ +function WiFiDown() { + var down = confirm("确定要关闭 wlan0 吗?"); + if(down) { + } else { + alert("操作已取消"); + } +} + +function UpdateNetworks() { + var existing = document.getElementById("networkbox").getElementsByTagName('div').length; + document.getElementById("Networks").value = existing; +} + +function AddNetwork() { + var Networks = document.getElementById('Networks').value; + document.getElementById('networkbox').innerHTML += '
网络 '+Networks+'
\ +SSID :
\ +PSK :
'; + Networks++; + document.getElementById('Networks').value=Networks; + +} + +function AddScanned(network) { + existing = document.getElementById("networkbox").getElementsByTagName('div').length; + var Networks = document.getElementById('Networks').value; + if(existing != 0) { + document.getElementById('Networks').value = Networks; + } + //document.getElementById('Networks').value=Networks; + document.getElementById('networkbox').innerHTML += '
网络 '+Networks+'
\ +SSID :
\ +PSK :
'; + document.getElementById('ssid'+Networks).value = network; + if(existing == 0) { + Networks++ + document.getElementById('Networks').value = Networks; + } + document.getElementById('Networks').value = Networks; + Networks++ +} + +function CheckSSID(ssid) { + if(ssid.value.length>31) { + ssid.style.background='#FFD0D0'; + document.getElementById('Save').disabled = true; + } else { + ssid.style.background='#D0FFD0'; + document.getElementById('Save').disabled = false; + } +} + +function CheckPSK(psk) { + if(psk.value.length > 0 && psk.value.length < 8) { + psk.style.background='#FFD0D0'; + document.getElementById('Save').disabled = true; + } else { + psk.style.background='#D0FFD0'; + document.getElementById('Save').disabled = false; + } +} + +function DeleteNetwork(network) { + element = document.getElementById('Networkbox'+network); + element.parentNode.removeChild(element); + var Networks = document.getElementById('Networks').value; + Networks-- + document.getElementById('Networks').value = Networks; +} diff --git a/admin/wifi/index.php b/admin/wifi/index.php new file mode 100644 index 0000000..702d3d1 --- /dev/null +++ b/admin/wifi/index.php @@ -0,0 +1,3 @@ + $arrConfig One line per element. + * @return array Keyed by the LHS of the first `=` per line. + */ +function ParseConfig($arrConfig) +{ + $config = array(); + foreach ($arrConfig as $line) { + if ($line[0] != "#") { + $arrLine = explode("=", $line); + $config[$arrLine[0]] = $arrLine[1]; + } + } + return $config; +} + +/** + * Map a wireless centre frequency to a friendly channel label. + * + * Accepts either MHz integers (e.g. "2412" → "2.4GHz Ch1", "5180" → + * "5.0GHz Ch36") or some legacy short forms emitted by older `iw` + * builds (e.g. "504" / "508" for 5GHz channels in the 5MHz grid). + * + * @param string $freq Centre frequency as reported by iwconfig/iw. + * @return string Friendly channel label, or "Invalid Channel" if unmapped. + */ +function ConvertToChannel($freq) +{ + $wifiFreqToChan = array ( + "2412" => "2.4GHz Ch1", + "2417" => "2.4GHz Ch2", + "2422" => "2.4GHz Ch3", + "2427" => "2.4GHz Ch4", + "2432" => "2.4GHz Ch5", + "2437" => "2.4GHz Ch6", + "2442" => "2.4GHz Ch7", + "2447" => "2.4GHz Ch8", + "2452" => "2.4GHz Ch9", + "2457" => "2.4GHz Ch10", + "2462" => "2.4GHz Ch11", + "2467" => "2.4GHz Ch12", + "2472" => "2.4GHz Ch13", + "2484" => "2.4GHz Ch14", + "504" => "5.0GHz Ch8", + "506" => "5.0GHz Ch12", + "508" => "5.0GHz Ch16", + "517" => "5.0GHz Ch34", + "518" => "5.0GHz Ch36", + "519" => "5.0GHz Ch38", + "520" => "5.0GHz Ch40", + "521" => "5.0GHz Ch42", + "522" => "5.0GHz Ch44", + "523" => "5.0GHz Ch46", + "524" => "5.0GHz Ch48", + "526" => "5.0GHz Ch52", + "528" => "5.0GHz Ch56", + "530" => "5.0GHz Ch60", + "532" => "5.0GHz Ch64", + "550" => "5.0GHz Ch100", + "552" => "5.0GHz Ch104", + "554" => "5.0GHz Ch108", + "556" => "5.0GHz Ch112", + "558" => "5.0GHz Ch116", + "560" => "5.0GHz Ch120", + "562" => "5.0GHz Ch124", + "564" => "5.0GHz Ch128", + "566" => "5.0GHz Ch132", + "568" => "5.0GHz Ch136", + "570" => "5.0GHz Ch140", + "492" => "5.0GHz Ch184", + "494" => "5.0GHz Ch188", + "496" => "5.0GHz Ch192", + "498" => "5.0GHz Ch196", + "5035" => "5.0GHz Ch7", + "5040" => "5.0GHz Ch8", + "5045" => "5.0GHz Ch9", + "5055" => "5.0GHz Ch11", + "5060" => "5.0GHz Ch12", + "5080" => "5.0GHz Ch16", + "5170" => "5.0GHz Ch34", + "5180" => "5.0GHz Ch36", + "5190" => "5.0GHz Ch38", + "5200" => "5.0GHz Ch40", + "5210" => "5.0GHz Ch42", + "5220" => "5.0GHz Ch44", + "5230" => "5.0GHz Ch46", + "5240" => "5.0GHz Ch48", + "5260" => "5.0GHz Ch52", + "5280" => "5.0GHz Ch56", + "5300" => "5.0GHz Ch60", + "5320" => "5.0GHz Ch64", + "5500" => "5.0GHz Ch100", + "5520" => "5.0GHz Ch104", + "5540" => "5.0GHz Ch108", + "5560" => "5.0GHz Ch112", + "5580" => "5.0GHz Ch116", + "5600" => "5.0GHz Ch120", + "5620" => "5.0GHz Ch124", + "5640" => "5.0GHz Ch128", + "5660" => "5.0GHz Ch132", + "5680" => "5.0GHz Ch136", + "5700" => "5.0GHz Ch140", + "5745" => "5.0GHz Ch149", + "5765" => "5.0GHz Ch153", + "5785" => "5.0GHz Ch157", + "5805" => "5.0GHz Ch161", + "5825" => "5.0GHz Ch165", + "4915" => "5.0GHz Ch183", + "4920" => "5.0GHz Ch184", + "4925" => "5.0GHz Ch185", + "4935" => "5.0GHz Ch187", + "4940" => "5.0GHz Ch188", + "4945" => "5.0GHz Ch189", + "4960" => "5.0GHz Ch192", + "4980" => "5.0GHz Ch196" + ); + if (array_key_exists($freq, $wifiFreqToChan)) { return $wifiFreqToChan[$freq]; } + else { return "Invalid Channel"; } +} + +/** + * Map a wpa_supplicant security flag string to a human-friendly label. + * + * Input is the bracketed token list reported by `wpa_cli scan_results` + * (e.g. "[WPA2-PSK-CCMP][ESS]" → "WPA2-PSK (AES)"). Unknown flag strings + * fall through to the default branch and are returned verbatim so the + * UI never silently swallows a network it doesn't recognise. + * + * @param string $security Bracketed flag list from wpa_cli output. + * @return string Friendly security descriptor, or the raw flag string. + */ +function ConvertToSecurity($security) +{ + switch ($security) { + case "[WPA2-PSK-CCMP][ESS]": + return "WPA2-PSK (AES)"; + break; + case "[WPA2-PSK-CCMP-preauth][ESS]": + return "WPA2-PSK (AES) with Preauth"; + break; + case "[WPA2-PSK-TKIP][ESS]": + return "WPA2-PSK (TKIP)"; + break; + case "[WPA2-PSK-CCMP][WPS][ESS]": + return "WPA2-PSK (TKIP) with WPS"; + break; + case "[WPA-PSK-TKIP+CCMP][WPS][ESS]": + return "WPA-PSK (TKIP/AES) with WPS"; + break; + case "[WPA-PSK-TKIP][WPA2-PSK-CCMP][WPS][ESS]": + return "WPA/WPA2-PSK (TKIP)"; + break; + case "[WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][ESS]": + return "WPA/WPA2-PSK (TKIP/AES)"; + break; + case "[WPA-EAP-CCMP+TKIP][WPA2-EAP-CCMP+TKIP-preauth][ESS]": + return "WPA/WPA2-PSK (TKIP/AES) with Preauth"; + break; + case "[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]": + return "WPA/WPA2-PSK (TKIP/AES) with WPS"; + break; + case "[WPA-PSK-CCMP][WPA2-PSK-CCMP][WPS][ESS]": + return "WPA/WPA2-PSK (AES) with WPS"; + break; + case "[WPA-PSK-TKIP][ESS]": + return "WPA-PSK (TKIP)"; + break; + case "[WEP][ESS]": + return "WEP"; + break; + case "[ESS]": + return "None"; + break; + default: + return $security; + break; + } +} diff --git a/admin/wifi/styles.css b/admin/wifi/styles.css new file mode 100644 index 0000000..5aa0281 --- /dev/null +++ b/admin/wifi/styles.css @@ -0,0 +1,100 @@ +body { + background-color: #dd4b39; +} + +.red { + color:red; +} + +.green { + color:green; +} + +.network { + border:1px solid; + text-align:left; + padding-top:5px; + padding-left:5px; + background-color: #d0d0d0; +} + +.tableft { + display:inline-block; + text-align:right; + width:120px; +} + +textarea { + width:699px; + height:499px; +} + +input.button { + width:80%; + border:1px solid; +} + +input[type=text],input[type=password] { + border:1px solid; +} + +input[type=button],input[type=submit] { + border:1px solid; + border-radius:5px; +} + +.infoheader { + width:100%; + text-align:center; + margin-top:10px; + border-bottom: 1px solid; +} + +.infobox { + background-color: #dd4b39; + font-weight: bold; + width:100%; +} + +.intinfo { + background-color: #d0d0d0; + color: #000000; + width: 398px; + text-align: left; + border-right: 1px solid; + border-left: 1px solid; + float: left; + border-bottom:1px solid; +} + +.wifiinfo { + background-color: #d0d0d0; + color: #000000; + margin: 0 0 0 400px; + border-right: 1px solid; + text-align: left; + border-bottom:1px solid; +} + +.intheader { + background-color: #dd4b39; + color: #ffffff; + text-align:center; + width:100%; + font-weight: bold; +} + +.intfooter { + background-color: #dd4b39; + color: #ffffff; + width:100%; + border-top:1px solid; + float:left; + text-align:center; +} + +.tail { + background-color: #dd4b39; + color: #dd4b39; + width:100%; +} diff --git a/admin/wifi/styles.php b/admin/wifi/styles.php new file mode 100644 index 0000000..f67f74e --- /dev/null +++ b/admin/wifi/styles.php @@ -0,0 +1,156 @@ + +body { + background-color: #; +} + +.red { + color:red; +} + +.green { + color:green; +} + +.network { + border:1px solid; + text-align:left; + padding-top:5px; + padding-left:5px; + background-color: #; +} + +.tableft { + display:inline-block; + text-align:right; + width:120px; +} + +textarea { + width:699px; + height:499px; +} + +input.button { + width:80%; + border:1px solid; +} + +input[type=text],input[type=password] { + border:1px solid; +} + +input[type=button],input[type=submit] { + border:1px solid; + border-radius:5px; +} + +.infoheader { + width:100%; + text-align:center; + margin-top:10px; + border-bottom: 1px solid; +} + +.infobox { + background-color: #; + font-weight: bold; + width:100%; +} + +.intinfo { + background-color: #; + color: #; + width: 398px; + text-align: left; + border-right: 1px solid; + border-left: 1px solid; + float: left; + border-bottom:1px solid; + font-family: "Courier New", Courier, monospace; +} + +.wifiinfo { + background-color: #; + color: #; + margin: 0 0 0 400px; + border-right: 1px solid; + text-align: left; + border-bottom:1px solid; + font-family: "Courier New", Courier, monospace; +} + +.intheader { + background-color: #; + color: #; + text-align:center; + width:100%; + font-weight: bold; +} + +.intfooter { + background-color: #; + color: #; + width:100%; + border-top:1px solid; + float:left; + text-align:center; +} + +.tail { + background-color: #; + color: #; + width:100%; +} diff --git a/api/index.php b/api/index.php new file mode 100644 index 0000000..e69de29 diff --git a/api/last_heard.php b/api/last_heard.php new file mode 100644 index 0000000..be72695 --- /dev/null +++ b/api/last_heard.php @@ -0,0 +1,65 @@ + shape used throughout the + * dashboard (no CSS class — same convention as the existing banners + * in index.php / configure.php this file replaces). + * + * @param string $html Banner body. INTERPOLATED RAW — callers must + * ensure any dynamic content is already escaped + * (none of the current banners include dynamic + * content, so this is a non-issue today; flagged + * for any future caller). + * @param string $level 'warn' (yellow) or 'danger' (red). + * + * @return void + */ +function _pistar_banner_emit($html, $level = 'warn') +{ + $style = ($level === 'danger') + ? 'background-color: #ff9090; color: #f01010;' + : 'background-color: #ffff90; color: #906000;'; + echo '
' . "\n"; + echo '
' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo '
' . $html . '
' . "\n"; + echo '' . "\n"; +} + +/** + * Extract the HTTP Basic auth user + password from the current request. + * + * On Pi-Star's stock nginx + PHP-FPM config, $_SERVER['PHP_AUTH_PW'] + * is populated automatically (PHP synthesises it from the forwarded + * Authorization header). The HTTP_AUTHORIZATION fallback handles + * unusual setups where only the raw header survives. + * + * Note: the REMOTE_USER fallback only sets $user — that var contains + * the authenticated username but never the password. Callers that need + * the password (default-password check, etc.) treat empty $pw as + * "unable to verify" and fail closed. + * + * @return array{0: string, 1: string} [$user, $password]; either or + * both may be '' when basic auth + * hasn't happened (e.g. on the + * unauthenticated public root). + */ +function _pistar_get_basic_auth() +{ + $user = isset($_SERVER['PHP_AUTH_USER']) ? (string)$_SERVER['PHP_AUTH_USER'] : ''; + $pw = isset($_SERVER['PHP_AUTH_PW']) ? (string)$_SERVER['PHP_AUTH_PW'] : ''; + if ($user !== '' && $pw !== '') { + return array($user, $pw); + } + if (!empty($_SERVER['HTTP_AUTHORIZATION']) + && stripos($_SERVER['HTTP_AUTHORIZATION'], 'Basic ') === 0) { + $decoded = base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6), true); + if ($decoded !== false && strpos($decoded, ':') !== false) { + list($user, $pw) = explode(':', $decoded, 2); + return array($user, $pw); + } + } + if ($user === '' && !empty($_SERVER['REMOTE_USER'])) { + $user = (string)$_SERVER['REMOTE_USER']; + } + return array($user, $pw); +} + +/** + * True iff the operator is the canonical 'pi-star' account AND + * still using the factory default password 'raspberry'. + * + * Account scope is deliberately narrow: the requirement is to + * protect against drive-by auth using the well-known stock + * credentials. Operators who created their own usernames are + * out of scope (they made an active choice). + * + * Constant-time compare via hash_equals — defence in depth; the + * default password is publicly documented so timing isn't really + * an oracle, but it's the right idiom for password compares. + * + * @return bool + */ +function _pistar_default_password_in_use() +{ + list($user, $pw) = _pistar_get_basic_auth(); + if ($user !== 'pi-star' || $pw === '') { + return false; + } + return hash_equals('raspberry', $pw); +} + +/** + * The Pi's local interface subnets (IPv4 + IPv6). + * + * Read once per request from `ip -j addr show` (iproute2 JSON output; + * always present on Pi-Star). Cached in a static for repeated calls. + * + * @return array + */ +function _pistar_local_subnets() +{ + static $cached = null; + if ($cached !== null) { + return $cached; + } + $cached = array(); + + $out = array(); + $rc = 1; + exec('ip -j addr show 2>/dev/null', $out, $rc); + if ($rc !== 0 || empty($out)) { + return $cached; + } + $ifaces = json_decode(implode('', $out), true); + if (!is_array($ifaces)) { + return $cached; + } + foreach ($ifaces as $iface) { + if (empty($iface['addr_info']) || !is_array($iface['addr_info'])) { + continue; + } + foreach ($iface['addr_info'] as $a) { + if (empty($a['local']) || !isset($a['prefixlen'])) { + continue; + } + $cached[] = array( + 'ip' => (string)$a['local'], + 'prefix' => (int)$a['prefixlen'], + ); + } + } + return $cached; +} + +/** + * Family-aware bitmask subnet membership test. + * + * Handles IPv4, IPv6, and the IPv4-mapped IPv6 form (`::ffff:a.b.c.d`) + * that nginx may surface in REMOTE_ADDR when the listener is dual-stack. + * + * @param string $remote The address to test. + * @param string $netIp The subnet's network/anchor address. + * @param int $prefix Prefix length in bits (0..32 for IPv4, 0..128 for IPv6). + * + * @return bool + */ +function _pistar_ip_in_subnet($remote, $netIp, $prefix) +{ + $remoteBin = @inet_pton($remote); + $netBin = @inet_pton($netIp); + if ($remoteBin === false || $netBin === false) { + return false; + } + // Handle v4-mapped v6 (::ffff:1.2.3.4) when comparing against a + // bare v4 subnet — strip the 12-byte v4-mapped prefix. Mapping is + // one-directional by design: nginx surfaces REMOTE_ADDR in mapped + // form on dual-stack listeners while `ip -j addr show` reports the + // interface as bare v4, so the asymmetric strip is what the real + // topology produces. The reverse case (bare v4 client, mapped + // subnet) cannot occur in practice and falls through to "not a + // match" below. + if (strlen($remoteBin) === 16 && strlen($netBin) === 4 + && substr($remoteBin, 0, 12) === "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff") { + $remoteBin = substr($remoteBin, 12); + } + if (strlen($remoteBin) !== strlen($netBin)) { + // Different families and no compatible mapping — not in subnet. + return false; + } + $bits = strlen($remoteBin) * 8; + if ($prefix < 0 || $prefix > $bits) { + return false; + } + $bytesFull = intdiv($prefix, 8); + $bitsExtra = $prefix % 8; + if ($bytesFull > 0 + && substr($remoteBin, 0, $bytesFull) !== substr($netBin, 0, $bytesFull)) { + return false; + } + if ($bitsExtra > 0) { + $mask = chr((0xFF << (8 - $bitsExtra)) & 0xFF); + if ((substr($remoteBin, $bytesFull, 1) & $mask) + !== (substr($netBin, $bytesFull, 1) & $mask)) { + return false; + } + } + return true; +} + +/** + * Is REMOTE_ADDR in the same L3 subnet as ANY of the Pi's interfaces? + * + * Loopback (127.0.0.0/8 on v4, ::1/128 on v6) is treated as local — + * those addresses appear when an admin page is hit via the device + * itself or via a CLI test harness on the Pi. + * + * @return bool + */ +function _pistar_remote_is_local() +{ + $remote = isset($_SERVER['REMOTE_ADDR']) ? (string)$_SERVER['REMOTE_ADDR'] : ''; + if ($remote === '') { + return false; + } + foreach (_pistar_local_subnets() as $sub) { + if (_pistar_ip_in_subnet($remote, $sub['ip'], $sub['prefix'])) { + return true; + } + } + return false; +} + +/** + * Layer 2: redirect to the forced-change page when default password + + * remote client. Must be called BEFORE any output so header() works. + * + * Skips: + * - Public (non-/admin/) URLs — anonymous visitors don't get told + * the device is on default credentials. + * - The /admin/change_password_required.php page itself — would + * loop. + * - Pages reached during the setup-not-done flow (no mode marker + * file yet) — let the existing "No Mode Defined" handler in + * index.php drive the operator through configure.php first. + * + * @return void + */ +function pistar_warnings_enforce_redirect() +{ + $self = isset($_SERVER['PHP_SELF']) ? (string)$_SERVER['PHP_SELF'] : ''; + if (strpos($self, '/admin/') !== 0) { + return; + } + if ($self === '/admin/change_password_required.php') { + return; + } + // Long-running operational tools — let the operator finish what + // they're doing. The Layer-1 banner still appears at the top of + // these pages so the warning isn't suppressed; only the redirect + // is. update.php and expert/upgrade.php both poll a `?ajax` + // log-tail every 1 s; redirecting on those polls causes jQuery + // to follow the 302 and append the full change-password HTML + // into the page's #tail div, "flooding" the operator with copies + // of the change-password form. + if ($self === '/admin/update.php' + || $self === '/admin/expert/upgrade.php') { + return; + } + // Defence in depth: any GET that includes ?ajax skips the redirect. + // Catches AJAX log-tail / status-poll endpoints elsewhere in the + // dashboard (live_modem_log.php, expert/jitter_test.php, etc.) so + // a future poll-style page can't accidentally regress the same way. + // The initial GET that loaded the parent page already carried the + // banner — defaulting AJAX to "no redirect" is safe. + if (isset($_GET['ajax'])) { + return; + } + if (!file_exists('/etc/dstar-radio.mmdvmhost') + && !file_exists('/etc/dstar-radio.dstarrepeater')) { + return; + } + if (!_pistar_default_password_in_use()) { + return; + } + if (_pistar_remote_is_local()) { + return; + } + header('Location: /admin/change_password_required.php', true, 302); + exit; +} + +/** + * Render every applicable warning banner. Admin URLs only. + * + * Adding a new banner here? The body string is interpolated into the + * raw — pre-escape any dynamic content with htmlspecialchars() + * before passing it to _pistar_banner_emit(). Today's seven banners + * are all string literals so the issue does not arise. + * + * @return void + */ +function pistar_warnings_render() +{ + $self = isset($_SERVER['PHP_SELF']) ? (string)$_SERVER['PHP_SELF'] : ''; + if (strpos($self, '/admin/') !== 0) { + return; + } + + // Default-password banner FIRST — highest severity, most actionable. + if (_pistar_default_password_in_use()) { + _pistar_banner_emit( + 'Security Alert: Your dashboard is using the default password. ' + . 'Please change it now ' + . 'from the Configuration page.', + 'danger' + ); + } + + // Pi-Star release-derived banners. parse_ini_file() once per request. + $release = @parse_ini_file('/etc/pistar-release', true); + $version = is_array($release) && isset($release['Pi-Star']['Version']) + ? (string)$release['Pi-Star']['Version'] + : ''; + $hardware = is_array($release) && isset($release['Pi-Star']['Hardware']) + ? (string)$release['Pi-Star']['Hardware'] + : ''; + + if ($version !== '' && $hardware === 'RPi' + && version_compare($version, '4.1', '<')) { + _pistar_banner_emit( + 'Alert: You are running an outdated version of Pi-Star, please upgrade.
' + . 'New versions are available from here: ' + . 'http://www.pistar.uk/downloads/.', + 'warn' + ); + } + if ($version !== '' + && version_compare($version, '4.1.0', '>=') + && version_compare($version, '4.1.13', '<')) { + _pistar_banner_emit( + 'Alert: An upgrade to Pi-Star has been released, click here to upgrade now: ' + . 'Upgrade Pi-Star.', + 'warn' + ); + } + if ($version !== '' + && version_compare($version, '4.2.0', '>=') + && version_compare($version, '4.2.6', '<')) { + _pistar_banner_emit( + 'Alert: An upgrade to Pi-Star has been released, click here to upgrade now: ' + . 'Upgrade Pi-Star.', + 'warn' + ); + } + if ($version !== '' + && version_compare($version, '4.3.0', '>=') + && version_compare($version, '4.3.7', '<')) { + _pistar_banner_emit( + 'Alert: An upgrade to Pi-Star has been released, click here to upgrade now: ' + . 'Upgrade Pi-Star.', + 'warn' + ); + } + + // DMR public mode without ACL (loop risk). Was configure.php-only; + // now appears across the admin surface so the operator sees it + // regardless of which page they land on first. + if (file_exists('/etc/dstar-radio.mmdvmhost')) { + $mmdvm = @parse_ini_file('/etc/mmdvmhost', true); + if (is_array($mmdvm) + && isset($mmdvm['DMR']['Enable']) && $mmdvm['DMR']['Enable'] == 1 + && isset($mmdvm['DMR']['SelfOnly']) && $mmdvm['DMR']['SelfOnly'] == 0 + && isset($mmdvm['General']['Id']) + && strlen((string)$mmdvm['General']['Id']) >= 7 + && !isset($mmdvm['DMR']['WhiteList'])) { + _pistar_banner_emit( + 'Alert: You are running a hotspot in public mode without an access list for DMR, ' + . 'this setup *could* participate in network loops!', + 'warn' + ); + } + } + + // BM API v1 key (length-based heuristic, kept identical to the + // configure.php trigger this consolidates). + $bmKey = '/etc/bmapi.key'; + if (file_exists($bmKey)) { + $bm = @parse_ini_file($bmKey, true); + if (is_array($bm) && !empty($bm['key']['apikey']) + && strlen((string)$bm['key']['apikey']) <= 200) { + _pistar_banner_emit( + 'Alert: You have a BM API v1 Key, click here for the announcement: ' + . 'BM API Keys - Announcement.', + 'danger' + ); + } + } +} diff --git a/config/browserdetect.php b/config/browserdetect.php new file mode 100644 index 0000000..0304f88 --- /dev/null +++ b/config/browserdetect.php @@ -0,0 +1,35 @@ +` + * tags wired up with media queries: `pistar-css.php` for desktop widths, + * `pistar-css-mini.php` for narrow / mobile widths. + * + * The big regex in this file is the standard "detectmobilebrowsers" + * UA list (https://detectmobilebrowsers.com/) and is intentionally not + * formatted — it's a single literal that must stay byte-identical to + * remain a valid match for the documented agent list. + * + * Included from the page by index.php / admin/index.php and + * the configure / power / update pages. + */ + +if (empty($_SERVER['HTTP_USER_AGENT'])) { + // No UA at all (curl, some CLIs, oddball clients): default to the + // desktop-vs-mobile width-based pair so behaviour matches a normal + // browser hitting the page. + print " \n"; + print " \n"; +} else { + $useragent = $_SERVER['HTTP_USER_AGENT']; + if (preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i',$useragent)||preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i',substr($useragent,0,4))) { + // Mobile UA detected — bias toward the narrow-screen stylesheet. + print " \n"; + print " \n"; + } else { + // Desktop UA — width-based pair (same as the no-UA branch). + print " \n"; + print " \n"; + } +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..e431a7e --- /dev/null +++ b/config/config.php @@ -0,0 +1,52 @@ +=` (column 0 — stricter than sed's "anywhere on line" + * pattern, which is actually the correct intent and avoids the + * classic substring-collision class of bug). If the key is absent, + * do nothing — same as sed. Append-if-missing would be a behaviour + * change for daemons with strict parsers and isn't safe without a + * per-file audit; defer to a later opt-in flag if needed. + * + * - **Atomic file replacement via `sudo install -m 644 -o root -g root`.** + * `cp` is not atomic — open + write + close on the destination + * leaves a window where the file is partially written. `install` + * uses rename(2) when src and dst share a filesystem; falls back to + * copy+chmod+chown when they don't (which is our case here — `/tmp` + * is tmpfs, the destination is on the SD card). The fallback is no + * less atomic than the existing `cp` pattern used elsewhere in this + * dashboard, and it sets mode/owner in one go. A future hardening + * pass could co-locate the staging file with the destination for + * true rename(2) atomicity. + * + * - **Hardcoded shell command literals.** `system()` is called with a + * fixed-shape command string; only fixed paths and `escapeshellarg()`- + * wrapped arguments are interpolated. PHP's `proc_open()` array form + * is PHP 7.4+ and this codebase targets PHP 7.0+, so we stay with + * `system()` plus `escapeshellarg()` instead. + * + * - **Whitespace preservation.** Existing files use `key=value` with + * no spaces around `=`. The helper preserves that exactly — it does + * NOT normalise existing files' formatting. Daemons in this stack + * parse their own configs with byte-precise matching; introducing + * spurious whitespace changes would risk breakage out of scope for + * this fix. + * + * - **Concurrent-edits.** Pi-Star is a single-operator embedded device + * in practice. The read-mutate-write window in commit() is wider + * than sed's, but two simultaneous configure.php POSTs are vanishingly + * rare on the target hardware. TODO: add file locking via flock() + * if this assumption ever stops holding. + */ + +/** + * Allow-list of paths the helper is permitted to write via the + * flat key=value editor. Any other path passed to + * `config_writer_stage_flat()` is rejected with an error_log() + * entry and a `false` return. + * + * Defined as a function (not a constant) so the file is safe to + * `require_once` multiple times under PHP 7.0 — array constants from + * `define()` work in 7.0 but `const` arrays at the file scope are + * 5.6+ and uniform-array-syntax limits make redefinition awkward. + * + * @return array + */ +function config_writer_allowed_paths() +{ + return array( + '/etc/ircddbgateway', + '/etc/dstarrepeater', + '/etc/timeserver', + '/etc/aprsgateway', + '/etc/mobilegps', + '/etc/starnetserver', + ); +} + +/** + * Allow-list of paths the helper is permitted to write via the + * privileged-flat editor (`config_writer_stage_privileged_flat()`). + * + * Same column-0 `key=value` semantics as the unprivileged flat + * editor, but the read step uses `sudo cat` so the helper can + * service files that are mode-600 root:root and therefore not + * readable by www-data. The destination is restored at mode 600 + * root:root via `sudo install`. Kept separate from the flat + * allowlist because the read path and the install mode differ. + * + * @return array + */ +function config_writer_allowed_paths_privileged_flat() +{ + return array( + '/root/.Remote Control', // note: literal space in filename + '/etc/hostapd/hostapd.conf', // contains wpa_passphrase=… ; mode 600 root:root + ); +} + +/** + * Allow-list of paths the helper is permitted to write via the + * PHP-statement editor (`config_writer_stage_php_string()`). + * + * These files are PHP source files included by the dashboard at + * runtime. Each contains one or more lines of the form + * `$varName='value';` at column 0. The PHP-statement editor + * rewrites the value with proper PHP-string escaping so the + * attacker-controlled bytes can never escape the single-quoted + * string literal — preventing PHP RCE via these files. + * + * Kept separate from the flat allow-list because the file shape + * and the editing semantics are different. A path that's writable + * under one editor is NOT automatically writable under the other. + * + * @return array + */ +function config_writer_allowed_paths_php_string() +{ + return array( + '/var/www/dashboard/config/language.php', + '/var/www/dashboard/config/ircddblocal.php', + ); +} + +/** + * Stage a single `key=value` edit against a flat config file. + * + * The edit is queued in process-local memory. Nothing touches disk + * until {@see config_writer_commit()} runs. Multiple stages against the + * same file accumulate; multiple stages of the same key in the same + * file overwrite each other (last write wins) and only the last is + * applied at commit time. + * + * @param string $path Absolute path. Must appear in + * {@see config_writer_allowed_paths()}. + * @param string $key The key name, e.g. `aprsHostname`. Must match + * `[A-Za-z_][A-Za-z0-9_]*` — defence in depth so + * a programming-error caller can't inject a + * regex/sed metachar via the key. + * @param string $value The new value. Must not contain NUL / CR / LF + * (those would break the line-oriented file + * format). All other bytes — including shell + * metachars like `"` `'` `;` `&` `$` — are stored + * verbatim, which is correct: the value is data, + * not a shell argument. + * + * @return bool True if staged. False if rejected (path not allow- + * listed, key malformed, or value contains NUL/CR/LF). + * On false, an error_log() entry is emitted. + */ +function config_writer_stage_flat($path, $key, $value) +{ + if (!in_array($path, config_writer_allowed_paths(), true)) { + error_log("config_writer: refusing to stage edit against non-allowlisted path '$path'"); + return false; + } + if (!preg_match('/\A[A-Za-z_][A-Za-z0-9_]*\z/', $key)) { + error_log("config_writer: refusing to stage malformed key '$key' for $path"); + return false; + } + if (preg_match('/[\x00\r\n]/', $value)) { + error_log("config_writer: refusing to stage value with NUL/CR/LF for $path:$key"); + return false; + } + + if (!isset($GLOBALS['__config_writer_stage'])) { + $GLOBALS['__config_writer_stage'] = array(); + } + if (!isset($GLOBALS['__config_writer_stage'][$path])) { + $GLOBALS['__config_writer_stage'][$path] = array(); + } + // Last write wins for repeated stages of the same key. + $GLOBALS['__config_writer_stage'][$path][$key] = $value; + return true; +} + +/** + * Stage a single PHP single-quoted string assignment edit. + * + * Targets PHP source files in + * {@see config_writer_allowed_paths_php_string()} that contain a + * line of the form `$varName='value';` at column 0. The first + * matching line is rewritten with the new value, properly escaped + * for a PHP single-quoted string literal — `\\` and `'` inside + * the value are escaped to `\\\\` and `\\'` respectively. All + * other bytes (including shell metachars `"` `;` `&` `$`) are + * stored verbatim — they are data inside a string literal, not + * code. This closes the PHP RCE class introduced by the previous + * `sudo sed -i` pattern, where attacker bytes could close the + * sed-emitted single-quote and inject arbitrary PHP statements. + * + * Like {@see config_writer_stage_flat()}, the edit is queued in + * memory until {@see config_writer_commit()} runs. + * + * @param string $path Absolute path. Must appear in + * {@see config_writer_allowed_paths_php_string()}. + * @param string $varName PHP variable name (no leading `$`). + * Must match `[A-Za-z_][A-Za-z0-9_]*`. + * @param string $value The new value to assign. Must not contain + * NUL/CR/LF (those would either break PHP + * parsing or break the line-oriented edit). + * + * @return bool True if staged. False if rejected. + */ +function config_writer_stage_php_string($path, $varName, $value) +{ + if (!in_array($path, config_writer_allowed_paths_php_string(), true)) { + error_log("config_writer: refusing PHP-string stage for non-allowlisted path '$path'"); + return false; + } + if (!preg_match('/\A[A-Za-z_][A-Za-z0-9_]*\z/', $varName)) { + error_log("config_writer: refusing malformed PHP var name '$varName' for $path"); + return false; + } + if (preg_match('/[\x00\r\n]/', $value)) { + error_log("config_writer: refusing PHP-string value with NUL/CR/LF for $path:\$$varName"); + return false; + } + + if (!isset($GLOBALS['__config_writer_stage_phpstr'])) { + $GLOBALS['__config_writer_stage_phpstr'] = array(); + } + if (!isset($GLOBALS['__config_writer_stage_phpstr'][$path])) { + $GLOBALS['__config_writer_stage_phpstr'][$path] = array(); + } + $GLOBALS['__config_writer_stage_phpstr'][$path][$varName] = $value; + return true; +} + +/** + * Stage a single `key=value` edit against a flat config file that + * lives under root-only permissions. + * + * Identical contract to {@see config_writer_stage_flat()} except the + * file is read via `sudo cat` instead of the PHP-side `file()` — + * because mode-600 root:root paths are not readable by www-data — + * and the destination is reinstalled at mode 600 root:root rather + * than 644. + * + * The only currently-allowlisted path is `/root/.Remote Control`, + * which holds the ircDDBGateway remote-control password and port. + * + * @param string $path Absolute path. Must appear in + * {@see config_writer_allowed_paths_privileged_flat()}. + * @param string $key Same key contract as the unprivileged flat + * editor. + * @param string $value Same value contract. + * + * @return bool True if staged. False if rejected. + */ +function config_writer_stage_privileged_flat($path, $key, $value) +{ + if (!in_array($path, config_writer_allowed_paths_privileged_flat(), true)) { + error_log("config_writer: refusing privileged-flat stage for non-allowlisted path '$path'"); + return false; + } + if (!preg_match('/\A[A-Za-z_][A-Za-z0-9_]*\z/', $key)) { + error_log("config_writer: refusing malformed privileged-flat key '$key' for $path"); + return false; + } + if (preg_match('/[\x00\r\n]/', $value)) { + error_log("config_writer: refusing privileged-flat value with NUL/CR/LF for $path:$key"); + return false; + } + + if (!isset($GLOBALS['__config_writer_stage_priv'])) { + $GLOBALS['__config_writer_stage_priv'] = array(); + } + if (!isset($GLOBALS['__config_writer_stage_priv'][$path])) { + $GLOBALS['__config_writer_stage_priv'][$path] = array(); + } + $GLOBALS['__config_writer_stage_priv'][$path][$key] = $value; + return true; +} + +/** + * Atomically install $newContent at $path with the given mode root:root. + * + * Internal helper shared by the flat and php-string commit paths. + * Returns null on success or a single-line diagnostic on failure. + * On failure the destination is left untouched and the temp file + * is unlinked. + * + * @param string $path Absolute destination path. + * @param string $newContent Full file content to install. + * + * @return string|null Error string, or null on success. + */ +function _config_writer_install_atomic($path, $newContent, $mode = '644') +{ + // Defence in depth — $mode is never attacker-controlled (the + // helper's commit() picks one of two hardcoded values), but we + // refuse anything outside the small expected set so a future + // typo can't inadvertently widen file permissions. + if ($mode !== '644' && $mode !== '600') { + return "config_writer: refusing invalid mode '$mode' for $path"; + } + $tmp = tempnam('/tmp', 'pistar_cw_'); + if ($tmp === false) { + return "config_writer: tempnam() failed for $path"; + } + // tempnam() creates the file mode 0600 on POSIX, but a non-default + // umask could in theory widen it. Force-narrow before writing — + // the temp content may be a freshly-set password. + @chmod($tmp, 0600); + if (file_put_contents($tmp, $newContent) === false) { + @unlink($tmp); + return "config_writer: file_put_contents() failed for $tmp; $path edits skipped"; + } + $cmd = 'sudo install -m ' . $mode . ' -o root -g root ' + . escapeshellarg($tmp) . ' ' + . escapeshellarg($path); + $rc = 0; + $out = array(); + exec($cmd . ' 2>&1', $out, $rc); + @unlink($tmp); + if ($rc !== 0) { + return "config_writer: install exit=$rc for $path: " . implode(' / ', $out); + } + return null; +} + +/** + * Apply every staged edit and clear the staging buffer. + * + * For each affected file: + * 1. Read the file into a line array. + * 2. For each staged `key => value`, find the FIRST line whose head + * is `key=` (column 0) and replace it with `key=value`. If the + * key is absent, the edit is silently skipped (matches sed's + * `c\` semantics). + * 3. Write the rebuilt content to a tempnam() in /tmp. + * 4. Atomically copy back via `sudo install -m 644 -o root -g root`. + * + * Wraps the per-file install calls in a single mount-rw / mount-ro + * pair so concurrent POSTs can't race on remount toggles. Callers + * that already manage their own mount cycle (e.g. configure.php's + * top-level POST handler, which keeps `/` rw across many edits) + * should pass `$manageMount = false` to skip the helper's own + * mount-rw/ro — otherwise the helper's mount-ro will prematurely + * close the caller's write window. + * + * @param bool $manageMount Whether commit() should issue its own + * `sudo mount -o remount,rw /` and + * `... remount,ro /` around the batch. + * Default true — safe for one-off callers. + * Pass false from inside an already-managed + * mount window. + * + * @return array Diagnostic strings — empty on full success. + * Non-empty entries describe per-file failures + * (read failure, file_put_contents failure, + * install non-zero exit). The caller decides + * whether to surface to the UI or just log. + */ +function config_writer_commit($manageMount = true) +{ + $errors = array(); + $flatStage = isset($GLOBALS['__config_writer_stage']) + ? $GLOBALS['__config_writer_stage'] + : array(); + $phpStrStage = isset($GLOBALS['__config_writer_stage_phpstr']) + ? $GLOBALS['__config_writer_stage_phpstr'] + : array(); + $privStage = isset($GLOBALS['__config_writer_stage_priv']) + ? $GLOBALS['__config_writer_stage_priv'] + : array(); + + if (empty($flatStage) && empty($phpStrStage) && empty($privStage)) { + return $errors; + } + + // Optional single mount-rw / batched-install / mount-ro envelope. + // When the caller already has `/` open rw (configure.php's POST + // handler does this for the duration of a save), we MUST NOT do + // our own mount-ro at the end — that would prematurely close the + // caller's window and break later writes (e.g. the timezone + // handler that runs after this commit). + if ($manageMount) { + system('sudo mount -o remount,rw /'); + } + + // Pass 1 — flat key=value edits. + foreach ($flatStage as $path => $kvPairs) { + if (!is_readable($path)) { + $errors[] = "config_writer: cannot read $path; edits skipped"; + error_log("config_writer: cannot read $path; " . count($kvPairs) . " edits skipped"); + continue; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + $errors[] = "config_writer: file() failed for $path; edits skipped"; + error_log("config_writer: file() failed for $path; " . count($kvPairs) . " edits skipped"); + continue; + } + + foreach ($kvPairs as $key => $value) { + $prefix = $key . '='; + $applied = false; + foreach ($lines as $i => $line) { + if (strpos($line, $prefix) === 0) { + $lines[$i] = $prefix . $value; + $applied = true; + break; + } + } + // Silent no-op when the key isn't present — matches the + // previous sed `c\` semantics. If the caller cares, log + // here. (Quiet by default to avoid filling the log on + // version-skew between the dashboard and the gateway + // configs.) + if (!$applied) { + error_log("config_writer: $path has no '$key=' line; edit skipped"); + } + } + + $err = _config_writer_install_atomic( + $path, + implode("\n", $lines) . "\n" + ); + if ($err !== null) { + $errors[] = $err; + error_log($err); + } + } + + // Pass 2 — PHP single-quoted string assignment edits. + foreach ($phpStrStage as $path => $varValuePairs) { + if (!is_readable($path)) { + $errors[] = "config_writer: cannot read $path; PHP-string edits skipped"; + error_log("config_writer: cannot read $path; " + . count($varValuePairs) . " PHP-string edits skipped"); + continue; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + $errors[] = "config_writer: file() failed for $path; PHP-string edits skipped"; + error_log("config_writer: file() failed for $path; " + . count($varValuePairs) . " PHP-string edits skipped"); + continue; + } + + foreach ($varValuePairs as $varName => $value) { + // Match column-0 `$varName` followed by optional whitespace + // then `=`. preg_quote is belt-and-braces — the var name is + // already validated to /^[A-Za-z_][A-Za-z0-9_]*$/ by + // config_writer_stage_php_string(). + $pattern = '/^\$' . preg_quote($varName, '/') . '\s*=/'; + $applied = false; + foreach ($lines as $i => $line) { + if (preg_match($pattern, $line)) { + // Escape value for a PHP single-quoted string + // literal: only `\` and `'` are special inside + // `'...'`. The two-char mask "\\'" tells + // addcslashes() to backslash-escape both: + // `\` → `\\` (first char of the mask) + // `'` → `\'` (second char of the mask) + // The result is parseable PHP that decodes back + // to the original $value bytes — so attacker + // bytes are stored as data, never executed. + $escaped = addcslashes($value, "\\'"); + $lines[$i] = '$' . $varName . "='" . $escaped . "';"; + $applied = true; + break; + } + } + if (!$applied) { + error_log("config_writer: $path has no \$$varName= line; PHP-string edit skipped"); + } + } + + $err = _config_writer_install_atomic( + $path, + implode("\n", $lines) . "\n" + ); + if ($err !== null) { + $errors[] = $err; + error_log($err); + } + } + + // Pass 3 — privileged flat key=value edits (root-only files). + // Same column-0 `key=` semantics as pass 1, but the read step + // uses `sudo cat` so we can service mode-600 root:root paths, + // and the install mode is 600 not 644. + foreach ($privStage as $path => $kvPairs) { + $rc = 0; + $out = array(); + // sudo -n: never prompt — fail loudly if sudoers ever changes. + // Output is the file content; stderr captured separately so a + // sudo / cat failure doesn't poison the parsed lines. + exec('sudo -n cat ' . escapeshellarg($path) . ' 2>/dev/null', + $out, $rc); + if ($rc !== 0) { + $errors[] = "config_writer: sudo cat exit=$rc for $path; privileged edits skipped"; + error_log("config_writer: sudo cat exit=$rc for $path; " + . count($kvPairs) . " privileged edits skipped"); + continue; + } + $lines = $out; + + foreach ($kvPairs as $key => $value) { + $prefix = $key . '='; + $applied = false; + foreach ($lines as $i => $line) { + if (strpos($line, $prefix) === 0) { + $lines[$i] = $prefix . $value; + $applied = true; + break; + } + } + if (!$applied) { + error_log("config_writer: $path has no '$key=' line; privileged edit skipped"); + } + } + + $err = _config_writer_install_atomic( + $path, + implode("\n", $lines) . "\n", + '600' + ); + if ($err !== null) { + $errors[] = $err; + error_log($err); + } + } + + if ($manageMount) { + system('sudo mount -o remount,ro /'); + } + + // Clear all stages so a subsequent commit() call doesn't + // re-apply already-applied edits. + $GLOBALS['__config_writer_stage'] = array(); + $GLOBALS['__config_writer_stage_phpstr'] = array(); + $GLOBALS['__config_writer_stage_priv'] = array(); + + return $errors; +} diff --git a/config/csrf.php b/config/csrf.php new file mode 100644 index 0000000..09a4901 --- /dev/null +++ b/config/csrf.php @@ -0,0 +1,426 @@ + string (64 hex chars; idempotent within a session) + * csrf_field() -> echoes a hidden tag for forms + * csrf_verify() -> dies with HTTP 403 if the POSTed token is missing + * or invalid; returns silently otherwise + * + * Usage + * ===== + * + * In a top-level GET-rendered page that contains a POST form: + * + * require_once $_SERVER['DOCUMENT_ROOT'] . '/config/csrf.php'; + * ... + *
+ * + * ... + *
+ * + * In the matching POST handler (top of the file, before any state + * change): + * + * require_once $_SERVER['DOCUMENT_ROOT'] . '/config/csrf.php'; + * if ($_SERVER['REQUEST_METHOD'] === 'POST') { + * csrf_verify(); + * } + * + * Design notes + * ============ + * + * - One token per session, reused across forms. Simpler retrofit + * than per-form tokens; no UX downside (the attacker still can't + * read the token, which is what matters). The token rotates + * when the session itself rotates (typically: browser closed, + * basic-auth re-prompted, or `session_regenerate_id()` called + * elsewhere). + * + * - Token = `bin2hex(random_bytes(32))` -> 64 hex chars (256 bits + * of entropy). `random_bytes()` is PHP 7.0+ and uses the + * OS CSPRNG (`/dev/urandom` on Linux). PHP 7.0 is this codebase's + * stated floor, so no fallback needed. + * + * - Verification uses `hash_equals()` (PHP 5.6+) for constant- + * time comparison. This is overkill for a hex-vs-hex comparison + * against a 256-bit secret, but costs nothing and removes any + * theoretical timing-leak class. + * + * - Failure mode (HTTP 403): write a minimal, self-contained HTML + * page rather than a JSON blob. The dashboard's forms post + * directly and the user lands on the response body in their + * browser — they need to understand what happened. + * + * - We deliberately do NOT call session_regenerate_id() on each + * request. Several existing pages (admin/update.php, + * admin/calibration.php, admin/live_modem_log.php) store + * log-tail offsets in $_SESSION across many AJAX requests; a + * mid-session ID rotation would lose those offsets and break + * the live log tails. + * + * - GET requests are NOT verified. CSRF protection only applies + * to state-changing requests, and the dashboard's idempotent + * read pages are POST-free by convention. + * + * - Session-cookie hardening. We set HttpOnly + SameSite=Lax on + * every issued PHPSESSID, and Secure conditionally when the + * request is HTTPS (via isHttps() in security_headers.php — + * covers direct TLS to nginx AND reverse-proxy / Cloudflare + * terminations that forward via X-Forwarded-Proto). See + * {@see _csrf_set_cookie_params()}. + */ + +require_once $_SERVER['DOCUMENT_ROOT'] . '/config/security_headers.php'; + +/** + * Configure the PHPSESSID cookie's flags for hardened delivery. + * + * Must be called BEFORE session_start(); has no effect once the + * session is active. Sets: + * + * HttpOnly — always. Blocks document.cookie access from JS, so + * a future XSS in any rendered page can't read the + * session ID. Defence in depth alongside the input + * escaping work in the rest of the security pass. + * + * SameSite=Lax — always. Browser stops sending the cookie on + * cross-site subresource fetches and cross-site POSTs; + * top-level navigation (clicking a bookmark, following + * a same-origin redirect) still carries it, so UX + * doesn't change. Belt-and-braces with the existing + * CSRF-token check. + * + * Secure — conditional on isHttps(). UNCONDITIONALLY setting + * Secure would invalidate the cookie for the (large) + * population of operators on plain-HTTP LAN access + * and silently break CSRF protection. isHttps() also + * returns true for X-Forwarded-Proto: https — so + * operators behind Cloudflare / a reverse proxy / + * Tailscale Funnel get the right Secure flag even + * though nginx itself only sees plain HTTP. + * + * Trust caveat: an attacker who can talk directly + * to nginx on port 80 (bypassing the proxy) could + * spoof X-Forwarded-Proto and trick the dashboard + * into setting Secure on their own session. The + * consequence is the spoofer's cookie won't replay + * over plain HTTP — a self-DoS, not an escalation. + * + * PHP version handling. The samesite option was added to + * session_set_cookie_params() in PHP 7.3 (the array form). On + * 7.0..7.2 we fall back to the well-known path-suffix kludge: + * appending `; SameSite=Lax` to the path argument. PHP doesn't + * validate the path string — it concatenates verbatim into the + * Set-Cookie header — and browsers parse `path=/; SameSite=Lax` + * correctly because `;` terminates the path attribute. The + * codebase floor is PHP 7.0; the production runtime is PHP 8.2. + * + * @return void + */ +function _csrf_set_cookie_params() +{ + $secure = function_exists('isHttps') ? isHttps() : false; + if (PHP_VERSION_ID >= 70300) { + // Modern array form. Available since PHP 7.3. + @session_set_cookie_params(array( + 'lifetime' => 0, + 'path' => '/', + 'domain' => '', + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + )); + } else { + // PHP 7.0..7.2: no native samesite support. The path-suffix + // kludge is the documented workaround — see PHP RFC for + // 7.3's array form, where this is acknowledged as the + // pre-7.3 idiom. lifetime/path/domain/secure/httponly here + // mirror the array values above. + @session_set_cookie_params(0, '/; SameSite=Lax', '', $secure, true); + } +} + +/** + * Ensure a session is started. Idempotent. + * + * Several dashboard pages already call `session_start()` for their + * own state (log offsets, etc.), so this primitive must coexist + * gracefully with prior `session_start()` calls. PHP_SESSION_ACTIVE + * is the canonical guard introduced in PHP 5.4. + */ +function csrf_session_start() +{ + if (session_status() !== PHP_SESSION_ACTIVE) { + // Pi-Star ships with `session.gc_probability=0` AND + // /var/lib/php/sessions mounted as a 64KB tmpfs (per + // /etc/fstab in the OS image). With CSRF, every dashboard + // visit creates a session file, and without automatic GC + // those accumulate until reboot — at which point the tmpfs + // fills (~15 sessions) and session_start() starts failing + // with "No space left on device", silently breaking CSRF. + // + // Force GC at session-start time by bumping the probability + // ratio to 1/1. PHP runs GC itself during session_start when + // (gc_probability / gc_divisor) > random — at 1/1 that's + // every call, but only for THIS request's session_start. + // Cost: a tmpfs `glob` + a few `unlink`s, microseconds. + // The @-suppression handles hosts that disallow ini_set on + // these keys; failure just means we fall back to PHP's + // default behaviour, same as before this fix. + // + // session_gc() (PHP 7.1+) would be cleaner but the codebase + // targets PHP 7.0. ini_set works on every supported version. + if ((int)ini_get('session.gc_probability') === 0) { + @ini_set('session.gc_probability', '1'); + @ini_set('session.gc_divisor', '1'); + } + // gc_maxlifetime is intentionally NOT overridden here — we + // defer to Pi-Star's stock /etc/php/*/fpm/php.ini value + // (1440 s, matching PHP's own default). The dashboard's + // AJAX-refreshing panels (lh.php, repeaterinfo.php, the + // bm_links / tgif_links partials, etc.) do not load csrf.php, + // so they don't update the session file's mtime — meaning the + // session counts as "idle" from the moment csrf_verify() last + // ran on a top-level page load, even while the dashboard is + // visibly active in the operator's tab. Anything shorter than + // ~24 min would routinely 403 BM-manager / TGIF-manager / + // configure.php POSTs whenever the operator left the dashboard + // tab open between page loads. tmpfs containment is the + // pre-emptive prune below, not maxlifetime. + // + // Pi-Star's /var/lib/php/sessions tmpfs is sized 64 KB (per + // /etc/fstab in the OS image) — about 15 session files at + // a 4 KB tmpfs block each. csrf.php is only loaded behind + // basic auth on /admin/*, so the only session-creators are + // authenticated operators (whose browsers reuse one cookie = + // one session) and tooling that hits the dashboard with + // fresh cookie jars per request. The latter has filled the + // tmpfs in practice; once full, session_start() fails with + // "No space left on device" and CSRF silently breaks for + // the operator — they get a 403 on the next form submit + // because $_SESSION['csrf_token'] could not be persisted. + // + // Belt-and-braces safety net: cap the session directory at + // 12 files BEFORE session_start() tries to write a new one. + // GC alone won't help here because a burst of fresh-cookie + // requests can fill the 64 KB tmpfs faster than gc_maxlifetime + // expires anything. This pre-emptive prune deletes the + // oldest sess_* files until 12 remain, leaving ~3 slots of + // headroom under the ~15-file tmpfs cap. + // + // Best-effort: any failure here (permissions, missing dir, + // glob/unlink errors) is silently ignored — session_start() + // will still try and either succeed or fall through to the + // existing failure-logging path below. Worst case for an + // evicted session is an operator gets a 403 on the next + // form submit and a page reload re-issues a token, which + // is far better than the disk-full failure this guards + // against. + $sessSaveDir = (string)@ini_get('session.save_path'); + if ($sessSaveDir !== '') { + $sessFiles = @glob($sessSaveDir . '/sess_*'); + if (is_array($sessFiles) && count($sessFiles) > 12) { + usort($sessFiles, function ($a, $b) { + return (int)@filemtime($a) - (int)@filemtime($b); + }); + $sessExcess = count($sessFiles) - 12; + for ($i = 0; $i < $sessExcess; $i++) { + @unlink($sessFiles[$i]); + } + } + } + // Harden the PHPSESSID cookie flags BEFORE session_start() + // emits the Set-Cookie header. See _csrf_set_cookie_params() + // for the per-flag rationale. + _csrf_set_cookie_params(); + // Suppress notices about headers already sent — some + // dashboard pages emit output before this is reached. + // The session won't be usable in that scenario, but + // failing closed (csrf_verify rejects the POST) is the + // correct outcome. Log the underlying cause so a future + // maintainer who accidentally reorders requires above an + // echo can see why their POSTs started 403'ing. + if (@session_start() === false) { + error_log('csrf_session_start: session_start() failed ' + . '(headers already sent? require_once order?)'); + } + } +} + +/** + * Return the session's CSRF token, issuing a fresh one on first + * call within a session. + * + * @return string 64-character hex string. + */ +function csrf_token() +{ + csrf_session_start(); + if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + } + return $_SESSION['csrf_token']; +} + +/** + * Echo a hidden form input carrying the CSRF token. + * + * Place INSIDE the `
` element, before the submit button. + * Output is htmlspecialchars-safe: a hex string never contains + * any character that needs escaping, but we encode anyway as a + * defence against future changes to the token format. + * + * For sites that build form HTML into a string variable rather + * than echoing inline, see {@see csrf_field_html()}. + */ +function csrf_field() +{ + echo csrf_field_html(); +} + +/** + * Return a hidden form input carrying the CSRF token, as a + * string. Useful for code that accumulates form HTML into a + * `$output` variable (e.g. wifi.php's wpa_conf form) where an + * `echo` mid-expression doesn't compose. + * + * @return string The hidden-input HTML. + */ +function csrf_field_html() +{ + $tok = htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8'); + return ''; +} + +/** + * Verify the POSTed CSRF token. On mismatch, emit HTTP 403 and exit. + * + * Call this from EVERY state-changing POST handler before any side + * effect (file write, system call, session mutation, etc.). It is + * a no-op for GET / HEAD / OPTIONS requests — those are read-only + * by convention in this dashboard. + * + * The function does not return on failure: it sets the response + * code, prints a minimal HTML error page, and calls exit(). + */ +function csrf_verify() +{ + // Bootstrap the session up front, even on GET, so the + // Set-Cookie header gets emitted before any HTML output. + // csrf_field() (called inside the page's tags) is + // lazy and may not run until well after output has started, + // so without an early csrf_verify() call sites that don't + // already have their own pre-output session_start() (most + // pages — power.php is the exception) never get a session + // cookie. Without a cookie the GET-issued token has no way + // to reach the POST handler. + // + // Pages should call csrf_verify() near the top of the file, + // BEFORE any output. On GET it bootstraps the session and + // returns; on POST it bootstraps, validates, and either + // returns silently or emits 403 + exit(). + csrf_session_start(); + + if (!isset($_SERVER['REQUEST_METHOD']) || + $_SERVER['REQUEST_METHOD'] !== 'POST') { + // Only POST is gated — GET pages render the token via + // csrf_field() and don't need verification. + return; + } + + $expected = isset($_SESSION['csrf_token']) ? $_SESSION['csrf_token'] : ''; + $supplied = isset($_POST['csrf_token']) ? $_POST['csrf_token'] : ''; + + // Distinguish "session has no token" from "session has a token + // and the supplied one is wrong". The former is almost always a + // benign expired-session POST (operator left the dashboard tab + // open past gc_maxlifetime, then clicked submit) and shouldn't + // throw the alarmist 403 page at them. Redirect 303 to the same + // URL — the browser switches to GET, the page re-renders fresh + // (forms that pre-populate from disk come back filled in; the + // session_start() in csrf_session_start() issues a new token), + // and the operator's submit retry just works. + // + // This is safe against forgery because a real attacker would be + // sending against an ACTIVE operator session whose + // $_SESSION['csrf_token'] is non-empty — that path stays on the + // strict 403 below. The only thing the redirect "lets through" + // is a fresh form render, which any GET would also serve. + if ($expected === '' || !is_string($expected) || strlen($expected) !== 64) { + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/admin/'; + // Defence in depth: REQUEST_URI is typically same-origin + // already, but force a relative path so a crafted Host or + // proxy can't turn this into an open redirect. + $uri = '/' . ltrim(preg_replace('#^https?://[^/]*#i', '', $uri), '/'); + header('Location: ' . $uri, true, 303); + header('Cache-Control: no-store'); + exit; + } + + // Reject only when the session actually had a token but the + // supplied one is missing or doesn't match — that IS a forgery. + if (!is_string($supplied) || strlen($supplied) !== 64 || + !hash_equals($expected, $supplied)) { + // Best-effort log line for the operator. The remote IP is + // typically a LAN address but worth recording in case a + // rogue device is fingerprinted by repeated 403s. + $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '?'; + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '?'; + error_log("csrf_verify: rejected POST from $remote to $uri"); + + http_response_code(403); + header('Content-Type: text/html; charset=utf-8'); + // Don't pollute browser history with this response. + header('Cache-Control: no-store'); + // English-only by design: the dashboard's lang/ system + // requires config/language.php, which reads /etc/pistar-release + // and the gateway configs. Pulling that whole stack into an + // error path that only fires under attack is disproportionate. + echo '' + . '403 Forbidden' + . '

403 Forbidden

' + . '

This request did not include a valid CSRF token. ' + . 'If you reached this page by clicking a link from another site, ' + . 'that other site may have been trying to perform an action on ' + . 'your behalf without your consent.

' + . '

If you reached this page by submitting a form on the ' + . 'dashboard, your session may have expired. Reload the page ' + . 'and try again.

' + . ''; + exit; + } + + // Token verified — strip it from $_POST so downstream handlers + // that iterate $_POST (e.g. fulledit_bmapikey.php and + // fulledit_dapnetapi.php's INI writers, which treat each top- + // level POST key as an [INI section]) don't accidentally write + // a stray `[csrf token]` block into /etc/. Without this, + // every successful submit on those editors prepended an empty + // `[csrf token]` section to the saved config and rendered a + // ghost table titled "csrf_token" on the response page. + // Centralising the unset here means every current and future + // POST handler is immune without needing to remember the dance. + unset($_POST['csrf_token']); +} diff --git a/config/index.php b/config/index.php new file mode 100644 index 0000000..e69de29 diff --git a/config/ircddbgateway_languages.inc b/config/ircddbgateway_languages.inc new file mode 100644 index 0000000..fabd552 --- /dev/null +++ b/config/ircddbgateway_languages.inc @@ -0,0 +1,24 @@ +# ircddbgateway_languages.inc +# +# Written for Pi-Star Digital Voice by Andy Taylor (MW0MWZ) +# Orrigional request by Dennis Bonesky +# +# Updated 28-May-2017 +# +########################################################### +#language;ircddbgateway;timeserver +########################################################### +English_(UK);0;0 +Deutsch;1;4 +Dansk;2;0 +Francais;3;6 +Italiano;4;0 +Polski;5;0 +English_(US);6;2 +Espanol;7;9 +Svenska;8;8 +Nederlands_(NL);9;7 +Nederlands_(BE);10;7 +Norsk;11;10 +Portugues;12;11 +########################################################### diff --git a/config/ircddblocal.php b/config/ircddblocal.php new file mode 100644 index 0000000..b36ad1f --- /dev/null +++ b/config/ircddblocal.php @@ -0,0 +1,28 @@ + diff --git a/config/security_headers.php b/config/security_headers.php new file mode 100644 index 0000000..15b608e --- /dev/null +++ b/config/security_headers.php @@ -0,0 +1,193 @@ + +.container { + width: 100%; + text-align: left; + margin: auto; + background : #; + border-radius: 10px 10px 10px 10px; + -moz-border-radius: 10px 10px 10px 10px; + -webkit-border-radius: 10px 10px 10px 10px; + -khtml-border-radius: 10px 10px 10px 10px; + -ms-border-radius: 10px 10px 10px 10px; + box-shadow: 3px 3px 3px #707070; +} + +body, font { + font: 12px verdana,arial,sans-serif; + color: #ffffff; +} + +.header { + background : #; + text-decoration : none; + color : #; + font-family : verdana, arial, sans-serif; + text-align : left; + padding : 5px 0px 5px 0px; + border-radius: 10px 10px 0 0; + -moz-border-radius: 10px 10px 0px 0px; + -webkit-border-radius: 10px 10px 0px 0px; + -khtml-border-radius: 10px 10px 0px 0px; + -ms-border-radius: 10px 10px 0px 0px; + } + +.nav { + display: none; + float : left; + margin : 0; + padding : 3px 3px 3px 3px; + width : 160px; + background : #242d31; + font-weight : normal; + min-height : 100%; +} + +.content { + padding : 5px 5px 5px 5px; + color : #; + background : #; + text-align: center; + font-size: 1.4em; +} + +.contentwide { + padding: 5px 5px 5px 5px; + color: #; + background: #; + text-align: center; + font-size: 1.4em; +} + +.contentwide h2 { + color: #; + font: 1em verdana,arial,sans-serif; + text-align: center; + font-weight: bold; + padding: 0px; + margin: 0px; +} + +.footer { + background : #; + text-decoration : none; + color : #; + font-family : verdana, arial, sans-serif; + font-size : 9px; + text-align : center; + padding : 10px 0 10px 0; + border-radius: 0 0 10px 10px; + -moz-border-radius: 0px 0px 10px 10px; + -webkit-border-radius: 0px 0px 10px 10px; + -khtml-border-radius: 0px 0px 10px 10px; + -ms-border-radius: 0px 0px 10px 10px; + clear : both; +} + +#tail { + height: 450px; + width: 805px; + overflow-y: scroll; + overflow-x: scroll; + color: #00ff00; + background: #000000; +} + +table { + vertical-align: middle; + text-align: center; + empty-cells: show; + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + padding-bottom: 0px; + border-collapse:collapse; + border-color: #000000; + border-style: solid; + border-spacing: 4px; + border-width: 2px; + text-decoration: none; + color: #ffffff; + background: #000000; + font-family: verdana,arial,sans-serif; + width: 100%; + white-space: nowrap; +} + +table th { + font-family: "Lucidia Console",Monaco,monospace; + text-shadow: 1px 1px #; + text-decoration: none; + background: #; + border: 1px solid #c0c0c0; +} + +table tr:nth-child(even) { + background: #; +} + +table tr:nth-child(odd) { + background: #; +} + +table td { + color: #000000; + font-family: "Lucidia Console",Monaco,monospace; + text-decoration: none; + border: 1px solid #000000; + overflow-x: hidden; +} + +body { + background: #; + color: #000000; +} + +a { + text-decoration:none; + +} + +a:link, a:visited { + text-decoration: none; + color: #0000e0; + font-weight: normal; +} + +a.tooltip, a.tooltip:link, a.tooltip:visited, a.tooltip:active { + text-decoration: none; + position: relative; + color: #FFFFFF; +} + +a.tooltip:hover { + text-shadow: none; + text-decoration: none; + color: #FFFFFF; + background: transparent; +} + +a.tooltip span { + text-shadow: none; + text-decoration: none; + display: none; +} + +a.tooltip:hover span { + text-shadow: none; + text-decoration: none; + display: block; + position: absolute; + top: 20px; + left: 0; + width: 200px; + z-index: 100; + color: #000000; + border:1px solid #000000; + background: #f7f7f7; + font: 12px Verdana, sans-serif; + text-align: left; +} + +a.tooltip span b { + text-shadow: none; + text-decoration: none; + display: block; + color: #000000; + margin: 0; + padding: 0; + font-size: 12px; + font-weight: bold; + border: 0px; + border-bottom: 1px solid black; + background: #d0d0d0; +} + +a.tooltip2, a.tooltip2:link, a.tooltip2:visited, a.tooltip2:active { + text-shadow: none; + text-decoration: none; + position: relative; + font-weight: bold; + color: #000000; +} + +a.tooltip2:hover { + text-shadow: none; + text-decoration: none; + color: #000000; + background: transparent; +} + +a.tooltip2 span { + text-shadow: none; + text-decoration: none; + display: none; +} + +a.tooltip2:hover span { + text-shadow: none; + text-decoration: none; + display: block; + position: absolute; + top: 20px; + left: 0; + width: 200px; + z-index: 100; + color: #000000; + border:1px solid #000000; + background: #f7f7f7; + font: 12px Verdana, sans-serif; + text-align: left; +} + +a.tooltip2 span b { + text-shadow: none; + text-decoration: none; + display: block; + color: #000000; + margin: 0; + padding: 0; + font-size: 12px; + font-weight: bold; + border: 0px; + border-bottom: 1px solid black; + background: #d0d0d0; +} + +ul { + padding: 5px; + margin: 10px 0; + list-style: none; + float: left; +} + +ul li { + float: left; + display: inline; /*For ignore double margin in IE6*/ + margin: 0 10px; +} + +ul li a { + text-decoration: none; + float:left; + color: #999; + cursor: pointer; + font: 900 14px/22px "Arial", Helvetica, sans-serif; +} + +ul li a span { + margin: 0 10px 0 -10px; + padding: 1px 8px 5px 18px; + position: relative; /*To fix IE6 problem (not displaying)*/ + float:left; +} + +ul.mmenu li a.current, ul.mmenu li a:hover { + background: url(/images/buttonbg.png) no-repeat top right; + color: #0d5f83; +} + +ul.mmenu li a.current span, ul.mmenu li a:hover span { + background: url(/images/buttonbg.png) no-repeat top left; +} + +h1 { + text-shadow: 2px 2px #; + text-align: center; +} + +/* CSS Toggle Code here */ +.toggle { + position: absolute; + margin-left: -9999px; + visibility: hidden; +} + +.toggle + label { + display: block; + position: relative; + cursor: pointer; + outline: none; +} + +input.toggle-round-flat + label { + padding: 1px; + width: 33px; + height: 18px; + background-color: #dddddd; + border-radius: 10px; + transition: background 0.4s; +} + +input.toggle-round-flat + label:before, +input.toggle-round-flat + label:after { + display: block; + position: absolute; + content: ""; +} + +input.toggle-round-flat + label:before { + top: 1px; + left: 1px; + bottom: 1px; + right: 1px; + background-color: #fff; + border-radius: 10px; + transition: background 0.4s; +} + +input.toggle-round-flat + label:after { + top: 2px; + left: 2px; + bottom: 2px; + width: 16px; + background-color: #dddddd; + border-radius: 12px; + transition: margin 0.4s, background 0.4s; +} + +input.toggle-round-flat:checked + label { + background-color: #; +} + +input.toggle-round-flat:checked + label:after { + margin-left: 14px; + background-color: #; +} + +/* Tame Firefox Buttons */ +@-moz-document url-prefix() { + select, + input { + margin : 0; + padding : 0; + border-width : 1px; + font : 12px verdana,arial,sans-serif; + } + input[type="button"], button, input[type="submit"] { + padding : 0px 3px 0px 3px; + border-radius : 3px 3px 3px 3px; + -moz-border-radius : 3px 3px 3px 3px; + } +} + +/* nice-select dropdowns */ +.nice-select.small, .nice-select-dropdown li.option { + height: 24px !important; + min-height: 24px !important; + line-height: 24px !important; +} +.nice-select.small ul li:nth-of-type(2) { + clear: both; +} diff --git a/css/pistar-css.php b/css/pistar-css.php new file mode 100644 index 0000000..7fba766 --- /dev/null +++ b/css/pistar-css.php @@ -0,0 +1,450 @@ + +.container { + width: 820px; + text-align: left; + margin: auto; + border-radius: 10px 10px 10px 10px; + -moz-border-radius: 10px 10px 10px 10px; + -webkit-border-radius: 10px 10px 10px 10px; + -khtml-border-radius: 10px 10px 10px 10px; + -ms-border-radius: 10px 10px 10px 10px; + box-shadow: 3px 3px 3px #707070; + background : #; +} + +body, font { + font: 12px verdana,arial,sans-serif; + color: #ffffff; +} + +.header { + background : #; + text-decoration : none; + color : #; + font-family : verdana, arial, sans-serif; + text-align : left; + padding : 5px 0px 5px 0px; + border-radius: 10px 10px 0 0; + -moz-border-radius: 10px 10px 0px 0px; + -webkit-border-radius: 10px 10px 0px 0px; + -khtml-border-radius: 10px 10px 0px 0px; + -ms-border-radius: 10px 10px 0px 0px; + } + +.nav { + float : left; + margin : 0; + padding : 3px 3px 3px 3px; + width : 160px; + background : #242d31; + font-weight : normal; + min-height : 100%; +} + +.content { + margin : 0 0 0 166px; + padding : 1px 5px 5px 5px; + color : #; + background : #; + text-align: center; +} + +.contentwide { + padding: 5px 5px 5px 5px; + color: #; + background: #; + text-align: center; +} + +.contentwide h2 { + color: #; + font: 1em verdana,arial,sans-serif; + text-align: center; + font-weight: bold; + padding: 0px; + margin: 0px; +} + + +.footer { + background : #; + text-decoration : none; + color : #; + font-family : verdana, arial, sans-serif; + font-size : 9px; + text-align : center; + padding : 10px 0 10px 0; + border-radius: 0 0 10px 10px; + -moz-border-radius: 0px 0px 10px 10px; + -webkit-border-radius: 0px 0px 10px 10px; + -khtml-border-radius: 0px 0px 10px 10px; + -ms-border-radius: 0px 0px 10px 10px; + clear : both; +} + +#tail { + height: 450px; + width: 805px; + overflow-y: scroll; + overflow-x: scroll; + color: #00ff00; + background: #000000; +} + +table { + vertical-align: middle; + text-align: center; + empty-cells: show; + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + padding-bottom: 0px; + border-collapse:collapse; + border-color: #000000; + border-style: solid; + border-spacing: 4px; + border-width: 2px; + text-decoration: none; + color: #ffffff; + background: #000000; + font-family: verdana,arial,sans-serif; + width: 100%; + white-space: nowrap; +} + +table th { + font-family: "Lucidia Console",Monaco,monospace; + text-shadow: 1px 1px #; + text-decoration: none; + background: #; + border: 1px solid #c0c0c0; +} + +table tr:nth-child(even) { + background: #; +} + +table tr:nth-child(odd) { + background: #; +} + +table td { + color: #000000; + font-family: "Lucidia Console",Monaco,monospace; + text-decoration: none; + border: 1px solid #000000; +} + +body { + background: #; + color: #000000; +} + +a { + text-decoration:none; + +} + +a:link, a:visited { + text-decoration: none; + color: #0000e0; + font-weight: normal; +} + +a.tooltip, a.tooltip:link, a.tooltip:visited, a.tooltip:active { + text-decoration: none; + position: relative; + color: #FFFFFF; +} + +a.tooltip:hover { + text-shadow: none; + text-decoration: none; + color: #FFFFFF; + background: transparent; +} + +a.tooltip span { + text-shadow: none; + text-decoration: none; + display: none; +} + +a.tooltip:hover span { + text-shadow: none; + text-decoration: none; + display: block; + position: absolute; + top: 20px; + left: 0; + z-index: 100; + color: #000000; + border:1px solid #000000; + background: #f7f7f7; + font: 12px Verdana, sans-serif; + text-align: left; + white-space: nowrap; +} + +th:last-child a.tooltip:hover span { + left: auto; + right: 0; +} + +a.tooltip span b { + text-shadow: none; + text-decoration: none; + display: block; + color: #000000; + margin: 0; + padding: 0; + font-size: 12px; + font-weight: bold; + border: 0px; + border-bottom: 1px solid black; + background: #d0d0d0; +} + +a.tooltip2, a.tooltip2:link, a.tooltip2:visited, a.tooltip2:active { + text-shadow: none; + text-decoration: none; + position: relative; + font-weight: bold; + color: #000000; +} + +a.tooltip2:hover { + text-shadow: none; + text-decoration: none; + color: #000000; + background: transparent; +} + +a.tooltip2 span { + text-shadow: none; + text-decoration: none; + display: none; +} + +a.tooltip2:hover span { + text-shadow: none; + text-decoration: none; + display: block; + position: absolute; + top: 20px; + left: 0; + width: 202px; + z-index: 100; + color: #000000; + border:1px solid #000000; + background: #f7f7f7; + font: 12px Verdana, sans-serif; + text-align: left; + white-space: normal; +} + +a.tooltip2 span b { + text-shadow: none; + text-decoration: none; + display: block; + color: #000000; + margin: 0; + padding: 0; + font-size: 12px; + font-weight: bold; + border: 0px; + border-bottom: 1px solid black; + background: #d0d0d0; +} + +ul { + padding: 5px; + margin: 10px 0; + list-style: none; + float: left; +} + +ul li { + float: left; + display: inline; /*For ignore double margin in IE6*/ + margin: 0 10px; +} + +ul li a { + text-decoration: none; + float:left; + color: #999; + cursor: pointer; + font: 900 14px/22px "Arial", Helvetica, sans-serif; +} + +ul li a span { + margin: 0 10px 0 -10px; + padding: 1px 8px 5px 18px; + position: relative; /*To fix IE6 problem (not displaying)*/ + float:left; +} + +ul.mmenu li a.current, ul.mmenu li a:hover { + background: url(/images/buttonbg.png) no-repeat top right; + color: #0d5f83; +} + +ul.mmenu li a.current span, ul.mmenu li a:hover span { + background: url(/images/buttonbg.png) no-repeat top left; +} + +h1 { + text-shadow: 2px 2px #; + text-align: center; +} + +/* CSS Toggle Code here */ +.toggle { + position: absolute; + margin-left: -9999px; + visibility: hidden; +} + +.toggle + label { + display: block; + position: relative; + cursor: pointer; + outline: none; +} + +input.toggle-round-flat + label { + padding: 1px; + width: 33px; + height: 18px; + background-color: #dddddd; + border-radius: 10px; + transition: background 0.4s; +} + +input.toggle-round-flat + label:before, +input.toggle-round-flat + label:after { + display: block; + position: absolute; + content: ""; +} + +input.toggle-round-flat + label:before { + top: 1px; + left: 1px; + bottom: 1px; + right: 1px; + background-color: #fff; + border-radius: 10px; + transition: background 0.4s; +} + +input.toggle-round-flat + label:after { + top: 2px; + left: 2px; + bottom: 2px; + width: 16px; + background-color: #dddddd; + border-radius: 12px; + transition: margin 0.4s, background 0.4s; +} + +input.toggle-round-flat:checked + label { + background-color: #; +} + +input.toggle-round-flat:checked + label:after { + margin-left: 14px; + background-color: #; +} + +/* Tame Firefox Buttons */ +@-moz-document url-prefix() { + select, + input { + margin : 0; + padding : 0; + border-width : 1px; + font : 12px verdana,arial,sans-serif; + } + input[type="button"], button, input[type="submit"] { + padding : 0px 3px 0px 3px; + border-radius : 3px 3px 3px 3px; + -moz-border-radius : 3px 3px 3px 3px; + } +} + +/* nice-select dropdowns */ +.nice-select.small, .nice-select-dropdown li.option { + height: 24px !important; + min-height: 24px !important; + line-height: 24px !important; +} +.nice-select.small ul li:nth-of-type(2) { + clear: both; +} + +/* Aria CSS Here +[role="checkbox"] { + padding:5px; +} + +[aria-checked="true"]::before { + content: "[x]"; +} + +[aria-checked="false"]::before { + content: "[ ]"; +} +*/ diff --git a/dstarrepeater/active_reflector_links.php b/dstarrepeater/active_reflector_links.php new file mode 100644 index 0000000..0d12b59 --- /dev/null +++ b/dstarrepeater/active_reflector_links.php @@ -0,0 +1,215 @@ + 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev="20141101"; +$MYCALL=strtoupper($callsign); +?> + + + + + + + + + + + + + + +"Never",1=>"Fixed",2=>"5min",3=>"10min",4=>"15min",5=>"20min",6=>"25min",7=>"30min",8=>"60min",9=>"90min",10=>"120min",11=>"180min",12=>" "); + $ci = 0; + $tr = 0; + for($i = 1;$i < 5; $i++){ + $param="repeaterBand" . $i; + if((isset($configs[$param])) && strlen($configs[$param]) == 1) { + $ci++; + if($ci > 1) { $ci = 0; } + print ""; + $tr = 1; + $module = $configs[$param]; + $rcall = sprintf("%-7.7s%-1.1s",$MYCALL,$module); + $param="repeaterCall" . $i; + if(isset($configs[$param])) { $rptrcall=sprintf("%-7.7s%-1.1s",$configs[$param],$module); } else { $rptrcall = $rcall;} + print ""; + $param="reflector" . $i; + if(isset($configs[$param])) { print ""; } else { print "";} + $param="atStartup" . $i; + if($configs[$param] == 1){print ""; } else { print ""; } + $param="reconnect" . $i; + if(isset($configs[$param])) { $t = $configs[$param]; } else { $t = 0; } + if($t > 12){ $t = 12; } + print ""; + $j=0; + if ($linkLog = @fopen($linkLogPath,'r')) { + while ($linkLine = fgets($linkLog)) { + //$statimg = ""; + $statimg = "Down"; + $linkDate = " "; + $protocol = " "; + $linkType = " "; + $linkRptr = " "; + $linkRefl = " "; +// Reflector-Link, sample: +// 2011-09-22 02:15:06: DExtra link - Type: Repeater Rptr: DB0LJ B Refl: XRF023 A Dir: Outgoing +// 2012-10-12 17:15:45: DCS link - Type: Repeater Rptr: DB0LJ B Refl: DCS001 L Dir: Outgoing +// 2012-10-12 17:56:10: DCS link - Type: Repeater Rptr: DB0RPL B Refl: DCS015 B Dir: Outgoing + if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*Rptr: (.{8}).*Refl: (.{8}).*Dir: Outgoing$/',$linkLine,$linx) > 0){ + $statimg = "Up"; + $linkDate = date("d-M-Y H:i:s", strtotime(substr($linx[1][0],0,19))); + $protocol = $linx[2][0]; + $linkType = $linx[3][0]; + $linkRptr = $linx[4][0]; + $linkRefl = $linx[5][0]; + if($linkRptr == $rptrcall){ + print ""; + print ""; + print ""; + print ""; + $utc_time = $linkDate; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + print ""; + print "\n"; + $tr = 0; + } + } + } + fclose($linkLog); + } + + if ($tr == 1){ + print"\n"; + } +// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 +// 2012-05-08 21:16:31: DExtra link - Type: Repeater Rptr: DB0LJ A Refl: DB0MYK B Dir: Incoming +// 2012-05-08 21:16:31: DPlus link - Type: Dongle User: W1CDG H Dir: Incoming + if ($linkLog = @fopen($linkLogPath,'r')) { + while ($linkLine = fgets($linkLog)) { + $statimg = "Down"; + $linkDate = " "; + $protocol = " "; + $linkType = " "; + $linkRptr = " "; + $linkRefl = " "; + if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*Rptr: (.{8}).*Refl: (.{8}).*Dir: Incoming$/',$linkLine,$linx) > 0){ + $statimg = "Up"; + $linkDate = date("d-M-Y H:i:s", strtotime(substr($linx[1][0],0,19))); + $protocol = $linx[2][0]; + $linkType = $linx[3][0]; + $linkRptr = $linx[4][0]; + $linkRefl = $linx[5][0]; + if($linkRptr == $rptrcall){ + $ci++; + if($ci > 1) { $ci = 0; } + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + $utc_time = $linkDate; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + print ""; + print "\n"; + //$tr = 0; + } + } + } + fclose($linkLog); + } +// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 +// 2012-05-08 21:16:31: DExtra link - Type: Repeater Rptr: DB0LJ A Refl: DB0MYK B Dir: Incoming +// 2012-05-08 21:16:31: DPlus link - Type: Dongle User: W1CDG H Dir: Incoming + if ($linkLog = @fopen($linkLogPath,'r')) { + while ($linkLine = fgets($linkLog)) { + $statimg = "Down"; + $linkDate = " "; + $protocol = " "; + $linkType = " "; + $linkRptr = " "; + $linkRefl = " "; + if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*User: (.[^\s]+).*Dir: Incoming$/',$linkLine,$linx) > 0){ + $statimg = "Up"; + $linkDate = date("d-M-Y H:i:s", strtotime(substr($linx[1][0],0,19))); + $protocol = $linx[2][0]; + $linkType = $linx[3][0]; + $linkRptr = $linx[4][0]; + $ci++; + if($ci > 1) { $ci = 0; } + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + $utc_time = $linkDate; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + print ""; + print "\n"; + } + } + fclose($linkLog); + } + // End + } + } +?> + +
RadioRadio ModuleDefaultDefault Link DestinationAutoAutoLink- green: enabled
- red: disabled
TimerReset/Restart TimerLinkLink-Status- green: enabled
- red: disabled
Linked toLinked DestinationModeMode or Protocol usedDirectionDirectionIncoming or OutgoingLast Change ()Timestamp of last changeTime of last change in time zone
".str_replace(' ', ' ', substr($rptrcall,0,8))."".str_replace(' ', ' ', substr($configs[$param],0,8))." AutoNo$tot[$t]$statimg".str_replace(' ', ' ', substr($linkRefl,0,8))."$protocolOutgoing$local_time
DownNone----------
".str_replace(' ', ' ', substr($rptrcall,0,8))."   $statimg".str_replace(' ', ' ', substr($linkRefl,0,8))."$protocolIncoming$local_time
".str_replace(' ', ' ', substr($rptrcall,0,8))."   $statimg".str_replace(' ', ' ', substr($linkRptr,0,8))."$protocolIncoming$local_time
diff --git a/dstarrepeater/active_starnet_groups.php b/dstarrepeater/active_starnet_groups.php new file mode 100644 index 0000000..ad99f5b --- /dev/null +++ b/dstarrepeater/active_starnet_groups.php @@ -0,0 +1,147 @@ + from + * StarNet group " line. We treat the log as the + * authoritative state: an "Adding" entry inserts; a "Removing" + * entry unsets. The final $groupsx map then holds current members. + * + * No security_headers call — it's a sub-sub-partial. Flagged for the + * security pass. + */ + + +require_once($_SERVER['DOCUMENT_ROOT'] . '/config/security_headers.php'); +setSecurityHeaders(); + +include_once $_SERVER['DOCUMENT_ROOT'].'/config/ircddblocal.php'; +include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code +$configs = array(); + +if ($configfile = fopen($gatewayConfigPath,'r')) { + while ($line = fgets($configfile)) { + list($key,$value) = preg_split('/=/',$line); + $value = trim(str_replace('"','',$value)); + if ($key != 'ircddbPassword' && strlen($value) > 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev="20141101"; +$MYCALL=strtoupper($callsign); +?> + + + + + + + + + + 1) { $ci = 0; } + print ""; + print ""; + $param="starNetLogoff" . $i; + if(isset($configs[$param])){ $output = str_replace(' ', ' ', substr($configs[$param],0,8)); print "";} else { print"";} + $param="starNetInfo" . $i; + if(isset($configs[$param])){ print "";} else { print"";} + $param="starNetUserTimeout" . $i; + if(isset($configs[$param])){ print "";} else { print"";} + $param="starNetGroupTimeout" . $i; + if(isset($configs[$param])){ print "";} else { print"";} + print "\n"; + } + } +?> +
Starnet CallsignStarnet Logoff CallsignInfotextUser TimeOut (min)inactivity time after which a user will be disconnectedGroup TimeOut (min)inactivity time after which the group will be disconnected
".str_replace(' ', ' ', substr($gname,0,8))."$output $configs[$param] $configs[$param] $configs[$param] 

+ += 1) { + + echo "".$lang['active_starnet_members']."\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + $ci = 0; + $ulist = array(); + $glist = array(); + for($i = 1;$i < 6; $i++){ + if(isset($groupsx[$i])){ + $glist = $groupsx[$i]; + foreach ($glist as $gcall => $ulist){ + foreach ($ulist as $ucall => $ulogin){ + $ci++; + if($ci > 1) { $ci = 0; } + $ulogin = date("d-M-Y H:i:s", strtotime(substr($ulogin,0,19))); + $utc_time = $ulogin; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + $groupz = str_replace(' ', ' ', substr($gcall,0,8)); + $ucall = str_replace(' ', '', substr($ucall,0,8)); + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + } + } + } + echo "
".$lang['time']." (".date('T').")Time of Login".$lang['group']."Starnet Callsign".$lang['callsign']."Callsign
$local_time$groupz$ucall
\n
\n"; + } diff --git a/dstarrepeater/css_connections.php b/dstarrepeater/css_connections.php new file mode 100644 index 0000000..d87b0f1 --- /dev/null +++ b/dstarrepeater/css_connections.php @@ -0,0 +1,95 @@ + 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev="20141101"; +$MYCALL=strtoupper($callsign); +?> +=1) { +?> + Active CCS Connections + + + + + + + + + 0){ + $utc_time = $linx[1][0]; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + $linkDate = $local_time; + $linkType = $linx[2][0]; + $linkRptr = $linx[3][0]; + $linkRem = $linx[4][0]; + $linkDir = $linx[5][0]; + $ci++; + if($ci > 1) { $ci = 0; } + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + } + fclose($linkLog); + } + + print "
RepeaterCallsign of connected repeaterLinked toActual link statusProtocolProtocolDirectionDirectionincoming or outgoingLast Change ()Timestamp of last change
$linkRptr$linkRemCCS$linkDir$linkDate
\n
\n"; +} + + $stn_is_set = 0; + for($i = 1;$i < 6; $i++){ + $param="starNetCallsign" . $i; + if(isset($configs[$param])) { + $stn_is_set = 1; + break; + } + } + if($stn_is_set > 0){ + include_once $_SERVER['DOCUMENT_ROOT'].'/dstarrepeater/active_starnet_groups.php'; + } diff --git a/dstarrepeater/gateway_software_config.php b/dstarrepeater/gateway_software_config.php new file mode 100644 index 0000000..e906978 --- /dev/null +++ b/dstarrepeater/gateway_software_config.php @@ -0,0 +1,18 @@ + + + + + + ON"; } else { print ""; } + if($configs['dcsEnabled'] == 1){print ""; } else { print ""; } + if($configs['dextraEnabled'] == 1){print ""; } else { print ""; } + if($configs['dplusEnabled'] == 1){print ""; } else { print ""; } + if($configs['dratsEnabled'] == 1){print ""; } else { print ""; } + if($configs['infoEnabled'] == 1){print ""; } else { print ""; } + if($configs['ircddbEnabled'] == 1){print ""; } else { print ""; } + if($configs['echoEnabled'] == 1){print ""; } else { print ""; } + if($configs['logEnabled'] == 1){print ""; } else { print ""; } + ?> + +
ircDDB NetworkAPRS HostCCSDCSDExtraDPlusD-RatsInfoircDDBEchoLog
";} ?>OFFONOFFONOFFONOFFONOFFONOFFONOFFONOFFONOFF
diff --git a/dstarrepeater/index.php b/dstarrepeater/index.php new file mode 100644 index 0000000..e69de29 diff --git a/dstarrepeater/last_herd.php b/dstarrepeater/last_herd.php new file mode 100644 index 0000000..e9c8332 --- /dev/null +++ b/dstarrepeater/last_herd.php @@ -0,0 +1,138 @@ + 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev="20141101"; +$MYCALL=strtoupper($callsign); + +// Check if the config file exists +if (file_exists('/etc/pistar-css.ini')) { + // Use the values from the file + $piStarCssFile = '/etc/pistar-css.ini'; + if (fopen($piStarCssFile,'r')) { $piStarCss = parse_ini_file($piStarCssFile, true); } + + // Set the Values from the config file + if (isset($piStarCss['Lookup']['Service'])) { $callsignLookupSvc = $piStarCss['Lookup']['Service']; } // Lookup Service "QRZ" or "RadioID" + else { $callsignLookupSvc = "RadioID"; } // Set the default if its missing // Set the default if its missing +} else { + // Default values + $callsignLookupSvc = "RadioID"; +} + +// Safety net +if (($callsignLookupSvc != "RadioID") && ($callsignLookupSvc != "QRZ")) { $callsignLookupSvc = "RadioID"; } + +// Setup the URL(s) +if ($callsignLookupSvc == "RadioID") { $callsignLookupUrl = "https://database.radioid.net/database/view?callsign="; } +if ($callsignLookupSvc == "QRZ") { $callsignLookupUrl = "https://www.qrz.com/db/"; } + +?> + + + + + + + + + +/tmp/lastheard.log) 2>&1 &'); + $ci = 0; + if ($LastHeardLog = fopen("/tmp/lastheard.log",'r')) { + while ($linkLine = fgets($LastHeardLog)) { + if(preg_match_all('/^(.{19}).*My: (.*).*Your: (.*).*Rpt1: (.*).*Rpt2: (.*).*Flags: (.*)$/',$linkLine,$linx) > 0){ + $ci++; + if($ci > 1) { $ci = 0; } + $QSODate = date("d-M-Y H:i:s", strtotime(substr($linx[1][0],0,19))); + + // Every value below is parsed out of /var/log/pi-star/Headers.log + // — log lines produced by RF traffic, where the + // transmitting station sets the callsign field. Without + // escaping, a hostile callsign lands in the dashboard's + // ~3 second AJAX refresh. Normalise once here so each + // echo below works on safe values. + // + // $myCallRaw — raw 8-char callsign window + // $myCallHtml — HTML-safe display form + // $myCallLink — first whitespace-delimited token (the + // callsign without the space-padded + // suffix) — used for href URLs + // $myCallLinkHtml / $myCallLinkUrl — the URL forms + // $myIdHtml — 4-char ID after the `/` (HTML-safe) + // $yourCallHtml / $rpt1Html / $rpt2Html — target + + // repeaters with ` ` substitution applied AFTER + // escaping so the literal entity isn't double-encoded. + $myCallRaw = str_replace(' ', '', substr($linx[2][0],0,8)); + $myCallHtml = htmlspecialchars($myCallRaw, ENT_QUOTES, 'UTF-8'); + $myCallLink = strtok(substr($linx[2][0],0,8), " "); + $myCallLinkUrl = rawurlencode((string)$myCallLink); + $myIdHtml = htmlspecialchars(str_replace(' ', '', substr($linx[2][0],9,4)), ENT_QUOTES, 'UTF-8'); + $yourCallHtml = str_replace(' ', ' ', + htmlspecialchars(substr($linx[3][0],0,8), ENT_QUOTES, 'UTF-8')); + $rpt1Html = str_replace(' ', ' ', + htmlspecialchars(substr($linx[4][0],0,8), ENT_QUOTES, 'UTF-8')); + $rpt2Html = str_replace(' ', ' ', + htmlspecialchars(substr($linx[5][0],0,8), ENT_QUOTES, 'UTF-8')); + + $utc_time = $QSODate; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + print ""; + print ""; + print ""; } + else { print ""; } + print ""; + print ""; + print ""; + print "\n"; + } + } + fclose($LastHeardLog); + } +?> +
()RPT 1RPT 2
$local_time
$myCallHtml"; + if($myIdHtml !== '') { print "/$myIdHtml
$yourCallHtml$rpt1Html$rpt2Html
diff --git a/dstarrepeater/link_manager.php b/dstarrepeater/link_manager.php new file mode 100644 index 0000000..7d1cc2b --- /dev/null +++ b/dstarrepeater/link_manager.php @@ -0,0 +1,233 @@ + ...HTML... ` + * template syntax — preserve the colons. + * + * Also surfaces a "PiStar-Keeper Logbook" panel below the form when + * /usr/local/sbin/pistar-keeper is running (last 5 entries from + * /var/pistar-keeper/pistar-keeper.log + a download link). + */ + + +require_once($_SERVER['DOCUMENT_ROOT'] . '/config/security_headers.php'); +setSecurityHeaders(); + +if ($_SERVER["PHP_SELF"] == "/admin/index.php") { +include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code + +if (!empty($_POST) && isset($_POST["dstrMgrSubmit"])): +//if (!empty($_POST)): +if (preg_match('/[^A-Z]/',$_POST["Link"])) { unset ($_POST["Link"]);} +if ($_POST["Link"] == "LINK") { + if (preg_match('/[^A-Za-z0-9 ]/',$_POST["RefName"])) { unset ($_POST["RefName"]);} + if (preg_match('/[^A-Z]/',$_POST["Letter"])) { unset ($_POST["Letter"]);} + if (preg_match('/[^A-Z0-9 ]/',$_POST["Module"])) { unset ($_POST["Module"]);} + } +if ($_POST["Link"] == "UNLINK") { + if (preg_match('/[^A-Z0-9 ]/',$_POST["Module"])) { unset ($_POST["Module"]);} + } + +if (empty($_POST["RefName"]) || empty($_POST["Letter"]) || empty($_POST["Module"])) { echo "Somthing wrong with your input, try again";} + + +else { + if (strlen($_POST["RefName"]) != 7) { + $targetRef = str_pad($_POST["RefName"], 7, " "); + } else { + $targetRef = $_POST["RefName"]; + } + $targetRef = $targetRef.$_POST["Letter"]; + $targetRef = strtoupper($targetRef); + $module = $_POST["Module"]; + + if (strlen($module) != 8) { //Fix the length of the module information + $moduleFixedCs = strlen($module) - 1; //Length of the string, -1 + $moduleFixedBand = substr($module, -1); //Single Band Letter in the 8th position + $moduleFixedCallPad = str_pad(substr($module, 0, $moduleFixedCs), 7); //Pad the callsign area to 7 chars + $module = $moduleFixedCallPad.$moduleFixedBand; //Re add the band information + }; + + $unlinkCommand = "sudo remotecontrold \"".$module."\" unlink"; + $linkCommand = "sudo remotecontrold \"".$module."\" link never \"".$targetRef."\""; + + if ($module != $targetRef && $_POST["Link"] == "LINK") { // Sanity check that we are not connecting to ourself + echo "D-Star Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo htmlspecialchars((string)exec($linkCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n"; + } + if ($module == $targetRef && $_POST["Link"] == "LINK") { // Sanity Check Failed + echo "D-Star Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Cannot link to myself - Aborting link request!"; + echo "
\n"; + } + if ($_POST["Link"] == "UNLINK") { // Allow Unlink no matter what + echo "D-Star Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo htmlspecialchars((string)exec($unlinkCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n"; + } + } + +unset($_POST); +echo ''; + +else: ?> + + + + + + + + + + + + + + + + +
Radio ModuleRadio ModuleReflectorReflectorLink / Un-LinkLink / Un-LinkActionAction
+ + + + + + Link + UnLink + + +
+ + + +\n"; + echo "PiStar-Keeper Logbook\n"; + echo "\n"; + echo " \n"; + echo " \n"; + echo " \n"; + + exec ("tail -n 5 /var/pistar-keeper/pistar-keeper.log", $lines); + $counter = 0; + foreach ($lines as $line) { + echo "\n"; + $counter++; + } + + echo "
PiStar-Keeper Log Entries (UTC)PiStar-Keeper Log Entries (UTC)
".$lines[$counter]."
\n"; + } +} diff --git a/dstarrepeater/local_tx.php b/dstarrepeater/local_tx.php new file mode 100644 index 0000000..4e0dd06 --- /dev/null +++ b/dstarrepeater/local_tx.php @@ -0,0 +1,123 @@ + 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev="20141101"; +$MYCALL=strtoupper($callsign); + +// Check if the config file exists +if (file_exists('/etc/pistar-css.ini')) { + // Use the values from the file + $piStarCssFile = '/etc/pistar-css.ini'; + if (fopen($piStarCssFile,'r')) { $piStarCss = parse_ini_file($piStarCssFile, true); } + + // Set the Values from the config file + if (isset($piStarCss['Lookup']['Service'])) { $callsignLookupSvc = $piStarCss['Lookup']['Service']; } // Lookup Service "QRZ" or "RadioID" + else { $callsignLookupSvc = "RadioID"; } // Set the default if its missing // Set the default if its missing +} else { + // Default values + $callsignLookupSvc = "RadioID"; +} + +// Safety net +if (($callsignLookupSvc != "RadioID") && ($callsignLookupSvc != "QRZ")) { $callsignLookupSvc = "RadioID"; } + +// Setup the URL(s) +if ($callsignLookupSvc == "RadioID") { $callsignLookupUrl = "https://database.radioid.net/database/view?callsign="; } +if ($callsignLookupSvc == "QRZ") { $callsignLookupUrl = "https://www.qrz.com/db/"; } + +?> + + + + + + + + + +/tmp/worked.log) 2>&1 &'); + $ci = 0; + if ($WorkedLog = fopen("/tmp/worked.log",'r')) { + while ($linkLine = fgets($WorkedLog)) { + if(preg_match_all('/^(.{19}).*My: (.*).*Your: (.*).*Rpt1: (.*).*Rpt2: (.*).*Flags: (.*)$/',$linkLine,$linx) > 0){ + $ci++; + if($ci > 1) { $ci = 0; } + $QSODate = date("d-M-Y H:i:s", strtotime(substr($linx[1][0],0,19))); + + // Same normalisation pattern as dstarrepeater/last_herd.php — + // see the note in that file for the rationale. + $myCallRaw = str_replace(' ', '', substr($linx[2][0],0,8)); + $myCallHtml = htmlspecialchars($myCallRaw, ENT_QUOTES, 'UTF-8'); + $myCallLink = strtok(substr($linx[2][0],0,8), " "); + $myCallLinkUrl = rawurlencode((string)$myCallLink); + $myIdHtml = htmlspecialchars(str_replace(' ', '', substr($linx[2][0],9,4)), ENT_QUOTES, 'UTF-8'); + $yourCallHtml = str_replace(' ', ' ', + htmlspecialchars(substr($linx[3][0],0,8), ENT_QUOTES, 'UTF-8')); + $rpt1Html = str_replace(' ', ' ', + htmlspecialchars(substr($linx[4][0],0,8), ENT_QUOTES, 'UTF-8')); + $rpt2Html = str_replace(' ', ' ', + htmlspecialchars(substr($linx[5][0],0,8), ENT_QUOTES, 'UTF-8')); + + $utc_time = $QSODate; + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $dt = new DateTime($utc_time, $utc_tz); + $dt->setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + print ""; + print ""; + print ""; } + else { print ""; } + print ""; + print ""; + print ""; + print "\n"; + } + } + fclose($WorkedLog); + } +?> +
()RPT 1RPT 2
$local_time
$myCallHtml"; + if($myIdHtml !== '') { print "/$myIdHtml
$yourCallHtml$rpt1Html$rpt2Html
diff --git a/dstarrepeater/system.php b/dstarrepeater/system.php new file mode 100644 index 0000000..e80d978 --- /dev/null +++ b/dstarrepeater/system.php @@ -0,0 +1,81 @@ + 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev="20141101"; +$MYCALL=strtoupper($callsign); +?> + 1000) { $cpuTempC = round($cpuTempCRaw / 1000, 1); } else { $cpuTempC = round($cpuTempCRaw, 1); } +$cpuTempF = round(+$cpuTempC * 9 / 5 + 32, 1); +if ($cpuTempC < 50) { $cpuTempHTML = "".$cpuTempC."°C / ".$cpuTempF."°F\n"; } +if ($cpuTempC >= 50) { $cpuTempHTML = "".$cpuTempC."°C / ".$cpuTempF."°F\n"; } +if ($cpuTempC >= 69) { $cpuTempHTML = "".$cpuTempC."°C / ".$cpuTempF."°F\n"; } +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +

System IP Address:
', exec('hostname -I'));?>
ReleaseUptime:
', exec('uptime -p'));?>
CPU LoadCPU Temp
= 16) { $h = substr($h, 0, 14) . '..'; } echo htmlspecialchars((string)$h, ENT_QUOTES, 'UTF-8'); ?> / /
">MMDVMHost">DStarRepeater">ircDDBGateway">TimeServer">PiStar-Watchdog">PiStar-Remote
+
diff --git a/functions.js b/functions.js new file mode 100644 index 0000000..a6432bd --- /dev/null +++ b/functions.js @@ -0,0 +1,359 @@ +function toggleField(hideObj,showObj) { + hideObj.disabled=true; + hideObj.style.display='none'; + showObj.disabled=false; + showObj.style.display='inline'; + showObj.focus(); +} +function checkPass(){ //used in confirm matching password entries + var pass1 = document.getElementById('pass1'); + var pass2 = document.getElementById('pass2'); + var goodColor = "#66cc66"; + var badColor = "#ff6666"; + if((pass1.value != '') && (pass1.value == pass2.value)){ + pass2.style.backgroundColor = goodColor; + document.getElementById('submitpwd').removeAttribute("disabled"); + }else{ + pass2.style.backgroundColor = badColor; + document.getElementById('submitpwd').setAttribute("disabled","disabled"); + } +} +function checkPsk() { + if(psk1.value.length > 0 && psk1.value.length < 8) { + psk1.style.background='#ff6666'; + } else { + psk1.style.background='#66cc66'; + } +} +function checkPskMatch(){ //used in confirm matching psk entries + var psk1 = document.getElementById('psk1'); + var psk2 = document.getElementById('psk2'); + var goodColor = "#66cc66"; + var badColor = "#ff6666"; + if((psk1.value != '') && (psk1.value == psk2.value)){ + psk2.style.backgroundColor = goodColor; + document.getElementById('submitpsk').removeAttribute("disabled"); + }else{ + psk2.style.backgroundColor = badColor; + document.getElementById('submitpsk').setAttribute("disabled","disabled"); + } +} +function checkFrequency(){ + // Set the colours + var goodColor = "#66cc66"; + var badColor = "#ff6666"; + // Get the objects from the config page + var freqTRX = document.getElementById('confFREQ'); + var freqRX = document.getElementById('confFREQrx'); + var freqTX = document.getElementById('confFREQtx'); + var freqPOCSAG = document.getElementById('pocsagFrequency'); + if(freqTRX){ + confFREQ.style.backgroundColor = badColor; // Set to bad colour first, then check + var intFreqTRX = parseFloat(freqTRX.value); // Swap to float + // TRX Good + if (144 <= intFreqTRX && intFreqTRX <= 148) { confFREQ.style.backgroundColor = goodColor; } + if (220 <= intFreqTRX && intFreqTRX <= 225) { confFREQ.style.backgroundColor = goodColor; } + if (420 <= intFreqTRX && intFreqTRX <= 450) { confFREQ.style.backgroundColor = goodColor; } + if (842 <= intFreqTRX && intFreqTRX <= 950) { confFREQ.style.backgroundColor = goodColor; } + if (1240 <= intFreqTRX && intFreqTRX <= 1300) { confFREQ.style.backgroundColor = goodColor; } + // TRX Bad + if (145.8 <= intFreqTRX && intFreqTRX <= 146) { confFREQ.style.backgroundColor = badColor; } + if (435 <= intFreqTRX && intFreqTRX <= 438) { confFREQ.style.backgroundColor = badColor; } + if (1260 <= intFreqTRX && intFreqTRX <= 1270) { confFREQ.style.backgroundColor = badColor; } + } + if(freqRX){ + confFREQrx.style.backgroundColor = badColor; // Set to bad colour first, then check + var intFreqRX = parseFloat(freqRX.value); // Swap to float + // RX Good + if (144 <= intFreqRX && intFreqRX <= 148) { confFREQrx.style.backgroundColor = goodColor; } + if (220 <= intFreqRX && intFreqRX <= 225) { confFREQrx.style.backgroundColor = goodColor; } + if (420 <= intFreqRX && intFreqRX <= 450) { confFREQrx.style.backgroundColor = goodColor; } + if (842 <= intFreqRX && intFreqRX <= 950) { confFREQrx.style.backgroundColor = goodColor; } + if (1240 <= intFreqRX && intFreqRX <= 1300) { confFREQrx.style.backgroundColor = goodColor; } + // RX Bad + if (145.8 <= intFreqRX && intFreqRX <= 146) { confFREQrx.style.backgroundColor = badColor; } + if (435 <= intFreqRX && intFreqRX <= 438) { confFREQrx.style.backgroundColor = badColor; } + if (1260 <= intFreqRX && intFreqRX <= 1270) { confFREQrx.style.backgroundColor = badColor; } + } + if(freqTX){ + confFREQtx.style.backgroundColor = badColor; // Set to bad colour first, then check + var intFreqTX = parseFloat(freqTX.value); // Swap to float + // TX Good + if (144 <= intFreqTX && intFreqTX <= 148) { confFREQtx.style.backgroundColor = goodColor; } + if (220 <= intFreqTX && intFreqTX <= 225) { confFREQtx.style.backgroundColor = goodColor; } + if (420 <= intFreqTX && intFreqTX <= 450) { confFREQtx.style.backgroundColor = goodColor; } + if (842 <= intFreqTX && intFreqTX <= 950) { confFREQtx.style.backgroundColor = goodColor; } + if (1240 <= intFreqTX && intFreqTX <= 1300) { confFREQtx.style.backgroundColor = goodColor; } + // TX Bad + if (145.8 <= intFreqTX && intFreqTX <= 146) { confFREQtx.style.backgroundColor = badColor; } + if (435 <= intFreqTX && intFreqTX <= 438) { confFREQtx.style.backgroundColor = badColor; } + if (1260 <= intFreqTX && intFreqTX <= 1270) { confFREQtx.style.backgroundColor = badColor; } + } + if(freqPOCSAG){ + pocsagFrequency.style.backgroundColor = badColor; // Set to bad colour first, then check + var intFreqPOCSAG = parseFloat(freqPOCSAG.value); // Swap to float + // TX Good + if (144 <= intFreqPOCSAG && intFreqPOCSAG <= 148) { pocsagFrequency.style.backgroundColor = goodColor; } + if (220 <= intFreqPOCSAG && intFreqPOCSAG <= 225) { pocsagFrequency.style.backgroundColor = goodColor; } + if (420 <= intFreqPOCSAG && intFreqPOCSAG <= 450) { pocsagFrequency.style.backgroundColor = goodColor; } + if (842 <= intFreqPOCSAG && intFreqPOCSAG <= 950) { pocsagFrequency.style.backgroundColor = goodColor; } + // TX Bad + if (145.8 <= intFreqPOCSAG && intFreqPOCSAG <= 146) { pocsagFrequency.style.backgroundColor = badColor; } + if (435 <= intFreqPOCSAG && intFreqPOCSAG <= 438) { pocsagFrequency.style.backgroundColor = badColor; } + } +} +function toggleDMRCheckbox(event) { + switch(document.getElementById('aria-toggle-dmr').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmr').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-dmr').click(); + break; + case "false": + document.getElementById('aria-toggle-dmr').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-dmr').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmr').click(); } +} +function toggleDSTARCheckbox(event) { + switch(document.getElementById('aria-toggle-dstar').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dstar').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-dstar').click(); + break; + case "false": + document.getElementById('aria-toggle-dstar').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-dstar').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dstar').click(); } +} +function toggleYSFCheckbox(event) { + switch(document.getElementById('aria-toggle-ysf').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-ysf').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-ysf').click(); + break; + case "false": + document.getElementById('aria-toggle-ysf').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-ysf').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-ysf').click(); } +} +function toggleP25Checkbox(event) { + switch(document.getElementById('aria-toggle-p25').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-p25').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-p25').click(); + break; + case "false": + document.getElementById('aria-toggle-p25').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-p25').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-p25').click(); } +} +function toggleNXDNCheckbox(event) { + switch(document.getElementById('aria-toggle-nxdn').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-nxdn').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-nxdn').click(); + break; + case "false": + document.getElementById('aria-toggle-nxdn').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-nxdn').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-nxdn').click(); } +} +function toggleM17Checkbox(event) { + switch(document.getElementById('aria-toggle-m17').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-m17').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-m17').click(); + break; + case "false": + document.getElementById('aria-toggle-m17').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-m17').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-m17').click(); } +} +function toggleYSF2DMRCheckbox(event) { + switch(document.getElementById('aria-toggle-ysf2dmr').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-ysf2dmr').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-ysf2dmr').click(); + break; + case "false": + document.getElementById('aria-toggle-ysf2dmr').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-ysf2dmr').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-ysf2dmr').click(); } +} +function toggleYSF2NXDNCheckbox(event) { + switch(document.getElementById('aria-toggle-ysf2nxdn').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-ysf2nxdn').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-ysf2nxdn').click(); + break; + case "false": + document.getElementById('aria-toggle-ysf2nxdn').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-ysf2nxdn').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-ysf2nxdn').click(); } +} +function toggleYSF2P25Checkbox(event) { + switch(document.getElementById('aria-toggle-ysf2p25').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-ysf2p25').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-ysf2p25').click(); + break; + case "false": + document.getElementById('aria-toggle-ysf2p25').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-ysf2p25').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-ysf2p25').click(); } +} +function toggleDMR2YSFCheckbox(event) { + switch(document.getElementById('aria-toggle-dmr2ysf').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmr2ysf').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-dmr2ysf').click(); + break; + case "false": + document.getElementById('aria-toggle-dmr2ysf').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-dmr2ysf').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmr2ysf').click(); } +} +function toggleDMR2NXDNCheckbox(event) { + switch(document.getElementById('aria-toggle-dmr2nxdn').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmr2nxdn').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-dmr2nxdn').click(); + break; + case "false": + document.getElementById('aria-toggle-dmr2nxdn').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-dmr2nxdn').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmr2nxdn').click(); } +} +function togglePOCSAGCheckbox(event) { + switch(document.getElementById('aria-toggle-pocsag').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-pocsag').setAttribute('aria-checked', "false"); + //document.getElementById('toggle-pocsag').click(); + break; + case "false": + document.getElementById('aria-toggle-pocsag').setAttribute('aria-checked', "true"); + //document.getElementById('toggle-pocsag').click(); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-pocsag').click(); } +} +function toggleDmrGatewayNet1EnCheckbox(event) { + switch(document.getElementById('aria-toggle-dmrGatewayNet1En').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmrGatewayNet1En').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-dmrGatewayNet1En').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmrGatewayNet1En').click(); } +} +function toggleDmrGatewayNet2EnCheckbox(event) { + switch(document.getElementById('aria-toggle-dmrGatewayNet2En').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmrGatewayNet2En').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-dmrGatewayNet2En').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmrGatewayNet2En').click(); } +} +function toggleDmrGatewayXlxEnCheckbox(event) { + switch(document.getElementById('aria-toggle-dmrGatewayXlxEn').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmrGatewayXlxEn').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-dmrGatewayXlxEn').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmrGatewayXlxEn').click(); } +} +function toggleDmrEmbeddedLCOnly(event) { + switch(document.getElementById('aria-toggle-dmrEmbeddedLCOnly').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmrEmbeddedLCOnly').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-dmrEmbeddedLCOnly').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmrEmbeddedLCOnly').click(); } +} +function toggleDmrDumpTAData(event) { + switch(document.getElementById('aria-toggle-dmrDumpTAData').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dmrDumpTAData').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-dmrDumpTAData').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dmrDumpTAData').click(); } +} +function toggleHostFilesYSFUpper(event) { + switch(document.getElementById('aria-toggle-confHostFilesYSFUpper').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-confHostFilesYSFUpper').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-confHostFilesYSFUpper').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-confHostFilesYSFUpper').click(); } +} +function toggleWiresXCommandPassthrough(event) { + switch(document.getElementById('aria-toggle-confWiresXCommandPassthrough').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-confWiresXCommandPassthrough').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-confWiresXCommandPassthrough').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-confWiresXCommandPassthrough').click(); } +} +function toggleDstarTimeAnnounce(event) { + switch(document.getElementById('aria-toggle-timeAnnounce').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-timeAnnounce').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-timeAnnounce').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-timeAnnounce').click(); } +} +function toggleDstarDplusHostfiles(event) { + switch(document.getElementById('aria-toggle-dplusHostFiles').getAttribute('aria-checked')) { + case "true": + document.getElementById('aria-toggle-dplusHostFiles').setAttribute('aria-checked', "false"); + break; + case "false": + document.getElementById('aria-toggle-dplusHostFiles').setAttribute('aria-checked', "true"); + break; + } + if(event.keyCode == '32') { document.getElementById('aria-toggle-dplusHostFiles').click(); } +} diff --git a/images/20green.png b/images/20green.png new file mode 100644 index 0000000..8245266 Binary files /dev/null and b/images/20green.png differ diff --git a/images/20red.png b/images/20red.png new file mode 100644 index 0000000..6cde8d8 Binary files /dev/null and b/images/20red.png differ diff --git a/images/download.png b/images/download.png new file mode 100644 index 0000000..bd1b12d Binary files /dev/null and b/images/download.png differ diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..b9f278c Binary files /dev/null and b/images/favicon.ico differ diff --git a/images/index.php b/images/index.php new file mode 100644 index 0000000..e69de29 diff --git a/images/reboot.png b/images/reboot.png new file mode 100644 index 0000000..1893f3a Binary files /dev/null and b/images/reboot.png differ diff --git a/images/restore.png b/images/restore.png new file mode 100644 index 0000000..d0c1736 Binary files /dev/null and b/images/restore.png differ diff --git a/images/shutdown.png b/images/shutdown.png new file mode 100644 index 0000000..838495b Binary files /dev/null and b/images/shutdown.png differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..87370b1 --- /dev/null +++ b/index.php @@ -0,0 +1,330 @@ + 0) + $configs[$key] = $value; + } + +} +$progname = basename($_SERVER['SCRIPT_FILENAME'],".php"); +$rev=$version; +//$MYCALL=strtoupper($callsign); +$MYCALL=strtoupper($configs['gatewayCallsign']); + +// Check if the config file exists +if (file_exists('/etc/pistar-css.ini')) { + $piStarCssFile = '/etc/pistar-css.ini'; + if (fopen($piStarCssFile,'r')) { $piStarCss = parse_ini_file($piStarCssFile, true); } + if ($piStarCss['BannerH1']['Enabled']) { + $piStarCssBannerH1 = $piStarCss['BannerH1']['Text']; + } + if ($piStarCss['BannerExtText']['Enabled']) { + $piStarCssBannerExtTxt = $piStarCss['BannerExtText']['Text']; + } +} + +//Load the Pi-Star Release file +$pistarReleaseConfig = '/etc/pistar-release'; +$configPistarRelease = array(); +$configPistarRelease = parse_ini_file($pistarReleaseConfig, true); +?> + + + + + + + + \n"; ?> + + + + + + + + + + <?php echo "$MYCALL"." - ".$lang['digital_voice']." ".$lang['dashboard'];?> + + + + + + +
+
+
Hostname:
CDN: /
+

CDN

+".$piStarCssBannerH1."\n"; } ?> +".$piStarCssBannerExtTxt."

\n"; }?> + +

+ | + | +'.$lang['live_logs'].' |'."\n"; + echo ' '.$lang['power'].' |'."\n"; + } ?> + +

+
+ +'."\n"; + echo ''."\n"; + echo '
'."\n"; + include 'dstarrepeater/system.php'; // Basic System Info + echo '
'."\n"; + echo '
'."\n"; + } +// First lets figure out if we are in MMDVMHost mode, or dstarrepeater mode; +if (file_exists('/etc/dstar-radio.mmdvmhost')) { + include 'config/config.php'; // MMDVMDash Config + include_once 'mmdvmhost/tools.php'; // MMDVMDash Tools + + function getMMDVMConfigFileContent() { + // loads /etc/mmdvmhost into array for further use + $conf = array(); + if ($configs = @fopen('/etc/mmdvmhost', 'r')) { + while ($config = fgets($configs)) { + array_push($conf, trim ( $config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $conf; + } + $mmdvmconfigfile = getMMDVMConfigFileContent(); + + $dashboardRowOpen = true; + echo '
'."\n"; // Pairs .nav + .content so they stretch to equal height + echo ''."\n"; + + echo '
'."\n"; + + $testMMDVModeDSTARnet = getConfigItem("D-Star Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeDSTARnet == 1 ) { // If D-Star network is enabled, add these extra features. + + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + echo ''."\n"; + echo ''."\n"; + echo '
'."\n"; + + include 'dstarrepeater/link_manager.php'; // D-Star Link Manager + echo "
\n"; + } + + echo ''."\n"; + echo '
'."\n"; + include 'dstarrepeater/css_connections.php'; // dstarrepeater gateway config + echo '
'."\n"; + } + + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + echo ''."\n"; + echo '
'."\n"; + include 'mmdvmhost/bm_links.php'; // BM Links + echo '
'."\n"; + } + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Options + include 'mmdvmhost/bm_manager.php'; // BM DMR Link Manager + } + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + echo ''."\n"; + echo '
'."\n"; + include 'mmdvmhost/tgif_links.php'; // TGIF Links + echo '
'."\n"; + } + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Options + include 'mmdvmhost/tgif_manager.php'; // TGIF DMR Link Manager + } + $testMMDVModeYSFnet = getConfigItem("System Fusion Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeYSFnet == 1 ) { // If YSF network is enabled, add these extra features. + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + include 'mmdvmhost/ysf_manager.php'; // YSF Links + } + } + $testMMDVModeP25net = getConfigItem("P25 Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeP25net == 1 ) { // If P25 network is enabled, add these extra features. + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + include 'mmdvmhost/p25_manager.php'; // P25 Links + } + } + $testMMDVModeNXDNnet = getConfigItem("NXDN Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeNXDNnet == 1 ) { // If NXDN network is enabled, add these extra features. + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + include 'mmdvmhost/nxdn_manager.php'; // NXDN Links + } + } + $testMMDVModeM17net = getConfigItem("M17 Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeM17net == 1 ) { // If NXDN network is enabled, add these extra features. + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Option + include 'mmdvmhost/m17_manager.php'; // M17 Links + } + } + echo ''."\n"; + echo '
'."\n"; + include 'mmdvmhost/lh.php'; // MMDVMDash Last Herd + echo '
'."\n"; + echo "
\n"; + echo '
'."\n"; + include 'mmdvmhost/localtx.php'; // MMDVMDash Local Trasmissions + echo '
'."\n"; + + // If POCSAG is enabled, show the information pannel + $testMMDVModePOCSAG = getConfigItem("POCSAG Network", "Enable", $mmdvmconfigfile); + if ( $testMMDVModePOCSAG == 1 ) { + echo ''."\n"; + echo "
\n"; + echo '
'."\n"; + include 'mmdvmhost/pages.php'; // POCSAG Messages + echo '
'."\n"; + } + +} elseif (file_exists('/etc/dstar-radio.dstarrepeater')) { + echo '
'."\n"; + include 'dstarrepeater/gateway_software_config.php'; // dstarrepeater gateway config + echo ''."\n"; + echo '
'."\n"; + echo ''."\n"; + echo '
'."\n"; + if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Admin Only Options + include 'dstarrepeater/link_manager.php'; // D-Star Link Manager + echo "
\n"; + } + + echo ''."\n"; + echo '
'."\n"; + include 'dstarrepeater/css_connections.php'; // dstarrepeater gateway config + echo '
'."\n"; + + echo ''."\n"; + echo '
'."\n"; + include 'dstarrepeater/last_herd.php'; //dstarrepeater Last Herd + echo '
'."\n"; + echo "
\n"; + echo '
'."\n"; + include 'dstarrepeater/local_tx.php'; //dstarrepeater Local Transmissions + echo '
'."\n"; + echo '
'."\n"; + +} else { + echo '
'."\n"; + //We dont know what mode we are in - fail... + echo "

未定义工作模式...

\n"; + echo ''."\n"; +} +?> +
+\n"; } ?> + +
+ + + + diff --git a/jquery-timing.min.js b/jquery-timing.min.js new file mode 100644 index 0000000..935e886 --- /dev/null +++ b/jquery-timing.min.js @@ -0,0 +1 @@ +(function(m,j){var d={},e=1,f=m.fn.each,k=m.fn.on||m.fn.bind,n=m.fn.off||m.fn.unbind,l={};function a(p,q){q=m(q);q.prevObject=p;var o=p.length;if(o!==q.length){return q}while(o--){if(p[o]!==q[o]){return q}}return p}function c(o){var p=[],q=o.length;while(q--){p[q]=o[q].g}return p}function h(p,w,t,s){t=t||[];var u={f:p,i:w},q=false,o,x,y;function v(A,B){A.e=false;function z(){A.k=a(A.f,A.k);A.e=true;r()}return typeof B.promise=="function"?B.promise().then(z):B.then(z,true)}function r(A){while(!q){try{q=!q;if(typeof s=="function"){s(m.makeArray(u.k||u.f))}if(u.e==false){break}if(!u.i.j){if(y&&(!t.length||t[0].b)){if(u.f&&typeof u.f.promise=="function"){u.f.promise().then(y.resolve)}else{y.resolveWith(u.f)}y=null}if(!t.length){return u.f}x=t[0].l&&t[0].l(r,u,t);if(!x){break}u=x;continue}o=u.f&&u.f[u.i.j]||l[u.i.j];if(!o){throw'no such method "'+u.i.j+'" on object ('+u.f+")"}if(o.timing&&!u.e){u.e=false;u=o.timing(r,u,t,s)||u}else{if(!o.timing&&!u.e){u.k=u.f[u.i.j].apply(u.f,u.i.c);if(t.length&&u.k&&u.k instanceof g){v(u,u.k);continue}}x={f:u.k,i:u.i.k};u.e=false;if(typeof u.d=="function"){u.d.apply(u.f,c(t))}u=x}}catch(z){q=!q;throw z}finally{q=!q}}return A}if(m.Deferred){r.promise=function(A,B){var z=(y=y||m.Deferred()).promise(B);r();return z}}return r}function g(q,r,p){this[".methods"]=r;this[".callback"]=p;this.length=0;Array.prototype.push.apply(this,m.makeArray(this._=q._=q));for(var o in q){if(!(o in g.prototype)&&typeof q[o]=="function"){this[o]=i(o)}}}if(m.Deferred){g.prototype.promise=function(o,p){if(typeof o=="object"){p=o;o=null}return(this[".callback"]&&typeof this[".callback"].promise=="function")?this[".callback"].promise(o,p):m.Deferred().resolveWith(this).promise(p)}}function i(o){return g.prototype[o]=function(){this[".methods"].j=o;this[".methods"].c=arguments;this[".methods"]=this[".methods"].k={};return this[".callback"]?this[".callback"](this,o,arguments):this}}m.each(["bind","on","one","live","delegate"],function(p,o){if(m.fn[o]){var q=m.fn[o];m.fn[o]=function(){var u,x,w,s,r,t=this;for(u=0;u1&&arguments[arguments.length-1]===m){var r="_timing"+e++;arguments[arguments.length-1]=function(){m(this).trigger(r)};return this.each().one(r).all(q.apply(this,arguments))}return q.apply(this,arguments)}}});m.each(["wait","repeat","join","then"],function(p,o){m.fn[o]=function(){var r={},q=new g(this,r,h(this,r,[],function(s){q.length=0;Array.prototype.push.apply(q,s)}));return q[o].apply(q,arguments)}});m.fn.join.timing=function(p,r){var q,o,s=r.f.length;if(typeof r.i.c[0]=="string"){q=r.i.c[0];if(typeof r.i.c[1]=="function"){r.d=r.i.c[1]}else{o=r.i.c[1];r.d=r.i.c[2]}}else{if(typeof r.i.c[0]=="function"){r.d=r.i.c[0]}else{o=r.i.c[0];r.d=r.i.c[1]}}r.k=r.f;r.e=!s;if(o){r.f.promise(q==null?"fx":q).then(function(){r.e=true;p()})}else{r.f.queue(q==null?"fx":q,function(t){r.e=!--s;p();t()})}};m.fn.then.timing=function(o,p){p.d=p.i.c[0];p.k=p.f;p.e=true;if(p.i.c[1]){Array.prototype.shift.apply(p.i.c)}};m.fn.wait.timing=function(s,t,u){var r,o,v,p=t.f;r=t.i.c[0];t.d=t.i.c[1];function w(){n.call(o?n.call(p,o,w):p,"unwait",q);t.e=true;t.k=a(t.f,t.k);s()}function q(x,y){n.call(o?n.call(m(this),o,w):m(this),"unwait",q);p=p.not(this);if(!y){t.k=t.k.not(this)}if(!p.length){t.e=t.k.length;t.k=a(t.f,t.k);j.clearTimeout(v);t={f:p}}s()}k.call(p,"unwait",q);t.k=p;if(r==null||r==m){r=p}if(typeof r=="function"){r=r.apply(p,c(u))}if(typeof r=="string"){k.call(p,o=r,w)}else{if(r&&typeof r.promise=="function"){r.promise().then(w)}else{if(r&&typeof r.then=="function"){r.then(w,true)}else{v=j.setTimeout(w,Math.max(0,r))}}}};m.fn.each=function(q){if(!q||q===m){var p={},o=new g(this,p,h(this,p,[],function(r){o.length=0;Array.prototype.push.apply(o,r)}));return o.each(q)}return f.apply(this,arguments)};m.fn.each.timing=function(r,v,w,u){if(v.i.c[0]&&v.i.c[0]!==m){v.e=true;v.k=f.apply(v.f,v.i.c);return}var B=Math.max(v.f.length,1),p=0,y,s,t,A=[],z=[],x=m.extend({},v.f),q=v.i.c[0]===m;if(q){j.setTimeout(function(){t=true;r()},0)}function o(){if(q){if(p=s-1}if(s){r.e=true;r.k=r.f;q.shift().n(s)}else{if(p){q[0].k=r.f}r=q[0];r.g++;r.n(s);return r}};new g(l);m.each(["unwait","unrepeat"],function(p,o){m.fn[o]=function(){return this.trigger(o,arguments)}});m.each(["wait","repeat","join","then","unwait","unrepeat"],function(p,o){m[o]=function(){var q=typeof arguments[0]=="string"?Array.prototype.shift.apply(arguments):"";return m.fn[o].apply(d[q]=(d[q]||m("
").text(q)),arguments)}});function b(r,u,q){if(typeof r=="string"){q=new Function("x","return ["+r+"\n,x]");r=function(w,v){v=q(w);p.x=v[1];return v[0]}}var o=typeof u=="function",t=typeof r=="function",p=function(v){if(arguments.length==1){p.x=v;if(o){u(v)}}else{return s()}};function s(v){v=o?u():p.x;return t?r(v):v}p.x=0;p._={toString:p.$=p.toString=s.toString=s};p.mod=function(v){return b(function(w){return w%v},p)};p.add=function(v){return b(function(w){return w+v},p)};p.neg=function(){return b("-x",p)};p.$$=p.X=function(v){return b(v,p)};m.each(["a","b","c","d","e","f","g","h","i","j"],function(v,w){p[v]=p[w]=function(){p(arguments[v])}});return p}j.$$=m.$$=m.X=b;m.fn.$=function(){var o=m.apply(j,arguments);o.prevObject=this;return o}})(jQuery,window); \ No newline at end of file diff --git a/jquery.min.js b/jquery.min.js new file mode 100644 index 0000000..b061403 --- /dev/null +++ b/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 "Veu Digital", + "configuration" => "Configuració", + "dashboard_for" => "Tauler de control de", + // Banner links + "dashboard" => "Tauler de Control", + "admin" => "Administrar", + "power" => "Reiniciar/Apagar", + "update" => "Actualitzar", + "backup_restore" => "Fer/Restaurar còpia seguretat", + "factory_reset" => "Restaurar estat de fàbrica", + "live_logs" => "Informes/Logs", + // Config page section headdings + "hardware_info" => "Informació del maquinari", + "control_software" => "Control de programari", + "mmdvmhost_config" => "Configuració de MMDVMHost", + "general_config" => "Configuració General", + "dmr_config" => "Configuració de DMR", + "dstar_config" => "Configuració de DSTAR", + "ysf_config" => "Configuració de Yaesu C4FM fusion", + "p25_config" => "Configuració de P25", + "nxdn_config" => "Configuració de NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Configuració de POCSAG", + "mobilegps_config" => "Configuració GPS mòbil", + "wifi_config" => "Configuració WIFI", + "fw_config" => "Configuració del Tallafocs", + "remote_access_pw" => "Mot clau per acccés remot", + // Config Page - Section General + "setting" => "Configuració", + "value" => "Valor", + "apply" => "Aplicar", + // Config Page - Gateway Hardware Information + "hostname" => "Nom de l'amfitrió", + "kernel" => "Nucli", + "platform" => "Plataforma", + "cpu_load" => "Càrrega CPU", + "cpu_temp" => "Temperatura CPU", + // Config Page - Control Software + "controller_software" => "Controlador programari", + "controller_mode" => "Controlador de Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Mode DMR", + "d-star_mode" => "Mode D-Star", + "ysf_mode" => "Mode YSF", + "p25_mode" => "Mode P25", + "nxdn_mode" => "Mode NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Tipus de pantalla", + "mode_hangtime" => "Mode temps de suspensió", + // Config Page - General Configuration + "node_call" => "Indicatiu del Node", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Freqüència RX/TX", + "lattitude" => "Latitud", + "longitude" => "Longitud", + "town" => "Ciutat", + "country" => "País", + "url" => "URL", + "radio_type" => "Ràdio/Tipus de mòdem", + "node_type" => "Tipus de Node", + "timezone" => "Zona horària", + "dash_lang" => "Idioma del Tauler de Control", + // Config Page - DMR Configuration + "dmr_master" => "Màster DMR (MMDVMHost)", + "bm_master" => "Màster BrandMeister", + "bm_network" => "Xarxa BrandMeister", + "dmr_plus_master" => "Màster DMR+", + "dmr_plus_network" => "Xarxa DMR+", + "xlx_master" => "Màster XLX", + "xlx_enable" => "Habilitar màster XLX", + "dmr_cc" => "Codi de color DMR", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 indicatiu de crida", + "dstar_rpt2" => "RPT2 indicatiu de crida", + "dstar_irc_password" => "mot clau d'ircDDBGateway", + "dstar_default_ref" => "Reflector predeterminat", + "aprs_host" => "Servidor d'APRS", + "dstar_irc_lang" => "Idioma d'ircDDBGateway", + "dstar_irc_time" => "Interval de Balissa", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Arrencant Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Arrencant Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Arrencant Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "Activació del GPS mòbil", + "mobilegps_port" => "Port del GPS", + "mobilegps_speed" => "Velocitat del port del GPS", + // Config Page - Firewall Configuration + "fw_dash" => "Tauler d'accés", + "fw_irc" => "ircDDBGateway Remot", + "fw_ssh" => "Accés per SSH", + // Config Page - Password + "user" => "Nom d'usuari", + "password" => "contrasenya", + "set_password" => "Establir contrasenya", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modes habilitats", + "net_status" => "Estat de la xarxa", + "internet" => "Internet", + "radio_info" => "Informació de Ràdio", + "dstar_repeater" => "Repetidor D-Star", + "dstar_net" => "Xarxa D-Star", + "dmr_repeater" => "Repetidor DMR", + "dmr_master" => "Màster DMR", + "ysf_net" => "Xarxa YSF", + "p25_radio" => "Ràdio P25", + "p25_net" => "Xarxa P25", + "nxdn_radio" => "Ràdio NXDN", + "nxdn_net" => "Xarxa NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Hora", + "mode" => "Mode", + "callsign" => "Indicatiu", + "target" => "Destí", + "src" => "SRC", + "dur" => "DUR", + "loss" => "Pèrdua", + "ber" => "BER", + // POCSAG Specific + "pocsag_list" => "Activitat de passarel·la DAPNET", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Missatge", + // Dashboard - Extra Info + "group" => "Grup", + "logoff" => "Finalitzar", + "info" => "Informació", + "utot" => "UTOT", + "gtot" => "GTOT", + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Últimes 20 trucades rebudes", + "local_tx_list" => "Últimes 20 trucades rebudes via ràdio", + "active_starnet_groups" => "Grups actius Starnet", + "active_starnet_members" => "Membres actius grup Starnet", + "d-star_link_manager" => "Gestor d'enllaços D-Star", + "d-star_link_status" => "Informació d'enllaços D-Star", + "service_status" => "Estat del servei" +); diff --git a/lang/chinese_cn.php b/lang/chinese_cn.php new file mode 100644 index 0000000..b445b04 --- /dev/null +++ b/lang/chinese_cn.php @@ -0,0 +1,156 @@ + "数字语音", + "configuration" => "配置", + "dashboard_for" => "仪表盘 -", + // Banner links + "dashboard" => "仪表盘", + "admin" => "管理", + "power" => "电源", + "update" => "更新", + "backup_restore" => "备份/恢复", + "factory_reset" => "恢复出厂设置", + "live_logs" => "日志", + // Config page section headdings + "hardware_info" => "网关硬件信息", + "control_software" => "控制软件", + "mmdvmhost_config" => "MMDVMHost 配置", + "general_config" => "常规配置", + "dmr_config" => "DMR 配置", + "dstar_config" => "D-Star 配置", + "ysf_config" => "Yaesu System Fusion 配置", + "p25_config" => "P25 配置", + "nxdn_config" => "NXDN 配置", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG 配置", + "mobilegps_config" => "Mobile GPS Configuration", + "fw_config" => "防火墙配置", + "remote_access_pw" => "远程访问密码", + // Config Page - Section General + "setting" => "设置", + "value" => "设置值", + "apply" => "应用设置", + // Config Page - Gateway Hardware Information + "hostname" => "主机名", + "kernel" => "内核", + "platform" => "平台", + "cpu_load" => "CPU 负荷", + "cpu_temp" => "CPU 温度", + // Config Page - Control Software + "controller_software" => "控制器软件", + "controller_mode" => "控制器模式", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR 模式", + "d-star_mode" => "D-Star 模式", + "ysf_mode" => "YSF 模式", + "p25_mode" => "P25 模式", + "nxdn_mode" => "NXDN 模式", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM 显示屏类型", + "mode_hangtime" => "模式停留时间", + // Config Page - General Configuration + "node_call" => "节点呼号", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "电台频率", + "lattitude" => "纬度", + "longitude" => "经度", + "town" => "城市", + "country" => "国家", + "url" => "URL", + "radio_type" => "电台/调制解调器类型", + "node_type" => "节点类型", + "timezone" => "时区", + "dash_lang" => "仪表盘语言", + // Config Page - DMR Configuration + "dmr_master" => "DMR 主机 (MMDVMHost)", + "bm_master" => "BrandMeister 主机 ", + "bm_network" => "BrandMeister 网络", + "dmr_plus_master" => "DMR+ 主机 ", + "dmr_plus_network" => "DMR+ 网络", + "xlx_master" => "XLX 主机 ", + "xlx_enable" => "XLX 主机启用", + "dmr_cc" => "DMR 彩色码", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 呼号", + "dstar_rpt2" => "RPT2 呼号", + "dstar_irc_password" => "ircDDBGateway 密码", + "dstar_default_ref" => "默认反射器", + "aprs_host" => "APRS 服务器", + "dstar_irc_lang" => "ircDDBGateway 语言", + "dstar_irc_time" => "时间通告", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF 默认服务器", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 默认服务器", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN 默认服务器", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "仪表盘访问", + "fw_irc" => "ircDDBGateway 远程", + "fw_ssh" => "SSH访问", + // Config Page - Password + "user" => "用户名", + "password" => "密码", + "set_password" => "设置密码", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "启动的模式", + "net_status" => "网络状态", + "internet" => "互联网", + "radio_info" => "电台信息", + "dstar_repeater" => "D-Star 中继", + "dstar_net" => "D-Star 网络", + "dmr_repeater" => "DMR 中继", + "dmr_master" => "DMR 主机", + "ysf_net" => "YSF 网络", + "p25_radio" => "P25 电台", + "p25_net" => "P25 网络", + "nxdn_radio" => "NXDN 电台", + "nxdn_net" => "NXDN 网络", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "时间", + "mode" => "模式", + "callsign" => "呼号", + "target" => "目标", + "src" => "源", // Short version of "Source" + "dur" => "时长", // Short version of "Duration" + "loss" => "丢失", + "ber" => "误码率", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "组", + "logoff" => "注销", + "info" => "信息", + "utot" => "用户超时", // Short for User Timeout + "gtot" => "组超时", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "网关上最后 20 个呼叫", + "local_tx_list" => "最后 20 个本地呼叫", + "active_starnet_groups" => "激活的 Starnet 组", + "active_starnet_members" => "激活的 Starnet 组成员", + "d-star_link_manager" => "D-Star 连接管理器", + "d-star_link_status" => "D-Star 连接信息", + "service_status" => "服务状态" +); diff --git a/lang/chinese_hk.php b/lang/chinese_hk.php new file mode 100644 index 0000000..3fed26c --- /dev/null +++ b/lang/chinese_hk.php @@ -0,0 +1,156 @@ + "數字語音", + "configuration" => "設定", + "dashboard_for" => "儀錶盤 -", + // Banner links + "dashboard" => "儀錶盤", + "admin" => "管理", + "power" => "電源", + "update" => "更新", + "backup_restore" => "備份/恢復", + "factory_reset" => "恢復出廠設定", + "live_logs" => "日誌", + // Config page section headdings + "hardware_info" => "網關硬體信息", + "control_software" => "控制軟體", + "mmdvmhost_config" => "MMDVMHost 設定", + "general_config" => "常規設定", + "dmr_config" => "DMR 設定", + "dstar_config" => "D-Star 設定", + "ysf_config" => "Yaesu System Fusion 設定", + "p25_config" => "P25 設定", + "nxdn_config" => "NXDN 設定", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG 設定", + "mobilegps_config" => "Mobile GPS Configuration", + "fw_config" => "防火墻設定", + "remote_access_pw" => "遠程訪問密碼", + // Config Page - Section General + "setting" => "設定", + "value" => "設定值", + "apply" => "應用設定", + // Config Page - Gateway Hardware Information + "hostname" => "主機名", + "kernel" => "內核", + "platform" => "平臺", + "cpu_load" => "CPU 負荷", + "cpu_temp" => "CPU 溫度", + // Config Page - Control Software + "controller_software" => "控制器軟體", + "controller_mode" => "控制器模式", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR 模式", + "d-star_mode" => "D-Star 模式", + "ysf_mode" => "YSF 模式", + "p25_mode" => "P25 模式", + "nxdn_mode" => "NXDN 模式", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM 顯示器類型", + "mode_hangtime" => "模式停留時間", + // Config Page - General Configuration + "node_call" => "節點呼號", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "電臺頻率", + "lattitude" => "緯度", + "longitude" => "經度", + "town" => "區域", + "country" => "國家", + "url" => "URL", + "radio_type" => "電臺/調製解調器類型", + "node_type" => "節點類型", + "timezone" => "時區", + "dash_lang" => "儀錶盤語言", + // Config Page - DMR Configuration + "dmr_master" => "DMR 主機 (MMDVMHost)", + "bm_master" => "BrandMeister 主機 ", + "bm_network" => "BrandMeister 網絡", + "dmr_plus_master" => "DMR+ 主機 ", + "dmr_plus_network" => "DMR+ 網絡", + "xlx_master" => "XLX 主機 ", + "xlx_enable" => "XLX 主機啟用", + "dmr_cc" => "DMR 彩色碼", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 呼號", + "dstar_rpt2" => "RPT2 呼號", + "dstar_irc_password" => "ircDDBGateway 密碼", + "dstar_default_ref" => "默認反射器", + "aprs_host" => "APRS 服務器", + "dstar_irc_lang" => "ircDDBGateway 語言", + "dstar_irc_time" => "時間通告", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF 默認服務器", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 默認服務器", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN 默認服務器", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "儀表盤訪問", + "fw_irc" => "ircDDBGateway 遠程", + "fw_ssh" => "SSH訪問", + // Config Page - Password + "user" => "用戶名", + "password" => "密碼", + "set_password" => "密碼設定", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "啟用的模式", + "net_status" => "網絡狀態", + "internet" => "互聯網", + "radio_info" => "電臺信息", + "dstar_repeater" => "D-Star 中繼", + "dstar_net" => "D-Star 網絡", + "dmr_repeater" => "DMR 中繼", + "dmr_master" => "DMR 主機", + "ysf_net" => "YSF 網絡", + "p25_radio" => "P25 電臺", + "p25_net" => "P25 網絡", + "nxdn_radio" => "NXDN 電臺", + "nxdn_net" => "NXDN 網絡", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "時間", + "mode" => "模式", + "callsign" => "呼號", + "target" => "目標", + "src" => "源", // Short version of "Source" + "dur" => "時長", // Short version of "Duration" + "loss" => "丟失", + "ber" => "誤碼率", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "組", + "logoff" => "註銷", + "info" => "信息", + "utot" => "用户超時", // Short for User Timeout + "gtot" => "組超時", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "網關上最後 20 個呼叫", + "local_tx_list" => "最後 20 個本地呼叫", + "active_starnet_groups" => "激活 Starnet 組", + "active_starnet_members" => "激活 Starnet 組成員", + "d-star_link_manager" => "D-Star 連接管理器", + "d-star_link_status" => "D-Star 連接信息", + "service_status" => "服務狀態" +); diff --git a/lang/chinese_tw.php b/lang/chinese_tw.php new file mode 100644 index 0000000..0c96f43 --- /dev/null +++ b/lang/chinese_tw.php @@ -0,0 +1,154 @@ + "數位語音", + "configuration" => "組態設定", + "dashboard_for" => "控制台 -", + // Banner links + "dashboard" => "控制台", + "admin" => "管理", + "power" => "電源", + "update" => "更新", + "backup_restore" => "備份/還原", + "factory_reset" => "恢復出廠設定", + "live_logs" => "日誌", + // Config page section headdings + "hardware_info" => "硬體資訊", + "control_software" => "控制軟體", + "mmdvmhost_config" => "MMDVMHost 設定", + "general_config" => "一般設定", + "dmr_config" => "DMR 設定", + "dstar_config" => "D-Star 設定", + "ysf_config" => "Yaesu System Fusion 設定", + "p25_config" => "P25 設定", + "nxdn_config" => "NXDN 設定", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG 設定", + "mobilegps_config" => "Mobile GPS Configuration", + "fw_config" => "防火牆設定", + "remote_access_pw" => "遠端遙控密碼", + // Config Page - Section General + "setting" => "設定", + "value" => "設定值", + "apply" => "變更設定", + // Config Page - Gateway Hardware Information + "hostname" => "主機名稱", + "kernel" => "核心", + "platform" => "平台", + "cpu_load" => "CPU 負載", + "cpu_temp" => "CPU 溫度", + // Config Page - Control Software + "controller_software" => "控制器軟體", + "controller_mode" => "控制器模式", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR 模式", + "d-star_mode" => "D-Star 模式", + "ysf_mode" => "YSF 模式", + "p25_mode" => "P25 模式", + "nxdn_mode" => "NXDN 模式", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM 螢幕類型", + "mode_hangtime" => "模式保持時間", + // Config Page - General Configuration + "node_call" => "節點呼號", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "電台頻率", + "lattitude" => "緯度", + "longitude" => "經度", + "town" => "城市", + "country" => "國家", + "url" => "URL", + "radio_type" => "Modem 類型", + "node_type" => "節點類型", + "timezone" => "時區", + "dash_lang" => "介面語言", + // Config Page - DMR Configuration + "dmr_master" => "DMR 伺服器 (MMDVMHost)", + "bm_master" => "BrandMeister 伺服器", + "bm_network" => "BrandMeister 網路", + "dmr_plus_master" => "DMR+ 伺服器", + "dmr_plus_network" => "DMR+ 網路", + "xlx_master" => "XLX 伺服器", + "xlx_enable" => "啟用 XLX 伺服器", + "dmr_cc" => "DMR 色碼", + "dmr_embeddedlconly" => "僅允許指定 ID 連接", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 呼號", + "dstar_rpt2" => "RPT2 呼號", + "dstar_irc_password" => "ircDDBGateway 密碼", + "dstar_default_ref" => "預設反射器", + "aprs_host" => "APRS 伺服器", + "dstar_irc_lang" => "ircDDBGateway 語言", + "dstar_irc_time" => "時間提示", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF 預設伺服器", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 預設伺服器", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN 預設伺服器", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "控制台存取", + "fw_irc" => "ircDDBGateway 遠端遙控", + "fw_ssh" => "SSH 存取", + // Config Page - Password + "user" => "用戶名", + "password" => "密碼", + "set_password" => "密碼設定", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "已啟用模式", + "net_status" => "網路狀態", + "internet" => "網際網路", + "radio_info" => "RF 電台資訊", + "dstar_repeater" => "D-Star 中繼", + "dstar_net" => "D-Star 網路", + "dmr_repeater" => "DMR 中繼", + "dmr_master" => "DMR 伺服器", + "ysf_net" => "YSF 網路", + "p25_radio" => "P25 電台", + "p25_net" => "P25 網路", + "nxdn_radio" => "NXDN 電台", + "nxdn_net" => "NXDN 網路", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "時間", + "mode" => "模式", + "callsign" => "呼號", + "target" => "目標", + "src" => "來源", // Short version of "Source" + "dur" => "歷時", // Short version of "Duration" + "loss" => "丟失率", + "ber" => "誤碼率", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "群組", + "logoff" => "登出", + "info" => "資訊", + "utot" => "用户逾時", // Short for User Timeout + "gtot" => "群組逾時", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "最近 20 個通訊", + "local_tx_list" => "最近 20 個本地 RF 通訊", + "active_starnet_groups" => "啟用 Starnet 群組", + "active_starnet_members" => "啟用 Starnet 成員", + "d-star_link_manager" => "D-Star 連線管理", + "d-star_link_status" => "D-Star 連線狀態", + "service_status" => "服務狀態" +); diff --git a/lang/dutch_nl.php b/lang/dutch_nl.php new file mode 100644 index 0000000..4e56eb5 --- /dev/null +++ b/lang/dutch_nl.php @@ -0,0 +1,155 @@ + "Digital Voice", + "configuration" => "Configuratie", + "dashboard_for" => "Dashboard voor", + // Banner links + "dashboard" => "Dashboard", + "admin" => "Admin", + "power" => "Power", + "update" => "Update", + "backup_restore" => "Backup/Restore", + "factory_reset" => "Reset all", + "live_logs" => "Actuele Logs", + // Config page section headdings + "hardware_info" => "Gateway hardware informatie", + "control_software" => "Controller Host software", + "mmdvmhost_config" => "MMDVMHost configuratie", + "general_config" => "Algemene configuratie", + "dmr_config" => "DMR configuratie", + "dstar_config" => "D-Star configuratie", + "ysf_config" => "Yaesu System Fusion configuratie", + "p25_config" => "P25 configuratie", + "nxdn_config" => "NXDN configuratie", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG configuratie", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Wireless configuratie", + "fw_config" => "Firewall configuratie", + "remote_access_pw" => "Remote Access paswoord", + // Config Page - Section General + "setting" => "Instelling", + "value" => "Waarde", + "apply" => "Toepassen", + // Config Page - Gateway Hardware Information + "hostname" => "Hostnaam", + "kernel" => "Kernel", + "platform" => "Platform", + "cpu_load" => "CPU Load", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Controller software", + "controller_mode" => "Controller modus", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR modus", + "d-star_mode" => "D-Star modus", + "ysf_mode" => "YSF modus", + "p25_mode" => "P25 modus", + "nxdn_mode" => "NXDN modus", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM display type", + "mode_hangtime" => "Mode hangtijd", + // Config Page - General Configuration + "node_call" => "Node roepletters", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radio frequentie", + "lattitude" => "Breedtegraad", + "longitude" => "Lengtegraad", + "town" => "Plaats", + "country" => "Land", + "url" => "URL", + "radio_type" => "Radio/Modem type", + "node_type" => "Node type", + "timezone" => "Systeem tijdzone", + "dash_lang" => "Dashboard taal", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister netwerk", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ netwerk", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master AAN", + "dmr_cc" => "DMR kleur code (CC)", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 roepletters", + "dstar_rpt2" => "RPT2 roepletters", + "dstar_irc_password" => "ircDDBGateway paswoord", + "dstar_default_ref" => "Standaard reflector", + "aprs_host" => "APRS Host", + "dstar_irc_lang" => "ircDDBGateway taal", + "dstar_irc_time" => "Tijd uitzenden", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF standaard Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 standaard Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN standaard Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Dashboard toegang", + "fw_irc" => "ircDDBGateway op afstand", + "fw_ssh" => "SSH toegang", + // Config Page - Password + "user" => "Gebruikersnaam", + "password" => "Paswoord", + "set_password" => "Voer paswoord in", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Actieve modi", + "net_status" => "Netwerk status", + "internet" => "Internet", + "radio_info" => "Radio info", + "dstar_repeater" => "D-Star repeater", + "dstar_net" => "D-Star netwerk", + "dmr_repeater" => "DMR repeater", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF netwerk", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 netwerk", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN netwerk", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Tijd", + "mode" => "Mode", + "callsign" => "Roepletters", + "target" => "Doel", + "src" => "Bron", // Short version of "Source" + "dur" => "Duur", // Short version of "Duration" + "loss" => "Verlies", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Groep", + "logoff" => "Uitloggen", + "info" => "Informatie", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Laatste 20 oproepen gehoord (RX) via deze Gateway", + "local_tx_list" => "Laatste 20 oproepen verzonden (TX) via deze Gateway", + "active_starnet_groups" => "Actieve Starnet groepen", + "active_starnet_members" => "Actieve Starnet groepsleden", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Informatie", + "service_status" => "Service Status" +); diff --git a/lang/english_uk.php b/lang/english_uk.php new file mode 100644 index 0000000..7d4b473 --- /dev/null +++ b/lang/english_uk.php @@ -0,0 +1,156 @@ + "Digital Voice", + "configuration" => "Configuration", + "dashboard_for" => "Dashboard for", + // Banner links + "dashboard" => "Dashboard", + "admin" => "Admin", + "power" => "Power", + "update" => "Update", + "backup_restore" => "Backup/Restore", + "factory_reset" => "Factory Reset", + "live_logs" => "Live Logs", + // Config page section headdings + "hardware_info" => "Gateway Hardware Information", + "control_software" => "Control Software", + "mmdvmhost_config" => "MMDVMHost Configuration", + "general_config" => "General Configuration", + "dmr_config" => "DMR Configuration", + "dstar_config" => "D-Star Configuration", + "ysf_config" => "Yaesu System Fusion Configuration", + "p25_config" => "P25 Configuration", + "nxdn_config" => "NXDN Configuration", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Configuration", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Wireless Configuration", + "fw_config" => "Firewall Configuration", + "remote_access_pw" => "Remote Access Password", + // Config Page - Section General + "setting" => "Setting", + "value" => "Value", + "apply" => "Apply Changes", + // Config Page - Gateway Hardware Information + "hostname" => "Hostname", + "kernel" => "Kernel", + "platform" => "Platform", + "cpu_load" => "CPU Load", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Controller Software", + "controller_mode" => "Controller Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Mode", + "d-star_mode" => "D-Star Mode", + "ysf_mode" => "YSF Mode", + "p25_mode" => "P25 Mode", + "nxdn_mode" => "NXDN Mode", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Display Type", + "mode_hangtime" => "Mode Hangtime", + // Config Page - General Configuration + "node_call" => "Node Callsign", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radio Frequency", + "lattitude" => "Latitude", + "longitude" => "Longitude", + "town" => "Town", + "country" => "Country", + "url" => "URL", + "radio_type" => "Radio/Modem Type", + "node_type" => "Node Type", + "timezone" => "System Time Zone", + "dash_lang" => "Dashboard Language", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Network", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Network", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "DMR Colour Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Callsign", + "dstar_rpt2" => "RPT2 Callsign", + "dstar_irc_password" => "Remote Password", + "dstar_default_ref" => "Default Reflector", + "aprs_host" => "APRS Host", + "dstar_irc_lang" => "ircDDBGateway Language", + "dstar_irc_time" => "Time Announcements", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Startup Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Startup Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Startup Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Dashboard Access", + "fw_irc" => "ircDDBGateway Remote", + "fw_ssh" => "SSH Access", + // Config Page - Password + "user" => "User Name", + "password" => "Password", + "set_password" => "Set Password", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modes Enabled", + "net_status" => "Network Status", + "internet" => "Internet", + "radio_info" => "Radio Info", + "dstar_repeater" => "D-Star Repeater", + "dstar_net" => "D-Star Network", + "dmr_repeater" => "DMR Repeater", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF Network", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 Network", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN Network", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Time", + "mode" => "Mode", + "callsign" => "Callsign", + "target" => "Target", + "src" => "Src", // Short version of "Source" + "dur" => "Dur", // Short version of "Duration" + "loss" => "Loss", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Group", + "logoff" => "LogOff", + "info" => "Information", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Gateway Activity", + "local_tx_list" => "Local RF Activity", + "active_starnet_groups" => "Active Starnet Groups", + "active_starnet_members" => "Active Starnet Group Members", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Information", + "service_status" => "Service Status" +); diff --git a/lang/english_us.php b/lang/english_us.php new file mode 100644 index 0000000..ac4a523 --- /dev/null +++ b/lang/english_us.php @@ -0,0 +1,156 @@ + "Digital Voice", + "configuration" => "Configuration", + "dashboard_for" => "Dashboard for", + // Banner links + "dashboard" => "Dashboard", + "admin" => "Admin", + "power" => "Power", + "update" => "Update", + "backup_restore" => "Backup/Restore", + "factory_reset" => "Factory Reset", + "live_logs" => "Live Logs", + // Config page section headdings + "hardware_info" => "Gateway Hardware Information", + "control_software" => "Control Software", + "mmdvmhost_config" => "MMDVMHost Configuration", + "general_config" => "General Configuration", + "dmr_config" => "DMR Configuration", + "dstar_config" => "D-Star Configuration", + "ysf_config" => "Yaesu System Fusion Configuration", + "p25_config" => "P25 Configuration", + "nxdn_config" => "NXDN Configuration", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Configuration", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Wireless Configuration", + "fw_config" => "Firewall Configuration", + "remote_access_pw" => "Remote Access Password", + // Config Page - Section General + "setting" => "Setting", + "value" => "Value", + "apply" => "Apply Changes", + // Config Page - Gateway Hardware Information + "hostname" => "Hostname", + "kernel" => "Kernel", + "platform" => "Platform", + "cpu_load" => "CPU Load", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Controller Software", + "controller_mode" => "Controller Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Mode", + "d-star_mode" => "D-Star Mode", + "ysf_mode" => "YSF Mode", + "p25_mode" => "P25 Mode", + "nxdn_mode" => "NXDN Mode", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Display Type", + "mode_hangtime" => "Mode Hangtime", + // Config Page - General Configuration + "node_call" => "Node Callsign", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radio Frequency", + "lattitude" => "Latitude", + "longitude" => "Longitude", + "town" => "Town", + "country" => "Country", + "url" => "URL", + "radio_type" => "Radio/Modem Type", + "node_type" => "Node Type", + "timezone" => "System Time Zone", + "dash_lang" => "Dashboard Language", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Network", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Network", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "DMR Color Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Callsign", + "dstar_rpt2" => "RPT2 Callsign", + "dstar_irc_password" => "Remote Password", + "dstar_default_ref" => "Default Reflector", + "aprs_host" => "APRS Host", + "dstar_irc_lang" => "ircDDBGateway Language", + "dstar_irc_time" => "Time Announcements", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Startup Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Startup Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Startup Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Dashboard Access", + "fw_irc" => "ircDDBGateway Remote", + "fw_ssh" => "SSH Access", + // Config Page - Password + "user" => "User Name", + "password" => "Password", + "set_password" => "Set Password", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modes Enabled", + "net_status" => "Network Status", + "internet" => "Internet", + "radio_info" => "Radio Info", + "dstar_repeater" => "D-Star Repeater", + "dstar_net" => "D-Star Network", + "dmr_repeater" => "DMR Repeater", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF Network", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 Network", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN Network", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Time", + "mode" => "Mode", + "callsign" => "Callsign", + "target" => "Target", + "src" => "Src", // Short version of "Source" + "dur" => "Dur", // Short version of "Duration" + "loss" => "Loss", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Group", + "logoff" => "LogOff", + "info" => "Information", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Gateway Activity", + "local_tx_list" => "Local RF Activity", + "active_starnet_groups" => "Active Starnet Groups", + "active_starnet_members" => "Active Starnet Group Members", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Information", + "service_status" => "Service Status" +); diff --git a/lang/french_fr.php b/lang/french_fr.php new file mode 100644 index 0000000..bdb534a --- /dev/null +++ b/lang/french_fr.php @@ -0,0 +1,156 @@ + "Relais numérique", + "configuration" => "Configuration", + "dashboard_for" => "Console pour", + // Banner links + "dashboard" => "Console", + "admin" => "Administration", + "power" => "Arrêt/Redémarrage", + "update" => "Mise à jour", + "backup_restore" => "Sauvegarde/Restauration", + "factory_reset" => "Réinitialisation Usine", + "live_logs" => "Surveillance des Logs", + // Config page section headdings + "hardware_info" => "Informations matérielles de la passerelle", + "control_software" => "Contrôle logiciel", + "mmdvmhost_config" => "Configuration de MMDVMHost", + "general_config" => "Configuration générale", + "dmr_config" => "Configuration DMR", + "dstar_config" => "Configuration D-STAR", + "ysf_config" => "Configuration Yaesu System Fusion", + "p25_config" => "Configuration P25", + "nxdn_config" => "Configuration NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Configuration POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Configuration Réseau WIFI", + "fw_config" => "Configuration du Firewall", + "remote_access_pw" => "Mot de passe accès distant", + // Config Page - Section General + "setting" => "Paramètres", + "value" => "Valeur", + "apply" => "Appliquer les modifications", + // Config Page - Gateway Hardware Information + "hostname" => "Nom d'hôte", + "kernel" => "Kernel", + "platform" => "Plateforme", + "cpu_load" => "Charge CPU", + "cpu_temp" => "Température CPU", + // Config Page - Control Software + "controller_software" => "Logiciel controleur", + "controller_mode" => "Mode controleur", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Mode DMR", + "d-star_mode" => "Mode D-Star", + "ysf_mode" => "Mode YSF", + "p25_mode" => "Mode P25", + "nxdn_mode" => "Mode NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "Afficheur MMDVM", + "mode_hangtime" => "Mode Hangtime", + // Config Page - General Configuration + "node_call" => "Indicatif du Node", + "dmr_id" => "Id CCS7/DMR", + "radio_freq" => "Fréquence radio", + "lattitude" => "Latitude", + "longitude" => "Longitude", + "town" => "Ville", + "country" => "Pays", + "url" => "URL", + "radio_type" => "Modèle Radio/Modem", + "node_type" => "Type de Node", + "timezone" => "Fuseau horaire", + "dash_lang" => "Langage de la console", + // Config Page - DMR Configuration + "dmr_master" => "Master DMR(MMDVMHost)", + "bm_master" => "Master BrandMeister", + "bm_network" => "Réseau BrandMeister", + "dmr_plus_master" => "Master DMR+", + "dmr_plus_network" => "Réseau DMR+", + "xlx_master" => "Master XLX", + "xlx_enable" => "Master XLX actif", + "dmr_cc" => "Code Couleur DMR", + "dmr_embeddedlconly" => "DMR LC intégré uniquement", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "Indicatif RPT1", + "dstar_rpt2" => "Indicatif RPT2", + "dstar_irc_password" => "Mot de passe ircDDBGateway", + "dstar_default_ref" => "Reflecteur par défaut", + "aprs_host" => "Hôte APRS", + "dstar_irc_lang" => "Langage ircDDBGateway", + "dstar_irc_time" => "Annonces horaires", + // Config Page - YSF Configuration + "ysf_startup_host" => "Room YSF au démarrage", + // Config Page - P25 Configuration + "p25_startup_host" => "Hôte de démarrage P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Hôte de démarrage NXDN", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Accès Console", + "fw_irc" => "Commande à distance ircDDBGateway", + "fw_ssh" => "Accès SSH", + // Config Page - Password + "user" => "Nom d'utilisateur", + "password" => "Mot de passe", + "set_password" => "Définir le mot de passe", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modes actifs", + "net_status" => "État du réseau", + "internet" => "Internet", + "radio_info" => "Info Radio", + "dstar_repeater" => "Relais D-Star", + "dstar_net" => "Réseau D-Star", + "dmr_repeater" => "Relais DMR", + "dmr_master" => "Master DMR", + "ysf_net" => "Réseau YSF", + "p25_radio" => "Radio P25", + "p25_net" => "Réseau P25", + "nxdn_radio" => "Radio NXDN", + "nxdn_net" => "Réseau NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Heure", + "mode" => "Mode", + "callsign" => "Indicatif ", + "target" => "Cible", + "src" => "Source ", // Short version of "Source" + "dur" => "Durée ", // Short version of "Duration" + "loss" => "Pertes", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Groupe", + "logoff" => "Déconnexion", + "info" => "Information", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Activité de la passerelle", + "local_tx_list" => "Activité locale de la voie radio", + "active_starnet_groups" => "Active Starnet Groups", + "active_starnet_members" => "Active Starnet Group Members", + "d-star_link_manager" => "Administrateur du lien D-Star", + "d-star_link_status" => "Informations du lien D-Star", + "service_status" => "État du service" +); diff --git a/lang/german_de.php b/lang/german_de.php new file mode 100644 index 0000000..1d1142f --- /dev/null +++ b/lang/german_de.php @@ -0,0 +1,156 @@ + "Digital Voice", + "configuration" => "Konfiguration", + "dashboard_for" => "Tableau für", + // Banner links + "dashboard" => "Tableau", + "admin" => "Admin", + "power" => "Strom", + "update" => "Aktualisieren", + "backup_restore" => "Datensicherung/Wiederherstellung", + "factory_reset" => "Werkseinstellung", + "live_logs" => "Protokoll", + // Config page section headdings + "hardware_info" => "Gateway Hardware Information", + "control_software" => "Kontrollsoftware", + "mmdvmhost_config" => "MMDVMHost Konfiguration", + "general_config" => "Basis Konfiguration", + "dmr_config" => "DMR Konfiguration", + "dstar_config" => "D-Star Konfiguration", + "ysf_config" => "Yaesu System Fusion Konfiguration", + "p25_config" => "P25 Konfiguration", + "nxdn_config" => "NXDN Konfiguration", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Konfiguration", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Wlan Konfiguration", + "fw_config" => "Firewall Konfiguration", + "remote_access_pw" => "Fernzugriff", + // Config Page - Section General + "setting" => "Einstellung", + "value" => "Wert", + "apply" => "Speichern", + // Config Page - Gateway Hardware Information + "hostname" => "Rechnername", + "kernel" => "Kernel", + "platform" => "Plattform", + "cpu_load" => "CPU Last", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Kontroller Software", + "controller_mode" => "Kontroller Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Modus", + "d-star_mode" => "D-Star Modus", + "ysf_mode" => "YSF Modus", + "p25_mode" => "P25 Modus", + "nxdn_mode" => "NXDN Modus", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Display Typ", + "mode_hangtime" => "Mode Nachlaufzeit", + // Config Page - General Configuration + "node_call" => "Node Rufzeichen", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radio Frequenz", + "lattitude" => "Breitengrad", + "longitude" => "Längengrad", + "town" => "Stadt", + "country" => "Land", + "url" => "URL", + "radio_type" => "Radio/Modem Typ", + "node_type" => "Node Typ", + "timezone" => "Systemzeit Zone", + "dash_lang" => "Tableau Sprache", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Netzwerk", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Netzwerk", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Aktiv", + "dmr_cc" => "DMR Color Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Rufzeichen", + "dstar_rpt2" => "RPT2 Rufzeichen", + "dstar_irc_password" => "ircDDBGateway Passwort", + "dstar_default_ref" => "Standard Reflektor", + "aprs_host" => "APRS Host", + "dstar_irc_lang" => "ircDDBGateway Sprache", + "dstar_irc_time" => "Zeit Ansagen", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Startup Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Startup Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Startup Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Tableau Zugriff", + "fw_irc" => "ircDDBGateway Remote", + "fw_ssh" => "SSH Zugriff", + // Config Page - Password + "user" => "Benutzername", + "password" => "Passwort", + "set_password" => "Passwort setzen", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Aktive Modi", + "net_status" => "Netzwerk Status", + "internet" => "Internet", + "radio_info" => "Radio Info", + "dstar_repeater" => "D-Star Relais", + "dstar_net" => "D-Star Netzwerk", + "dmr_repeater" => "DMR Relais", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF Netzwerk", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 Netzwerk", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN Netzwerk", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Zeit", + "mode" => "Mode", + "callsign" => "Rufzeichen", + "target" => "Ziel", + "src" => "Quelle", // Short version of "Source" + "dur" => "Dauer", // Short version of "Duration" + "loss" => "Verlust", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Gruppe", + "logoff" => "LogOff", + "info" => "Information", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Letzten 20 Rufzeichen, die gehört wurden", + "local_tx_list" => "Letzten 20 Rufzeichen, die dieses Gateway nutzten", + "active_starnet_groups" => "Aktive Starnet Gruppen", + "active_starnet_members" => "Aktive Starnet Gruppen Mitglieder", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Information", + "service_status" => "Service Status" + ); diff --git a/lang/greek_gr.php b/lang/greek_gr.php new file mode 100644 index 0000000..c5051a1 --- /dev/null +++ b/lang/greek_gr.php @@ -0,0 +1,156 @@ + "Ψηφιακή Τηλεφωνία", + "configuration" => "Ρυθμίσεις", + "dashboard_for" => "Πίνακας Ελέγχου για τον", + // Banner links + "dashboard" => "Πίνακας Ελέγχου", + "admin" => "Διαχείριση", + "power" => "Απενεργοποίηση", + "update" => "Ενημέρωση", + "backup_restore" => "Εφεδρικό Αντίγραφο/Επαναφορά", + "factory_reset" => "Εργοστασιακές Ρυθμίσεις", + "live_logs" => "Αρχείο Καταγραφής", + // Config page section headdings + "hardware_info" => "Πληροφορίες Υλικού Πύλης", + "control_software" => "Πρόγραμμα Ελέγχου", + "mmdvmhost_config" => "Ρυθμίσεις του MMDVMHost", + "general_config" => "Γενικές Ρυθμίσεις", + "dmr_config" => "Ρυθμίσεις DMR", + "dstar_config" => "Ρυθμίσεις D-Star", + "ysf_config" => "Ρυθμίσεις Fusion", + "p25_config" => "Ρυθμίσεις P25", + "nxdn_config" => "Ρυθμίσεις NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Ρυθμίσεις POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Ρυθμίσεις WiFi", + "fw_config" => "Ρυθμίσεις Τείχους Προστασίας", + "remote_access_pw" => "Κωδικός Πρόσβασης", + // Config Page - Section General + "setting" => "Ρυθμίσεις", + "value" => "Τιμή", + "apply" => "Εφαρμογή", + // Config Page - Gateway Hardware Information + "hostname" => "Ονομα Συσκευής", + "kernel" => "Πυρήνας", + "platform" => "Πλατφόρμα", + "cpu_load" => "Φόρτος CPU", + "cpu_temp" => "Θερμοκρασία CPU", + // Config Page - Control Software + "controller_software" => "Πρόγραμμα Ελεγκτή", + "controller_mode" => "Κατάσταση Ελεγκτή", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Λειτουργία DMR", + "d-star_mode" => "Λειτουργία D-Star", + "ysf_mode" => "Λειτουργία YSF", + "p25_mode" => "Λειτουργία P25", + "nxdn_mode" => "Λειτουργία NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "Τύπος Οθόνης MMDVM", + "mode_hangtime" => "Χρόνος Επαναφοράς Σάρωσης", + // Config Page - General Configuration + "node_call" => "Διακριτικό Κόμβου", + "dmr_id" => "Ταυτότητα CCS7/DMR", + "radio_freq" => "Συχνότητα", + "lattitude" => "Γεωγραφικό Πλάτος", + "longitude" => "Γεωγραφικό Μήκος", + "town" => "Πόλη", + "country" => "Χώρα", + "url" => "URL", + "radio_type" => "Τύπος Modem", + "node_type" => "Τύπος Κόμβου", + "timezone" => "Ζώνη Ωρας", + "dash_lang" => "Γλώσσα Πίνακα Ελέγχου", + // Config Page - DMR Configuration + "dmr_master" => "Εξυπηρετητής DMR (MMDVMHost)", + "bm_master" => "Εξυπηρετητής BrandMeister", + "bm_network" => "Δίκτυο BrandMeister", + "dmr_plus_master" => "Εξυπηρετητής DMR+", + "dmr_plus_network" => "Δίκτυο DMR+", + "xlx_master" => "Εξυπηρετητής XLX", + "xlx_enable" => "Ενεργοποίηση Εξυπηρετητή XLX", + "dmr_cc" => "Κωδικός Χρώματος DMR", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "Διακριτικό RPT1", + "dstar_rpt2" => "Διακριτικό RPT2", + "dstar_irc_password" => "Κωδικός ircDDBGateway", + "dstar_default_ref" => "Reflector Προεπιλογής", + "aprs_host" => "Κεντρικός Υπολ. APRS", + "dstar_irc_lang" => "Γλώσσα ircDDBGateway", + "dstar_irc_time" => "Αναγγελίες Ωρας", + // Config Page - YSF Configuration + "ysf_startup_host" => "Εξυπηρετητής Εκκίνησης YSF", + // Config Page - P25 Configuration + "p25_startup_host" => "Εξυπηρετητής Εκκίνησης P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Εξυπηρετητής Εκκίνησης NXDN", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Πρόσβαση στον Πίνακα Ελέγχου", + "fw_irc" => "Απομακρυσμένος ircDDBGateway", + "fw_ssh" => "Πρόσβαση SSH", + // Config Page - Password + "user" => "Ονομα Χρήστη", + "password" => "Κωδικός Πρόσβασης", + "set_password" => "Αλλαγή Κωδικού Πρόσβασης", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Ενεργές Λειτουργίες", + "net_status" => "Κατάσταση Δικτύου", + "internet" => "Διαδίκτυο", + "radio_info" => "Πληροφ. Ασυρμάτου", + "dstar_repeater" => "Αναμεταδότης D-Star", + "dstar_net" => "Δίκτυο D-Star", + "dmr_repeater" => "Αναμεταδότης DMR", + "dmr_master" => "Εξυπηρετητής DMR", + "ysf_net" => "Δίκτυο YSF", + "p25_radio" => "Ασύρματος P25", + "p25_net" => "Δίκτυο P25", + "nxdn_radio" => "Ασύρματος NXDN", + "nxdn_net" => "Δίκτυο NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Ωρα", + "mode" => "Λειτουργία", + "callsign" => "Διακριτικό", + "target" => "Προορισμός", + "src" => "Πηγή", // Short version of "Source" + "dur" => "Διάρκεια", // Short version of "Duration" + "loss" => "Απώλεια", + "ber" => "Σφάλματα", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Ομάδα", + "logoff" => "Αποσύνδεση", + "info" => "Πληροφορίες", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Τελευταία 20 διακριτικά που ακούστηκαν μέσω αυτής της Πύλης", + "local_tx_list" => "Τελευταία 20 διακριτικά που είχαν πρόσβαση σε αυτή την Πύλη", + "active_starnet_groups" => "Ενεργές Ομάδες Starnet", + "active_starnet_members" => "Ενεργά Μέλη Ομάδων Starnet", + "d-star_link_manager" => "Διαχειριστής Ζεύζης D-Star", + "d-star_link_status" => "Πληροφορίες Ζεύξης D-Star", + "service_status" => "Κατάσταση Υπηρεσίας" +); diff --git a/lang/hungarian_hu.php b/lang/hungarian_hu.php new file mode 100644 index 0000000..2f0ad9b --- /dev/null +++ b/lang/hungarian_hu.php @@ -0,0 +1,156 @@ + "Digitális", + "configuration" => "Konfiguráció", + "dashboard_for" => "Irányítópult", + // Banner links + "dashboard" => "Irányítópult", + "admin" => "Admin", + "power" => "Tápellátás", + "update" => "Frissítés", + "backup_restore" => "Mentés/Visszaállítás", + "factory_reset" => "Gyári beállítás", + "live_logs" => "Élő forgalom", + // Config page section headdings + "hardware_info" => "Hardver információ", + "control_software" => "Vezérlő szoftver", + "mmdvmhost_config" => "MMDVMHost konfiguráció", + "general_config" => "Általános konfiguráció", + "dmr_config" => "DMR konfiguráció", + "dstar_config" => "D-Star konfiguráció", + "ysf_config" => "Yaesu System Fusion konfiguráció", + "p25_config" => "P25 konfiguráció", + "nxdn_config" => "NXDN konfiguráció", + "m17_config" => "M17 konfiguráció", + "pocsag_config" => "POCSAG konfiguráció", + "mobilegps_config" => "Mobil GPS konfiguráció", + "wifi_config" => "Wi-fi konfiguráció", + "fw_config" => "Tűzfal konfiguráció", + "remote_access_pw" => "Távoli hozzáférés jelszó", + // Config Page - Section General + "setting" => "Beállítás", + "value" => "Érték", + "apply" => "Alkalmaz", + // Config Page - Gateway Hardware Information + "hostname" => "Kiszolgáló neve", + "kernel" => "Kernel", + "platform" => "Rendszer", + "cpu_load" => "CPU Terhelés", + "cpu_temp" => "CPU Hőmérséklet", + // Config Page - Control Software + "controller_software" => "Vezérlő Szoftver", + "controller_mode" => "Mód", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Mód", + "d-star_mode" => "D-Star Mód", + "ysf_mode" => "YSF Mód", + "p25_mode" => "P25 Mód", + "nxdn_mode" => "NXDN Mód", + "m17_mode" => "M17 Mód", + "mmdvm_display" => "MMDVM kijelző típusa", + "mode_hangtime" => "Mód tartási ideje", + // Config Page - General Configuration + "node_call" => "Hívójel", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Frekvencia", + "lattitude" => "Szélességi kör", + "longitude" => "Hosszúsági kör", + "town" => "Város", + "country" => "Ország", + "url" => "URL", + "radio_type" => "Rádió/Modem típusa", + "node_type" => "Hozzáférési pont típusa", + "timezone" => "Időzóna", + "dash_lang" => "Irányítópult nyelve", + // Config Page - DMR Configuration + "dmr_master" => "DMR Szerver (MMDVMHost)", + "bm_master" => "BrandMeister Szerver", + "bm_network" => "BrandMeister Hálózat", + "dmr_plus_master" => "DMR+ Szerver", + "dmr_plus_network" => "DMR+ Hálózat", + "xlx_master" => "XLX Szerver", + "xlx_enable" => "XLX Szerver engedélyezése", + "dmr_cc" => "DMR Color kód", + "dmr_embeddedlconly" => "DMR GPS/Talker Alias kikapcsolása", + "dmr_dumptadata" => "DMR GPS/Talker Alias mentése logba", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Hívójel", + "dstar_rpt2" => "RPT2 Hívójel", + "dstar_irc_password" => "Jelszó", + "dstar_default_ref" => "Alapértelmezett reflektor", + "aprs_host" => "APRS", + "dstar_irc_lang" => "ircDDBGateway Nyelv", + "dstar_irc_time" => "Idő hirdetmények", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF kiszolgáló", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 kiszolgáló", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN kiszolgáló", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 kiszolgáló", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "Mobil GPS", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Sebessége", + // Config Page - Firewall Configuration + "fw_dash" => "Irányítópult hozzáférés", + "fw_irc" => "ircDDBGateway távoli hozzáférés", + "fw_ssh" => "SSH hozzáférés", + // Config Page - Password + "user" => "Felhasználónév", + "password" => "Jelszó", + "set_password" => "Beállítás", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Bekapcsolt módok", + "net_status" => "Hálózat státusza", + "internet" => "Internet", + "radio_info" => "Rádió infó", + "dstar_repeater" => "D-Star átjátszó", + "dstar_net" => "D-Star hálózat", + "dmr_repeater" => "DMR átjátszó", + "dmr_master" => "DMR hálózat", + "ysf_net" => "YSF hálózat", + "p25_radio" => "P25 rádió", + "p25_net" => "P25 hálózat", + "nxdn_radio" => "NXDN rádió", + "nxdn_net" => "NXDN hálózat", + "m17_radio" => "M17 rádió", + "m17_net" => "M17 hálózat", + // Dashboard Front Page - Calls + "time" => "Idő", + "mode" => "Mód", + "callsign" => "Hívójel", + "target" => "Cél", + "src" => "Forrás", // Short version of "Source" + "dur" => "Hossz", // Short version of "Duration" + "loss" => "Veszteség", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET aktivitás", + "pocsag_timeslot" => "Időrés", + "pocsag_msg" => "Üzenet", + // Dashboard - Extra Info + "group" => "Csoport", + "logoff" => "LogOff", + "info" => "Információ", + "utot" => "FIT", // Short for User Timeout + "gtot" => "CSIT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Átjáró aktivitás", + "local_tx_list" => "Helyi RF aktivitás", + "active_starnet_groups" => "Aktív Starnet csoport", + "active_starnet_members" => "Aktív Starnet csoport résztvevők", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Információ", + "service_status" => "Szolgáltatás állapota" +); diff --git a/lang/index.php b/lang/index.php new file mode 100644 index 0000000..702d3d1 --- /dev/null +++ b/lang/index.php @@ -0,0 +1,3 @@ + "Modi Digitali", + "configuration" => "Configurazioni", + "dashboard_for" => "Cruscotto", + // Banner links + "dashboard" => "Cruscotto", + "admin" => "Admin", + "power" => "Power", + "update" => "Aggiornamento", + "backup_restore" => "Backup/Restore", + "factory_reset" => "Ripristino", + "live_logs" => "Live Logs", + // Config page section headdings + "hardware_info" => "Informazioni Hardware del Gateway", + "control_software" => "Software di controllo", + "mmdvmhost_config" => "MMDVMHost Config", + "general_config" => "Configurazione Generale", + "dmr_config" => "DMR Config", + "dstar_config" => "D-Star Configuration", + "ysf_config" => "Yaesu System Fusion Config", + "p25_config" => "P25 Config", + "nxdn_config" => "NXDN Config", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Config", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Wireless Config", + "fw_config" => "Firewall Config", + "remote_access_pw" => "Password Accesso Remoto", + // Config Page - Section General + "setting" => "Settaggi", + "value" => "Valori", + "apply" => "Applicare le modifiche", + // Config Page - Gateway Hardware Information + "hostname" => "Hostname", + "kernel" => "Kernel", + "platform" => "Platform", + "cpu_load" => "CPU Load", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Controller Software", + "controller_mode" => "Controller Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Mode", + "d-star_mode" => "D-Star Mode", + "ysf_mode" => "YSF Mode", + "p25_mode" => "P25 Mode", + "nxdn_mode" => "NXDN Mode", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Display Type", + "mode_hangtime" => "Tempo Ritardo", + // Config Page - General Configuration + "node_call" => "Nominativo", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Frequenza Radio", + "lattitude" => "Latitudine", + "longitude" => "Longitudine", + "town" => "Città", + "country" => "Stato", + "url" => "URL", + "radio_type" => "Radio/Modem Modello", + "node_type" => "Tipo di Nodo", + "timezone" => "Fuso Orario", + "dash_lang" => "Linguaggio Cruscotto", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Network", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Network", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Attivo", + "dmr_cc" => "DMR Colour Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "Nominativo RPT1", + "dstar_rpt2" => "Nominativo RPT2", + "dstar_irc_password" => "ircDDBGateway Password", + "dstar_default_ref" => "Default Reflector", + "aprs_host" => "APRS Host", + "dstar_irc_lang" => "ircDDBGateway Linguaggio", + "dstar_irc_time" => "Annuncio (Vocale) Orario", + // Config Page - YSF Configuration + "ysf_startup_host" => "Room YSF Di Partenza", + // Config Page - P25 Configuration + "p25_startup_host" => "Room P25 Di Partenza", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Room NXDN Di Partenza", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Accesso Cruscotto", + "fw_irc" => "ircDDBGateway Remote", + "fw_ssh" => "Accesso SSH", + // Config Page - Password + "user" => "Nome Utente", + "password" => "Password", + "set_password" => "Imposta Password", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modi Attivi", + "net_status" => "Stato Rete", + "internet" => "Internet", + "radio_info" => "Info Radio", + "dstar_repeater" => "D-Star Repeater", + "dstar_net" => "D-Star Network", + "dmr_repeater" => "DMR Repeater", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF Network", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 Network", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN Network", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Ora", + "mode" => "Modo", + "callsign" => "Nominativo", + "target" => "Target", + "src" => "Src", // Short version of "Source" + "dur" => "Dur", // Short version of "Duration" + "loss" => "Loss", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Gruppo", + "logoff" => "LogOff", + "info" => "Informazioni", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Le Ultime 20 Trasmissioni Di Questo Nodo", + "local_tx_list" => "Gli Ultimi 20 Accessi RF Su Questo Nodo", + "active_starnet_groups" => "Gruppi Attivi Starnet", + "active_starnet_members" => "Menbri Attivi Nel Gruppo Starnet", + "d-star_link_manager" => "Gestione D-Star Link", + "d-star_link_status" => "Info D-Star Link", + "service_status" => "Stato Servizi" +); diff --git a/lang/japanese_jp.php b/lang/japanese_jp.php new file mode 100644 index 0000000..0cd2ae1 --- /dev/null +++ b/lang/japanese_jp.php @@ -0,0 +1,158 @@ + "デジタルボイス", + "configuration" => "設定", + "dashboard_for" => "ダッシュボード for", + // Banner links + "dashboard" => "ダッシュボード", + "admin" => "制御", + "power" => "電源", + "update" => "更新", + "backup_restore" => "バックアップ/復旧", + "factory_reset" => "工場出荷に戻る", + "live_logs" => "ログ", + // Config page section headdings + "hardware_info" => "ゲートウェイハード情報", + "control_software" => "制御ソフトウェア", + "mmdvmhost_config" => "MMDVMHost設定", + "general_config" => "全般設定", + "dmr_config" => "DMR設定", + "dstar_config" => "D-Star設定", + "ysf_config" => "YSF設定", + "p25_config" => "P25設定", + "nxdn_config" => "NXDN設定", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG設定", + "mobilegps_config" => "外部GPS設定", + "wifi_config" => "無線LAN設定", + "fw_config" => "ファイアウォール設定", + "remote_access_pw" => "遠隔ログインパスワード", + // Config Page - Section General + "setting" => "設定項目", + "value" => "設定内容", + "apply" => "更新", + // Config Page - Gateway Hardware Information + "hostname" => "ホスト名", + "kernel" => "カーネル", + "platform" => "マイコン", + "cpu_load" => "CPU負荷", + "cpu_temp" => "CPU温度", + // Config Page - Control Software + "controller_software" => "制御ソフト", + "controller_mode" => "制御モード", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMRモード", + "d-star_mode" => "D-Starモード", + "ysf_mode" => "YSFモード", + "p25_mode" => "P25モード", + "nxdn_mode" => "NXDNモード", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVMディスプレイタイプ", + "mode_hangtime" => "モード保持時間", + // Config Page - General Configuration + "node_call" => "ノード コールサイン", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "周波数", + "lattitude" => "緯度", + "longitude" => "経度", + "town" => "市町村", + "country" => "国", + "url" => "URL", + "radio_type" => "Radio/Modem タイプ", + "node_type" => "ノードタイプ", + "timezone" => "タイムゾーン", + "dash_lang" => "言語", + // Config Page - DMR Configuration + "dmr_master" => "DMRマスター(MMDVMHost)", + "bm_master" => "BrandMeisterマスター", + "bm_network" => "BrandMeisterネットワーク", + "dmr_plus_master" => "DMR+マスター", + "dmr_plus_network" => "DMR+ネットワーク", + "xlx_master" => "XLXマスター", + "xlx_enable" => "XLXマスター有効", + "dmr_cc" => "DMRカラーコード", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1コールサイン", + "dstar_rpt2" => "RPT2コールサイン", + "dstar_irc_password" => "ircDDBGatewayパスワード", + "dstar_default_ref" => "初期接続リフレクター", + "aprs_host" => "APRSホスト", + "dstar_irc_lang" => "ircDDBGateway言語", + "dstar_irc_time" => "時刻アナウンス", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF初期接続ホスト", + // Config Page - P25 Configuration + "p25_startup_host" => "P25初期接続ホスト", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN初期接続ホスト", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "外部GPS有効", + "mobilegps_port" => "GPSポート", + "mobilegps_speed" => "GPSポート速度", + // Config Page - Firewall Configuration + "fw_dash" => "ダッシュボードアクセス", + "fw_irc" => "ircDDBGatewayリモート", + "fw_ssh" => "SSHアクセス", + // Config Page - Password + "user" => "ユーザー名", + "password" => "パスワード", + "set_password" => "パスワード保存", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "使用可能モード", + "net_status" => "ネットワーク状況", + "internet" => "インターネット", + "radio_info" => "無線モデム情報", + "dstar_repeater" => "D-Starレピーター", + "dstar_net" => "D-Starネットワーク", + "dmr_repeater" => "DMRレピーター", + "dmr_master" => "DMRマスター", + "ysf_net" => "YSFネットワーク", + "p25_radio" => "P25 Radio", + "p25_net" => "P25ネットワーク", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDNネットワーク", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "時刻", + "mode" => "モード", + "callsign" => "コールサイン", + "target" => "ターゲット", + "src" => "ソース", // Short version of "Source" + "dur" => "時間", // Short version of "Duration" + "loss" => "ロス", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNETゲートウェイ状態", + "pocsag_timeslot" => "タイムスロット", + "pocsag_msg" => "メッセージ", + // Dashboard - Extra Info + "group" => "グループ", + "logoff" => "ログオフ", + "info" => "情報", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "受信リスト(最新20局)", + "local_tx_list" => "送信リスト(最新20局)", + "active_starnet_groups" => "Active Starnet Groups", + "active_starnet_members" => "Active Starnet Group Members", + "d-star_link_manager" => "D-Star接続マネージャー", + "d-star_link_status" => "D-Star接続情報", + "service_status" => "サービス状態" +); diff --git a/lang/norwegian_no.php b/lang/norwegian_no.php new file mode 100644 index 0000000..1b52c72 --- /dev/null +++ b/lang/norwegian_no.php @@ -0,0 +1,156 @@ + "Digital Voice", + "configuration" => "Konfigurasjon", + "dashboard_for" => "Skrivebord for", + // Banner links + "dashboard" => "Skrivebord", + "admin" => "Admin", + "power" => "Strøm", + "update" => "Oppdater", + "backup_restore" => "Backup/Restore", + "factory_reset" => "Tilbakestill", + "live_logs" => "Live Logg", + // Config page section headdings + "hardware_info" => "Gateway Hardware Informasjon", + "control_software" => "Kontroller Programvare", + "mmdvmhost_config" => "MMDVMHost Konfigurasjon", + "general_config" => "Generel Konfigurasjon", + "dmr_config" => "DMR Konfigurasjon", + "dstar_config" => "D-Star Konfigurasjon", + "ysf_config" => "Yaesu System Fusion Konfigurasjon", + "p25_config" => "P25 Konfigurasjon", + "nxdn_config" => "NXDN Konfigurasjon", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Konfigurasjon", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Trådløs Konfigurasjon", + "fw_config" => "Firewall Konfigurasjon", + "remote_access_pw" => "Fjerntilgangs passord", + // Config Page - Section General + "setting" => "Innstillinger", + "value" => "Verdi", + "apply" => "Bruk endringer", + // Config Page - Gateway Hardware Information + "hostname" => "Vertsnavn", + "kernel" => "Kernel", + "platform" => "Platform", + "cpu_load" => "CPU-belastning", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Kontroller Programvare", + "controller_mode" => "Kontroller Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Mode", + "d-star_mode" => "D-Star Mode", + "ysf_mode" => "YSF Mode", + "p25_mode" => "P25 Mode", + "nxdn_mode" => "NXDN Mode", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Skjerm Type", + "mode_hangtime" => "Mode Hangtime", + // Config Page - General Configuration + "node_call" => "Node Kallesignal", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radio Frekvens", + "lattitude" => "Breddegrad", + "longitude" => "lengdegrad", + "town" => "By", + "country" => "Land", + "url" => "URL", + "radio_type" => "Radio/Modem Type", + "node_type" => "Node Type", + "timezone" => "Tidssone", + "dash_lang" => "Skrivebord Språk", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Nettverk", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Nettverk", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Aktivert", + "dmr_cc" => "DMR Farge Kode", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Kallesignal", + "dstar_rpt2" => "RPT2 Kallesignal", + "dstar_irc_password" => "ircDDBGateway Passord", + "dstar_default_ref" => "Standard Reflektor", + "aprs_host" => "APRS Vert", + "dstar_irc_lang" => "ircDDBGateway Språk", + "dstar_irc_time" => "Tids annonsering", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Oppstarts Vert", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Oppstarts Vert", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Oppstarts Vert", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Skrivebord Adgang", + "fw_irc" => "ircDDBGateway Remote", + "fw_ssh" => "SSH Adgang", + // Config Page - Password + "user" => "Bruker navn", + "password" => "Passord", + "set_password" => "Set Passord", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modes Aktivert", + "net_status" => "Nettverk Status", + "internet" => "Internett", + "radio_info" => "Radio Info", + "dstar_repeater" => "D-Star Repeater", + "dstar_net" => "D-Star Nettverk", + "dmr_repeater" => "DMR Repeater", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF Nettverk", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 Nettverk", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN Nettverk", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Tid", + "mode" => "Mode", + "callsign" => "Kallesignal", + "target" => "Destinasjon", + "src" => "Src", // Short version of "Source" + "dur" => "Var", // Short version of "Duration" + "loss" => "Loss", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Gruppe", + "logoff" => "Logg Av", + "info" => "Informasjon", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "De siste 20 anropene hørt via denne Gatewayen", + "local_tx_list" => "De siste 20 anropene som åpnet denne Gatewayen", + "active_starnet_groups" => "Aktive Starnet Grupper", + "active_starnet_members" => "Aktive Starnet Gruppe medlemer", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Informasjon", + "service_status" => "Service Status" +); diff --git a/lang/polish_pl.php b/lang/polish_pl.php new file mode 100644 index 0000000..204853b --- /dev/null +++ b/lang/polish_pl.php @@ -0,0 +1,156 @@ + "Tryb cyfrowy", + "configuration" => "Konfiguracja", + "dashboard_for" => "Panel użytkownika", + // Banner links + "dashboard" => "Panel", + "admin" => "Zarządzanie", + "power" => "Zasilanie", + "update" => "Aktualizacja", + "backup_restore" => "Kopia/Przywracanie", + "factory_reset" => "Resetowanie", + "live_logs" => "Logi na żywo", + // Config page section headdings + "hardware_info" => "Informacje sprzętowe bramki", + "control_software" => "Oprogramowanie sterujące", + "mmdvmhost_config" => "Konfiguracja MMDVMHost", + "general_config" => "Konfiguracja główna", + "dmr_config" => "Konfiguracja DMR", + "dstar_config" => "Konfiguracja D-Star", + "ysf_config" => "Konfiguracja Yaesu System Fusion", + "p25_config" => "Konfiguracja P25", + "nxdn_config" => "Konfiguracja NXDN", + "m17_config" => "Konfiguracja M17", + "pocsag_config" => "Konfiguracja POCSAG", + "mobilegps_config" => "Konfiguracja GPS", + "wifi_config" => "Konfiguracja WiFi", + "fw_config" => "Konfiguracja zapory sieciowej", + "remote_access_pw" => "Hasło dostępu zdalnego", + // Config Page - Section General + "setting" => "Ustawienie", + "value" => "Wartość", + "apply" => "Zatwierdź zmiany", + // Config Page - Gateway Hardware Information + "hostname" => "Nazwa hosta", + "kernel" => "Kernel", + "platform" => "Platforma", + "cpu_load" => "Obciążenie CPU", + "cpu_temp" => "Temp. CPU", + // Config Page - Control Software + "controller_software" => "Oprogramowanie kontrolera", + "controller_mode" => "Tryb kontrolera", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Tryb DMR", + "d-star_mode" => "Tryb D-Star", + "ysf_mode" => "Tryb YSF", + "p25_mode" => "Tryb P25", + "nxdn_mode" => "Tryb NXDN", + "m17_mode" => "Tryb M17", + "mmdvm_display" => "Ekran MMDVM", + "mode_hangtime" => "Tryb czasu oczekiwania", + // Config Page - General Configuration + "node_call" => "Znak krótkofalarski bramki", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Częstotliwość radia", + "lattitude" => "Szerokość geograficzna", + "longitude" => "Długość geograficzna", + "town" => "Miejscowość", + "country" => "Państwo", + "url" => "URL", + "radio_type" => "Typ radia/modemu", + "node_type" => "Rodzaj bramki", + "timezone" => "Strefa czasowa", + "dash_lang" => "Język panelu", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "Sieć BrandMeister", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "Sieć DMR+", + "xlx_master" => "XLX Master", + "xlx_enable" => "Włączony XLX Master", + "dmr_cc" => "DMR Colour Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "Znak RPT1", + "dstar_rpt2" => "Znak RPT2", + "dstar_irc_password" => "Hasło dostępu zdalnego", + "dstar_default_ref" => "Domyślny reflektor", + "aprs_host" => "Host APRS", + "dstar_irc_lang" => "Język ircDDBGateway", + "dstar_irc_time" => "Czas zapowiedzi", + // Config Page - YSF Configuration + "ysf_startup_host" => "Startowy host YSF", + // Config Page - P25 Configuration + "p25_startup_host" => "Startowy host P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Startowy host NXDN", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "Startowy host M17", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "Włączanie GPS", + "mobilegps_port" => "Port GPS", + "mobilegps_speed" => "Prędkość portu GPS", + // Config Page - Firewall Configuration + "fw_dash" => "Dostęp do panelu", + "fw_irc" => "Zdalny ircDDBGateway", + "fw_ssh" => "Dostęp SSH", + // Config Page - Password + "user" => "Nazwa użytkownika", + "password" => "Hasło", + "set_password" => "Ustaw hasło", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Włączone tryby", + "net_status" => "Status sieci", + "internet" => "Internet", + "radio_info" => "Radio Info", + "dstar_repeater" => "Przemiennik D-Star", + "dstar_net" => "Sieć D-Star", + "dmr_repeater" => "Przemiennik DMR", + "dmr_master" => "DMR Master", + "ysf_net" => "Sieć YSF", + "p25_radio" => "Radio P25", + "p25_net" => "Sieć P25", + "nxdn_radio" => "Radio NXDN", + "nxdn_net" => "Sieć NXDN", + "m17_radio" => "Radio M17", + "m17_net" => "Sieć M17", + // Dashboard Front Page - Calls + "time" => "Czas", + "mode" => "Tryb", + "callsign" => "Znak", + "target" => "Cel", + "src" => "Źródło", + "dur" => "Okres", + "loss" => "Straty", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "Aktywność bramy DAPNET", + "pocsag_timeslot" => "Szczelina czasowa", + "pocsag_msg" => "Wiadomość", + // Dashboard - Extra Info + "group" => "Grupa", + "logoff" => "Wyloguj", + "info" => "Informacja", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Lista ostatnio słyszanych stacji", + "local_tx_list" => "Atywność lokalna", + "active_starnet_groups" => "Aktywne grupy Starnet", + "active_starnet_members" => "Aktywni członkowie grupy Starnet", + "d-star_link_manager" => "Zarządzanie linkami D-Star", + "d-star_link_status" => "Status linków D-Star", + "service_status" => "Status serwisu" +); diff --git a/lang/portuguese_br.php b/lang/portuguese_br.php new file mode 100644 index 0000000..917e50f --- /dev/null +++ b/lang/portuguese_br.php @@ -0,0 +1,156 @@ + "Digital Voz", + "configuration" => "Configuração", + "dashboard_for" => "Painel de Controle de", + // Banner links + "dashboard" => "Painel", + "admin" => "Admin", + "power" => "Ação", + "update" => "Atualizar", + "backup_restore" => "Backup/Restaurar", + "factory_reset" => "Reset", + "live_logs" => "Logs online", + // Config page section headdings + "hardware_info" => "Informações Hardware do Gateway", + "control_software" => "Controle Software", + "mmdvmhost_config" => "MMDVMHost Configuração", + "general_config" => "Configurações Gerais", + "dmr_config" => "Configuração do DMR", + "dstar_config" => "Configuração do D-Star", + "ysf_config" => "Configuração do Sistema Yaesu Fusion", + "p25_config" => "Configuração do P25", + "nxdn_config" => "Configuração do NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Configuração do POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Configuração do Wireless", + "fw_config" => "Configuração do Firewall", + "remote_access_pw" => "Senha do Acesso Remoto", + // Config Page - Section General + "setting" => "Configurações", + "value" => "Valores", + "apply" => "Aplicar", + // Config Page - Gateway Hardware Information + "hostname" => "Nome do Host", + "kernel" => "Kernel", + "platform" => "Plataforma", + "cpu_load" => "Leitura de CPU", + "cpu_temp" => "Temperatura de CPU", + // Config Page - Control Software + "controller_software" => "Tipo de Software", + "controller_mode" => "Modo de Controle", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Modo DMR", + "d-star_mode" => "Modo D-Star", + "ysf_mode" => "Modo YSF", + "p25_mode" => "Modo P25", + "nxdn_mode" => "Modo NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "Exibição MMDVM", + "mode_hangtime" => "Modo Tempo de Espera", + // Config Page - General Configuration + "node_call" => "Indicativo do Node", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Frequencia do Rádio", + "lattitude" => "Latitude", + "longitude" => "Longitude", + "town" => "Cidade", + "country" => "País", + "url" => "URL", + "radio_type" => "Tipo de Radio/Modem", + "node_type" => "Tipo de Node", + "timezone" => "Horário do Sistema", + "dash_lang" => "Idioma do Painel de Controle", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Rede", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Network", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "Código de Cores DMR", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Indicativo", + "dstar_rpt2" => "RPT2 Indicativo", + "dstar_irc_password" => "Senha ircDDBGateway", + "dstar_default_ref" => "Refletor Principal", + "aprs_host" => "Servidor APRS", + "dstar_irc_lang" => "Idioma ircDDBGateway", + "dstar_irc_time" => "Anúncio a cada Hora", + // Config Page - YSF Configuration + "ysf_startup_host" => "Servidor Inicial YSF", + // Config Page - P25 Configuration + "p25_startup_host" => "Servidor Inicial P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Servidor Inicial NXDN", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Acesso ao Painel", + "fw_irc" => "Remoto ircDDBGateway", + "fw_ssh" => "Acesso via SSH", + // Config Page - Password + "user" => "Nome do Usuário", + "password" => "Senha", + "set_password" => "Modificar Senha", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Habilitar Modos", + "net_status" => "Status de Rede", + "internet" => "Internet", + "radio_info" => "Info do Rádio", + "dstar_repeater" => "Repetidora D-Star", + "dstar_net" => "Rede D-Star", + "dmr_repeater" => "Repetidora DMR", + "dmr_master" => "DMR Master", + "ysf_net" => "Rede YSF", + "p25_radio" => "Rádio P25", + "p25_net" => "Rede P25", + "nxdn_radio" => "Rádio NDXN", + "nxdn_net" => "Rede NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Hora", + "mode" => "Modo", + "callsign" => "Indicativo", + "target" => "Alvo", + "src" => "Src", // Short version of "Source" + "dur" => "Dur", // Short version of "Duration" + "loss" => "Loss", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Grupo", + "logoff" => "Sair", + "info" => "Informação", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Últimas 20 chamadas via Gateway", + "local_tx_list" => "Últimas 20 chamadas por este Gateway", + "active_starnet_groups" => "Grupo Ativos na Starnet", + "active_starnet_members" => "Grupo de Membros Ativos na Starnet", + "d-star_link_manager" => "Gerenciar Conexão D-Star", + "d-star_link_status" => "Informações do Link D-Star", + "service_status" => "Status do Sistema" +); diff --git a/lang/portuguese_pt.php b/lang/portuguese_pt.php new file mode 100644 index 0000000..2bd52a9 --- /dev/null +++ b/lang/portuguese_pt.php @@ -0,0 +1,156 @@ + "Digital Voz", + "configuration" => "Configuração", + "dashboard_for" => "Painel de Controle de", + // Banner links + "dashboard" => "Painel", + "admin" => "Admin", + "power" => "Ação", + "update" => "Atualizar", + "backup_restore" => "Backup/Restauro", + "factory_reset" => "Reset", + "live_logs" => "Logs online", + // Config page section headdings + "hardware_info" => "Informações Hardware do Gateway", + "control_software" => "Controle Software", + "mmdvmhost_config" => "MMDVMHost Configuração", + "general_config" => "Configurações Gerais", + "dmr_config" => "Configuração do DMR", + "dstar_config" => "Configuração do DSTAR", + "ysf_config" => "Configuração do Yaesu System Fusion", + "p25_config" => "Configuração do P25", + "nxdn_config" => "Configuração do NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Configuração do POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Configuração do Wireless", + "fw_config" => "Configuração do Firewall", + "remote_access_pw" => "Senha do Acesso Remoto", + // Config Page - Section General + "setting" => "Configurações", + "value" => "Valores", + "apply" => "Aplicar", + // Config Page - Gateway Hardware Information + "hostname" => "Nome do Host", + "kernel" => "Kernel", + "platform" => "Plataforma", + "cpu_load" => "Carga do CPU", + "cpu_temp" => "Temperatura do CPU", + // Config Page - Control Software + "controller_software" => "Tipo de Software", + "controller_mode" => "Modo de Controle", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Modo DMR", + "d-star_mode" => "Modo DSTAR", + "ysf_mode" => "Modo YSF", + "p25_mode" => "Modo P25", + "nxdn_mode" => "Modo NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "Exibição MMDVM", + "mode_hangtime" => "Espera tempo do Modo", + // Config Page - General Configuration + "node_call" => "Indicativo do Nó", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Frequência do Rádio", + "lattitude" => "Latitude", + "longitude" => "Longitude", + "town" => "Cidade", + "country" => "País", + "url" => "URL", + "radio_type" => "Tipo de Radio/Modem", + "node_type" => "Tipo de Nó", + "timezone" => "Horário do Sistema", + "dash_lang" => "Idioma do Painel de Controle", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "Rede BrandMeister", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "Rede DMR+", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "Código de Cores DMR", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "R1 Indicativo", + "dstar_rpt2" => "R2 Indicativo", + "dstar_irc_password" => "Senha ircDDBGateway", + "dstar_default_ref" => "Refletor Principal", + "aprs_host" => "Servidor APRS", + "dstar_irc_lang" => "Idioma ircDDBGateway", + "dstar_irc_time" => "Anúncio a cada hora", + // Config Page - YSF Configuration + "ysf_startup_host" => "Servidor Inicial YSF", + // Config Page - P25 Configuration + "p25_startup_host" => "Servidor Inicial P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Servidor Inicial NXDN", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Acesso ao Painel", + "fw_irc" => "Remoto ircDDBGateway", + "fw_ssh" => "Acesso via SSH", + // Config Page - Password + "user" => "Nome utilizador", + "password" => "Senha", + "set_password" => "Modificar Senha", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Habilitar Modos", + "net_status" => "Estado da Rede", + "internet" => "Internet", + "radio_info" => "Info do Rádio", + "dstar_repeater" => "Repetidor DSTAR", + "dstar_net" => "Rede DSTAR", + "dmr_repeater" => "Repetidor DMR", + "dmr_master" => "DMR Master", + "ysf_net" => "Rede YSF", + "p25_radio" => "Rádio P25", + "p25_net" => "Rede P25", + "nxdn_radio" => "Rádio NXDN", + "nxdn_net" => "Rede NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Hora", + "mode" => "Modo", + "callsign" => "Indicativo", // versão traduzida de "callsign" + "target" => "Alvo", // versão traduzida de "Target" + "src" => "Origem", // versão traduzida de "Source" + "dur" => "Dur.", // versão abreviada de "Duration" + "loss" => "Perda", + "ber" => "BER", // versão abreviada de "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Grupo", + "logoff" => "Sair", + "info" => "Informação", + "utot" => "UTOT", // abreviatura para Timeout do utilizador + "gtot" => "GTOT", // abreviatura para Timeout do grupo + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Últimas 20 chamadas através deste Gateway", + "local_tx_list" => "Últimas 20 chamadas deste Gateway", + "active_starnet_groups" => "Grupos ativos na Starnet", + "active_starnet_members" => "Grupo de membros ativos na Starnet", + "d-star_link_manager" => "Gerir a ligação DSTAR", + "d-star_link_status" => "Informações do Link DSTAR", + "service_status" => "Estado do Sistema" +); diff --git a/lang/romanian_ro.php b/lang/romanian_ro.php new file mode 100644 index 0000000..0d80b45 --- /dev/null +++ b/lang/romanian_ro.php @@ -0,0 +1,156 @@ + "Voce Digitala", + "configuration" => "Configuratii", + "dashboard_for" => "Panou Control pentru", + // Banner links + "dashboard" => "Panou Control", + "admin" => "Administrator", + "power" => "Alimentare", + "update" => "Actualizare", + "backup_restore" => "Copie Rezerva/Restabilire", + "factory_reset" => "Resetare Stare Initiala", + "live_logs" => "Jurnal Live", + // Config page section headdings + "hardware_info" => "Informatii Hardware Gateway", + "control_software" => "Control Software", + "mmdvmhost_config" => "Configurare MMDVMHost", + "general_config" => "Configurari Generale", + "dmr_config" => "Configurari DMR", + "dstar_config" => "Configurari D-Star", + "ysf_config" => "Configurari Yaesu System Fusion", + "p25_config" => "Configurari P25", + "nxdn_config" => "Configurari NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Configurari POCSAG", + "mobilegps_config" => "Configurari Mobile GPS", + "wifi_config" => "Configurari Retea Wireless", + "fw_config" => "Configurari Firewall", + "remote_access_pw" => "Parola Acces Exterior", + // Config Page - Section General + "setting" => "Setari", + "value" => "Valoare", + "apply" => "Salvare Setari", + // Config Page - Gateway Hardware Information + "hostname" => "Denumire Detinator", + "kernel" => "Nucleu", + "platform" => "Platforma", + "cpu_load" => "Incarcare CPU", + "cpu_temp" => "Temeratura CPU", + // Config Page - Control Software + "controller_software" => "Program de Control", + "controller_mode" => "Mod Control", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Mod DMR", + "d-star_mode" => "Mod D-Star", + "ysf_mode" => "Mod YSF", + "p25_mode" => "Mod P25", + "nxdn_mode" => "Mod NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "Tip Display MMDVM", + "mode_hangtime" => "Control Hangtime", + // Config Page - General Configuration + "node_call" => "Indicativ Node", + "dmr_id" => "CCS7/ID DMR", + "radio_freq" => "Frecventa Radio", + "lattitude" => "Latitudine", + "longitude" => "Longitudine", + "town" => "Oras", + "country" => "Tara", + "url" => "URL", + "radio_type" => "Model Radio/Modem", + "node_type" => "Model Node", + "timezone" => "Time Zone Sistem", + "dash_lang" => "Limba Panou Control", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "Reteaua BrandMeister", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "Retea DMR+", + "xlx_master" => "XLX Master", + "xlx_enable" => "Activare XLX Master", + "dmr_cc" => "Cod Culoare DMR", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "Indicativ RPT1", + "dstar_rpt2" => "Indicativ RPT2", + "dstar_irc_password" => "parola ircDDBGateway", + "dstar_default_ref" => "Reflector Implicit ", + "aprs_host" => "Detinator APRS", + "dstar_irc_lang" => "Limba ircDDBGateway", + "dstar_irc_time" => "Timp instiintare", + // Config Page - YSF Configuration + "ysf_startup_host" => "lansare Detinator YSF", + // Config Page - P25 Configuration + "p25_startup_host" => "lansare Detinator P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "lansare Detinator NXDN", + "nxdn_ran" => "Lansare NXDN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "Activare Mobile GPS", + "mobilegps_port" => "Port GPS", + "mobilegps_speed" => "Viteza Port GPS", + // Config Page - Firewall Configuration + "fw_dash" => "Acces Panou Control", + "fw_irc" => "Control Exterior ircDDBGateway", + "fw_ssh" => "Acces SSH", + // Config Page - Password + "user" => "Nume Utilizator", + "password" => "Parola", + "set_password" => "Setare Parola", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Moduri Activate ", + "net_status" => "Stare Retea", + "internet" => "Internet", + "radio_info" => "Info Radio", + "dstar_repeater" => "Repetor D-Star", + "dstar_net" => "Reteaua D-Star", + "dmr_repeater" => "Repetor DMR", + "dmr_master" => "DMR Master", + "ysf_net" => "Reteaua YSF", + "p25_radio" => "P25 Radio", + "p25_net" => "Reteaua P25", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "Reteaua NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Ora", + "mode" => "Mod", + "callsign" => "Indicativ", + "target" => "Apel Catre", + "src" => "Src", // Prescurtare "Sursa" + "dur" => "Dur", // prescurtare "Durata" + "loss" => "Pierderi", + "ber" => "BER", // Prescurtare "Rata Eroare Biti" + // POCSAG Specific + "pocsag_list" => "Activitate Gateway DAPNET", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Mesaj", + // Dashboard - Extra Info + "group" => "Grup", + "logoff" => "Deconectare", + "info" => "Informatii", + "utot" => "UTOT", // Prescurtare User Timeout + "gtot" => "GTOT", // Prescurtare Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Ultimele 20 Apeluri Receptionate prin acest Gateway", + "local_tx_list" => "Ultimele 20 Apeluri care au accesat acest Gateway", + "active_starnet_groups" => "Grupuri Active Starnet", + "active_starnet_members" => "Membrii activi in Grupuri Starnet ", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "Informatii Legatura D-Star", + "service_status" => "Stare Serviciu" +); diff --git a/lang/slovenian_sl.php b/lang/slovenian_sl.php new file mode 100644 index 0000000..72f76a3 --- /dev/null +++ b/lang/slovenian_sl.php @@ -0,0 +1,156 @@ + "DV", + "configuration" => "Nastavitve", + "dashboard_for" => "Nadzorna plošča", + // Banner links + "dashboard" => "Nadzorna plošča", + "admin" => "Skrbniški način", + "power" => "Vklop/Izklop", + "update" => "Posodobitev", + "backup_restore" => "Varnostno kopiranje", + "factory_reset" => "Tovarniške nastavitve", + "live_logs" => "Dnevnik", + // Config page section headdings + "hardware_info" => "Informacije o strojni opremi prehoda", + "control_software" => "Nadzorna programska oprema", + "mmdvmhost_config" => "Nastavitve MMDVMHost", + "general_config" => "Splošne nastavitve", + "dmr_config" => "Nastavitve DMR", + "dstar_config" => "Nastavitve D-Star", + "ysf_config" => "Nastavitve Yaesu System Fusion", + "p25_config" => "Nastavitve P25", + "nxdn_config" => "Nastavitve NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Nastavitve POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Nastavitve brezžičnega omrežja", + "fw_config" => "Nastavitve požarnega zidu", + "remote_access_pw" => "Geslo za oddaljeni dostop", + // Config Page - Section General + "setting" => "Nastavitev", + "value" => "Vrednost", + "apply" => "Potrdi spremembe", + // Config Page - Gateway Hardware Information + "hostname" => "Ime gostitelja", + "kernel" => "Jedro", + "platform" => "Platforma", + "cpu_load" => "Obremenitev proc.", + "cpu_temp" => "Temperatura proc.", + // Config Page - Control Software + "controller_software" => "Programska oprema krmilnika", + "controller_mode" => "Način krmilnika", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Način DMR", + "d-star_mode" => "Način D-Star", + "ysf_mode" => "Način YSF", + "p25_mode" => "Način P25", + "nxdn_mode" => "Način NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "Tip MMDVM zaslona", + "mode_hangtime" => "Obstanek načina", + // Config Page - General Configuration + "node_call" => "Klicni znak vozlišča", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Frekvenca", + "lattitude" => "Zemljepisna širina", + "longitude" => "Zemljepisna dolžina", + "town" => "Kraj", + "country" => "Država", + "url" => "URL", + "radio_type" => "Vrsta sprejemnika/modema", + "node_type" => "Vrsta vozlišča", + "timezone" => "Časovni pas", + "dash_lang" => "Jezik nadzorne plošče", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister omrežje", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ omrežje", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "DMR Barvna Koda (CC)", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "Klicni znak RPT1", + "dstar_rpt2" => "Klicni znak RPT2", + "dstar_irc_password" => "Geslo za oddaljeni dostop", + "dstar_default_ref" => "Privzeti reflektor", + "aprs_host" => "Gostitelj APRS", + "dstar_irc_lang" => "Jezik ircDDBGateway", + "dstar_irc_time" => "Napoved časa", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF zagonski gostitelj", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 zagonski gostitelj", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN zagonski gostitelj", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Dostop do nadzorne plošče", + "fw_irc" => "Dostop ircDDBGateway", + "fw_ssh" => "Dostop SSH", + // Config Page - Password + "user" => "Uporabniški ime", + "password" => "Geslo", + "set_password" => "Nastavi geslo", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Omogočeni načini", + "net_status" => "Stanje omrežja", + "internet" => "Internet", + "radio_info" => "Info o sprejemniku", + "dstar_repeater" => "Repetitor D-Star", + "dstar_net" => "D-Star Omrežje", + "dmr_repeater" => "Repetitor DMR", + "dmr_master" => "Master DMR", + "ysf_net" => "Omrežje YSF", + "p25_radio" => "Radio P25", + "p25_net" => "Omrežje P25", + "nxdn_radio" => "Radio NXDN", + "nxdn_net" => "Omrežje NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Čas", + "mode" => "Način", + "callsign" => "Klicni znak", + "target" => "Cilj", + "src" => "Vir", // Short version of "Source" + "dur" => "Dol", // Short version of "Duration" + "loss" => "Izguba", + "ber" => "DNB", // Short version of "Bit Error Rate" - delež napačnih bitov + // POCSAG Specific + "pocsag_list" => "Aktivnost DAPNET prehoda", + "pocsag_timeslot" => "Časovno okno (TS)", + "pocsag_msg" => "Sporočilo", + // Dashboard - Extra Info + "group" => "Skupina", + "logoff" => "Odjava", + "info" => "Informacija", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Aktivnost prehoda", + "local_tx_list" => "Lokalna RF aktivnost", + "active_starnet_groups" => "Aktivne skupine Starnet", + "active_starnet_members" => "Aktivni uporabniki Starnet", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Informacije", + "service_status" => "Stanje storitev" +); diff --git a/lang/spanish_es.php b/lang/spanish_es.php new file mode 100644 index 0000000..4b4833c --- /dev/null +++ b/lang/spanish_es.php @@ -0,0 +1,156 @@ + "Voz Digital", + "configuration" => "Configuracion", + "dashboard_for" => "Panel de control de", + // Banner links + "dashboard" => "Panel de Control", + "admin" => "Administrar", + "power" => "Reiniciar/Apagar", + "update" => "Actualizar", + "backup_restore" => "Backup/Restaurar copia de seguridad", + "factory_reset" => "Restaurar datos de fabrica", + "live_logs" => "Informes-Logs", + // Config page section headdings + "hardware_info" => "Informacion de hardware", + "control_software" => "Control de software", + "mmdvmhost_config" => "Configuracion de MMDVMHost", + "general_config" => "Configuracion General", + "dmr_config" => "Configuracion de DMR", + "dstar_config" => "Configuracion de DSTAR", + "ysf_config" => "Configuracion de Yaesu C4FM fusion", + "p25_config" => "Configuracion de P25", + "nxdn_config" => "Configuracion de NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "Configuracion de POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Configuracion WIFI", + "fw_config" => "Configuracion del cortafuegos", + "remote_access_pw" => "Contraseña accceso Remoto", + // Config Page - Section General + "setting" => "Ajustes", + "value" => "Valor", + "apply" => "Aplicar", + // Config Page - Gateway Hardware Information + "hostname" => "Hostname", + "kernel" => "Kernel", + "platform" => "Plataforma", + "cpu_load" => "Carga de la CPU", + "cpu_temp" => "Temperatura CPU", + // Config Page - Control Software + "controller_software" => "Controlador Software", + "controller_mode" => "Controlador de Modo", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "Modo DMR", + "d-star_mode" => "Modo D-Star", + "ysf_mode" => "Modo YSF", + "p25_mode" => "Modo P25", + "nxdn_mode" => "Modo NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Tipo Display", + "mode_hangtime" => "Modo tiempo de suspension", + // Config Page - General Configuration + "node_call" => "Nodo indicativo de llamada", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Frecuencia RX/TX", + "lattitude" => "Latitud", + "longitude" => "Longitud", + "town" => "Ciudad", + "country" => "Pais", + "url" => "URL", + "radio_type" => "Radio/Tipo modem", + "node_type" => "Nodo Tipo", + "timezone" => "Zona horaria", + "dash_lang" => "Idioma del Panel de Control", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "Master de BrandMeister", + "bm_network" => "Red de BrandMeister", + "dmr_plus_master" => "Master de DMR+", + "dmr_plus_network" => "Red de DMR+", + "xlx_master" => "Master de XLX", + "xlx_enable" => "Habilitar Master XLX", + "dmr_cc" => "Codigo de color de DMR", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 indicativo de llamada", + "dstar_rpt2" => "RPT2 indicativo de llamada", + "dstar_irc_password" => "contrasena de ircDDBGateway", + "dstar_default_ref" => "Reflector predeterminado", + "aprs_host" => "Servidor de APRS", + "dstar_irc_lang" => "Idioma de ircDDBGateway", + "dstar_irc_time" => "Intervalo de Balizas", + // Config Page - YSF Configuration + "ysf_startup_host" => "Servidor de Inicio YSF", + // Config Page - P25 Configuration + "p25_startup_host" => "Servidor de Inicio P25", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "Servidor de Inicio NXDN", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Tablero de acceso", + "fw_irc" => "ircDDBGateway Remoto", + "fw_ssh" => "Acceso por SSH", + // Config Page - Password + "user" => "Nombre de usuario", + "password" => "Contrasena", + "set_password" => "Establecer contrasena", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modos habilitados", + "net_status" => "Estado de la red", + "internet" => "Internet", + "radio_info" => "Informacion de Radio", + "dstar_repeater" => "Repetidor de D-Star", + "dstar_net" => "Red de D-Star", + "dmr_repeater" => "Repetidor de DMR", + "dmr_master" => "Master de DMR", + "ysf_net" => "Red de YSF", + "p25_radio" => "Radio de P25", + "p25_net" => "Red de P25", + "nxdn_radio" => "Radio de NXDN", + "nxdn_net" => "Red de NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Hora", + "mode" => "Modo", + "callsign" => "Indicativo de llamada", + "target" => "Destino", + "src" => "SRC", //version corta de "fuente" + "dur" => "DUR", //version corta de "duracion" + "loss" => "Perdida", + "ber" => "BER", //version corta de "Error de bit" + // POCSAG Specific + "pocsag_list" => "Actividad de DAPNET Gateway", + "pocsag_timeslot" => "Slot de Tiempo", + "pocsag_msg" => "Mensaje", + // Dashboard - Extra Info + "group" => "Grupos", + "logoff" => "Finalizar", + "info" => "Informacion", + "utot" => "UTOT", //tiempo agotado para usuario + "gtot" => "GTOT", // tiempo agotado para grupo + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Ultimas 20 llamadas que accedieron al sistema", + "local_tx_list" => "Ultimas 20 llamadas que accedieron a ese puerto", + "active_starnet_groups" => "Grupos activos Starnet", + "active_starnet_members" => "Miembros activos del grupo Starnet", + "d-star_link_manager" => "Gestor de enlaces D-Star", + "d-star_link_status" => "Informacion de enlaces D-Star", + "service_status" => "Estado del servicio" +); diff --git a/lang/spanish_mx.php b/lang/spanish_mx.php new file mode 100644 index 0000000..411d914 --- /dev/null +++ b/lang/spanish_mx.php @@ -0,0 +1,156 @@ + "Voz Digital", + "configuration" => "Configuracion", + "dashboard_for" => "Tablero para", + // Banner links + "dashboard" => "Tablero", + "admin" => "Admin", + "power" => "Poder", + "update" => "Actualizar", + "backup_restore" => "Copia deseguridad_restaurar", + "factory_reset" => "Restablecimiento de fabrica", + "live_logs" => "Vivo Logs", + // Config page section headdings + "hardware_info" => "Puerta hardware Informacion", + "control_software" => "Control software", + "mmdvmhost_config" => "MMDVMHost Configuracion", + "general_config" => "General Configuracion", + "dmr_config" => "DMR Configuracion", + "dstar_config" => "D-Star Configuracion", + "ysf_config" => "Yaesu Configuracion de sistema fusion", + "p25_config" => "P25 Configuracion", + "nxdn_config" => "NXDN Configuracion", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Configuracion", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Configuracion Inalambrico", + "fw_config" => "Configuracion firewall", + "remote_access_pw" => "Remoto Accesso contrasena", + // Config Page - Section General + "setting" => "Ajustes", + "value" => "Valor", + "apply" => "Aplicar", + // Config Page - Gateway Hardware Information + "hostname" => "Hostname", + "kernel" => "Kernel", + "platform" => "Plataforma", + "cpu_load" => "CPU Load", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Controlador Software", + "controller_mode" => "Controlador Modo", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Modo", + "d-star_mode" => "D-Star Modo", + "ysf_mode" => "YSF Modo", + "p25_mode" => "P25 Modo", + "nxdn_mode" => "NXDN Modo", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Tipo de muestra", + "mode_hangtime" => "Modo tiempo de suspension", + // Config Page - General Configuration + "node_call" => "Nodo signo de llamada", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radio Frecuencia", + "lattitude" => "Latitud", + "longitude" => "Longitud", + "town" => "Ciudad", + "country" => "Pais", + "url" => "URL", + "radio_type" => "Radio/Tipo modem", + "node_type" => "Nodo Tipo", + "timezone" => "Sistema zona horaria", + "dash_lang" => "Tablero de ldiomas", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister red", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ red", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master habilitar", + "dmr_cc" => "DMR Codigo de color", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 signo de llamada", + "dstar_rpt2" => "RPT2 signo de llamada", + "dstar_irc_password" => "ircDDBGateway contrasena", + "dstar_default_ref" => "Reflector predeterminado", + "aprs_host" => "APRS Host", + "dstar_irc_lang" => "ircDDBGateway Language", + "dstar_irc_time" => "Tiempo Anuncios", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Lanzamiento Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Lanzamiento Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Lanzamiento Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Tablero de acceso", + "fw_irc" => "ircDDBGateway Remoto", + "fw_ssh" => "SSH Acceso", + // Config Page - Password + "user" => "Usuario Nombre", + "password" => "Contrasena", + "set_password" => "Set contrasena", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Modos habilitado", + "net_status" => "Estatus de red", + "internet" => "Internet", + "radio_info" => "Radio Informacion", + "dstar_repeater" => "D-Star Repetidor", + "dstar_net" => "D-Star red", + "dmr_repeater" => "DMR Repetidor", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF red", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 red", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN red", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Time", + "mode" => "Modo", + "callsign" => "signo de llamada", + "target" => "Objetivo", + "src" => "Src", //version corta de "fuente" + "dur" => "Dur", //version corta de "duracion" + "loss" => "Perdida", + "ber" => "BER", //version corta de "Error de bit" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "Grupos", + "logoff" => "Finalizar", + "info" => "Informacion", + "utot" => "UTOT", //tiempo agotado para usuario + "gtot" => "GTOT", // tiempo agotado para grupo + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Ultimos 20 llamadas escuchadas atraves de este puerto", + "local_tx_list" => "Ultimo 20 llamadas que accesaron este puerto", + "active_starnet_groups" => "Activo grupo Starnet Groups", + "active_starnet_members" => "Activo miembros de grupo Starnet", + "d-star_link_manager" => "D-Star gestar de enlaces", + "d-star_link_status" => "D-Star Informacion de enlaces", + "service_status" => "Estado servicio" +); diff --git a/lang/swedish_se.php b/lang/swedish_se.php new file mode 100644 index 0000000..d8afba8 --- /dev/null +++ b/lang/swedish_se.php @@ -0,0 +1,157 @@ + "Digital Voice", + "configuration" => "Konfiguration", + "dashboard_for" => "Dashboard för", + // Banner links + "dashboard" => "Dashboard", + "admin" => "Admin", + "power" => "Power", + "update" => "Uppdatera", + "backup_restore" => "Backup/Återställ", + "factory_reset" => "Fabriksåterställning", + "live_logs" => "Liveloggar", + // Config page section headdings + "hardware_info" => "Gateway Hårdvaruinformation", + "control_software" => "Kontroll Mjukvara", + "mmdvmhost_config" => "MMDVMHost Konfiguration", + "general_config" => "Generell Konfiguration", + "dmr_config" => "DMR Konfiguration", + "dstar_config" => "D-Star Konfiguration", + "ysf_config" => "Yaesu System Fusion Konfiguration", + "p25_config" => "P25 Konfiguration", + "nxdn_config" => "NXDN Konfiguration", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Konfiguration", + "mobilegps_config" => "Mobil GPS Konfiguration", + "wifi_config" => "WiFi Konfiguration", + "fw_config" => "Brandvägg Konfiguration", + "remote_access_pw" => "Remote Access Password", + // Config Page - Section General + "setting" => "Inställning", + "value" => "Värde", + "apply" => "Spara", + // Config Page - Gateway Hardware Information + "hostname" => "Värdnamn", + "kernel" => "Kärna", + "platform" => "Plattform", + "cpu_load" => "CPU Last", + "cpu_temp" => "CPU Temp", + // Config Page - Control Software + "controller_software" => "Controller Software", + "controller_mode" => "Controller Mode", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Mode", + "d-star_mode" => "D-Star Mode", + "ysf_mode" => "YSF Mode", + "p25_mode" => "P25 Mode", + "nxdn_mode" => "NXDN Mode", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Displaytyp", + "mode_hangtime" => "Mode Hängtid", + // Config Page - General Configuration + "node_call" => "Node Signal", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "Radiofrekvens", + "lattitude" => "Latitud", + "longitude" => "Longitud", + "town" => "Stad", + "country" => "Land", + "url" => "URL", + "radio_type" => "Radio-/Modemtyp", + "node_type" => "Nodetyp", + "timezone" => "Systemets Tidzon", + "dash_lang" => "Dashboard Språk", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Network", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Network", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "DMR Colour Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Signal", + "dstar_rpt2" => "RPT2 Signal", + "dstar_irc_password" => "Lösen ircDDB", + "dstar_default_ref" => "Default Reflektor", + "aprs_host" => "APRS-värd", + "dstar_irc_lang" => "ircDDBGateway Språk", + "dstar_irc_time" => "Tidsannonseringar", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Startvärd", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Startvärd", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Startvärd", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobilGPS På", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Hastighet", + // Config Page - Firewall Configuration + "fw_dash" => "Dashboard Access", + "fw_irc" => "ircDDBGateway Remote", + "fw_ssh" => "SSH-access", + // Config Page - Password + "user" => "Användarnamn", + "password" => "Lösenord", + "set_password" => "Byt Lösenord", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Moder Aktiverade", + "net_status" => "Nätverkstatus", + "internet" => "Internet", + "radio_info" => "Radio Info", + "dstar_repeater" => "D-Star Repeater", + "dstar_net" => "D-Star Nätverk", + "dmr_repeater" => "DMR Repeater", + "dmr_master" => "DMR Master", + "ysf_net" => "YSF Nätverk", + "p25_radio" => "P25 Radio", + "p25_net" => "P25 Nätverk", + "nxdn_radio" => "NXDN Radio", + "nxdn_net" => "NXDN Nätverk", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Tid", + "mode" => "Mode", + "callsign" => "Signal", + "target" => "Mål", + "src" => "Src", // Short version of "Source" + "dur" => "Dur", // Short version of "Duration" + "loss" => "Loss", + "ber" => "BER", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gatewayaktivitet", + "pocsag_timeslot" => "Tidslucka", + "pocsag_msg" => "Meddelande", + // Dashboard - Extra Info + "group" => "Grupp", + "logoff" => "Logga Av", + "info" => "Information", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Gatewayaktivitet", + "local_tx_list" => "Lokal RF-aktivitet", + "active_starnet_groups" => "Aktiva Starnet Grupper", + "active_starnet_members" => "Aktiva Starnet Gruppmedlemmar", + "d-star_link_manager" => "D-Star Link Manager", + "d-star_link_status" => "D-Star Link Information", + "service_status" => "Service Status" +); diff --git a/lang/thai_th.php b/lang/thai_th.php new file mode 100644 index 0000000..a9b8291 --- /dev/null +++ b/lang/thai_th.php @@ -0,0 +1,156 @@ + "Digital Voice", + "configuration" => "ตั้งค่า", + "dashboard_for" => "แผงควบคุมสำหรับ", + // Banner links + "dashboard" => "แผงควบคุม", + "admin" => "ผู้ดูแล", + "power" => "เพาเวอร์", + "update" => "อัพเดท", + "backup_restore" => "สำรองข้อมูล", + "factory_reset" => "รีเซ็ต", + "live_logs" => "ประวัติการติดต่อ (สด)", + // Config page section headdings + "hardware_info" => "รายละเอียดของอุปกรณ์เกตเวย์", + "control_software" => "ซอฟต์แวร์ควบคุม", + "mmdvmhost_config" => "ตั้งค่า MMDVMHost", + "general_config" => "ตั้งค่าทั่วไป", + "dmr_config" => "ตั้งค่า DMR", + "dstar_config" => "ตั้งค่า D-Star", + "ysf_config" => "ตั้งค่า Yaesu System Fusion", + "p25_config" => "ตั้งค่า P25", + "nxdn_config" => "ตั้งค่า NXDN", + "m17_config" => "M17 Configuration", + "pocsag_config" => "ตั้งค่า POCSAG", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "ตั้งค่าวายฟาย", + "fw_config" => "ตั้งค่าระบบป้องกัน", + "remote_access_pw" => "ตั้งรหัสผ่าน", + // Config Page - Section General + "setting" => "ตั้งค่า", + "value" => "ค่า", + "apply" => "ยืนยัน", + // Config Page - Gateway Hardware Information + "hostname" => "ชื่ออุปกรณ์", + "kernel" => "เคอร์เนิล", + "platform" => "อุปกรณ์", + "cpu_load" => "โหลด CPU", + "cpu_temp" => "อุณหภูมิ CPU", + // Config Page - Control Software + "controller_software" => "ชนิดของอุปกรณ์", + "controller_mode" => "โหมดการทำงาน", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR", + "d-star_mode" => "D-Star", + "ysf_mode" => "YSF", + "p25_mode" => "P25", + "nxdn_mode" => "NXDN", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "หน้าจอ MMDVM", + "mode_hangtime" => "Hangtime", + // Config Page - General Configuration + "node_call" => "นามเรียกขานของสถานี", + "dmr_id" => "CCS7/DMR ID", + "radio_freq" => "ความถี่ที่ใช้งาน", + "lattitude" => "ละติจูด", + "longitude" => "ลองจิจูด", + "town" => "เมือง", + "country" => "ประเทศ", + "url" => "URL", + "radio_type" => "ชนิดของอุปกรณ์", + "node_type" => "รูปแบบของสถานี", + "timezone" => "เขตเวลาที่ใช้", + "dash_lang" => "ภาษาสำหรับแผงควบคุม", + // Config Page - DMR Configuration + "dmr_master" => "DMR Master (MMDVMHost)", + "bm_master" => "BrandMeister Master", + "bm_network" => "BrandMeister Network", + "dmr_plus_master" => "DMR+ Master", + "dmr_plus_network" => "DMR+ Network", + "xlx_master" => "XLX Master", + "xlx_enable" => "XLX Master Enable", + "dmr_cc" => "DMR Color Code", + "dmr_embeddedlconly" => "DMR EmbeddedLCOnly", + "dmr_dumptadata" => "DMR DumpTAData", + // Config Page - D-Star Configuration + "dstar_rpt1" => "นามเรียกขาน RPT1", + "dstar_rpt2" => "นามเรียกขาน RPT2", + "dstar_irc_password" => "รหัสผ่านเข้าถึงจากภายนอก", + "dstar_default_ref" => "ค่าเริ่มต้นของ Reflector", + "aprs_host" => "เซิร์ฟเวอร์ APRS", + "dstar_irc_lang" => "ภาษาสำหรับ ircDDBGateway", + "dstar_irc_time" => "ประกาศเวลาทุกชั่วโมง", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF Startup Host", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 Startup Host", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN Startup Host", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "การเข้าถึงแผงควบคุม", + "fw_irc" => "การเข้าถึง ircDDBGateway", + "fw_ssh" => "การเข้าถึง SSH", + // Config Page - Password + "user" => "ชื่อผู้ใช้", + "password" => "รหัสผ่าน", + "set_password" => "ยืนยัน", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "โหมดที่เปิดใช้งาน", + "net_status" => "สถานะเครือข่าย", + "internet" => "อินเตอร์เน็ต", + "radio_info" => "ข้อมูลวิทยุ", + "dstar_repeater" => "รีพีทเตอร์ D-Star", + "dstar_net" => "เครือข่าย D-Star", + "dmr_repeater" => "รีพีทเตอร์ DMR", + "dmr_master" => "DMR Master", + "ysf_net" => "เครือข่าย YSF", + "p25_radio" => "วิทยุ P25", + "p25_net" => "เครือข่าย P25", + "nxdn_radio" => "วิทยุ NXDN", + "nxdn_net" => "เครือข่าย NXDN", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "เวลา", + "mode" => "โหมด", + "callsign" => "นามเรียกขาน", + "target" => "เป้าหมาย", + "src" => "สัญญาณจาก", // Short version of "Source" + "dur" => "ระยะเวลา", // Short version of "Duration" + "loss" => "สัญญาณสูญเสีย", + "ber" => "สัญญาณผิดพลาด", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Gateway Activity", + "pocsag_timeslot" => "Time Slot", + "pocsag_msg" => "Message", + // Dashboard - Extra Info + "group" => "กลุ่ม", + "logoff" => "ออกจากระบบ", + "info" => "ข้อมูล", + "utot" => "ผู้ใช้", // Short for User Timeout + "gtot" => "กลุ่ม", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "ความเคลื่อนไหวของเกตเวย์", + "local_tx_list" => "ความเคลื่อนไหวของคลื่นที่รับได้", + "active_starnet_groups" => "Active Starnet Groups", + "active_starnet_members" => "Active Starnet Group Members", + "d-star_link_manager" => "จัดการการเชื่อมต่อของ D-Star", + "d-star_link_status" => "ข้อมูลการเชื่อมต่อของ D-Star", + "service_status" => "สถานะของระบบ" +); diff --git a/lang/turkish_tr.php b/lang/turkish_tr.php new file mode 100644 index 0000000..f85ea62 --- /dev/null +++ b/lang/turkish_tr.php @@ -0,0 +1,156 @@ + "Dijital Ses", + "configuration" => "Konfigürasyon", + "dashboard_for" => "Panosu", + // Banner links + "dashboard" => "Pano", + "admin" => "Yönetici", + "power" => "Güç", + "update" => "Güncelle", + "backup_restore" => "Yedekle/Geriyükle", + "factory_reset" => "Fabrika Ayarları", + "live_logs" => "Sistem Günlüğü", + // Config page section headdings + "hardware_info" => "Ağ Geçidi Donanım Bilgisi", + "control_software" => "Kontrolcü Yazılımı", + "mmdvmhost_config" => "MMDVMHost Konfigürasyonu", + "general_config" => "Genel Konfigürasyon", + "dmr_config" => "DMR Konfigürasyonu", + "dstar_config" => "D-Star Konfigürasyonu", + "ysf_config" => "Yaesu System Fusion Konfigürasyonu", + "p25_config" => "P25 Konfigürasyonu", + "nxdn_config" => "NXDN Konfigürasyonu", + "m17_config" => "M17 Configuration", + "pocsag_config" => "POCSAG Konfigürasyonu", + "mobilegps_config" => "Mobile GPS Configuration", + "wifi_config" => "Kablosuz Konfigürasyonu", + "fw_config" => "Güv.Duv. Konfigürasyonu", + "remote_access_pw" => "Uzak Erişim Parolası", + // Config Page - Section General + "setting" => "Ayar", + "value" => "Değer", + "apply" => "Değişiklikleri Uygula", + // Config Page - Gateway Hardware Information + "hostname" => "Sistem Adı", + "kernel" => "Çekirdek Sürümü", + "platform" => "Altyapı", + "cpu_load" => "CPU Kullanımı", + "cpu_temp" => "CPU Sıcaklığı", + // Config Page - Control Software + "controller_software" => "Kontrolcünün Yazılımı", + "controller_mode" => "Kontrolcünün Modu", + // Config Page - MMDVMHost Configuration + "dmr_mode" => "DMR Modu", + "d-star_mode" => "D-Star Modu", + "ysf_mode" => "YSF Modu", + "p25_mode" => "P25 Modu", + "nxdn_mode" => "NXDN Modu", + "m17_mode" => "M17 Mode", + "mmdvm_display" => "MMDVM Ekran Tipi", + "mode_hangtime" => "Mod Askı Süresi", + // Config Page - General Configuration + "node_call" => "Çağrı İşaretiniz", + "dmr_id" => "CCS7/DMR No", + "radio_freq" => "Radyo Frekansı", + "lattitude" => "Enlem", + "longitude" => "Boylam", + "town" => "Şehir", + "country" => "Ülke", + "url" => "URL", + "radio_type" => "Radyo/Modem Tipi", + "node_type" => "Yayın Tipi", + "timezone" => "Sistem Saati Dilimi", + "dash_lang" => "Pano Dili", + // Config Page - DMR Configuration + "dmr_master" => "DMR Sunucu (MMDVMHost)", + "bm_master" => "BrandMeister Sunucu", + "bm_network" => "BrandMeister Ağı", + "dmr_plus_master" => "DMR+ Sunucu", + "dmr_plus_network" => "DMR+ Ağı", + "xlx_master" => "XLX Sunucu", + "xlx_enable" => "XLX Sunucu Etkinleştirme", + "dmr_cc" => "DMR Renk Kodu", + "dmr_embeddedlconly" => "DMR GömülüLC", + "dmr_dumptadata" => "DMR TAVeriDökümü", + // Config Page - D-Star Configuration + "dstar_rpt1" => "RPT1 Çağrı İşareti", + "dstar_rpt2" => "RPT2 Çağrı İşareti", + "dstar_irc_password" => "Uzak Erişim Parolası", + "dstar_default_ref" => "Varsayılan Yansıtıcı", + "aprs_host" => "APRS Sunucusu", + "dstar_irc_lang" => "ircDDBAğGeçidi Dili", + "dstar_irc_time" => "Bildirimler", + // Config Page - YSF Configuration + "ysf_startup_host" => "YSF İşletme Ağı", + // Config Page - P25 Configuration + "p25_startup_host" => "P25 İşletme Ağı", + "p25_nac" => "P25 NAC", + // Config Page - NXDN Configuration + "nxdn_startup_host" => "NXDN İşletme Ağı", + "nxdn_ran" => "NXDN RAN", + // Config Page - M17 Configuration + "m17_startup_host" => "M17 Startup Host", + "m17_can" => "M17 CAN", + // Config Page - MobileGPS Configuration + "mobilegps_enable" => "MobileGPS Enable", + "mobilegps_port" => "GPS Port", + "mobilegps_speed" => "GPS Port Speed", + // Config Page - Firewall Configuration + "fw_dash" => "Pano Erişimi", + "fw_irc" => "ircDDBAğGeçidi Erişimi", + "fw_ssh" => "SSH Erişimi", + // Config Page - Password + "user" => "Kullanıcı Adı", + "password" => "Parola", + "set_password" => "Parolayı Uygula", + // Dashboard Front Page - Repeater Info Pannel + "modes_enabled" => "Etkin Modlar", + "net_status" => "Ağ Durumu", + "internet" => "İnternet", + "radio_info" => "Radyo Bilgisi", + "dstar_repeater" => "D-Star Röle", + "dstar_net" => "D-Star Ağı", + "dmr_repeater" => "DMR Röle", + "dmr_master" => "DMR Sunucu", + "ysf_net" => "YSF Ağı", + "p25_radio" => "P25 Radyo", + "p25_net" => "P25 Ağı", + "nxdn_radio" => "NXDN Radyo", + "nxdn_net" => "NXDN Ağı", + "m17_radio" => "M17 Radio", + "m17_net" => "M17 Network", + // Dashboard Front Page - Calls + "time" => "Zaman", + "mode" => "Mod", + "callsign" => "Çağrı İşareti", + "target" => "Hedef", + "src" => "Kynk", // Short version of "Source" + "dur" => "Süre", // Short version of "Duration" + "loss" => "Kayıp", + "ber" => "BHO", // Short version of "Bit Error Rate" + // POCSAG Specific + "pocsag_list" => "DAPNET Ağ Geçidi Etkinliği", + "pocsag_timeslot" => "Zaman Aralığı", + "pocsag_msg" => "Mesaj", + // Dashboard - Extra Info + "group" => "Grup", + "logoff" => "Oturum Kapat", + "info" => "Ayrıntılar", + "utot" => "UTOT", // Short for User Timeout + "gtot" => "GTOT", // Short for Group Timeout + // Dashboard Front Page / Admin - Section Headders + "last_heard_list" => "Ağ Geçidi Etkinliği", + "local_tx_list" => "Yerel RF Etkinliği", + "active_starnet_groups" => "Aktif Starnet Grupları", + "active_starnet_members" => "Aktif Starnet Grup Üyeleri", + "d-star_link_manager" => "D-Star Bağlantı Yöneticisi", + "d-star_link_status" => "D-Star Bağlantı Ayrıntıları", + "service_status" => "Servis Durumu" +); diff --git a/mmdvmhost/bm_links.php b/mmdvmhost/bm_links.php new file mode 100644 index 0000000..c4822b9 --- /dev/null +++ b/mmdvmhost/bm_links.php @@ -0,0 +1,156 @@ += 200 ) { $bmAPIkeyV2 = $bmAPIkey; unset($bmAPIkey); } + } + + //Load the dmrgateway config file + $dmrGatewayConfigFile = '/etc/dmrgateway'; + if (fopen($dmrGatewayConfigFile,'r')) { $configdmrgateway = parse_ini_file($dmrGatewayConfigFile, true); } + + // Get the current DMR Master from the config + $dmrMasterHost = getConfigItem("DMR Network", "Address", $mmdvmconfigs); + if ( $dmrMasterHost == '127.0.0.1' ) { + $dmrMasterHost = $configdmrgateway['DMR Network 1']['Address']; + if (isset($configdmrgateway['DMR Network 1']['Id'])) { $dmrID = $configdmrgateway['DMR Network 1']['Id']; } + } elseif (getConfigItem("DMR", "Id", $mmdvmconfigs)) { + $dmrID = getConfigItem("DMR", "Id", $mmdvmconfigs); + } else { + $dmrID = getConfigItem("General", "Id", $mmdvmconfigs); + } + + // Store the DMR Master IP, we will need this for the JSON lookup + $dmrMasterHostIP = $dmrMasterHost; + + // Make sure the master is a BrandMeister Master + $dmrMasterFile = fopen("/usr/local/etc/DMR_Hosts.txt", "r"); + while (!feof($dmrMasterFile)) { + $dmrMasterLine = fgets($dmrMasterFile); + $dmrMasterHostF = preg_split('/\s+/', $dmrMasterLine); + if ((strpos($dmrMasterHostF[0], '#') === FALSE) && ($dmrMasterHostF[0] != '')) { + if ($dmrMasterHost == $dmrMasterHostF[2]) { $dmrMasterHost = str_replace('_', ' ', $dmrMasterHostF[0]); } + } + } + + if (substr($dmrMasterHost, 0, 2) == "BM") { + + // Use BM API to get information about current TGs + $jsonContext = stream_context_create(array('http'=>array('timeout' => 2, 'header' => 'User-Agent: Pi-Star Dashboard for '.$dmrID) )); // Add Timout and User Agent to include DMRID + if (isset($bmAPIkeyV2)) { + $json = json_decode(@file_get_contents("https://api.brandmeister.network/v2/device/$dmrID/profile", true, $jsonContext)); + } else { + $json = json_decode(@file_get_contents("https://api.brandmeister.network/v1.0/repeater/?action=PROFILE&q=$dmrID", true, $jsonContext)); + } + + // Set some Variable + $bmStaticTGList = ""; + $bmDynamicTGList = ""; + + // Pull the information from JSON. talkgroup/slot are documented + // as integers in the BrandMeister API but PHP's json_decode + // doesn't enforce that — cast to (int) so a hostile / compromised + // upstream response can't smuggle HTML/JS into the rendered + // bytes below. (int) of a non-numeric string is 0, which renders + // as plain "0" — predictable, inert. + if (isset($json->staticSubscriptions)) { $bmStaticTGListJson = $json->staticSubscriptions; + foreach($bmStaticTGListJson as $staticTG) { + $tgNum = (int)$staticTG->talkgroup; + $tgSlot = (int)$staticTG->slot; + if (getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) && $tgSlot === 1) { + $bmStaticTGList .= "TG".$tgNum."(".$tgSlot.") "; + } + else if (getConfigItem("DMR Network", "Slot2", $mmdvmconfigs) && $tgSlot === 2) { + $bmStaticTGList .= "TG".$tgNum."(".$tgSlot.") "; + } + else if (getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) == "0" && getConfigItem("DMR Network", "Slot2", $mmdvmconfigs) && $tgSlot === 0) { + $bmStaticTGList .= "TG".$tgNum." "; + } + } + $bmStaticTGList = wordwrap($bmStaticTGList, 15, "
\n"); + if (preg_match('/TG/', $bmStaticTGList) == false) { $bmStaticTGList = "None"; } + } else { $bmStaticTGList = "None"; } + if (isset($json->dynamicSubscriptions)) { $bmDynamicTGListJson = $json->dynamicSubscriptions; + foreach($bmDynamicTGListJson as $dynamicTG) { + $tgNum = (int)$dynamicTG->talkgroup; + $tgSlot = (int)$dynamicTG->slot; + if (getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) && $tgSlot === 1) { + $bmDynamicTGList .= "TG".$tgNum."(".$tgSlot.") "; + } + else if (getConfigItem("DMR Network", "Slot2", $mmdvmconfigs) && $tgSlot === 2) { + $bmDynamicTGList .= "TG".$tgNum."(".$tgSlot.") "; + } + else if (getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) == "0" && getConfigItem("DMR Network", "Slot2", $mmdvmconfigs) && $tgSlot === 0) { + $bmDynamicTGList .= "TG".$tgNum." "; + } + } + $bmDynamicTGList = wordwrap($bmDynamicTGList, 15, "
\n"); + if (preg_match('/TG/', $bmDynamicTGList) == false) { $bmDynamicTGList = "None"; } + } else { $bmDynamicTGList = "None"; } + + echo 'Active BrandMeister Connections + + + + + + + '."\n"; + + echo ' '."\n"; + // $dmrMasterHost / $dmrID come from /etc/dmrgateway — operator + // edits via the expert editor. htmlspecialchars defence-in-depth. + // $bmStatic/DynamicTGList already contain wordwrap-injected `
` + // tags by design, so they intentionally aren't escaped here; the + // talkgroup/slot integers inside them were cast to (int) above. + echo ' '; + echo ''; + echo ''; + echo ''; + echo ''."\n"; + echo '
'.$lang['bm_master'].'Connected MasterRepeater IDThe ID for this Repeater/HotspotStatic TGsStatically linked talkgroupsDynamic TGsDynamically linked talkgroups
'.htmlspecialchars((string)$dmrMasterHost, ENT_QUOTES, 'UTF-8').''.htmlspecialchars((string)$dmrID, ENT_QUOTES, 'UTF-8').''.$bmStaticTGList.''.$bmDynamicTGList.'
'."\n"; + echo '
'."\n"; + } +} diff --git a/mmdvmhost/bm_manager.php b/mmdvmhost/bm_manager.php new file mode 100644 index 0000000..e036540 --- /dev/null +++ b/mmdvmhost/bm_manager.php @@ -0,0 +1,164 @@ += 200 ) { $bmAPIkeyV2 = $bmAPIkey; unset($bmAPIkey); } + } + + //Load the dmrgateway config file + $dmrGatewayConfigFile = '/etc/dmrgateway'; + if (fopen($dmrGatewayConfigFile,'r')) { $configdmrgateway = parse_ini_file($dmrGatewayConfigFile, true); } + + // Get the current DMR Master from the config + $dmrMasterHost = getConfigItem("DMR Network", "Address", $mmdvmconfigs); + if ( $dmrMasterHost == '127.0.0.1' ) { + $dmrMasterHost = $configdmrgateway['DMR Network 1']['Address']; + if (isset($configdmrgateway['DMR Network 1']['Id'])) { $dmrID = $configdmrgateway['DMR Network 1']['Id']; } + } elseif (getConfigItem("DMR", "Id", $mmdvmconfigs)) { + $dmrID = getConfigItem("DMR", "Id", $mmdvmconfigs); + } else { + $dmrID = getConfigItem("General", "Id", $mmdvmconfigs); + } + + // Store the DMR Master IP, we will need this for the JSON lookup + $dmrMasterHostIP = $dmrMasterHost; + + // Make sure the master is a BrandMeister Master + $dmrMasterFile = fopen("/usr/local/etc/DMR_Hosts.txt", "r"); + while (!feof($dmrMasterFile)) { + $dmrMasterLine = fgets($dmrMasterFile); + $dmrMasterHostF = preg_split('/\s+/', $dmrMasterLine); + if ((strpos($dmrMasterHostF[0], '#') === FALSE) && ($dmrMasterHostF[0] != '')) { + if ($dmrMasterHost == $dmrMasterHostF[2]) { $dmrMasterHost = str_replace('_', ' ', $dmrMasterHostF[0]); } + } + } + if (substr($dmrMasterHost, 0, 2) == "BM") { + if (isset($bmAPIkeyV2) && !empty($_POST) && (isset($_POST["dropDyn"]) || isset($_POST["dropQso"]) || isset($_POST["tgSubmit"]))) : // Data has been posted for this page + $bmAPIurl = 'https://api.brandmeister.network/v2/device/'; + // Are we a repeater + if ( getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) == "0" ) { + unset($_POST["TS"]); + $targetSlot = "0"; + } else { + $targetSlot = $_POST["TS"]; + } + // Set the API URLs + if (isset($_POST["dropDyn"])) { $bmAPIurl = $bmAPIurl.$dmrID."/action/dropDynamicGroups/".$targetSlot; $method = "GET"; } + if (isset($_POST["dropQso"])) { $bmAPIurl = $bmAPIurl.$dmrID."/action/dropCallRoute/".$targetSlot; $method = "GET"; } + if ( (isset($_POST["tgNr"])) && (isset($_POST["tgSubmit"])) ) { $targetTG = preg_replace("/[^0-9]/", "", $_POST["tgNr"]); } + if ( ($_POST["TGmgr"] == "ADD") && (isset($_POST["tgSubmit"])) ) { $bmAPIurl = $bmAPIurl.$dmrID."/talkgroup/"; $method = "POST"; } + if ( ($_POST["TGmgr"] == "DEL") && (isset($_POST["tgSubmit"])) ) { $bmAPIurl = $bmAPIurl.$dmrID."/talkgroup/".$targetSlot."/".$targetTG; $method = "DELETE"; } + + // Build the Data + $postHeaders = array( + 'Content-Type: application/json', + 'Accept: application/json', + 'Authorization: Bearer '.$bmAPIkeyV2, + 'User-Agent: Pi-Star Dashboard for '.$dmrID, + ); + $opts = array( + 'http' => array( + 'method' => $method, + //'header' => $postHeaders, + 'header' => implode("\r\n", $postHeaders), + 'password' => '', + 'success' => '', + 'timeout' => 2, + ), + ); + + // If we are adding a TG + if ( (!isset($_POST["dropDyn"])) && (!isset($_POST["dropQso"])) && isset($targetTG) && $_POST["TGmgr"] == "ADD" ) { + $postDataTG = array( + 'slot' => $targetSlot, + 'group' => $targetTG + ); + $postData = json_encode($postDataTG); + $postHeaders[] = 'Content-Length: '.strlen($postData); + $opts['http']['content'] = $postData; + } + + // Make the request + $context = stream_context_create($opts); + $result = @file_get_contents($bmAPIurl, false, $context); + $feeback=json_decode($result); + // Output to the browser + echo 'BrandMeister Manager'."\n"; + echo "\n\n\n
Command Output
"; + if (isset($feeback)) { print "BrandMeister APIv2: OK"; } else { print "BrandMeister APIv2: No Response"; } + echo "
\n"; + echo "
\n"; + // Clean up... + unset($_POST); + echo ''; + + else: // Do this when we are not handling post data + // If there is a BM API Key + if (isset($bmAPIkeyV2)) { + echo 'BrandMeister Manager'."\n"; + echo '
'."\n"; + echo csrf_field_html()."\n"; + echo ''."\n"; + echo ' + + + + + '."\n"; + echo ' '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''."\n"; + echo ' '; + echo ''; + echo ''."\n"; + echo '
ActionTake Action
'."\n"; + echo '
'."\n"; + } + endif; + } + } +} + + diff --git a/mmdvmhost/functions.php b/mmdvmhost/functions.php new file mode 100644 index 0000000..65e9cb6 --- /dev/null +++ b/mmdvmhost/functions.php @@ -0,0 +1,1480 @@ + up to $maxMatches matching lines + */ +function pistar_log_tail_match($path, $matchRegex, $excludeRegex, + $maxMatches, $stripFirstField = false, + $reverse = false) +{ + $bufSize = 262144; // 256 KB tail window — see docblock above + + $fp = @fopen($path, 'rb'); + if ($fp === false) { + return array(); + } + if (fseek($fp, 0, SEEK_END) !== 0) { + fclose($fp); + return array(); + } + $fileSize = ftell($fp); + if ($fileSize === false || $fileSize === 0) { + fclose($fp); + return array(); + } + + $readSize = min($bufSize, $fileSize); + $readFrom = $fileSize - $readSize; + if (fseek($fp, $readFrom, SEEK_SET) !== 0) { + fclose($fp); + return array(); + } + $buf = fread($fp, $readSize); + fclose($fp); + if ($buf === false || $buf === '') { + return array(); + } + + // If we started reading mid-file, the first line in $buf is + // almost certainly cut by the chunk boundary. Drop bytes up + // to and including the first newline so we never feed a + // partial line to the regex pass. (When $readFrom == 0 we're + // at the start of the file — the first line is real, keep it.) + if ($readFrom > 0) { + $nl = strpos($buf, "\n"); + if ($nl === false) { + return array(); + } + $buf = substr($buf, $nl + 1); + } + + $matched = array(); + $lines = explode("\n", $buf); + foreach ($lines as $line) { + if ($line === '') continue; + if (!preg_match($matchRegex, $line)) continue; + if ($excludeRegex !== '' && preg_match($excludeRegex, $line)) continue; + $matched[] = $line; + } + + if (count($matched) > $maxMatches) { + $matched = array_slice($matched, -$maxMatches); + } + if ($stripFirstField) { + foreach ($matched as &$mline) { + $sp = strpos($mline, ' '); + if ($sp !== false) { + $mline = substr($mline, $sp + 1); + } + } + unset($mline); + } + if ($reverse) { + $matched = array_reverse($matched); + } + return $matched; +} + +function getMMDVMConfig() +{ + // /etc/mmdvmhost is read once per request and cached for the + // remainder. The top-level init at the bottom of this file + // calls us once, and getDSTARLinks() calls us again on D-Star + // hosts — same request, same file, identical result. The + // static cache resets at request shutdown (PHP-FPM does not + // preserve userland state between requests), so this is purely + // an in-request dedup, not a cross-request cache. + static $cached; + if ($cached !== null) { + return $cached; + } + $cached = array(); + if ($configs = @fopen(MMDVMINIPATH."/".MMDVMINIFILENAME, 'r')) { + while ($config = fgets($configs)) { + array_push($cached, trim($config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $cached; +} + +function getDMRHostsLines() +{ + // /usr/local/etc/DMR_Hosts.txt is the BrandMeister master list, + // typically 3-6K lines on SD storage. repeaterinfo.php scans it + // twice per request when both DMR and YSF2DMR are enabled (lines + // 243-282 and 379-389) — both consumers iterate the same lines + // looking for an IP→name match. Read once into an in-memory + // line array; foreach() across it is free. + // + // Per-request scope only (PHP-FPM resets userland state between + // requests). file() loads the whole file in one syscall, which + // is cheaper than ~6K fgets() calls and roughly the same memory + // (~400-800 KB for the typical hosts file — fine on the 512 MB+ + // Pi-Star supports). + static $cached; + if ($cached !== null) { + return $cached; + } + $cached = @file('/usr/local/etc/DMR_Hosts.txt', + FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($cached === false) { + $cached = array(); + } + return $cached; +} + +function getTGNameMap() +{ + // /usr/local/etc/TGList_CN.json is this operator's own domestic + // talkgroup registry, synced once a day by pistar-cn-tglist-sync + // from https://860.radiowo.com/api/v1/talkgroups. MMDVMHost's log + // only ever prints the numeric ID ("to TG 86990"), never a name, + // so lh.php needs this map to show what that network's own + // dashboard shows for free. + // + // Deliberately CN-only: BrandMeister's global TGList_BM.txt is NOT + // read/merged here — this operator's TG IDs are a private/domestic + // numbering space, and mixing in BM's global names would show the + // wrong name for an ID that means something else on this network. + // + // Per-request scope only (PHP-FPM resets userland state between + // requests); cheap enough (a few dozen entries) to re-parse each + // request. + static $cached; + if ($cached !== null) { + return $cached; + } + $cached = array(); + $json = @file_get_contents('/usr/local/etc/TGList_CN.json'); + if ($json !== false) { + $rows = json_decode($json, true); + if (is_array($rows)) { + foreach ($rows as $row) { + if (!is_array($row)) { continue; } + if (!isset($row['tg_id'], $row['name'])) { continue; } + if (!is_numeric($row['tg_id'])) { continue; } + $cached[(int)$row['tg_id']] = (string)$row['name']; + } + } + } + return $cached; +} + +function getYSFGatewayConfig() +{ + // loads /etc/ysfgateway into array for further use + $conf = array(); + if ($configs = @fopen(YSFGATEWAYINIPATH."/".YSFGATEWAYINIFILENAME, 'r')) { + while ($config = fgets($configs)) { + array_push($conf, trim ( $config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $conf; +} + +function getP25GatewayConfig() +{ + // loads /etc/p25gateway into array for further use + $conf = array(); + if ($configs = @fopen(P25GATEWAYINIPATH."/".P25GATEWAYINIFILENAME, 'r')) { + while ($config = fgets($configs)) { + array_push($conf, trim ( $config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $conf; +} + +function getNXDNGatewayConfig() +{ + // loads /etc/nxdngateway into array for further use + $conf = array(); + if ($configs = @fopen('/etc/nxdngateway', 'r')) { + while ($config = fgets($configs)) { + array_push($conf, trim ( $config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $conf; +} + +function getM17GatewayConfig() +{ + // loads /etc/m17gateway into array for further use + $conf = array(); + if ($configs = @fopen('/etc/m17gateway', 'r')) { + while ($config = fgets($configs)) { + array_push($conf, trim ( $config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $conf; +} + +function getDAPNETGatewayConfig() +{ + // loads /etc/dapnetgateway into array for further use + $conf = array(); + if ($configs = @fopen('/etc/dapnetgateway', 'r')) { + while ($config = fgets($configs)) { + array_push($conf, trim ( $config, " \t\n\r\0\x0B")); + } + fclose($configs); + } + return $conf; +} + +function getConfigItem($section, $key, $configs) +{ + // retrieves the corresponding config-entry within a [section] + $sectionpos = array_search("[" . $section . "]", $configs) + 1; + $len = count($configs); + while(startsWith($configs[$sectionpos],$key."=") === false && $sectionpos <= ($len) ) { + if (startsWith($configs[$sectionpos],"[")) { + return null; + } + $sectionpos++; + } + + return substr($configs[$sectionpos], strlen($key) + 1); +} + +function getEnabled ($mode, $mmdvmconfigs) +{ + // returns enabled/disabled-State of mode + return getConfigItem($mode, "Enable", $mmdvmconfigs); +} + +function checkDMRLogin ($dmrDaemon) +{ + if ($dmrDaemon == "MMDVMHost") { + if (file_exists(MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log")) { + $logPath = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log"; + $logCheckMMDVMHostDMRLogin = `tail -n 5 $logPath | awk '/master/ && /successfully/ || /master/ && /failed/' | tail -n 1`; + if (strpos($logCheckMMDVMHostDMRLogin, "success")) { return 0; } + elseif (strpos($logCheckMMDVMHostDMRLogin, "fail")) { return 1; } + else { return 0; } + } + } + elseif ($dmrDaemon == "DMRGateway") { + if (file_exists("/var/log/pi-star/DMRGateway-".gmdate("Y-m-d").".log")) { + $logPath = "/var/log/pi-star/DMRGateway-".gmdate("Y-m-d").".log"; + $logCheckDMRGatewayDMRLogin = `tail -n 5 $logPath | awk '/master/ && /successfully/ || /master/ && /failed/' | tail -n 1`; + if (strpos($logCheckDMRGatewayDMRLogin, "success")) { return 0; } + elseif (strpos($logCheckDMRGatewayDMRLogin, "fail")) { return 1; } + else { return 0; } + } + } + else { + return 0; + } +} + +function showMode($mode, $mmdvmconfigs) +{ + // shows if mode is enabled or not. + if (getEnabled($mode, $mmdvmconfigs) == 1) { + if ($mode == "D-Star Network") { + if (isProcessRunning("ircddbgatewayd")) { + echo ""; + } else { + echo ""; + } + } + elseif ($mode == "System Fusion Network") { + if (isProcessRunning("YSFGateway")) { + echo ""; + } else { + echo ""; + } + } + elseif ($mode == "P25 Network") { + if (isProcessRunning("P25Gateway")) { + echo ""; + } else { + echo ""; + } + } + elseif ($mode == "NXDN Network") { + if (isProcessRunning("NXDNGateway")) { + echo ""; + } else { + echo ""; + } + } + elseif ($mode == "M17 Network") { + if (isProcessRunning("M17Gateway")) { + echo ""; + } else { + echo ""; + } + } + elseif ($mode == "POCSAG Network") { + if (isProcessRunning("DAPNETGateway")) { + echo ""; + } else { + echo ""; + } + } + elseif ($mode == "DMR Network") { + if (getConfigItem("DMR Network", "Address", $mmdvmconfigs) == '127.0.0.1') { + if (isProcessRunning("DMRGateway")) { + if (checkDMRLogin("DMRGateway") > 0) { echo ""; } + else { echo ""; } + } else { + echo ""; + } + } + else { + if (isProcessRunning("MMDVMHost")) { + if (checkDMRLogin("MMDVMHost") > 0) { echo ""; } + else { echo ""; } + } else { + echo ""; + } + } + } + else { + if ($mode == "D-Star" || $mode == "DMR" || $mode == "System Fusion" || $mode == "P25" || $mode == "NXDN" || $mode == "M17" || $mode == "FM" || $mode == "POCSAG") { + if (isProcessRunning("MMDVMHost")) { + echo ""; + } else { + echo ""; + } + } + } + } + elseif ( ($mode == "YSF XMode") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) { + if ( (isProcessRunning("MMDVMHost")) && (isProcessRunning("YSF2DMR") || isProcessRunning("YSF2NXDN") || isProcessRunning("YSF2P25")) ) { + echo ""; + } else { + echo "\">"; + } + } + elseif ( ($mode == "DMR XMode") && (getEnabled("DMR", $mmdvmconfigs) == 1) ) { + if ( (isProcessRunning("MMDVMHost")) && (isProcessRunning("DMR2YSF") || isProcessRunning("DMR2NXDN")) ) { + echo ""; + } else { + echo "\">"; + } + } + elseif ( ($mode == "YSF2DMR Network") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) { + if (isProcessRunning("YSF2DMR")) { + echo ""; + } else { + echo "\">"; + } + } + elseif ( ($mode == "YSF2NXDN Network") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) { + if (isProcessRunning("YSF2NXDN")) { + echo ""; + } else { + echo "\">"; + } + } + elseif ( ($mode == "YSF2P25 Network") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) { + if (isProcessRunning("YSF2P25")) { + echo ""; + } else { + echo "\">"; + } + } + elseif ( ($mode == "DMR2NXDN Network") && (getEnabled("DMR", $mmdvmconfigs) == 1) ) { + if (isProcessRunning("DMR2NXDN")) { + echo ""; + } else { + echo "\">"; + } + } + elseif ( ($mode == "DMR2YSF Network") && (getEnabled("DMR", $mmdvmconfigs) == 1) ) { + if (isProcessRunning("DMR2YSF")) { + echo ""; + } else { + echo "\">"; + } + } + else { + echo "\">"; + } + $mode = str_replace("System Fusion", "YSF", $mode); + $mode = str_replace("Network", "Net", $mode); + if (strpos($mode, 'YSF2') > -1) { $mode = str_replace(" Net", "", $mode); } + if (strpos($mode, 'DMR2') > -1) { $mode = str_replace(" Net", "", $mode); } + echo $mode."\n"; +} + +function getMMDVMLog() +{ + // Last 250 dashboard-relevant lines from today's MMDVM log, + // falling back to yesterday's tail if today is sparse (just- + // past-midnight case). The egrep|sed|tail shell pipeline that + // used to live here forked three processes per call AND + // streamed the whole day's log through them on every poll — + // see pistar_log_tail_match() for the rationale. + $logToday = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log"; + $logYstday = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log"; + $matchR = '/^M.*(from|end|watchdog|lost|slow data)/'; + $excR = '/(CSBK|overflow|Downlink)/'; + + $logLines1 = file_exists($logToday) + ? pistar_log_tail_match($logToday, $matchR, $excR, 250) + : array(); + $logLines2 = array(); + if (sizeof($logLines1) < 250 && file_exists($logYstday)) { + $logLines2 = pistar_log_tail_match($logYstday, $matchR, $excR, 250); + } + // Preserve the original union-by-key + final tail-slice + // semantics: today's matches take the low indices, yesterday's + // fill any higher slots up to 250 total. + $logLines = $logLines1 + $logLines2; + return array_slice($logLines, -250); +} + +function getYSFGatewayLog() +{ + // Last 1 link-state line from today's YSFGateway log, falling + // back to yesterday if today has none. + $logToday = YSFGATEWAYLOGPATH."/".YSFGATEWAYLOGPREFIX."-".gmdate("Y-m-d").".log"; + $logYstday = YSFGATEWAYLOGPATH."/".YSFGATEWAYLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log"; + $matchR = '/^M.*(onnection to|onnect to|inked|isconnect|Opening YSF network)/'; + $excR = '/(Linked to MMDVM|Link successful to MMDVM|\*Link)/'; + + $logLines = file_exists($logToday) + ? pistar_log_tail_match($logToday, $matchR, $excR, 1) + : array(); + if (sizeof($logLines) === 0 && file_exists($logYstday)) { + $logLines = pistar_log_tail_match($logYstday, $matchR, $excR, 1); + } + return $logLines; +} + +function getP25GatewayLog() +{ + // Last 1 link-state line from today's P25Gateway log, falling + // back to yesterday if today has none. The first space- + // delimited field (severity letter) is stripped to match the + // original `cut -d" " -f2-` step. + $logToday = P25GATEWAYLOGPATH."/".P25GATEWAYLOGPREFIX."-".gmdate("Y-m-d").".log"; + $logYstday = P25GATEWAYLOGPATH."/".P25GATEWAYLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log"; + $matchR = '/^M.*(ink|Starting|witched)/'; + + $logLines = file_exists($logToday) + ? pistar_log_tail_match($logToday, $matchR, '', 1, true) + : array(); + if (sizeof($logLines) === 0 && file_exists($logYstday)) { + $logLines = pistar_log_tail_match($logYstday, $matchR, '', 1, true); + } + return $logLines; +} + +function getNXDNGatewayLog() +{ + // Last 1 link-state line from today's NXDNGateway log; see + // getP25GatewayLog for the full rationale (same shape). + $logToday = "/var/log/pi-star/NXDNGateway-".gmdate("Y-m-d").".log"; + $logYstday = "/var/log/pi-star/NXDNGateway-".gmdate("Y-m-d", time() - 86340).".log"; + $matchR = '/^M.*(ink|Starting|witched)/'; + + $logLines = file_exists($logToday) + ? pistar_log_tail_match($logToday, $matchR, '', 1, true) + : array(); + if (sizeof($logLines) === 0 && file_exists($logYstday)) { + $logLines = pistar_log_tail_match($logYstday, $matchR, '', 1, true); + } + return $logLines; +} + +function getM17GatewayLog() +{ + // Last 1 link-state line from today's M17Gateway log; see + // getP25GatewayLog for the full rationale (same shape). + $logToday = "/var/log/pi-star/M17Gateway-".gmdate("Y-m-d").".log"; + $logYstday = "/var/log/pi-star/M17Gateway-".gmdate("Y-m-d", time() - 86340).".log"; + $matchR = '/^M.*(ink|Starting|witched)/'; + + $logLines = file_exists($logToday) + ? pistar_log_tail_match($logToday, $matchR, '', 1, true) + : array(); + if (sizeof($logLines) === 0 && file_exists($logYstday)) { + $logLines = pistar_log_tail_match($logYstday, $matchR, '', 1, true); + } + return $logLines; +} + +function getDAPNETGatewayLog() +{ + // Last 20 "Sending message" lines from today's DAPNETGateway + // log (or yesterday's if today is silent), reversed so the + // most recent message appears first. The original pipeline + // ended with `tail -n 20 | tac`; the helper does both. + $logToday = "/var/log/pi-star/DAPNETGateway-".gmdate("Y-m-d").".log"; + $logYstday = "/var/log/pi-star/DAPNETGateway-".gmdate("Y-m-d", time() - 86340).".log"; + $matchR = '/^M.*(Sending message)/'; + + $logLines = file_exists($logToday) + ? pistar_log_tail_match($logToday, $matchR, '', 20, true, true) + : array(); + if (sizeof($logLines) === 0 && file_exists($logYstday)) { + $logLines = pistar_log_tail_match($logYstday, $matchR, '', 20, true, true); + } + return $logLines; +} + +// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: DVMEGA HR3.14 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS-ADF7021 20170414 (D-Star/DMR/YSF/P25) (Build: 20:16:25 May 20 2017) +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM 20170206 TCXO (D-Star/DMR/System Fusion/P25/RSSI/CW Id) +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: ZUMspot ADF7021 v1.0.0 20170728 (DStar/DMR/YSF/P25) GitID #c16dd5a +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_MDO ADF7021 v1.0.1 20170826 (DStar/DMR/YSF/P25) GitID #BD7KLE +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: ZUMspot-v1.0.3 20171226 ADF7021 FW by CA6JAU GitID #bfb82b4 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS_Hat-v1.0.3 20171226 ADF7021 FW by CA6JAU GitID #bfb82b4 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS-v1.0.3 20171226 ADF7021 FW by CA6JAU GitID #bfb82b4 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS_Dual_Hat-v1.3.6 20180521 dual ADF7021 FW by CA6JAU GitID #bd6217a +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: D2RG_MMDVM_HS-v1.4.17 20190529 14.7456MHz ADF7021 FW by CA6JAU GitID #cc451c4 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: Nano_hotSPOT-v1.3.3 20180224 ADF7021 FW by CA6JAU GitID #62323e7 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: Nano-Spot-v1.3.3 20180224 ADF7021 FW by CA6JAU GitID #62323e7 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: Nano_DV-v1.4.3 20180716 12.2880MHz ADF7021 FW by CA6JAU GitID #6729d23 +// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: SkyBridge-v1.5.2 20201108 14.7456MHz ADF7021 FW by CA6JAU GitID #89daa20 + +function getDVModemFirmware() +{ + $logMMDVMNow = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log"; + $logMMDVMPrevious = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log"; + $logSearchString = "MMDVM protocol version"; + $logLine = ''; + $modemFirmware = ''; + + $logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMNow." | tail -1"); + if (!$logLine) { $logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMPrevious." | tail -1"); } + + if ($logLine) { + if (strpos($logLine, 'DVMEGA')) { + $modemFirmware = substr($logLine, 67, 15); + } + if (strpos($logLine, 'description: MMDVM_HS')) { + $modemFirmware = "MMDVM_HS:".ltrim(substr($logLine, 84, 8), 'v'); + } + if (strpos($logLine, 'description: MMDVM ')) { + $modemFirmware = "MMDVM:".substr($logLine, 73, 8); + } + if (strpos($logLine, 'description: ZUMspot ')) { + $modemFirmware = "ZUMspot:".strtok(substr($logLine, 83, 12), ' '); + } + if (strpos($logLine, 'description: MMDVM_MDO ')) { + $modemFirmware = "MMDVM_MDO:".ltrim(strtok(substr($logLine, 85, 12), ' '), 'v'); + } + if (strpos($logLine, 'description: ZUMspot-')) { + $modemFirmware = "ZUMspot:".strtok(substr($logLine, 75, 12), ' '); + } + if (strpos($logLine, 'description: MMDVM_HS_Hat-')) { + $modemFirmware = "HS_Hat:".strtok(substr($logLine, 80, 12), ' '); + } + if (strpos($logLine, 'description: MMDVM_HS_Dual_Hat-')) { + $modemFirmware = "HS_Hat:".strtok(substr($logLine, 85, 12), ' '); + } + if (strpos($logLine, 'description: D2RG_MMDVM_HS-')) { + $modemFirmware = "HS_Hat:".strtok(substr($logLine, 81, 12), ' '); + } + if (strpos($logLine, 'description: MMDVM_HS-')) { + $modemFirmware = "MMDVM_HS:".ltrim(strtok(substr($logLine, 76, 12), ' '), 'v'); + } + if (strpos($logLine, 'description: Nano_hotSPOT-')) { + $modemFirmware = "MMDVM_HS:".ltrim(strtok(substr($logLine, 80, 12), ' '), 'v'); + } + if (strpos($logLine, 'description: Nano-Spot-')) { + $modemFirmware = "NanoSpot:".strtok(substr($logLine, 77, 12), ' '); + } + if (strpos($logLine, 'description: Nano_DV-')) { + $modemFirmware = "NanoDV:".strtok(substr($logLine, 75, 12), ' '); + } + if (strpos($logLine, 'description: OpenGD77 Hotspot')) { + $modemFirmware = "OpenGD77:".strtok(substr($logLine, 83, 12), ' '); + } + if (strpos($logLine, 'description: OpenGD77_HS ')) { + $modemFirmware = "OpenGD77:".strtok(substr($logLine, 79, 12), ' '); + } + if (strpos($logLine, 'description: SkyBridge-')) { + $modemFirmware = "SkyBrg:".strtok(substr($logLine, 77, 12), ' '); + } + } + return $modemFirmware; +} + +function getDVModemTCXOFreq() +{ + $logMMDVMNow = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log"; + $logMMDVMPrevious = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log"; + $logSearchString = "MMDVM protocol version"; + $logLine = ''; + $modemTCXOFreq = ''; + + $logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMNow." | tail -1"); + if (!$logLine) { $logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMPrevious." | tail -1"); } + + if ($logLine) { + if ((strpos($logLine, 'Mhz') !== false) or (strpos($logLine, 'MHz') !== false)) { + $modemTCXOFreq = $logLine; + $modemTCXOFreq = preg_replace('/.*(\d{2}\.\d{3,4}\s{0,1}M[Hh]z).*/', "$1", $modemTCXOFreq); + $modemTCXOFreq = str_replace("MHz"," MHz", $modemTCXOFreq); + } + } + return $modemTCXOFreq; +} + +// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 +// M: 2000-00-00 00:00:00.000 Mode set to D-Star +// M: 2000-00-00 00:00:00.000 D-Star, received RF header from M1ABC /ABCD to CQCQCQ +// M: 2000-00-00 00:00:00.000 D-Star, received RF end of transmission from M1ABC /ABCD to CQCQCQ , 00.0 seconds, BER: 0.0%, RSSI: -43/-43/-43 dBm +// M: 2000-00-00 00:00:00.000 D-Star, received network header from M1ABC /ABCD to CQCQCQ via REF000 A +// M: 2000-00-00 00:00:00.000 D-Star, received network end of transmission from M1ABC /ABCD to CQCQCQ , 0.0 seconds, 0% packet loss, BER: 0.0% +// M: 2000-00-00 00:00:00.000 D-Star, starting fast data mode +// M: 2000-00-00 00:00:00.000 D-Star, leaving fast data mode +// M: 2000-00-00 00:00:00.000 D-Star, invalid slow data header +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network voice header from M1ABC to TG 1 +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received RF voice header from M1ABC to 5000 +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received RF end of voice transmission, 1.8 seconds, BER: 3.9% +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network end of voice transmission from M1ABC to TG 2, 0.0 seconds, 0% packet loss, BER: 0.0% +// M: 2000-00-00 00:00:00.000 DMR Slot 2, RF voice transmission lost, 1.1 seconds, BER: 6.5% +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received RF CSBK Preamble CSBK (1 to follow) from M1ABC to TG 1 +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network Data Preamble VSBK (11 to follow) from 123456 to TG 123456 +// M: 2000-00-00 00:00:00.000 DMR Talker Alias (Data Format 1, Received 24/24 char): 'Hide the bottle from Ont' +// M: 2000-00-00 00:00:00.000 0000: 07 00 20 4F 6E 74 00 00 00 *.. Ont...* +// M: 2000-00-00 00:00:00.000 DMR Slot 2, Embedded Talker Alias Block 3 +// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network data header from 123999 to M1ABC, 0 blocks +// M: 2000-00-00 00:00:00.000 DMR Slot 2, ended network data transmission from 123999 to M1ABC +// M: 2000-00-00 00:00:00.000 P25, received RF transmission from M1ABC to TG 10200 +// M: 2000-00-00 00:00:00.000 Debug: P25RX: pos/neg/centre/threshold 106 -105 0 106 +// M: 2000-00-00 00:00:00.000 Debug: P25RX: sync found in Ldu pos/centre/threshold 3986 9 104 +// M: 2000-00-00 00:00:00.000 Debug: P25RX: pos/neg/centre/threshold 267 -222 22 245 +// M: 2000-00-00 00:00:00.000 Debug: P25RX: sync found in Ldu pos/centre/threshold 3986 10 112 +// M: 2000-00-00 00:00:00.000 P25, received RF end of transmission, 0.4 seconds, BER: 0.0% +// M: 2000-00-00 00:00:00.000 P25, received network transmission from 10999 to TG 10200 +// M: 2000-00-00 00:00:00.000 P25, network end of transmission, 1.8 seconds, 0% packet loss +// M: 2000-00-00 00:00:00.000 YSF, received RF data from MW0MWZ to ALL +// M: 2000-00-00 00:00:00.000 YSF, received RF end of transmission, 5.1 seconds, BER: 3.8% +// M: 2000-00-00 00:00:00.000 YSF, received network data from M1ABC to ALL at MB6IBK +// M: 2000-00-00 00:00:00.000 YSF, network watchdog has expired, 5.0 seconds, 0% packet loss, BER: 0.0% +// M: 2000-00-00 00:00:00.000 NXDN, received RF transmission from MW0MWZ to TG 65000 +// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: pos/neg/centre/threshold 106 -105 0 106 +// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: sync found in Ldu pos/centre/threshold 3986 9 104 +// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: pos/neg/centre/threshold 267 -222 22 245 +// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: sync found in Ldu pos/centre/threshold 3986 10 112 +// M: 2000-00-00 00:00:00.000 NXDN, received RF end of transmission, 0.4 seconds, BER: 0.0% +// M: 2000-00-00 00:00:00.000 NXDN, received network transmission from 10999 to TG 65000 +// M: 2000-00-00 00:00:00.000 NXDN, network end of transmission, 1.8 seconds, 0% packet loss +// M: 2000-00-00 00:00:00.000 M17, received RF late entry voice transmission from M1ABC to INFO +// M: 2000-00-00 00:00:00.000 M17, received RF end of transmission from M1ABC to INFO, 2.1 seconds, BER: 0.2%, RSSI: -60/-60/-60 dBm +// M: 2000-00-00 00:00:00.000 M17, received network voice transmission from M1ABC to ECHO +// M: 2000-00-00 00:00:00.000 M17, received network end of transmission from M1ABC to ECHO, 0.0 seconds +// M: 2000-00-00 00:00:00.000 POCSAG, transmitted 1 frame(s) of data from 1 message(s) +// M: 2000-00-00 00:00:00.000 Mode set to Idle +function getHeardList($logLines) +{ + //array_multisort($logLines,SORT_DESC); + $heardList = array(); + $ts1duration = ""; + $ts1loss = ""; + $ts1ber = ""; + $ts1rssi = ""; + $ts2duration = ""; + $ts2loss = ""; + $ts2ber = ""; + $ts2rssi = ""; + $dstarduration = ""; + $dstarloss = ""; + $dstarber = ""; + $dstarrssi = ""; + $ysfduration = ""; + $ysfloss = ""; + $ysfber = ""; + $ysfrssi = ""; + $p25duration = ""; + $p25loss = ""; + $p25ber = ""; + $p25rssi = ""; + $nxdnduration = ""; + $nxdnloss = ""; + $nxdnber = ""; + $nxdnrssi = ""; + $pocsagduration = ""; + foreach ($logLines as $logLine) { + $duration = ""; + $loss = ""; + $ber = ""; + $rssi = ""; + //removing invalid lines + if(strpos($logLine,"BS_Dwn_Act")) { + continue; + } else if(strpos($logLine,"invalid access")) { + continue; + } else if(strpos($logLine,"received RF header for wrong repeater")) { + continue; + } else if(strpos($logLine,"unable to decode the network CSBK")) { + continue; + } else if(strpos($logLine,"overflow in the DMR slot RF queue")) { + continue; + } else if(strpos($logLine,"overflow in the M17 RF queue")) { + continue; + } else if(strpos($logLine,"non repeater RF header received")) { + continue; + } else if(strpos($logLine,"Embedded Talker Alias")) { + continue; + } else if(strpos($logLine,"DMR Talker Alias")) { + continue; + } else if(strpos($logLine,", Talker Alias ")) { + continue; + } else if(strpos($logLine,", text Data: ")) { + continue; + } else if(strpos($logLine,"CSBK Preamble")) { + continue; + } else if(strpos($logLine,"Preamble CSBK")) { + continue; + } + + if(strpos($logLine, "end of") || strpos($logLine, "watchdog has expired") || strpos($logLine, "invalid slow data header") || strpos($logLine, "ended RF data") || strpos($logLine, "d network data") || strpos($logLine, "RF user has timed out") || strpos($logLine, "transmission lost") || strpos($logLine, "POCSAG")) { + $lineTokens = explode(", ",$logLine); + if (array_key_exists(2,$lineTokens)) { + $duration = strtok($lineTokens[2], " "); + } + if (array_key_exists(3,$lineTokens)) { + $loss = $lineTokens[3]; + } + // The change to this code was causing all FCS traffic to always show TOut rather than the timer. + // This version should still show time-out when needed, AND show the time if it exists. + if (strpos($logLine,"RF user has timed out") || strpos($logLine,"watchdog has expired") || strpos($logLine, "invalid slow data header")) { + if (array_key_exists(2,$lineTokens) && strpos($lineTokens[2], "seconds")) { + $duration = strtok($lineTokens[2], " "); + } else { + $duration = "TOut"; + } + $ber = "??%"; + } + + // if RF-Packet with no BER reported (e.g. YSF Wires-X commands) then RSSI is in LOSS position + if (startsWith($loss,"RSSI")) { + $lineTokens[4] = $loss; //move RSSI to the position expected on code below + $loss = 'BER: ??%'; + } + + // if RF-Packet, no LOSS would be reported, so BER is in LOSS position + if (startsWith($loss,"BER")) { + $ber = substr($loss, 5); + $loss = "0%"; + if (array_key_exists(4,$lineTokens) && startsWith($lineTokens[4],"RSSI")) { + $rssi = substr($lineTokens[4], 6); + $dBraw = substr($rssi, strrpos($rssi,'/')+1); //average only + $relint = intval($dBraw) + 93; + $signal = round(($relint/6)+9, 0); + if ($signal < 0) $signal = 0; + if ($signal > 9) $signal = 9; + if ($relint > 0) { + $rssi = "S{$signal}+{$relint}dB ({$dBraw})"; + } else { + $rssi = "S{$signal} ({$dBraw})"; + } + } + } else { + $loss = strtok($loss, " "); + if (array_key_exists(4,$lineTokens)) { + $ber = substr($lineTokens[4], 5); + } + } + + if (strpos($logLine,"ended RF data") || strpos($logLine,"d network data")) { + switch (substr($logLine, 27, strpos($logLine,",") - 27)) { + case "DMR Slot 1": + $ts1duration = "DMR Data"; + break; + case "DMR Slot 2": + $ts2duration = "DMR Data"; + break; + } + } else { + switch (substr($logLine, 27, strpos($logLine,",") - 27)) { + case "D-Star": + $dstarduration = $duration; + $dstarloss = $loss; + $dstarber = $ber; + $dstarrssi = $rssi; + break; + case "DMR Slot 1": + $ts1duration = $duration; + $ts1loss = $loss; + $ts1ber = $ber; + $ts1rssi = $rssi; + break; + case "DMR Slot 2": + $ts2duration = $duration; + $ts2loss = $loss; + $ts2ber = $ber; + $ts2rssi = $rssi; + break; + case "YSF": + $ysfduration = $duration; + $ysfloss = $loss; + $ysfber = $ber; + $ysfrssi = $rssi; + break; + case "P25": + $p25duration = $duration; + $p25loss = $loss; + $p25ber = $ber; + $p25rssi = $rssi; + break; + case "NXDN": + $nxdnduration = $duration; + $nxdnloss = $loss; + $nxdnber = $ber; + $nxdnrssi = $rssi; + break; + case "M17": + $m17duration = $duration; + $m17loss = $loss; + $m17ber = $ber; + $m17rssi = $rssi; + break; + case "POCSAG": + $pocsagduration = "POCSAG Data"; + break; + } + } + } + + $timestamp = substr($logLine, 3, 19); + $mode = substr($logLine, 27, strpos($logLine,",") - 27); + $callsign2 = substr($logLine, strpos($logLine,"from") + 5, strpos($logLine,"to") - strpos($logLine,"from") - 6); + $callsign = $callsign2; + if (strpos($callsign2,"/") > 0) { + $callsign = substr($callsign2, 0, strpos($callsign2,"/")); + } + $callsign = trim($callsign); + + $id =""; + if ($mode == "D-Star") { + $id = substr($callsign2, strpos($callsign2,"/") + 1); + } + + $target = trim(substr($logLine, strpos($logLine, "to") + 3)); + // Handle more verbose logging from MMDVMHost + if (strpos($target,",") !== 'false') { $target = explode(",", $target)[0]; } + + $source = "RF"; + if (strpos($logLine,"network") > 0 || strpos($logLine,"POCSAG") > 0) { + $source = "Net"; + } + + switch ($mode) { + case "D-Star": + $duration = $dstarduration; + $loss = $dstarloss; + $ber = $dstarber; + $rssi = $dstarrssi; + break; + case "DMR Slot 1": + $duration = $ts1duration; + $loss = $ts1loss; + $ber = $ts1ber; + $rssi = $ts1rssi; + break; + case "DMR Slot 2": + $duration = $ts2duration; + $loss = $ts2loss; + $ber = $ts2ber; + $rssi = $ts2rssi; + break; + case "YSF": + $duration = $ysfduration; + $loss = $ysfloss; + $ber = $ysfber; + $rssi = $ysfrssi; + $target = preg_replace('!\s+!', ' ', $target); + break; + case "P25": + if ($source == "Net" && $target == "TG 10") {$callsign = "PARROT";} + if ($source == "Net" && $callsign == "10999") {$callsign = "MMDVM";} + $duration = $p25duration; + $loss = $p25loss; + $ber = $p25ber; + $rssi = $p25rssi; + break; + case "NXDN": + if ($source == "Net" && $target == "TG 10") {$callsign = "PARROT";} + $duration = $nxdnduration; + $loss = $nxdnloss; + $ber = $nxdnber; + $rssi = $nxdnrssi; + break; + case "M17": + $duration = $m17duration; + $loss = $m17loss; + $ber = $m17ber; + $rssi = $m17rssi; + break; + case "POCSAG": + $callsign = "DAPNET"; + $target = "DAPNET User"; + $duration = "POCSAG Data"; + break; + } + + // Callsign or ID should be less than 11 chars long, otherwise it could be errorneous + if ( strlen($callsign) < 11 ) { + array_push($heardList, array($timestamp, $mode, $callsign, $id, $target, $source, $duration, $loss, $ber, $rssi)); + $duration = ""; + $loss =""; + $ber = ""; + $rssi = ""; + } + } + return $heardList; +} + +function getLastHeard($logLines) +{ + //returns last heard list from log + $lastHeard = array(); + $heardCalls = array(); + $heardList = getHeardList($logLines); + $counter = 0; + foreach ($heardList as $listElem) { + if ( ($listElem[1] == "D-Star") || ($listElem[1] == "YSF") || ($listElem[1] == "P25") || ($listElem[1] == "NXDN") || ($listElem[1] == "M17") || ($listElem[1] == "POCSAG") || (startsWith($listElem[1], "DMR")) ) { + $callUuid = $listElem[2]."#".$listElem[1].$listElem[3].$listElem[5]; + if(!(array_search($callUuid, $heardCalls) > -1)) { + array_push($heardCalls, $callUuid); + array_push($lastHeard, $listElem); + $counter++; + } + } + } + return $lastHeard; +} + +function getActualMode($metaLastHeard, $mmdvmconfigs) +{ + // returns mode of repeater actual working in + $utc_tz = new DateTimeZone('UTC'); + $local_tz = new DateTimeZone(date_default_timezone_get ()); + $listElem = $metaLastHeard[0]; + $timestamp = new DateTime($listElem[0], $utc_tz); + $timestamp->setTimeZone($local_tz); + $mode = $listElem[1]; + if (startsWith($mode, "DMR")) { + $mode = "DMR"; + } + + $now = new DateTime(); + $hangtime = getConfigItem("General", "ModeHang", $mmdvmconfigs); + + if ($hangtime != "") { + $timestamp->add(new DateInterval('PT' . $hangtime . 'S')); + } else { + $source = $listElem[5]; + if ($source == "RF" && $mode === "D-Star") { + $hangtime = getConfigItem("D-Star", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "D-Star") { + $hangtime = getConfigItem("D-Star Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "RF" && $mode === "DMR") { + $hangtime = getConfigItem("DMR", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "DMR") { + $hangtime = getConfigItem("DMR Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "RF" && $mode === "YSF") { + $hangtime = getConfigItem("System Fusion", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "YSF") { + $hangtime = getConfigItem("System Fusion Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "RF" && $mode === "P25") { + $hangtime = getConfigItem("P25", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "P25") { + $hangtime = getConfigItem("P25 Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "RF" && $mode === "NXDN") { + $hangtime = getConfigItem("NXDN", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "NXDN") { + $hangtime = getConfigItem("NXDN Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "RF" && $mode === "M17") { + $hangtime = getConfigItem("M17", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "M17") { + $hangtime = getConfigItem("M17 Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "RF" && $mode === "FM") { + $hangtime = getConfigItem("FM", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "FM") { + $hangtime = getConfigItem("FM Network", "ModeHang", $mmdvmconfigs); + } + else if ($source == "Net" && $mode === "POCSAG") { + $hangtime = getConfigItem("POCSAG Network", "ModeHang", $mmdvmconfigs); + } + else { + $hangtime = getConfigItem("General", "RFModeHang", $mmdvmconfigs); + } + $timestamp->add(new DateInterval('PT' . $hangtime . 'S')); + } + if ($listElem[6] != null) { //if terminated, hangtime counts after end of transmission + $seconds = is_numeric($listElem[6]) ? ceil((float)$listElem[6]) : 0; + $timestamp->add(new DateInterval('PT' . $seconds . 'S')); + //$timestamp->add(new DateInterval('PT' . ceil($listElem[6]) . 'S')); + } else { //if not terminated, always return mode + return $mode; + } + if ($now->format('U') > $timestamp->format('U')) { + return "idle"; + } else { + return $mode; + } +} + +function getDSTARLinks() +{ + // Get our current configured callsign / module + $mmdvmconfigs = getMMDVMConfig(); + $dstarCallsign = str_pad(getConfigItem("General", "Callsign", $mmdvmconfigs), 7, " ", STR_PAD_RIGHT).getConfigItem("D-Star", "Module", $mmdvmconfigs); + + // returns link-states of all D-Star-modules + if (filesize(LINKLOGPATH."/Links.log") == 0) { + return "Not Linked"; + } + if ($linkLog = fopen(LINKLOGPATH."/Links.log",'r')) { + while ($linkLine = fgets($linkLog)) { + $linkDate = " "; + $protocol = " "; + $linkType = " "; + $linkSource = " "; + $linkDest = " "; + $linkDir = " "; +// Reflector-Link, sample: +// 2011-09-22 02:15:06: DExtra link - Type: Repeater Rptr: DB0LJ B Refl: XRF023 A Dir: Outgoing +// 2012-04-03 08:40:07: DPlus link - Type: Dongle Rptr: DB0ERK B Refl: REF006 D Dir: Outgoing +// 2012-04-03 08:40:07: DCS link - Type: Repeater Rptr: DB0ERK C Refl: DCS001 C Dir: Outgoing + if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*Rptr: (.{8}).*Refl: (.{8}).*Dir: (.{8})/',$linkLine,$linx) > 0){ + $linkDate = $linx[1][0]; + $protocol = $linx[2][0]; + $linkType = $linx[3][0]; + $linkSource = $linx[4][0]; + $linkDest = $linx[5][0]; + $linkDir = $linx[6][0]; + if ($linkSource != $dstarCallsign) { + $linkDate = " "; + $protocol = " "; + $linkType = " "; + $linkSource = " "; + $linkDest = " "; + $linkDir = " "; + } + } +// CCS-Link, sample: +// 2013-03-30 23:21:53: CCS link - Rptr: PE1AGO C Remote: PE1KZU Dir: Incoming + if(preg_match_all('/^(.{19}).*(CC[A-Za-z]*).*Rptr: (.{8}).*Remote: (.{8}).*Dir: (.{8})/',$linkLine,$linx) > 0){ + $linkDate = $linx[1][0]; + $protocol = $linx[2][0]; + $linkType = $linx[2][0]; + $linkSource = $linx[3][0]; + $linkDest = $linx[4][0]; + $linkDir = $linx[5][0]; + } +// Dongle-Link, sample: +// 2011-09-24 07:26:59: DPlus link - Type: Dongle User: DC1PIA Dir: Incoming +// 2012-03-14 21:32:18: DPlus link - Type: Dongle User: DC1PIA Dir: Incoming + if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*User: (.{6,8}).*Dir: (.*)$/',$linkLine,$linx) > 0){ + $linkDate = $linx[1][0]; + $protocol = $linx[2][0]; + $linkType = $linx[3][0]; + $linkSource = " "; + $linkDest = $linx[4][0]; + $linkDir = $linx[5][0]; + } + if (strtolower(substr($linkDir, 0, 2)) == "in") { $linkDir = "In"; } + if (strtolower(substr($linkDir, 0, 3)) == "out") { $linkDir = "Out"; } + $out = $linkDest." ".$protocol."/".$linkDir; + } + } + fclose($linkLog); + return $out; +} + +function getActualLink($logLines, $mode) +{ + // returns actual link state of specific mode + //M: 2016-05-02 07:04:10.504 D-Star link status set to "Verlinkt zu DCS002 S" + //M: 2016-04-03 16:16:18.638 DMR Slot 2, received network voice header from 4000 to 2625094 + //M: 2016-04-03 19:30:03.099 DMR Slot 2, received network voice header from 4020 to 2625094 + //M: 2017-09-03 08:10:42.862 DMR Slot 2, received network data header from M6JQD to TG 9, 5 blocks + switch ($mode) { + case "D-Star": + if (isProcessRunning(IRCDDBGATEWAY)) { + return getDSTARLinks(); + } else { + return "No D-Star Network"; + } + break; + case "DMR Slot 1": + foreach ($logLines as $logLine) { + if(strpos($logLine,"unable to decode the network CSBK")) { + continue; + } else if(substr($logLine, 27, strpos($logLine,",") - 27) == "DMR Slot 1") { + $to = ""; + if (strpos($logLine,"to")) { + $to = trim(substr($logLine, strpos($logLine,"to") + 3)); + } + if ($to !== "") { + if (substr($to, 0, 3) !== 'TG ') { + continue; + } + if ($to === "TG 4000") { + return "No TG"; + } + if (strpos($to, ',') !== false) { + $to = substr($to, 0, strpos($to, ',')); + } + return $to; + } + } + } + return "No TG"; + break; + case "DMR Slot 2": + foreach ($logLines as $logLine) { + if(strpos($logLine,"unable to decode the network CSBK")) { + continue; + } else if(substr($logLine, 27, strpos($logLine,",") - 27) == "DMR Slot 2") { + $to = ""; + if (strpos($logLine,"to")) { + $to = trim(substr($logLine, strpos($logLine,"to") + 3)); + } + if ($to !== "") { + if (substr($to, 0, 3) !== 'TG ') { + continue; + } + if ($to === "TG 4000") { + return "No TG"; + } + if (strpos($to, ',') !== false) { + $to = substr($to, 0, strpos($to, ',')); + } + return $to; + } + } + } + return "No TG"; + break; + + case "YSF": + // 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 + // M: 0000-00-00 00:00:00.000 Connect to 62829 has been requested by M1ABC + // M: 0000-00-00 00:00:00.000 Automatic connection to 62829 + // New YSFGateway Format + // M: 0000-00-00 00:00:00.000 Opening YSF network connection + // M: 0000-00-00 00:00:00.000 Automatic (re-)connection to 16710 - "GB SOUTH WEST " + // M: 0000-00-00 00:00:00.000 Automatic (re-)connection to FCS00290 + // M: 0000-00-00 00:00:00.000 Linked to GB SOUTH WEST + // M: 0000-00-00 00:00:00.000 Linked to FCS002-90 + // M: 0000-00-00 00:00:00.000 Disconnect via DTMF has been requested by M1ABC + // M: 0000-00-00 00:00:00.000 Connect to 00003 - "YSF2NXDN " has been requested by M1ABC + // M: 0000-00-00 00:00:00.000 Link has failed, polls lost + + if (isProcessRunning("YSFGateway")) { + $to = ""; + foreach($logLines as $logLine) { + if ( (strpos($logLine,"Linked to")) && (!strpos($logLine,"Linked to MMDVM")) ) { + $to = trim(substr($logLine, 37, 16)); + if (substr($to, 0, 3) === "FCS") { $to = str_replace(' ', '', str_replace('-', '', $to)); } + } + if (strpos($logLine,"Automatic (re-)connection to")) { + if (strpos($logLine,"Automatic (re-)connection to FCS")) { + $to = substr($logLine, 56, 8); + } + else { + $to = substr($logLine, 56, 5); + } + } + if (strpos($logLine,"Connect to")) { + $to = substr($logLine, 38, 5); + } + if (strpos($logLine,"Automatic connection to")) { + $to = substr($logLine, 51, 5); + } + if (strpos($logLine,"Disconnect via DTMF")) { + $to = "Not Linked"; + } + if (strpos($logLine,"Opening YSF network connection")) { + $to = "Not Linked"; + } + if (strpos($logLine,"Link has failed")) { + $to = "Not Linked"; + } + if (strpos($logLine,"DISCONNECT Reply")) { + $to = "Not Linked"; + } + if ($to !== "") { + return $to; + } + } + return "Not Linked"; + } else { + return "Service Not Started"; + } + break; + + case "NXDN": + // 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 + // 2000-01-01 00:00:00.000 Linked at startup to reflector 10100 + // 2000-01-01 00:00:00.000 Unlinked from reflector 10100 by M1ABC + // 2000-01-01 00:00:00.000 Linked to reflector 10100 by M1ABC + // 2000-01-01 00:00:00.000 No response from 10200, unlinking + // 2000-01-01 00:00:00.000 Switched to reflector 10100 by remote command + // 2000-01-01 00:00:00.000 Unlinking from 10100 due to inactivity + // 2000-01-01 00:00:00.000 Statically linked to reflector 10100 + if (isProcessRunning("NXDNGateway")) { + foreach($logLines as $logLine) { + $to = ""; + if (strpos($logLine,"Linked to")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 44, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Linked at start")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Statically linked to reflector")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Switched to reflector")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 46, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Starting NXDNGateway")) { + return "Not Linked"; + } + if (strpos($logLine,"unlinking")) { + return "Not Linked"; + } + if (strpos($logLine,"Unlinked from")) { + return "Not Linked"; + } + if (strpos($logLine,"Unlinking from")) { + return "Not Linked"; + } + } + return "Not Linked"; + } else { + return "Service Not Started"; + } + break; + + case "M17": + // 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 + if (isProcessRunning("M17Gateway")) { + foreach($logLines as $logLine) { + if(preg_match_all('/Linked .* reflector (M17-.{3} [A-Z])/',$logLine,$linx) > 0){ + return $linx[1][0]; + } + if (strpos($logLine,"Starting M17Gateway")) { + return "Not Linked"; + } + if (strpos($logLine,"unlinking")) { + return "Not Linked"; + } + if (strpos($logLine,"Unlinking")) { + return "Not Linked"; + } + if (strpos($logLine,"Unlinked")) { + return "Not Linked"; + } + } + return "Not Linked"; + } else { + return "Service Not Started"; + } + break; + + case "P25": + // 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 + // 2000-01-01 00:00:00.000 Linked at startup to reflector 10100 + // 2000-01-01 00:00:00.000 Unlinked from reflector 10100 by M1ABC + // 2000-01-01 00:00:00.000 Linked to reflector 10100 by M1ABC + // 2000-01-01 00:00:00.000 No response from 10100, unlinking + // 2000-01-01 00:00:00.000 Switched to reflector 10100 due to RF activity from 12345 + // 2000-01-01 00:00:00.000 Unlinking from reflector 10100 by 12345 + // 2000-01-01 00:00:00.000 Switched to reflector 10100 by remote command + if (isProcessRunning("P25Gateway")) { + foreach($logLines as $logLine) { + $to = ""; + if (strpos($logLine,"Linked to")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 44, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Linked at startup to")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Statically linked to reflector")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Switched to reflector")) { + $to = preg_replace('/[^0-9]/', '', substr($logLine, 46, 5)); + $to = preg_replace('/[^0-9]/', '', $to); + return "TG ".$to; + } + if (strpos($logLine,"Starting P25Gateway")) { + return "Not Linked"; + } + if (strpos($logLine,"unlinking")) { + return "Not Linked"; + } + if (strpos($logLine,"Unlinking")) { + return "Not Linked"; + } + if (strpos($logLine,"Unlinked")) { + return "Not Linked"; + } + } + return "Not Linked"; + } else { + return "Service Not Started"; + } + break; + } + return "Service Not Started"; +} + +function getActualReflector($logLines, $mode) +{ + // 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 + // M: 2016-05-02 07:04:10.504 D-Star link status set to "Verlinkt zu DCS002 S" + // M: 2016-04-03 16:16:18.638 DMR Slot 2, received network voice header from 4000 to 2625094 + // M: 2016-04-03 19:30:03.099 DMR Slot 2, received network voice header from 4020 to 2625094 + foreach ($logLines as $logLine) { + if (substr($logLine, 27, strpos($logLine,",") - 27) == $mode) { + $from = substr($logLine, strpos($logLine,"from") + 5, strpos($logLine,"to") - strpos($logLine,"from") - 6); + if (strlen($from) == 4 && startsWith($from,"4")) { + if ($from == "4000") { + return "No Ref"; + } else { + return "Ref ".$from; + } + } + } + } + return "No Ref"; +} + +//Some basic inits +$mmdvmconfigs = getMMDVMConfig(); +if (!in_array($_SERVER["PHP_SELF"],array('/mmdvmhost/bm_links.php','/mmdvmhost/bm_manager.php'),true)) { + $logLinesMMDVM = getMMDVMLog(); + $reverseLogLinesMMDVM = $logLinesMMDVM; + array_multisort($reverseLogLinesMMDVM,SORT_DESC); + $lastHeard = getLastHeard($reverseLogLinesMMDVM); + + // Only need these in repeaterinfo.php + if (strpos($_SERVER["PHP_SELF"], 'repeaterinfo.php') !== false || strpos($_SERVER["PHP_SELF"], 'index.php') !== false) { + $logLinesYSFGateway = getYSFGatewayLog(); + $reverseLogLinesYSFGateway = $logLinesYSFGateway; + array_multisort($reverseLogLinesYSFGateway,SORT_DESC); + $logLinesP25Gateway = getP25GatewayLog(); + $logLinesNXDNGateway = getNXDNGatewayLog(); + $logLinesM17Gateway = getM17GatewayLog(); + } + // Only need these in index.php + if (strpos($_SERVER["PHP_SELF"], 'index.php') !== false || strpos($_SERVER["PHP_SELF"], 'pages.php') !== false) { + $logLinesDAPNETGateway = getDAPNETGatewayLog(); + } +} diff --git a/mmdvmhost/index.php b/mmdvmhost/index.php new file mode 100644 index 0000000..702d3d1 --- /dev/null +++ b/mmdvmhost/index.php @@ -0,0 +1,3 @@ + + + + + + + + + + + + + +setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + + // Every value below comes from log-line parsing in + // mmdvmhost/functions.php, which in turn parses log lines + // produced by RF traffic. A station transmitting on RF + // can put almost any byte sequence into the callsign or + // target field; without escaping it lands in the + // dashboard's HTML refresh every 1.5s. Normalise once + // here so every echo below works on safe values. + // + // $modeHtml — "Slot 1" -> "TS1" cosmetic + escape + // $cs — callsign (HTML-safe text) + // $csUrl — callsign URL-encoded for href= path + // $csSuffix — D-Star station ID after `/` (HTML-safe) + // $tgt — target callsign / talkgroup (HTML-safe) + // $src — source ("RF"/"Net"/etc.; HTML-safe) + // $dur, $loss, $ber — numeric; HTML-safe defensively. + // + // The downstream str_replace ' '->' ' has to run + // AFTER htmlspecialchars; the entity reference ` ` + // is intentional raw HTML output, not data. + $modeHtml = htmlspecialchars(str_replace('Slot ', 'TS', $listElem[1]), ENT_QUOTES, 'UTF-8'); + $cs = htmlspecialchars((string)$listElem[2], ENT_QUOTES, 'UTF-8'); + $csUrl = rawurlencode((string)$listElem[2]); + $csSuffix = htmlspecialchars((string)$listElem[3], ENT_QUOTES, 'UTF-8'); + // Target normalisation: if it's a single character left-pad + // it to 8 spaces (cosmetic, matches legacy layout). The + // visible value is HTML-escaped, then spaces become + // non-breaking-space entities AFTER the escape so the + // entity isn't double-encoded. + $tgtRaw = (string)$listElem[4]; + // Append this operator's own alias in parentheses when the + // target is a plain "TG " and TGList_CN.json has a name for + // it — MMDVMHost's log never carries a name, only the numeric + // ID, so without this lookup the column can only ever show + // digits (see getTGNameMap() in functions.php). + if (preg_match('/^TG\s+(\d+)$/', trim($tgtRaw), $tgMatch)) { + $tgName = getTGNameMap()[(int)$tgMatch[1]] ?? null; + if ($tgName !== null) { $tgtRaw .= " ($tgName)"; } + } + if (strlen($tgtRaw) === 1) { $tgtRaw = str_pad($tgtRaw, 8, ' ', STR_PAD_LEFT); } + $tgtHtml = htmlspecialchars($tgtRaw, ENT_QUOTES, 'UTF-8'); + $src = htmlspecialchars((string)$listElem[5], ENT_QUOTES, 'UTF-8'); + $dur = htmlspecialchars((string)$listElem[6], ENT_QUOTES, 'UTF-8'); + $loss = htmlspecialchars((string)(isset($listElem[7]) ? $listElem[7] : ''), ENT_QUOTES, 'UTF-8'); + $ber = htmlspecialchars((string)(isset($listElem[8]) ? $listElem[8] : ''), ENT_QUOTES, 'UTF-8'); + + echo ""; + echo ""; + echo ""; + if (is_numeric($listElem[2])) { + if ($listElem[2] > 9999) { echo ""; } + else { echo ""; } + } elseif (!preg_match('/[A-Za-z].*[0-9]|[0-9].*[A-Za-z]/', $listElem[2])) { + echo ""; + } else { + // Strip any "-suffix" before linking — re-derive the + // url-encoded form from the trimmed value. + $csTrim = (strpos($listElem[2], "-") > 0) + ? substr($listElem[2], 0, strpos($listElem[2], "-")) + : (string)$listElem[2]; + $csTrimHtml = htmlspecialchars($csTrim, ENT_QUOTES, 'UTF-8'); + $csTrimUrl = rawurlencode($csTrim); + if ( $listElem[3] && $listElem[3] != ' ' ) { + echo ""; + } else { + echo ""; + } + } + + if ( substr($tgtRaw, 0, 6) === 'CQCQCQ' ) { + echo ""; + } else { + echo ""; + } + + + if ($listElem[5] == "RF"){ + echo ""; + }else{ + echo ""; + } + if ($listElem[6] == null) { + // Live duration + $utc_time = $listElem[0]; + $utc_tz = new DateTimeZone('UTC'); + $now = new DateTime("now", $utc_tz); + $dt = new DateTime($utc_time, $utc_tz); + $duration = $now->getTimestamp() - $dt->getTimestamp(); + $duration_string = $duration<999 ? round($duration) . "+" : "∞"; + echo ""; + } else if ($listElem[6] == "DMR Data") { + echo ""; + } else if ($listElem[6] == "POCSAG Data") { + echo ""; + } else { + echo ""; + + // Colour the Loss Field + if (floatval($listElem[7]) < 1) { echo ""; } + elseif (floatval($listElem[7]) == 1) { echo ""; } + elseif (floatval($listElem[7]) > 1 && floatval($listElem[7]) <= 3) { echo ""; } + else { echo ""; } + + // Colour the BER Field + if (floatval($listElem[8]) == 0) { echo ""; } + elseif (floatval($listElem[8]) >= 0.0 && floatval($listElem[8]) <= 1.9) { echo ""; } + elseif (floatval($listElem[8]) >= 2.0 && floatval($listElem[8]) <= 4.9) { echo ""; } + else { echo ""; } + } + echo "\n"; + } + } +} + +?> +
()Time in time zoneTransmitted ModeCallsignTarget, D-Star Reflector, DMR Talk Group etcReceived from source(s)Duration in SecondsPacket LossBit Error Rate
$local_time$modeHtml$cs$cs$cs
$csTrimHtml/$csSuffix
(GPS)
(GPS)
$tgtHtml".str_replace(' ', ' ', $tgtHtml)."RF$srcTX " . $duration_string . " secDMR DataPOCSAG Data$dur$loss$loss$loss$loss$ber$ber$ber$ber
+ + + diff --git a/mmdvmhost/localtx.php b/mmdvmhost/localtx.php new file mode 100644 index 0000000..92d6ccd --- /dev/null +++ b/mmdvmhost/localtx.php @@ -0,0 +1,157 @@ + + + + + + + + + + + + + +setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + + // Same normalisation pattern as mmdvmhost/lh.php — every + // value below comes from log-line parsing of RF traffic + // and could carry hostile bytes from a transmitting + // station. See the note in lh.php for the rationale. + $modeHtml = htmlspecialchars(str_replace('Slot ', 'TS', $listElem[1]), ENT_QUOTES, 'UTF-8'); + $cs = htmlspecialchars((string)$listElem[2], ENT_QUOTES, 'UTF-8'); + $csUrl = rawurlencode((string)$listElem[2]); + $csSuffix = htmlspecialchars((string)$listElem[3], ENT_QUOTES, 'UTF-8'); + $tgtRaw = (string)$listElem[4]; + if (strlen($tgtRaw) === 1) { $tgtRaw = str_pad($tgtRaw, 8, ' ', STR_PAD_LEFT); } + $tgtHtml = htmlspecialchars($tgtRaw, ENT_QUOTES, 'UTF-8'); + $src = htmlspecialchars((string)$listElem[5], ENT_QUOTES, 'UTF-8'); + $dur = htmlspecialchars((string)$listElem[6], ENT_QUOTES, 'UTF-8'); + $ber = htmlspecialchars((string)(isset($listElem[8]) ? $listElem[8] : ''), ENT_QUOTES, 'UTF-8'); + $rssi = htmlspecialchars((string)(isset($listElem[9]) ? $listElem[9] : ''), ENT_QUOTES, 'UTF-8'); + + echo ""; + echo ""; + echo ""; + if (is_numeric($listElem[2])) { + if ($listElem[2] > 9999) { echo ""; } + else { echo ""; } + } elseif (!preg_match('/[A-Za-z].*[0-9]|[0-9].*[A-Za-z]/', $listElem[2])) { + echo ""; + } else { + $csTrim = (strpos($listElem[2], "-") > 0) + ? substr($listElem[2], 0, strpos($listElem[2], "-")) + : (string)$listElem[2]; + $csTrimHtml = htmlspecialchars($csTrim, ENT_QUOTES, 'UTF-8'); + $csTrimUrl = rawurlencode($csTrim); + if ($listElem[3] && $listElem[3] != ' ' ) { + echo ""; + } else { + echo ""; + } + } + echo ""; + if ($listElem[5] == "RF"){ + echo ""; + } else { + echo ""; + } + if ($listElem[6] == null) { + // Live duration + $utc_time = $listElem[0]; + $utc_tz = new DateTimeZone('UTC'); + $now = new DateTime("now", $utc_tz); + $dt = new DateTime($utc_time, $utc_tz); + $duration = $now->getTimestamp() - $dt->getTimestamp(); + $duration_string = $duration<999 ? round($duration) . "+" : "∞"; + echo ""; + } else if ($listElem[6] == "DMR Data") { + echo ""; + } else { + echo ""; //duration + + // Colour the BER Field + if (floatval($listElem[8]) == 0) { echo ""; } + elseif (floatval($listElem[8]) >= 0.0 && floatval($listElem[8]) <= 1.9) { echo ""; } + elseif (floatval($listElem[8]) >= 2.0 && floatval($listElem[8]) <= 4.9) { echo ""; } + else { echo ""; } + + echo ""; //rssi + } + echo "\n"; + $counter++; } + } + } + +?> +
()Time in time zoneTransmitted ModeCallsignTarget, D-Star Reflector, DMR Talk Group etcReceived from source(s)Duration in SecondsBit Error RateRSSIReceived Signal Strength Indication
$local_time$modeHtml$cs$cs$cs
$csTrimHtml/$csSuffix
(GPS)
(GPS)
".str_replace(' ', ' ', $tgtHtml)."RF$srcTX " . $duration_string . " secDMR Data$dur$ber$ber$ber$ber$rssi
+ + diff --git a/mmdvmhost/m17_manager.php b/mmdvmhost/m17_manager.php new file mode 100644 index 0000000..7abbf10 --- /dev/null +++ b/mmdvmhost/m17_manager.php @@ -0,0 +1,168 @@ + Reflector + * ` against the M17Gateway remote-control port + * configured in /etc/m17gateway. After action, triggers a 2 s + * `setTimeout` reload. + * + * Inputs read at render time: + * /etc/m17gateway Confirms the gateway is configured. + * /usr/local/etc/M17Hosts.txt + optional /root/M17Hosts.txt for the + * operator's reflector picklist. + * + * NOTE for the security pass: validation is whitelist-regex; no + * setEmbeddableSecurityHeaders() call in this file. Coverage gap. + */ + +if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Stop this working outside of the admin page + include_once $_SERVER['DOCUMENT_ROOT'].'/config/config.php'; // MMDVMDash Config + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/tools.php'; // MMDVMDash Tools + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/functions.php'; // MMDVMDash Functions + include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code + + // Check if M17 is Enabled + $testMMDVModeM17 = getConfigItem("M17 Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeM17 == 1 ) { + + //Load the m17gateway config file + $m17GatewayConfigFile = '/etc/m17gateway'; + if (fopen($m17GatewayConfigFile,'r')) { $configm17gateway = parse_ini_file($m17GatewayConfigFile, true); } + + // Check that the remote is enabled + if ( $configm17gateway['Remote Commands']['Enable'] == 1 ) { + $remotePort = $configm17gateway['Remote Commands']['Port']; + if (!empty($_POST) && isset($_POST["m17MgrSubmit"])) { + // Handle Posted Data + if (preg_match('/[^A-Za-z0-9-]/',$_POST['m17LinkHost'])) { unset ($_POST['m17LinkHost']); unset ($_POST['m17LinkRoom']); } + if (preg_match('/[^A-Z]/',$_POST['m17LinkRoom'])) { unset ($_POST['m17LinkHost']); unset ($_POST['m17LinkRoom']); } + if ($_POST["Link"] == "LINK") { + if ($_POST['m17LinkHost'] == "none") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." Reflector unlink"; + } else { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." Reflector ".$_POST['m17LinkHost']." ".$_POST['m17LinkRoom']; + } + } elseif ($_POST["Link"] == "UNLINK") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." Reflector unlink"; + } else { + echo "M17 Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (Neither Link nor Unlink Sent) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (empty($_POST['m17LinkHost'])) { + echo "M17 Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (No target specified) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (isset($remoteCommand)) { + echo "M17 Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo htmlspecialchars((string)exec($remoteCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n
\n"; + echo ''; + } + } else { + // Output HTML + ?> + M17 Link Manager + + + + + + + + + + + + + +
ReflectorReflectorLink / Un-LinkLink / Un-LinkActionAction
+ + + + Link + UnLink + + +
+ +
+ TalkGroup + * ` against the NXDNGateway remote-control port + * configured in /etc/nxdngateway. Triggers a 2 s `setTimeout` reload. + * + * Inputs: + * /etc/nxdngateway Gateway config + remote port. + * /usr/local/etc/NXDNHosts.txt Operator picklist. + * /usr/local/etc/NXDNHostsLocal.txt Local additions (optional). + * + * NOTE for the security pass: validation is whitelist-regex; no + * setEmbeddableSecurityHeaders() call in this file. Coverage gap. + */ + +if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Stop this working outside of the admin page + include_once $_SERVER['DOCUMENT_ROOT'].'/config/config.php'; // MMDVMDash Config + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/tools.php'; // MMDVMDash Tools + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/functions.php'; // MMDVMDash Functions + include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code + + // Check if NXDN is Enabled + $testMMDVModeNXDN = getConfigItem("NXDN Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeNXDN == 1 ) { + + //Load the nxdngateway config file + $nxdnGatewayConfigFile = '/etc/nxdngateway'; + if (fopen($nxdnGatewayConfigFile,'r')) { $confignxdngateway = parse_ini_file($nxdnGatewayConfigFile, true); } + + // Check that the remote is enabled + if ( $confignxdngateway['Remote Commands']['Enable'] == 1 ) { + $remotePort = $confignxdngateway['Remote Commands']['Port']; + if (!empty($_POST) && isset($_POST["nxdnMgrSubmit"])) { + // Handle Posted Data + if (preg_match('/[^A-Za-z0-9]/',$_POST['nxdnLinkHost'])) { unset ($_POST['nxdnLinkHost']);} + if ($_POST["Link"] == "LINK") { + if ($_POST['nxdnLinkHost'] == "none") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." TalkGroup unlink"; + } else { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." TalkGroup ".$_POST['nxdnLinkHost']; + } + } elseif ($_POST["Link"] == "UNLINK") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." TalkGroup unlink"; + } else { + echo "NXDN Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (Neither Link nor Unlink Sent) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (empty($_POST['nxdnLinkHost'])) { + echo "NXDN Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (No target specified) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (isset($remoteCommand)) { + echo "NXDN Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo htmlspecialchars((string)exec($remoteCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n
\n"; + echo ''; + } + } else { + // Output HTML + ?> + NXDN Link Manager +
+ + + + + + + + + + + + +
ReflectorReflectorLink / Un-LinkLink / Un-LinkActionAction
+ + + Link + UnLink + + +
+
+
+ TalkGroup + * ` against the P25Gateway remote-control port configured + * in /etc/p25gateway. Triggers a 2 s `setTimeout` reload. + * + * Inputs: + * /etc/p25gateway Gateway config + remote port. + * /usr/local/etc/P25Hosts.txt Operator picklist. + * /usr/local/etc/P25HostsLocal.txt Local additions (optional). + * + * NOTE for the security pass: validation is whitelist-regex; no + * setEmbeddableSecurityHeaders() call in this file. Coverage gap. + */ + +if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Stop this working outside of the admin page + include_once $_SERVER['DOCUMENT_ROOT'].'/config/config.php'; // MMDVMDash Config + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/tools.php'; // MMDVMDash Tools + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/functions.php'; // MMDVMDash Functions + include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code + + // Check if P25 is Enabled + $testMMDVModeP25 = getConfigItem("P25 Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeP25 == 1 ) { + + //Load the p25gateway config file + $p25GatewayConfigFile = '/etc/p25gateway'; + if (fopen($p25GatewayConfigFile,'r')) { $configp25gateway = parse_ini_file($p25GatewayConfigFile, true); } + + // Check that the remote is enabled + if ( $configp25gateway['Remote Commands']['Enable'] == 1 ) { + $remotePort = $configp25gateway['Remote Commands']['Port']; + if (!empty($_POST) && isset($_POST["p25MgrSubmit"])) { + // Handle Posted Data + if (preg_match('/[^A-Za-z0-9]/',$_POST['p25LinkHost'])) { unset ($_POST['p25LinkHost']);} + if ($_POST["Link"] == "LINK") { + if ($_POST['p25LinkHost'] == "none") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." TalkGroup 9999"; + } else { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." TalkGroup ".$_POST['p25LinkHost']; + } + } elseif ($_POST["Link"] == "UNLINK") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." TalkGroup 9999"; + } else { + echo "P25 Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (Neither Link nor Unlink Sent) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (empty($_POST['p25LinkHost'])) { + echo "P25 Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (No target specified) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (isset($remoteCommand)) { + echo "P25 Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo htmlspecialchars((string)exec($remoteCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n
\n"; + echo ''; + } + } else { + // Output HTML + ?> + P25 Link Manager +
+ + + + + + + + + + + + +
ReflectorReflectorLink / Un-LinkLink / Un-LinkActionAction
+ + Link + UnLink + + +
+
+
+ " preview where applicable. + * + * Data flow: parses the in-memory `$logLinesDAPNETGateway` array + * populated by mmdvmhost/functions.php from + * /var/log/pi-star/DAPNETGateway-YYYY-MM-DD.log. + * + * Most of the work here contributed by geeks4hire (Ben Horan); Skyper + * decode added by Andy Taylor (MW0MWZ). + */ + + +// AJAX-loaded partial — embeddable variant only. The earlier +// duplicate setSecurityHeaders() call has been removed; that +// non-embeddable variant raced with this one and won via the +// headers_sent() guard, so the file was effectively shipping +// X-Frame-Options / frame-ancestors despite the embeddable +// version being added below it. +require_once($_SERVER['DOCUMENT_ROOT'] . '/config/security_headers.php'); +setEmbeddableSecurityHeaders(); + +include_once $_SERVER['DOCUMENT_ROOT'].'/config/config.php'; // MMDVMDash Config +include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/tools.php'; // MMDVMDash Tools +include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/functions.php'; // MMDVMDash Functions +include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code + +// Function to reverse the ROT1 used for Skyper +function un_rot($message) +{ + $output = ""; + $messageTextArray = str_split($message); + + // ROT -1 + foreach($messageTextArray as $asciiChar) { + $asciiAsInt = ord($asciiChar); + $convretedAsciiAsInt = $asciiAsInt -1; + $convertedAsciiChar = chr($convretedAsciiAsInt); + $output .= $convertedAsciiChar; + } + + // Return the clear text + return $output; +} + +// Function to handle Skyper Messages +function skyper($message, $pocsagric) +{ + $output = ""; + $messageTextArray = str_split($message); + + if ($pocsagric == "0002504") { // Skyper OTA TimeSync Messages + $output = "[Skyper OTA Time] ".$message; + return $output; + } + + if ($pocsagric == "0004512") { // Skyper Rubric Index + if (isset($messageTextArray[0])) { // This is hard coded to 1 for rubric index + unset($messageTextArray[0]); + } + if (isset($messageTextArray[1])) { // Rubric Number + $skyperRubric = ord($messageTextArray[1]) - 31; + unset($messageTextArray[1]); + } + if (isset($messageTextArray[2])) { // Message number, hard coded to 10 for Rubric Index + unset($messageTextArray[2]); + } + + if (count($messageTextArray) >= 1) { // Check to see if there is a message to decode + $output = "[Skyper Index Rubric:$skyperRubric] ".un_rot(implode($messageTextArray)); + } + else { + $output = "[Skyper Index Rubric:$skyperRubric] No Name"; + } + return $output; + } + + if ($pocsagric == "0004520") { // Skyper Message + if (isset($messageTextArray[0])) { // Rubric Number + $skyperRubric = ord($messageTextArray[0]) - 31; + unset($messageTextArray[0]); + } + if (isset($messageTextArray[1])) { // Message number + $skyperMsgNr = ord($messageTextArray[1]) - 32; + unset($messageTextArray[1]); + } + + if (count($messageTextArray) >= 1) { // Check to see if there is a message to decode + $output = "[Skyper Rubric:$skyperRubric Msg:$skyperMsgNr] ".un_rot(implode($messageTextArray)); + } + else { + $output = "[Skyper Rubric:$skyperRubric] No Message"; + } + return $output; + } +} +?> + + + + + + + + + +setTimeZone($local_tz); + $local_time = $dt->format('H:i:s M jS'); + $pocsag_timeslot = $dapnetMessageArr["6"]; + $pocsag_ric = str_replace(',', '', $dapnetMessageArr["8"]); + // Fix incorrectly truncated strings containing double quotes + unset($dapnetMessageTxtArr[0]); + if (count($dapnetMessageTxtArr) > 2) { + unset($dapnetMessageTxtArr[count($dapnetMessageTxtArr)]); + $pocsag_msg = implode('"', $dapnetMessageTxtArr); + } else { + $pocsag_msg = $dapnetMessageTxtArr[1]; + } + + // Decode Skyper Messages + if ( ($pocsag_ric == "0004520") || ($pocsag_ric == "0004512") || ($pocsag_ric == "0002504") ) { + $pocsag_msg = skyper($pocsag_msg, $pocsag_ric); + } + + // Formatting long messages without spaces + if (strpos($pocsag_msg, ' ') == 0 && strlen($pocsag_msg) >= 45) { + $pocsag_msg = wordwrap($pocsag_msg, 45, ' ', true); + } + + // Sanitise the data before displaying the HTML + if (isset($local_time)) { $local_time = htmlspecialchars($local_time, ENT_QUOTES, 'UTF-8'); } + if (isset($pocsag_timeslot)) { $pocsag_timeslot = htmlspecialchars($pocsag_timeslot, ENT_QUOTES, 'UTF-8'); } + if (isset($pocsag_ric)) { $pocsag_ric = htmlspecialchars($pocsag_ric, ENT_QUOTES, 'UTF-8'); } + if (isset($pocsag_msg)) { $pocsag_msg = htmlspecialchars($pocsag_msg, ENT_QUOTES, 'UTF-8'); } + +?> + + + + + + + + + + +
()Time in time zoneMessage ModeRIC / CapCode of the receiving PagerMessage contents
+ diff --git a/mmdvmhost/repeaterinfo.php b/mmdvmhost/repeaterinfo.php new file mode 100644 index 0000000..c831574 --- /dev/null +++ b/mmdvmhost/repeaterinfo.php @@ -0,0 +1,476 @@ + 0) + $configs[$key] = $value; + } + +} + +//Load the DStarRepeater config file +$configdstar = array(); +if ($configdstarfile = fopen('/etc/dstarrepeater','r')) { + while ($line1 = fgets($configdstarfile)) { + if (strpos($line1, '=') !== false) { + list($key1,$value1) = preg_split('/=/',$line1); + $value1 = trim(str_replace('"','',$value1)); + if (strlen($value1) > 0) + $configdstar[$key1] = $value1; + } + } +} + +//Load the dmrgateway config file +$dmrGatewayConfigFile = '/etc/dmrgateway'; +if (fopen($dmrGatewayConfigFile,'r')) { $configdmrgateway = parse_ini_file($dmrGatewayConfigFile, true); } + +//Load the dapnetgateway config file +$dapnetGatewayConfigFile = '/etc/dapnetgateway'; +if (fopen($dapnetGatewayConfigFile,'r')) { $configdapnetgateway = parse_ini_file($dapnetGatewayConfigFile, true); } + +// Load the ysf2dmr config file +if (file_exists('/etc/ysf2dmr')) { + $ysf2dmrConfigFile = '/etc/ysf2dmr'; + if (fopen($ysf2dmrConfigFile,'r')) { $configysf2dmr = parse_ini_file($ysf2dmrConfigFile, true); } +} +// Load the ysf2nxdn config file +if (file_exists('/etc/ysf2nxdn')) { + $ysf2nxdnConfigFile = '/etc/ysf2nxdn'; + if (fopen($ysf2nxdnConfigFile,'r')) { $configysf2nxdn = parse_ini_file($ysf2nxdnConfigFile, true); } +} +// Load the ysf2p25 config file +if (file_exists('/etc/ysf2p25')) { + $ysf2p25ConfigFile = '/etc/ysf2p25'; + if (fopen($ysf2p25ConfigFile,'r')) { $configysf2p25 = parse_ini_file($ysf2p25ConfigFile, true); } +} +// Load the dmr2ysf config file +if (file_exists('/etc/dmr2ysf')) { + $dmr2ysfConfigFile = '/etc/dmr2ysf'; + if (fopen($dmr2ysfConfigFile,'r')) { $configdmr2ysf = parse_ini_file($dmr2ysfConfigFile, true); } +} +// Load the dmr2nxdn config file +if (file_exists('/etc/dmr2nxdn')) { + $dmr2nxdnConfigFile = '/etc/dmr2nxdn'; + if (fopen($dmr2nxdnConfigFile,'r')) { $configdmr2nxdn = parse_ini_file($dmr2nxdnConfigFile, true); } +} +?> + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + +TX " . htmlspecialchars((string)$listElem[1], ENT_QUOTES, 'UTF-8') . ""; + } + else { + if (getActualMode($lastHeard, $mmdvmconfigs) === 'idle') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === NULL) { + if (isProcessRunning("MMDVMHost")) { echo ""; } else { echo ""; } + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'D-Star') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'D-Star') { + echo ""; + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'DMR') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'DMR') { + echo ""; + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'YSF') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'YSF') { + echo ""; + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'P25') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'P25') { + echo ""; + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'NXDN') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'NXDN') { + echo ""; + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'M17') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'M17') { + echo ""; + } + elseif ($listElem[2] && $listElem[6] == null && getActualMode($lastHeard, $mmdvmconfigs) === 'FM') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'FM') { + echo ""; + } + elseif (getActualMode($lastHeard, $mmdvmconfigs) === 'POCSAG') { + echo ""; + } + else { + // getActualMode() returns log-derived strings; + // escape before HTML interpolation. + echo ""; + } + } + } +else { + echo ""; +} +?> + + +'."\n"; +} ?> +'."\n"; +} ?> +
TrxListeningListeningOFFLINERX D-StarListening D-StarRX DMRListening DMRRX YSFListening YSFRX P25Listening P25RX NXDNListening NXDNRX M17Listening M17RX FMListening FMPOCSAG" . htmlspecialchars((string)getActualMode($lastHeard, $mmdvmconfigs), ENT_QUOTES, 'UTF-8') . "
Tx
Rx
FW'.htmlspecialchars((string)$fwVersion, ENT_QUOTES, 'UTF-8').'
TCXO'.htmlspecialchars((string)$tcxoFreq, ENT_QUOTES, 'UTF-8').'
+ +\n"; +echo "\n"; +echo "\n"; +// Config values from /etc/dstarrepeater and /etc/ircddbgateway +// — operator-controlled, but defensive escape before HTML +// interpolation. The legacy ` ` substitution stays but +// runs AFTER the htmlspecialchars so the entity isn't double- +// encoded. +echo "\n"; +echo "\n"; +echo "\n"; +if ($configs['aprsEnabled']) { + echo "\n"; +} +if ($configs['ircddbEnabled']) { + echo "\n"; +} +// getActualLink() returns log-parsed strings (DMR talkgroups, +// D-Star reflector names, NXDN/M17 link state etc.) which can +// carry RF-controllable bytes; escape before display. +echo "\n"; +echo "
".$lang['dstar_repeater']."
RPT1".str_replace(' ', ' ', htmlspecialchars((string)$configdstar['callsign'], ENT_QUOTES, 'UTF-8'))."
RPT2".str_replace(' ', ' ', htmlspecialchars((string)$configdstar['gateway'], ENT_QUOTES, 'UTF-8'))."
".$lang['dstar_net']."
APRS".htmlspecialchars(substr((string)$configs['aprsHostname'], 0, 16), ENT_QUOTES, 'UTF-8')."
IRC".htmlspecialchars(substr((string)$configs['ircddbHostname'], 0, 16), ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)getActualLink($reverseLogLinesMMDVM, "D-Star"), ENT_QUOTES, 'UTF-8')."
\n"; +} + +$testMMDVModeDMR = getConfigItem("DMR", "Enable", $mmdvmconfigs); +if ( $testMMDVModeDMR == 1 ) { //Hide the DMR information when DMR mode not enabled. +// DMR_Hosts.txt: read once via getDMRHostsLines() (functions.php) and +// cached for the request, so this scan and the YSF2DMR scan further +// down both iterate the same in-memory line array instead of two +// separate fopen/fgets passes over a 3-6K-line SD-card file. +$dmrMasterHost = getConfigItem("DMR Network", "Address", $mmdvmconfigs); +$dmrMasterPort = getConfigItem("DMR Network", "Port", $mmdvmconfigs); +if ($dmrMasterHost == '127.0.0.1') { + if (isset($configdmrgateway['XLX Network 1']['Address'])) { $xlxMasterHost1 = $configdmrgateway['XLX Network 1']['Address']; } + else { $xlxMasterHost1 = ""; } + $dmrMasterHost1 = $configdmrgateway['DMR Network 1']['Address']; + $dmrMasterHost2 = $configdmrgateway['DMR Network 2']['Address']; + $dmrMasterHost3 = str_replace('_', ' ', $configdmrgateway['DMR Network 3']['Name']); + if (isset($configdmrgateway['DMR Network 4']['Name'])) {$dmrMasterHost4 = str_replace('_', ' ', $configdmrgateway['DMR Network 4']['Name']);} + if (isset($configdmrgateway['DMR Network 5']['Name'])) {$dmrMasterHost5 = str_replace('_', ' ', $configdmrgateway['DMR Network 5']['Name']);} + if (isset($configdmrgateway['DMR Network 6']['Name'])) {$dmrMasterHost6 = str_replace('_', ' ', $configdmrgateway['DMR Network 6']['Name']);} + foreach (getDMRHostsLines() as $dmrMasterLine) { + $dmrMasterHostF = preg_split('/\s+/', $dmrMasterLine); + if ((count($dmrMasterHostF) >= 2) && (strpos($dmrMasterHostF[0], '#') === FALSE) && ($dmrMasterHostF[0] != '')) { + if ((strpos($dmrMasterHostF[0], 'XLX_') === 0) && ($xlxMasterHost1 == $dmrMasterHostF[2])) { $xlxMasterHost1 = str_replace('_', ' ', $dmrMasterHostF[0]); } + if ((strpos($dmrMasterHostF[0], 'BM_') === 0) && ($dmrMasterHost1 == $dmrMasterHostF[2])) { $dmrMasterHost1 = str_replace('_', ' ', $dmrMasterHostF[0]); } + if ((strpos($dmrMasterHostF[0], 'DMR+_') === 0) && ($dmrMasterHost2 == $dmrMasterHostF[2])) { $dmrMasterHost2 = str_replace('_', ' ', $dmrMasterHostF[0]); } + } + } + if (strlen($xlxMasterHost1) > 19) { $xlxMasterHost1 = substr($xlxMasterHost1, 0, 17) . '..'; } + if (strlen($dmrMasterHost1) > 19) { $dmrMasterHost1 = substr($dmrMasterHost1, 0, 17) . '..'; } + if (strlen($dmrMasterHost2) > 19) { $dmrMasterHost2 = substr($dmrMasterHost2, 0, 17) . '..'; } + if (strlen($dmrMasterHost3) > 19) { $dmrMasterHost3 = substr($dmrMasterHost3, 0, 17) . '..'; } + if (isset($dmrMasterHost4)) { if (strlen($dmrMasterHost4) > 19) { $dmrMasterHost4 = substr($dmrMasterHost4, 0, 17) . '..'; } } + if (isset($dmrMasterHost5)) { if (strlen($dmrMasterHost5) > 19) { $dmrMasterHost5 = substr($dmrMasterHost5, 0, 17) . '..'; } } + if (isset($dmrMasterHost6)) { if (strlen($dmrMasterHost6) > 19) { $dmrMasterHost6 = substr($dmrMasterHost6, 0, 17) . '..'; } } +} +else { + foreach (getDMRHostsLines() as $dmrMasterLine) { + $dmrMasterHostF = preg_split('/\s+/', $dmrMasterLine); + if ((count($dmrMasterHostF) >= 4) && (strpos($dmrMasterHostF[0], '#') === FALSE) && ($dmrMasterHostF[0] != '')) { + if (($dmrMasterHost == $dmrMasterHostF[2]) && ($dmrMasterPort == $dmrMasterHostF[4])) { $dmrMasterHost = str_replace('_', ' ', $dmrMasterHostF[0]); } + } + } + if (strlen($dmrMasterHost) > 19) { $dmrMasterHost = substr($dmrMasterHost, 0, 17) . '..'; } +} + +echo "
\n"; +echo "\n"; +echo "\n"; +// All getConfigItem returns and $dmrMasterHost* values below +// originate from /etc/mmdvmhost or /etc/dmrgateway (parse_ini) +// or from log-parsed `exec(grep | awk)` pipelines. Escape every +// echoed value so an editor-injected payload (closes the read +// path of the M-3 stored XSS class) or a hostile log line can't +// break out of the table cell. +echo "\n"; +echo "\n"; +echo ""; +if (getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) == 1) { echo "\n"; } else { echo "\n"; } +echo ""; +if (getConfigItem("DMR Network", "Slot2", $mmdvmconfigs) == 1) { echo "\n"; } else { echo "\n"; } +echo "\n"; +if (getEnabled("DMR Network", $mmdvmconfigs) == 1) { + if ($dmrMasterHost == '127.0.0.1') { + if ((isset($configdmrgateway['XLX Network 1']['Enabled'])) && ($configdmrgateway['XLX Network 1']['Enabled'] == 1)) { + echo "\n"; + } + if ( !isset($configdmrgateway['XLX Network 1']['Enabled']) && isset($configdmrgateway['XLX Network']['Enabled']) && $configdmrgateway['XLX Network']['Enabled'] == 1) { + if (file_exists("/var/log/pi-star/DMRGateway-".gmdate("Y-m-d").".log")) { $xlxMasterHost1 = exec('grep \'XLX, Linking\|XLX, Unlinking\|XLX, Logged\' /var/log/pi-star/DMRGateway-'.gmdate("Y-m-d").'.log | tail -1 | awk \'{print $5 " " $8 " " $9}\''); } + else { $xlxMasterHost1 = exec('grep \'XLX, Linking\|XLX, Unlinking\|XLX, Logged\' /var/log/pi-star/DMRGateway-'.gmdate("Y-m-d", time() - 86340).'.log | tail -1 | awk \'{print $5 " " $8 " " $9}\''); } + if ( strpos($xlxMasterHost1, 'Linking') !== false ) { $xlxMasterHost1 = str_replace('Linking ', '', $xlxMasterHost1); } + else if ( strpos($xlxMasterHost1, 'Unlinking') !== false ) { $xlxMasterHost1 = "XLX Not Linked"; } + else if ( strpos($xlxMasterHost1, 'Logged') !== false ) { $xlxMasterHost1 = "XLX Not Linked"; } + echo "\n"; + } + if ($configdmrgateway['DMR Network 1']['Enabled'] == 1) { + echo "\n"; + } + if ($configdmrgateway['DMR Network 2']['Enabled'] == 1) { + echo "\n"; + } + if ($configdmrgateway['DMR Network 3']['Enabled'] == 1) { + echo "\n"; + } + if (isset($configdmrgateway['DMR Network 4']['Enabled'])) { + if ($configdmrgateway['DMR Network 4']['Enabled'] == 1) { + echo "\n"; + } + } + if (isset($configdmrgateway['DMR Network 5']['Enabled'])) { + if ($configdmrgateway['DMR Network 5']['Enabled'] == 1) { + echo "\n"; + } + } + if (isset($configdmrgateway['DMR Network 6']['Enabled'])) { + if ($configdmrgateway['DMR Network 6']['Enabled'] == 1) { + echo "\n"; + } + } + } + else { + echo "\n"; + } + } + else { + echo "\n"; + } +echo "
".$lang['dmr_repeater']."
DMR ID".htmlspecialchars((string)getConfigItem("General", "Id", $mmdvmconfigs), ENT_QUOTES, 'UTF-8')."
DMR CC".htmlspecialchars((string)getConfigItem("DMR", "ColorCode", $mmdvmconfigs), ENT_QUOTES, 'UTF-8')."
TS1enabled
disabled
TS2enabled
disabled
".$lang['dmr_master']."
".htmlspecialchars((string)$xlxMasterHost1, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$xlxMasterHost1, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost1, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost2, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost3, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost4, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost5, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost6, ENT_QUOTES, 'UTF-8')."
".htmlspecialchars((string)$dmrMasterHost, ENT_QUOTES, 'UTF-8')."
No DMR Network
\n"; +} + +$testMMDVModeYSF = getConfigItem("System Fusion Network", "Enable", $mmdvmconfigs); +if ( isset($configdmr2ysf['Enabled']['Enabled']) ) { $testDMR2YSF = $configdmr2ysf['Enabled']['Enabled']; } +if ( $testMMDVModeYSF == 1 || $testDMR2YSF ) { //Hide the YSF information when System Fusion Network mode not enabled. + $ysfLinkedTo = getActualLink($reverseLogLinesYSFGateway, "YSF"); + if ($ysfLinkedTo == 'Not Linked' || $ysfLinkedTo == 'Service Not Started') { + $ysfLinkedToTxt = $ysfLinkedTo; + } else { + $ysfHostFile = fopen("/usr/local/etc/YSFHosts.txt", "r"); + $ysfLinkedToTxt = "null"; + while (!feof($ysfHostFile)) { + $ysfHostFileLine = fgets($ysfHostFile); + $ysfRoomTxtLine = preg_split('/;/', $ysfHostFileLine); + if (empty($ysfRoomTxtLine[0]) || empty($ysfRoomTxtLine[1])) continue; + if (($ysfRoomTxtLine[0] == $ysfLinkedTo) || ($ysfRoomTxtLine[1] == $ysfLinkedTo)) { + $ysfLinkedToTxt = $ysfRoomTxtLine[1]; + break; + } + } + if ($ysfLinkedToTxt != "null") { $ysfLinkedToTxt = $ysfLinkedToTxt; } else { $ysfLinkedToTxt = $ysfLinkedTo; } + $ysfLinkedToTxt = str_replace('_', ' ', $ysfLinkedToTxt); + } + if (strlen($ysfLinkedToTxt) > 19) { $ysfLinkedToTxt = substr($ysfLinkedToTxt, 0, 17) . '..'; } + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
".$lang['ysf_net']."
".htmlspecialchars((string)$ysfLinkedToTxt, ENT_QUOTES, 'UTF-8')."
\n"; +} + +if ( isset($configysf2dmr['Enabled']['Enabled']) ) { $testYSF2DMR = $configysf2dmr['Enabled']['Enabled']; } +if ( $testYSF2DMR ) { //Hide the YSF2DMR information when YSF2DMR Network mode not enabled. + // Reuses the in-memory DMR_Hosts.txt line array cached by + // getDMRHostsLines() — same data the DMR block above just + // walked, no second SD-card read. + $dmrMasterHost = $configysf2dmr['DMR Network']['Address']; + foreach (getDMRHostsLines() as $dmrMasterLine) { + $dmrMasterHostF = preg_split('/\s+/', $dmrMasterLine); + if ((count($dmrMasterHostF) >= 2) && (strpos($dmrMasterHostF[0], '#') === FALSE) && ($dmrMasterHostF[0] != '')) { + if ($dmrMasterHost == $dmrMasterHostF[2]) { $dmrMasterHost = str_replace('_', ' ', $dmrMasterHostF[0]); } + } + } + if (strlen($dmrMasterHost) > 19) { $dmrMasterHost = substr($dmrMasterHost, 0, 17) . '..'; } + + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
YSF2DMR
DMR ID".htmlspecialchars((string)$configysf2dmr['DMR Network']['Id'], ENT_QUOTES, 'UTF-8')."
YSF2".$lang['dmr_master']."
".htmlspecialchars((string)$dmrMasterHost, ENT_QUOTES, 'UTF-8')."
\n"; +} + +$testMMDVModeP25 = getConfigItem("P25 Network", "Enable", $mmdvmconfigs); +if ( isset($configysf2p25['Enabled']['Enabled']) ) { $testYSF2P25 = $configysf2p25['Enabled']['Enabled']; } +if ( $testMMDVModeP25 == 1 || $testYSF2P25 ) { //Hide the P25 information when P25 Network mode not enabled. + echo "
\n"; + echo "\n"; + if (getConfigItem("P25", "NAC", $mmdvmconfigs)) { + echo "\n"; + echo "\n"; + } else { + echo "\n"; + echo "\n"; + } + echo "\n"; + echo "\n"; + echo "
".$lang['p25_radio']."
NAC".htmlspecialchars((string)getConfigItem("P25", "NAC", $mmdvmconfigs), ENT_QUOTES, 'UTF-8')."
".$lang['p25_radio']."
NAC0
".$lang['p25_net']."
".htmlspecialchars((string)getActualLink($logLinesP25Gateway, "P25"), ENT_QUOTES, 'UTF-8')."
\n"; +} + +$testMMDVModeNXDN = getConfigItem("NXDN Network", "Enable", $mmdvmconfigs); +if ( isset($configysf2nxdn['Enabled']['Enabled']) ) { if ($configysf2nxdn['Enabled']['Enabled'] == 1) { $testYSF2NXDN = 1; } } +if ( isset($configdmr2nxdn['Enabled']['Enabled']) ) { if ($configdmr2nxdn['Enabled']['Enabled'] == 1) { $testDMR2NXDN = 1; } } +if ( $testMMDVModeNXDN == 1 || isset($testYSF2NXDN) || isset($testDMR2NXDN) ) { //Hide the NXDN information when NXDN Network mode not enabled. + echo "
\n"; + echo "\n"; + if (getConfigItem("NXDN", "RAN", $mmdvmconfigs)) { + echo "\n"; + echo "\n"; + } else { + echo "\n"; + echo "\n"; + } + echo "\n"; + if (file_exists('/etc/nxdngateway')) { + echo "\n"; + } else { + echo "\n"; + } + echo "
".$lang['nxdn_radio']."
RAN".htmlspecialchars((string)getConfigItem("NXDN", "RAN", $mmdvmconfigs), ENT_QUOTES, 'UTF-8')."
".$lang['nxdn_radio']."
RAN0
".$lang['nxdn_net']."
".htmlspecialchars((string)getActualLink($logLinesNXDNGateway, "NXDN"), ENT_QUOTES, 'UTF-8')."
TG 65000
\n"; +} + +$testMMDVModeM17 = getConfigItem("M17 Network", "Enable", $mmdvmconfigs); +if ( $testMMDVModeM17 == 1 ) { //Hide the M17 information when P25 Network mode not enabled. + echo "
\n"; + echo "\n"; + if (getConfigItem("M17", "CAN", $mmdvmconfigs)) { + echo "\n"; + echo "\n"; + } else { + echo "\n"; + echo "\n"; + } + echo "\n"; + echo "\n"; + echo "
".$lang['m17_radio']."
CAN".htmlspecialchars((string)getConfigItem("M17", "CAN", $mmdvmconfigs), ENT_QUOTES, 'UTF-8')."
".$lang['m17_radio']."
CAN0
".$lang['m17_net']."
".htmlspecialchars((string)getActualLink($logLinesM17Gateway, "M17"), ENT_QUOTES, 'UTF-8')."
\n"; +} + +$testMMDVModePOCSAG = getConfigItem("POCSAG Network", "Enable", $mmdvmconfigs); +if ( $testMMDVModePOCSAG == 1 ) { //Hide the POCSAG information when POCSAG Network mode not enabled. + echo "
\n"; + echo "\n"; + echo "\n"; + echo "\n"; + if (isset($configdapnetgateway['DAPNET']['Address'])) { + $dapnetGatewayRemoteAddr = $configdapnetgateway['DAPNET']['Address']; + if (strlen($dapnetGatewayRemoteAddr) > 19) { $dapnetGatewayRemoteAddr = substr($dapnetGatewayRemoteAddr, 0, 17) . '..'; } + echo "\n"; + echo "\n"; + } + echo "
POCSAG
Tx".htmlspecialchars((string)getMHZ(getConfigItem("POCSAG", "Frequency", $mmdvmconfigs)), ENT_QUOTES, 'UTF-8')."
POCSAG Master
".htmlspecialchars((string)$dapnetGatewayRemoteAddr, ENT_QUOTES, 'UTF-8')."
\n"; +} diff --git a/mmdvmhost/tgif_manager.php b/mmdvmhost/tgif_manager.php new file mode 100644 index 0000000..87868a8 --- /dev/null +++ b/mmdvmhost/tgif_manager.php @@ -0,0 +1,192 @@ + 'Continue', + '101' => 'Switching Protocols', + '200' => 'OK', + '201' => 'Created', + '202' => 'Accepted', + '203' => 'Non-Authoritative Information', + '204' => 'No Content', + '205' => 'Reset Content', + '206' => 'Partial Content', + '300' => 'Multiple Choices', + '302' => 'Found', + '303' => 'See Other', + '304' => 'Not Modified', + '305' => 'Use Proxy', + '400' => 'Bad Request', + '401' => 'Unauthorized', + '402' => 'Payment Required', + '403' => 'Forbidden', + '404' => 'Not Found', + '405' => 'Method Not Allowed', + '406' => 'Not Acceptable', + '407' => 'Proxy Authentication Required', + '408' => 'Request Timeout', + '409' => 'Conflict', + '410' => 'Gone', + '411' => 'Length Required', + '412' => 'Precondition Failed', + '413' => 'Request Entity Too Large', + '414' => 'Request-URI Too Long', + '415' => 'Unsupported Media Type', + '416' => 'Requested Range Not Satisfiable', + '417' => 'Expectation Failed', + '500' => 'Internal Server Error', + '501' => 'Not Implemented', + '502' => 'Bad Gateway', + '503' => 'Service Unavailable', + '504' => 'Gateway Timeout', + '505' => 'HTTP Version Not Supported' + ); + // Caste the status code to a string. + $code = preg_replace("/[^0-9]/", "", $code); + $code = (string)$code; + // Determine if it exists in the array. + if(array_key_exists($code, $statuslist) ) { + // Return the status text + return $statuslist[$code]; + } else { + // If it doesn't exists, degrade by returning the code. + return $code; + } + } + + // Set some Variable + $dmrID = ""; + + // Check if DMR is Enabled + $testMMDVModeDMR = getConfigItem("DMR", "Enable", $mmdvmconfigs); + + if ( $testMMDVModeDMR == 1 ) { + //Load the dmrgateway config file + $dmrGatewayConfigFile = '/etc/dmrgateway'; + if (fopen($dmrGatewayConfigFile,'r')) { $configdmrgateway = parse_ini_file($dmrGatewayConfigFile, true); } + + // Get the current DMR Master from the config + $dmrMasterHost = getConfigItem("DMR Network", "Address", $mmdvmconfigs); + if ( $dmrMasterHost == '127.0.0.1' ) { + // DMRGateway, need to check each config + if (isset($configdmrgateway['DMR Network 1']['Address'])) { + if (($configdmrgateway['DMR Network 1']['Address'] == "tgif.network") && ($configdmrgateway['DMR Network 1']['Enabled'])) { + $dmrID = $configdmrgateway['DMR Network 1']['Id']; + } + } + if (isset($configdmrgateway['DMR Network 2']['Address'])) { + if (($configdmrgateway['DMR Network 2']['Address'] == "tgif.network") && ($configdmrgateway['DMR Network 2']['Enabled'])) { + $dmrID = $configdmrgateway['DMR Network 2']['Id']; + } + } + if (isset($configdmrgateway['DMR Network 3']['Address'])) { + if (($configdmrgateway['DMR Network 3']['Address'] == "tgif.network") && ($configdmrgateway['DMR Network 3']['Enabled'])) { + $dmrID = $configdmrgateway['DMR Network 3']['Id']; + } + } + if (isset($configdmrgateway['DMR Network 4']['Address'])) { + if (($configdmrgateway['DMR Network 4']['Address'] == "tgif.network") && ($configdmrgateway['DMR Network 4']['Enabled'])) { + $dmrID = $configdmrgateway['DMR Network 4']['Id']; + } + } + if (isset($configdmrgateway['DMR Network 5']['Address'])) { + if (($configdmrgateway['DMR Network 5']['Address'] == "tgif.network") && ($configdmrgateway['DMR Network 5']['Enabled'])) { + $dmrID = $configdmrgateway['DMR Network 5']['Id']; + } + } + } else if ( $dmrMasterHost == 'tgif.network' ) { + // MMDVMHost Connected directly to TGIF, get the ID form here + if (getConfigItem("DMR", "Id", $mmdvmconfigs)) { + $dmrID = getConfigItem("DMR", "Id", $mmdvmconfigs); + } else { + $dmrID = getConfigItem("General", "Id", $mmdvmconfigs); + } + } + } + + if ( $dmrID ) { + // Work out if the data has been posted or not + if ( !empty($_POST) && isset($_POST["tgifSubmit"]) ): // Data has been posted for this page + // Are we a repeater + if ( getConfigItem("DMR Network", "Slot1", $mmdvmconfigs) == "0" ) { + $targetSlot = "1"; + } else { + $targetSlot = preg_replace("/[^0-9]/", "", $_POST["tgifSlot"]); + $targetSlot--; + } + // Figure out what has been posted + if ( (isset($_POST["tgifNumber"])) && (isset($_POST["tgifSubmit"])) ) { + $targetTG = preg_replace("/[^0-9]/", "", $_POST["tgifNumber"]); + if ($targetTG < 1) { $targetTG = "4000"; } + } else { + $targetTG = "4000"; + } + if ($_POST["tgifAction"] == "UNLINK") { $targetTG = "4000"; } + // Perform the GET request + $tgifApiUrl = "http://tgif.network:5040/api/sessions/update/".$dmrID."/".$targetSlot."/".$targetTG; + $result = file_get_contents($tgifApiUrl); + // Output to the browser + echo 'TGIF Manager'."\n"; + echo "\n\n\n
Command Output
"; + //echo "Sending command to TGIF API"; + echo "TGIF API: "; + echo httpStatusText($result); + echo "
\n"; + echo "
\n"; + // Clean up... + unset($_POST); + echo ''; + else: // Do this when we are not handling post data + echo 'TGIF Manager'."\n"; + echo '
'."\n"; + echo csrf_field_html()."\n"; + echo ' + + + + + + + + + + + + +
Talkgroup NumberEnter the Talkgroup numberSlotWhere to link/unlinkLink / UnlinkLink or unlinkActionTake Action
TS1 TS2Link UnLink

'."\n"; + endif; + } +} diff --git a/mmdvmhost/tools.php b/mmdvmhost/tools.php new file mode 100644 index 0000000..7050b9f --- /dev/null +++ b/mmdvmhost/tools.php @@ -0,0 +1,129 @@ + 0) { + $uptimeString .= $days; + $uptimeString .= (($days == 1) ? " day" : " days"); + } + if ($hours > 0) { + $uptimeString .= (($days > 0) ? ", " : "") . $hours; + $uptimeString .= (($hours == 1) ? " hr" : " hrs"); + } + if ($mins > 0) { + $uptimeString .= (($days > 0 || $hours > 0) ? ", " : "") . $mins; + $uptimeString .= (($mins == 1) ? " min" : " mins"); + } + if ($secs > 0) { + $uptimeString .= (($days > 0 || $hours > 0 || $mins > 0) ? ", " : "") . $secs; + $uptimeString .= (($secs == 1) ? " s" : " s"); + } + return $uptimeString; +} + +function startsWith($haystack, $needle) +{ + return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false; +} + +function getMHZ($freq) +{ + return substr($freq,0,3) . "." . substr($freq,3,6) . " MHz"; +} + +function isProcessRunning($processName, $full = false, $refresh = false) +{ + if ($full) { + static $processes_full = array(); + if ($refresh) $processes_full = array(); + if (empty($processes_full)) + exec('ps -eo args', $processes_full); + } else { + static $processes = array(); + if ($refresh) $processes = array(); + if (empty($processes)) + exec('ps -eo comm', $processes); + } + foreach (($full ? $processes_full : $processes) as $processString) { + if (strpos($processString, $processName) !== false) + return true; + } + return false; +} + +function createConfigLines() +{ + $out =""; + foreach($_GET as $key=>$val) { + if($key != "cmd") { + $out .= "define(\"$key\", \"$val\");"."\n"; + } + } + return $out; +} + +function getSize($filesize, $precision = 2) +{ + $units = array('', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'); + foreach ($units as $idUnit => $unit) { + if ($filesize > 1024) + $filesize /= 1024; + else + break; + } + return round($filesize, $precision).' '.$units[$idUnit].'B'; +} + +function checkSetup() +{ + $el = error_reporting(); + error_reporting(E_ERROR | E_WARNING | E_PARSE); + if (defined(DISTRIBUTION)) { +?> + + + + Link|UnLink + * ` against the YSFGateway remote-control port configured in + * /etc/ysfgateway. Triggers a 2 s `setTimeout` reload. + * + * Inputs: + * /etc/ysfgateway Gateway config + remote port. Remote + * Commands must be enabled there for + * this form to do anything. + * /usr/local/etc/YSFHosts.txt Reflector picklist. + * /usr/local/etc/FCSHosts.txt Optional FCS rooms picklist. + * + * NOTE for the security pass: validation is whitelist-regex; no + * setEmbeddableSecurityHeaders() call in this file. Coverage gap. + */ + +if ($_SERVER["PHP_SELF"] == "/admin/index.php") { // Stop this working outside of the admin page + include_once $_SERVER['DOCUMENT_ROOT'].'/config/config.php'; // MMDVMDash Config + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/tools.php'; // MMDVMDash Tools + include_once $_SERVER['DOCUMENT_ROOT'].'/mmdvmhost/functions.php'; // MMDVMDash Functions + include_once $_SERVER['DOCUMENT_ROOT'].'/config/language.php'; // Translation Code + + // Check if YSF is Enabled + $testMMDVModeYSF = getConfigItem("System Fusion Network", "Enable", $mmdvmconfigs); + if ( $testMMDVModeYSF == 1 ) { + + //Load the ysfgateway config file + $ysfGatewayConfigFile = '/etc/ysfgateway'; + if (fopen($ysfGatewayConfigFile,'r')) { $configysfgateway = parse_ini_file($ysfGatewayConfigFile, true); } + + // Check that the remote is enabled + if ( $configysfgateway['Remote Commands']['Enable'] == 1 ) { + $remotePort = $configysfgateway['Remote Commands']['Port']; + if (!empty($_POST) && isset($_POST["ysfMgrSubmit"])) { + // Handle Posted Data + if (preg_match('/[^A-Za-z0-9]/',$_POST['ysfLinkHost'])) { unset ($_POST['ysfLinkHost']);} + if ($_POST["Link"] == "LINK") { + if ($_POST['ysfLinkHost'] == "none") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." UnLink"; + } else { + $formattedLinkTarget = preg_replace('/([a-zA-Z]+)([0-9]+)/', '$1 $2', $_POST['ysfLinkHost']); + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." Link".$formattedLinkTarget; + } + } elseif ($_POST["Link"] == "UNLINK") { + $remoteCommand = "cd /var/log/pi-star && sudo /usr/local/bin/RemoteCommand ".$remotePort." UnLink"; + } else { + echo "YSF Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (Neither Link nor Unlink Sent) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (empty($_POST['ysfLinkHost'])) { + echo "YSF Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo "Somthing wrong with your input, (No target specified) - please try again"; + echo "
\n
\n"; + unset($_POST); + echo ''; + } + if (isset($remoteCommand)) { + echo "YSF Link Manager\n"; + echo "\n\n\n
Command Output
"; + echo htmlspecialchars((string)exec($remoteCommand), ENT_QUOTES, 'UTF-8'); + echo "
\n
\n"; + echo ''; + } + } else { + // Output HTML + ?> + YSF Link Manager + + + + + + + + + + + + + +
ReflectorReflectorLink / Un-LinkLink / Un-LinkActionAction
+ + Link + UnLink + + +
+ +
+ {"use strict";var e={d:(t,i)=>{for(var s in i)e.o(i,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:i[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};function i(e){var t=document.createEvent("MouseEvents");t.initEvent("click",!0,!1),e.dispatchEvent(t)}function s(e){var t=document.createEvent("HTMLEvents");t.initEvent("change",!0,!1),e.dispatchEvent(t)}function o(e){var t=document.createEvent("FocusEvent");t.initEvent("focusin",!0,!1),e.dispatchEvent(t)}function n(e){var t=document.createEvent("FocusEvent");t.initEvent("focusout",!0,!1),e.dispatchEvent(t)}function d(e,t){return e.getAttribute(t)}function r(e,t){return!!e&&e.classList.contains(t)}function l(e,t){if(e)return e.classList.add(t)}function a(e,t){if(e)return e.classList.remove(t)}e.r(t),e.d(t,{default:()=>p,bind:()=>u});var c={data:null,searchable:!1};function p(e,t){this.el=e,this.config=Object.assign({},c,t||{}),this.data=this.config.data,this.selectedOptions=[],this.placeholder=d(this.el,"placeholder")||this.config.placeholder||"Select an option",this.dropdown=null,this.multiple=d(this.el,"multiple"),this.disabled=d(this.el,"disabled"),this.create()}function u(e,t){return new p(e,t)}return p.prototype.create=function(){this.el.style.display="none",this.data?this.processData(this.data):this.extractData(),this.renderDropdown(),this.bindEvent()},p.prototype.processData=function(e){var t=[];e.forEach((e=>{t.push({data:e,attributes:{selected:!1,disabled:!1,optgroup:"optgroup"==e.value}})})),this.options=t},p.prototype.extractData=function(){var e=this.el.querySelectorAll("option,optgroup"),t=[],i=[],s=[];e.forEach((e=>{if("OPTGROUP"==e.tagName)var s={text:e.label,value:"optgroup"};else s={text:e.innerText,value:e.value};var o={selected:null!=e.getAttribute("selected"),disabled:null!=e.getAttribute("disabled"),optgroup:"OPTGROUP"==e.tagName};t.push(s),i.push({data:s,attributes:o})})),this.data=t,this.options=i,this.options.forEach((function(e){e.attributes.selected&&s.push(e)})),this.selectedOptions=s},p.prototype.renderDropdown=function(){var e=`
\n \n
\n ${this.config.searchable?'':""}\n
    \n
    \n`;this.el.insertAdjacentHTML("afterend",e),this.dropdown=this.el.nextElementSibling,this._renderSelectedItems(),this._renderItems()},p.prototype._renderSelectedItems=function(){if(this.multiple){var e="";"auto"==window.getComputedStyle(this.dropdown).width||this.selectedOptions.length<2?(this.selectedOptions.forEach((function(t){e+=`${t.data.text}`})),e=""==e?this.placeholder:e):e=this.selectedOptions.length+" selected",this.dropdown.querySelector(".multiple-options").innerHTML=e}else{var t=this.selectedOptions.length>0?this.selectedOptions[0].data.text:this.placeholder;this.dropdown.querySelector(".current").innerHTML=t}},p.prototype._renderItems=function(){var e=this.dropdown.querySelector("ul");this.options.forEach((t=>{e.appendChild(this._renderItem(t))}))},p.prototype._renderItem=function(e){var t=document.createElement("li");if(t.innerHTML=e.data.text,e.attributes.optgroup)t.classList.add("optgroup");else{t.setAttribute("data-value",e.data.value);var i=["option",e.attributes.selected?"selected":null,e.attributes.disabled?"disabled":null];t.addEventListener("click",this._onItemClicked.bind(this,e)),t.classList.add(...i)}return e.element=t,t},p.prototype.update=function(){if(this.extractData(),this.dropdown){var e=r(this.dropdown,"open");this.dropdown.parentNode.removeChild(this.dropdown),this.create(),e&&i(this.dropdown)}},p.prototype.disable=function(){this.disabled||(this.disabled=!0,l(this.dropdown,"disabled"))},p.prototype.enable=function(){this.disabled&&(this.disabled=!1,a(this.dropdown,"disabled"))},p.prototype.clear=function(){this.selectedOptions=[],this._renderSelectedItems(),this.updateSelectValue(),s(this.el)},p.prototype.destroy=function(){this.dropdown&&(this.dropdown.parentNode.removeChild(this.dropdown),this.el.style.display="")},p.prototype.bindEvent=function(){this.dropdown.addEventListener("click",this._onClicked.bind(this)),this.dropdown.addEventListener("keydown",this._onKeyPressed.bind(this)),this.dropdown.addEventListener("focusin",o.bind(this,this.el)),this.dropdown.addEventListener("focusout",n.bind(this,this.el)),window.addEventListener("click",this._onClickedOutside.bind(this)),this.config.searchable&&this._bindSearchEvent()},p.prototype._bindSearchEvent=function(){var e=this.dropdown.querySelector(".nice-select-search");e&&e.addEventListener("click",(function(e){return e.stopPropagation(),!1})),e.addEventListener("input",this._onSearchChanged.bind(this))},p.prototype._onClicked=function(e){if(this.multiple?this.dropdown.classList.add("open"):this.dropdown.classList.toggle("open"),this.dropdown.classList.contains("open")){var t=this.dropdown.querySelector(".nice-select-search");t&&(t.value="",t.focus());var i=this.dropdown.querySelector(".focus");a(i,"focus"),l(i=this.dropdown.querySelector(".selected"),"focus"),this.dropdown.querySelectorAll("ul li").forEach((function(e){e.style.display=""}))}else this.dropdown.focus()},p.prototype._onItemClicked=function(e,t){var i=t.target;r(i,"disabled")||(this.multiple?r(i,"selected")?(a(i,"selected"),this.selectedOptions.splice(this.selectedOptions.indexOf(e),1),this.el.querySelector('option[value="'+i.dataset.value+'"]').selected=!1):(l(i,"selected"),this.selectedOptions.push(e)):(this.selectedOptions.forEach((function(e){a(e.element,"selected")})),l(i,"selected"),this.selectedOptions=[e]),this._renderSelectedItems(),this.updateSelectValue())},p.prototype.updateSelectValue=function(){if(this.multiple){var e=this.el;this.selectedOptions.forEach((function(t){var i=e.querySelector('option[value="'+t.data.value+'"]');i&&i.setAttribute("selected",!0)}))}else this.selectedOptions.length>0&&(this.el.value=this.selectedOptions[0].data.value);s(this.el)},p.prototype._onClickedOutside=function(e){this.dropdown.contains(e.target)||this.dropdown.classList.remove("open")},p.prototype._onKeyPressed=function(e){var t=this.dropdown.querySelector(".focus"),s=this.dropdown.classList.contains("open");if(32==e.keyCode||13==e.keyCode)i(s?t:this.dropdown);else if(40==e.keyCode){if(s){var o=this._findNext(t);o&&(a(this.dropdown.querySelector(".focus"),"focus"),l(o,"focus"))}else i(this.dropdown);e.preventDefault()}else if(38==e.keyCode){if(s){var n=this._findPrev(t);n&&(a(this.dropdown.querySelector(".focus"),"focus"),l(n,"focus"))}else i(this.dropdown);e.preventDefault()}else 27==e.keyCode&&s&&i(this.dropdown);return!1},p.prototype._findNext=function(e){for(e=e?e.nextElementSibling:this.dropdown.querySelector(".list .option");e;){if(!r(e,"disabled")&&"none"!=e.style.display)return e;e=e.nextElementSibling}return null},p.prototype._findPrev=function(e){for(e=e?e.previousElementSibling:this.dropdown.querySelector(".list .option:last-child");e;){if(!r(e,"disabled")&&"none"!=e.style.display)return e;e=e.previousElementSibling}return null},p.prototype._onSearchChanged=function(e){var t=this.dropdown.classList.contains("open"),i=e.target.value;if(""==(i=i.toLowerCase()))this.options.forEach((function(e){e.element.style.display=""}));else if(t){var s=new RegExp(i);this.options.forEach((function(e){var t=e.data.text.toLowerCase(),i=s.test(t);e.element.style.display=i?"":"none"}))}this.dropdown.querySelectorAll(".focus").forEach((function(e){a(e,"focus")})),l(this._findNext(null),"focus")},t})()})); \ No newline at end of file diff --git a/plotly-basic.min.js b/plotly-basic.min.js new file mode 100644 index 0000000..89bff42 --- /dev/null +++ b/plotly-basic.min.js @@ -0,0 +1,24 @@ +/** +* plotly.js (basic - minified) v2.7.0 +* Copyright 2012-2021, Plotly, Inc. +* All rights reserved. +* Licensed under the MIT license +*/ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Plotly=e()}}((function(){return function e(t,r,n){function a(o,l){if(!r[o]){if(!t[o]){var s="function"==typeof require&&require;if(!l&&s)return s(o,!0);if(i)return i(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var u=r[o]={exports:{}};t[o][0].call(u.exports,(function(e){return a(t[o][1][e]||e)}),u,u.exports,e,t,r,n)}return r[o].exports}for(var i="function"==typeof require&&require,o=0;o:not(.watermark)":"opacity:0;-webkit-transition:opacity .3s ease 0s;-moz-transition:opacity .3s ease 0s;-ms-transition:opacity .3s ease 0s;-o-transition:opacity .3s ease 0s;transition:opacity .3s ease 0s;","X:hover .modebar--hover .modebar-group":"opacity:1;","X .modebar-group":"float:left;display:inline-block;box-sizing:border-box;padding-left:8px;position:relative;vertical-align:middle;white-space:nowrap;","X .modebar-btn":"position:relative;font-size:16px;padding:3px 4px;height:22px;cursor:pointer;line-height:normal;box-sizing:border-box;","X .modebar-btn svg":"position:relative;top:2px;","X .modebar.vertical":"display:flex;flex-direction:column;flex-wrap:wrap;align-content:flex-end;max-height:100%;","X .modebar.vertical svg":"top:-1px;","X .modebar.vertical .modebar-group":"display:block;float:none;padding-left:0px;padding-bottom:8px;","X .modebar.vertical .modebar-group .modebar-btn":"display:block;text-align:center;","X [data-title]:before,X [data-title]:after":"position:absolute;-webkit-transform:translate3d(0, 0, 0);-moz-transform:translate3d(0, 0, 0);-ms-transform:translate3d(0, 0, 0);-o-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);display:none;opacity:0;z-index:1001;pointer-events:none;top:110%;right:50%;","X [data-title]:hover:before,X [data-title]:hover:after":"display:block;opacity:1;","X [data-title]:before":'content:"";position:absolute;background:transparent;border:6px solid transparent;z-index:1002;margin-top:-12px;border-bottom-color:#69738a;margin-right:-6px;',"X [data-title]:after":"content:attr(data-title);background:#69738a;color:#fff;padding:8px 10px;font-size:12px;line-height:12px;white-space:nowrap;margin-right:-18px;border-radius:2px;","X .vertical [data-title]:before,X .vertical [data-title]:after":"top:0%;right:200%;","X .vertical [data-title]:before":"border:6px solid transparent;border-left-color:#69738a;margin-top:8px;margin-right:-30px;","X .select-outline":"fill:none;stroke-width:1;shape-rendering:crispEdges;","X .select-outline-1":"stroke:#fff;","X .select-outline-2":"stroke:#000;stroke-dasharray:2px 2px;",Y:'font-family:"Open Sans",verdana,arial,sans-serif;position:fixed;top:50px;right:20px;z-index:10000;font-size:10pt;max-width:180px;',"Y p":"margin:0;","Y .notifier-note":"min-width:180px;max-width:250px;border:1px solid #fff;z-index:3000;margin:0;background-color:#8c97af;background-color:rgba(140,151,175,.9);color:#fff;padding:10px;overflow-wrap:break-word;word-wrap:break-word;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto;","Y .notifier-close":"color:#fff;opacity:.8;float:right;padding:0 5px;background:none;border:none;font-size:20px;font-weight:bold;line-height:20px;","Y .notifier-close:hover":"color:#444;text-decoration:none;cursor:pointer;"};for(var i in a){var o=i.replace(/^,/," ,").replace(/X/g,".js-plotly-plot .plotly").replace(/Y/g,".plotly-notifier");n.addStyleRule(o,a[i])}},{"../src/lib":232}],2:[function(e,t,r){"use strict";t.exports=e("../src/transforms/aggregate")},{"../src/transforms/aggregate":386}],3:[function(e,t,r){"use strict";t.exports=e("../src/traces/bar")},{"../src/traces/bar":336}],4:[function(e,t,r){"use strict";t.exports=e("../src/components/calendars")},{"../src/components/calendars":100}],5:[function(e,t,r){"use strict";t.exports=e("../src/core")},{"../src/core":214}],6:[function(e,t,r){"use strict";t.exports=e("../src/transforms/filter")},{"../src/transforms/filter":387}],7:[function(e,t,r){"use strict";t.exports=e("../src/transforms/groupby")},{"../src/transforms/groupby":388}],8:[function(e,t,r){"use strict";var n=e("./core");n.register([e("./bar"),e("./pie"),e("./aggregate"),e("./filter"),e("./groupby"),e("./sort"),e("./calendars")]),t.exports=n},{"./aggregate":2,"./bar":3,"./calendars":4,"./core":5,"./filter":6,"./groupby":7,"./pie":9,"./sort":10}],9:[function(e,t,r){"use strict";t.exports=e("../src/traces/pie")},{"../src/traces/pie":351}],10:[function(e,t,r){"use strict";t.exports=e("../src/transforms/sort")},{"../src/transforms/sort":390}],11:[function(e,t,r){(function(){var e={version:"3.8.0"},r=[].slice,n=function(e){return r.call(e)},a=self.document;function i(e){return e&&(e.ownerDocument||e.document||e).documentElement}function o(e){return e&&(e.ownerDocument&&e.ownerDocument.defaultView||e.document&&e||e.defaultView)}if(a)try{n(a.documentElement.childNodes)[0].nodeType}catch(e){n=function(e){for(var t=e.length,r=new Array(t);t--;)r[t]=e[t];return r}}if(Date.now||(Date.now=function(){return+new Date}),a)try{a.createElement("DIV").style.setProperty("opacity",0,"")}catch(e){var l=this.Element.prototype,s=l.setAttribute,c=l.setAttributeNS,u=this.CSSStyleDeclaration.prototype,d=u.setProperty;l.setAttribute=function(e,t){s.call(this,e,t+"")},l.setAttributeNS=function(e,t,r){c.call(this,e,t,r+"")},u.setProperty=function(e,t,r){d.call(this,e,t+"",r)}}function f(e,t){return et?1:e>=t?0:NaN}function p(e){return null===e?NaN:+e}function h(e){return!isNaN(e)}function m(e){return{left:function(t,r,n,a){for(arguments.length<3&&(n=0),arguments.length<4&&(a=t.length);n>>1;e(t[i],r)<0?n=i+1:a=i}return n},right:function(t,r,n,a){for(arguments.length<3&&(n=0),arguments.length<4&&(a=t.length);n>>1;e(t[i],r)>0?a=i:n=i+1}return n}}}e.ascending=f,e.descending=function(e,t){return te?1:t>=e?0:NaN},e.min=function(e,t){var r,n,a=-1,i=e.length;if(1===arguments.length){for(;++a=n){r=n;break}for(;++an&&(r=n)}else{for(;++a=n){r=n;break}for(;++an&&(r=n)}return r},e.max=function(e,t){var r,n,a=-1,i=e.length;if(1===arguments.length){for(;++a=n){r=n;break}for(;++ar&&(r=n)}else{for(;++a=n){r=n;break}for(;++ar&&(r=n)}return r},e.extent=function(e,t){var r,n,a,i=-1,o=e.length;if(1===arguments.length){for(;++i=n){r=a=n;break}for(;++in&&(r=n),a=n){r=a=n;break}for(;++in&&(r=n),a1)return o/(s-1)},e.deviation=function(){var t=e.variance.apply(this,arguments);return t?Math.sqrt(t):t};var g=m(f);function v(e){return e.length}e.bisectLeft=g.left,e.bisect=e.bisectRight=g.right,e.bisector=function(e){return m(1===e.length?function(t,r){return f(e(t),r)}:e)},e.shuffle=function(e,t,r){(i=arguments.length)<3&&(r=e.length,i<2&&(t=0));for(var n,a,i=r-t;i;)a=Math.random()*i--|0,n=e[i+t],e[i+t]=e[a+t],e[a+t]=n;return e},e.permute=function(e,t){for(var r=t.length,n=new Array(r);r--;)n[r]=e[t[r]];return n},e.pairs=function(e){for(var t=0,r=e.length-1,n=e[0],a=new Array(r<0?0:r);t=0;)for(t=(n=e[a]).length;--t>=0;)r[--o]=n[t];return r};var y=Math.abs;function x(e){for(var t=1;e*t%1;)t*=10;return t}function b(e,t){for(var r in t)Object.defineProperty(e.prototype,r,{value:t[r],enumerable:!1})}function _(){this._=Object.create(null)}e.range=function(e,t,r){if(arguments.length<3&&(r=1,arguments.length<2&&(t=e,e=0)),(t-e)/r==1/0)throw new Error("infinite range");var n,a=[],i=x(y(r)),o=-1;if(e*=i,t*=i,(r*=i)<0)for(;(n=e+r*++o)>t;)a.push(n/i);else for(;(n=e+r*++o)=a.length)return r?r.call(n,i):t?i.sort(t):i;for(var s,c,u,d,f=-1,p=i.length,h=a[l++],m=new _;++f=a.length)return t;var n=[],o=i[r++];return t.forEach((function(t,a){n.push({key:t,values:e(a,r)})})),o?n.sort((function(e,t){return o(e.key,t.key)})):n}(o(e.map,t,0),0)},n.key=function(e){return a.push(e),n},n.sortKeys=function(e){return i[a.length-1]=e,n},n.sortValues=function(e){return t=e,n},n.rollup=function(e){return r=e,n},n},e.set=function(e){var t=new D;if(e)for(var r=0,n=e.length;r=0&&(n=e.slice(r+1),e=e.slice(0,r)),e)return arguments.length<2?this[e].on(n):this[e].on(n,t);if(2===arguments.length){if(null==t)for(e in this)this.hasOwnProperty(e)&&this[e].on(n,null);return this}},e.event=null,e.requote=function(e){return e.replace(H,"\\$&")};var H=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,B={}.__proto__?function(e,t){e.__proto__=t}:function(e,t){for(var r in t)e[r]=t[r]};function Y(e){return B(e,G),e}var V=function(e,t){return t.querySelector(e)},U=function(e,t){return t.querySelectorAll(e)},q=function(e,t){var r=e.matches||e[P(e,"matchesSelector")];return(q=function(e,t){return r.call(e,t)})(e,t)};"function"==typeof Sizzle&&(V=function(e,t){return Sizzle(e,t)[0]||null},U=Sizzle,q=Sizzle.matchesSelector),e.selection=function(){return e.select(a.documentElement)};var G=e.selection.prototype=[];function Z(e){return"function"==typeof e?e:function(){return V(e,this)}}function W(e){return"function"==typeof e?e:function(){return U(e,this)}}G.select=function(e){var t,r,n,a,i=[];e=Z(e);for(var o=-1,l=this.length;++o=0&&"xmlns"!==(r=e.slice(0,t))&&(e=e.slice(t+1)),J.hasOwnProperty(r)?{space:J[r],local:e}:e}},G.attr=function(t,r){if(arguments.length<2){if("string"==typeof t){var n=this.node();return(t=e.ns.qualify(t)).local?n.getAttributeNS(t.space,t.local):n.getAttribute(t)}for(r in t)this.each(Q(r,t[r]));return this}return this.each(Q(t,r))},G.classed=function(e,t){if(arguments.length<2){if("string"==typeof e){var r=this.node(),n=(e=ee(e)).length,a=-1;if(t=r.classList){for(;++a=0;)(r=n[a])&&(i&&i!==r.nextSibling&&i.parentNode.insertBefore(r,i),i=r);return this},G.sort=function(e){e=ce.apply(this,arguments);for(var t=-1,r=this.length;++t=t&&(t=a+1);!(o=l[t])&&++t0&&(t=t.slice(0,o));var s=me.get(t);function c(){var e=this[i];e&&(this.removeEventListener(t,e,e.$),delete this[i])}return s&&(t=s,l=ve),o?r?function(){var e=l(r,n(arguments));c.call(this),this.addEventListener(t,this[i]=e,e.$=a),e._=r}:c:r?R:function(){var r,n=new RegExp("^__on([^.]+)"+e.requote(t)+"$");for(var a in this)if(r=a.match(n)){var i=this[a];this.removeEventListener(r[1],i,i.$),delete this[a]}}}e.selection.enter=de,e.selection.enter.prototype=fe,fe.append=G.append,fe.empty=G.empty,fe.node=G.node,fe.call=G.call,fe.size=G.size,fe.select=function(e){for(var t,r,n,a,i,o=[],l=-1,s=this.length;++l1?Se:e<-1?-Se:Math.asin(e)}function Pe(e){return((e=Math.exp(e))+1/e)/2}var Ie=Math.SQRT2;e.interpolateZoom=function(e,t){var r,n,a=e[0],i=e[1],o=e[2],l=t[0],s=t[1],c=t[2],u=l-a,d=s-i,f=u*u+d*d;if(f<1e-12)n=Math.log(c/o)/Ie,r=function(e){return[a+e*u,i+e*d,o*Math.exp(Ie*e*n)]};else{var p=Math.sqrt(f),h=(c*c-o*o+4*f)/(2*o*2*p),m=(c*c-o*o-4*f)/(2*c*2*p),g=Math.log(Math.sqrt(h*h+1)-h),v=Math.log(Math.sqrt(m*m+1)-m);n=(v-g)/Ie,r=function(e){var t,r=e*n,l=Pe(g),s=o/(2*p)*(l*(t=Ie*r+g,((t=Math.exp(2*t))-1)/(t+1))-function(e){return((e=Math.exp(e))-1/e)/2}(g));return[a+s*u,i+s*d,o*l/Pe(Ie*r+g)]}}return r.duration=1e3*n,r},e.behavior.zoom=function(){var t,r,n,i,l,s,c,u,d,f={x:0,y:0,k:1},p=[960,500],h=Ne,m=250,g=0,v="mousedown.zoom",y="mousemove.zoom",x="mouseup.zoom",b="touchstart.zoom",_=j(w,"zoomstart","zoom","zoomend");function w(e){e.on(v,O).on(ze+".zoom",I).on("dblclick.zoom",R).on(b,P)}function T(e){return[(e[0]-f.x)/f.k,(e[1]-f.y)/f.k]}function M(e){f.k=Math.max(h[0],Math.min(h[1],e))}function k(e,t){t=function(e){return[e[0]*f.k+f.x,e[1]*f.k+f.y]}(t),f.x+=e[0]-t[0],f.y+=e[1]-t[1]}function A(t,n,a,i){t.__chart__={x:f.x,y:f.y,k:f.k},M(Math.pow(2,i)),k(r=n,a),t=e.select(t),m>0&&(t=t.transition().duration(m)),t.call(w.event)}function L(){c&&c.domain(s.range().map((function(e){return(e-f.x)/f.k})).map(s.invert)),d&&d.domain(u.range().map((function(e){return(e-f.y)/f.k})).map(u.invert))}function S(e){g++||e({type:"zoomstart"})}function D(e){L(),e({type:"zoom",scale:f.k,translate:[f.x,f.y]})}function C(e){--g||(e({type:"zoomend"}),r=null)}function O(){var t=this,r=_.of(t,arguments),n=0,a=e.select(o(t)).on(y,s).on(x,c),i=T(e.mouse(t)),l=be(t);function s(){n=1,k(e.mouse(t),i),D(r)}function c(){a.on(y,null).on(x,null),l(n),C(r)}za.call(t),S(r)}function P(){var t,r=this,n=_.of(r,arguments),a={},i=0,o=".zoom-"+e.event.changedTouches[0].identifier,s="touchmove"+o,c="touchend"+o,u=[],d=e.select(r),p=be(r);function h(){var n=e.touches(r);return t=f.k,n.forEach((function(e){e.identifier in a&&(a[e.identifier]=T(e))})),n}function m(){var t=e.event.target;e.select(t).on(s,g).on(c,y),u.push(t);for(var n=e.event.changedTouches,o=0,d=n.length;o1){v=p[0];var x=p[1],b=v[0]-x[0],_=v[1]-x[1];i=b*b+_*_}}function g(){var o,s,c,u,d=e.touches(r);za.call(r);for(var f=0,p=d.length;f360?e-=360:e<0&&(e+=360),e<60?n+(a-n)*e/60:e<180?a:e<240?n+(a-n)*(240-e)/60:n}(e))}return e=isNaN(e)?0:(e%=360)<0?e+360:e,t=isNaN(t)||t<0?0:t>1?1:t,n=2*(r=r<0?0:r>1?1:r)-(a=r<=.5?r*(1+t):r+t-r*t),new Ke(i(e+120),i(e),i(e-120))}function Be(t,r,n){return this instanceof Be?(this.h=+t,this.c=+r,void(this.l=+n)):arguments.length<2?t instanceof Be?new Be(t.h,t.c,t.l):We(t instanceof Ue?t.l:(t=it((t=e.rgb(t)).r,t.g,t.b)).l,t.a,t.b):new Be(t,r,n)}je.brighter=function(e){return e=Math.pow(.7,arguments.length?e:1),new Ee(this.h,this.s,this.l/e)},je.darker=function(e){return e=Math.pow(.7,arguments.length?e:1),new Ee(this.h,this.s,e*this.l)},je.rgb=function(){return He(this.h,this.s,this.l)},e.hcl=Be;var Ye=Be.prototype=new Fe;function Ve(e,t,r){return isNaN(e)&&(e=0),isNaN(t)&&(t=0),new Ue(r,Math.cos(e*=De)*t,Math.sin(e)*t)}function Ue(e,t,r){return this instanceof Ue?(this.l=+e,this.a=+t,void(this.b=+r)):arguments.length<2?e instanceof Ue?new Ue(e.l,e.a,e.b):e instanceof Be?Ve(e.h,e.c,e.l):it((e=Ke(e)).r,e.g,e.b):new Ue(e,t,r)}Ye.brighter=function(e){return new Be(this.h,this.c,Math.min(100,this.l+qe*(arguments.length?e:1)))},Ye.darker=function(e){return new Be(this.h,this.c,Math.max(0,this.l-qe*(arguments.length?e:1)))},Ye.rgb=function(){return Ve(this.h,this.c,this.l).rgb()},e.lab=Ue;var qe=18,Ge=Ue.prototype=new Fe;function Ze(e,t,r){var n=(e+16)/116,a=n+t/500,i=n-r/200;return new Ke(Qe(3.2404542*(a=.95047*Xe(a))-1.5371385*(n=1*Xe(n))-.4985314*(i=1.08883*Xe(i))),Qe(-.969266*a+1.8760108*n+.041556*i),Qe(.0556434*a-.2040259*n+1.0572252*i))}function We(e,t,r){return e>0?new Be(Math.atan2(r,t)*Ce,Math.sqrt(t*t+r*r),e):new Be(NaN,NaN,e)}function Xe(e){return e>.206893034?e*e*e:(e-4/29)/7.787037}function Je(e){return e>.008856?Math.pow(e,1/3):7.787037*e+4/29}function Qe(e){return Math.round(255*(e<=.00304?12.92*e:1.055*Math.pow(e,1/2.4)-.055))}function Ke(e,t,r){return this instanceof Ke?(this.r=~~e,this.g=~~t,void(this.b=~~r)):arguments.length<2?e instanceof Ke?new Ke(e.r,e.g,e.b):nt(""+e,Ke,He):new Ke(e,t,r)}function $e(e){return new Ke(e>>16,e>>8&255,255&e)}function et(e){return $e(e)+""}Ge.brighter=function(e){return new Ue(Math.min(100,this.l+qe*(arguments.length?e:1)),this.a,this.b)},Ge.darker=function(e){return new Ue(Math.max(0,this.l-qe*(arguments.length?e:1)),this.a,this.b)},Ge.rgb=function(){return Ze(this.l,this.a,this.b)},e.rgb=Ke;var tt=Ke.prototype=new Fe;function rt(e){return e<16?"0"+Math.max(0,e).toString(16):Math.min(255,e).toString(16)}function nt(e,t,r){var n,a,i,o=0,l=0,s=0;if(n=/([a-z]+)\((.*)\)/.exec(e=e.toLowerCase()))switch(a=n[2].split(","),n[1]){case"hsl":return r(parseFloat(a[0]),parseFloat(a[1])/100,parseFloat(a[2])/100);case"rgb":return t(lt(a[0]),lt(a[1]),lt(a[2]))}return(i=st.get(e))?t(i.r,i.g,i.b):(null==e||"#"!==e.charAt(0)||isNaN(i=parseInt(e.slice(1),16))||(4===e.length?(o=(3840&i)>>4,o|=o>>4,l=240&i,l|=l>>4,s=15&i,s|=s<<4):7===e.length&&(o=(16711680&i)>>16,l=(65280&i)>>8,s=255&i)),t(o,l,s))}function at(e,t,r){var n,a,i=Math.min(e/=255,t/=255,r/=255),o=Math.max(e,t,r),l=o-i,s=(o+i)/2;return l?(a=s<.5?l/(o+i):l/(2-o-i),n=e==o?(t-r)/l+(t0&&s<1?0:n),new Ee(n,a,s)}function it(e,t,r){var n=Je((.4124564*(e=ot(e))+.3575761*(t=ot(t))+.1804375*(r=ot(r)))/.95047),a=Je((.2126729*e+.7151522*t+.072175*r)/1);return Ue(116*a-16,500*(n-a),200*(a-Je((.0193339*e+.119192*t+.9503041*r)/1.08883)))}function ot(e){return(e/=255)<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)}function lt(e){var t=parseFloat(e);return"%"===e.charAt(e.length-1)?Math.round(2.55*t):t}tt.brighter=function(e){e=Math.pow(.7,arguments.length?e:1);var t=this.r,r=this.g,n=this.b,a=30;return t||r||n?(t&&t=200&&t<300||304===t){try{e=a.call(o,c)}catch(e){return void l.error.call(o,e)}l.load.call(o,e)}else l.error.call(o,c)}return self.XDomainRequest&&!("withCredentials"in c)&&/^(http(s)?:)?\/\//.test(t)&&(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=d:c.onreadystatechange=function(){c.readyState>3&&d()},c.onprogress=function(t){var r=e.event;e.event=t;try{l.progress.call(o,c)}finally{e.event=r}},o.header=function(e,t){return e=(e+"").toLowerCase(),arguments.length<2?s[e]:(null==t?delete s[e]:s[e]=t+"",o)},o.mimeType=function(e){return arguments.length?(r=null==e?null:e+"",o):r},o.responseType=function(e){return arguments.length?(u=e,o):u},o.response=function(e){return a=e,o},["get","post"].forEach((function(e){o[e]=function(){return o.send.apply(o,[e].concat(n(arguments)))}})),o.send=function(e,n,a){if(2===arguments.length&&"function"==typeof n&&(a=n,n=null),c.open(e,t,!0),null==r||"accept"in s||(s.accept=r+",*/*"),c.setRequestHeader)for(var i in s)c.setRequestHeader(i,s[i]);return null!=r&&c.overrideMimeType&&c.overrideMimeType(r),null!=u&&(c.responseType=u),null!=a&&o.on("error",a).on("load",(function(e){a(null,e)})),l.beforesend.call(o,c),c.send(null==n?null:n),o},o.abort=function(){return c.abort(),o},e.rebind(o,l,"on"),null==i?o:o.get(function(e){return 1===e.length?function(t,r){e(null==t?r:null)}:e}(i))}st.forEach((function(e,t){st.set(e,$e(t))})),e.functor=ct,e.xhr=ut(C),e.dsv=function(e,t){var r=new RegExp('["'+e+"\n]"),n=e.charCodeAt(0);function a(e,r,n){arguments.length<3&&(n=r,r=null);var a=dt(e,t,null==r?i:o(r),n);return a.row=function(e){return arguments.length?a.response(null==(r=e)?i:o(e)):r},a}function i(e){return a.parse(e.responseText)}function o(e){return function(t){return a.parse(t.responseText,e)}}function l(t){return t.map(s).join(e)}function s(e){return r.test(e)?'"'+e.replace(/\"/g,'""')+'"':e}return a.parse=function(e,t){var r;return a.parseRows(e,(function(e,n){if(r)return r(e,n-1);var a=function(t){for(var r={},n=e.length,a=0;a=s)return o;if(a)return a=!1,i;var t=c;if(34===e.charCodeAt(t)){for(var r=t;r++24?(isFinite(t)&&(clearTimeout(mt),mt=setTimeout(yt,t)),ht=0):(ht=1,gt(yt))}function xt(){for(var e=Date.now(),t=ft;t;)e>=t.t&&t.c(e-t.t)&&(t.c=null),t=t.n;return e}function bt(){for(var e,t=ft,r=1/0;t;)t.c?(t.t1&&(t=e[i[o-2]],r=e[i[o-1]],n=e[l],(r[0]-t[0])*(n[1]-t[1])-(r[1]-t[1])*(n[0]-t[0])<=0);)--o;i[o++]=l}return i.slice(0,o)}function Mt(e,t){return e[0]-t[0]||e[1]-t[1]}e.timer=function(){vt.apply(this,arguments)},e.timer.flush=function(){xt(),bt()},e.round=function(e,t){return t?Math.round(e*(t=Math.pow(10,t)))/t:Math.round(e)},e.geom={},e.geom.hull=function(e){var t=_t,r=wt;if(arguments.length)return n(e);function n(e){if(e.length<3)return[];var n,a=ct(t),i=ct(r),o=e.length,l=[],s=[];for(n=0;n=0;--n)p.push(e[l[c[n]][2]]);for(n=+d;nMe)l=l.L;else{if(!((a=i-Yt(l,o))>Me)){n>-Me?(t=l.P,r=l):a>-Me?(t=l,r=l.N):t=r=l;break}if(!l.R){t=l;break}l=l.R}var s=Ft(e);if(Ot.insert(t,s),t||r){if(t===r)return Zt(t),r=Ft(t.site),Ot.insert(s,r),s.edge=r.edge=Jt(t.site,s.site),Gt(t),void Gt(r);if(r){Zt(t),Zt(r);var c=t.site,u=c.x,d=c.y,f=e.x-u,p=e.y-d,h=r.site,m=h.x-u,g=h.y-d,v=2*(f*g-p*m),y=f*f+p*p,x=m*m+g*g,b={x:(g*y-p*x)/v+u,y:(f*x-m*y)/v+d};Kt(r.edge,c,h,b),s.edge=Jt(c,e,null,b),r.edge=Jt(e,h,null,b),Gt(t),Gt(r)}else s.edge=Jt(t.site,s.site)}}function Bt(e,t){var r=e.site,n=r.x,a=r.y,i=a-t;if(!i)return n;var o=e.P;if(!o)return-1/0;var l=(r=o.site).x,s=r.y,c=s-t;if(!c)return l;var u=l-n,d=1/i-1/c,f=u/c;return d?(-f+Math.sqrt(f*f-2*d*(u*u/(-2*c)-s+c/2+a-i/2)))/d+n:(n+l)/2}function Yt(e,t){var r=e.N;if(r)return Bt(r,t);var n=e.site;return n.y===t?n.x:1/0}function Vt(e){this.site=e,this.edges=[]}function Ut(e,t){return t.angle-e.angle}function qt(){tr(this),this.x=this.y=this.arc=this.site=this.cy=null}function Gt(e){var t=e.P,r=e.N;if(t&&r){var n=t.site,a=e.site,i=r.site;if(n!==i){var o=a.x,l=a.y,s=n.x-o,c=n.y-l,u=i.x-o,d=2*(s*(g=i.y-l)-c*u);if(!(d>=-1e-12)){var f=s*s+c*c,p=u*u+g*g,h=(g*f-c*p)/d,m=(s*p-u*f)/d,g=m+l,v=zt.pop()||new qt;v.arc=e,v.site=a,v.x=h+o,v.y=g+Math.sqrt(h*h+m*m),v.cy=g,e.circle=v;for(var y=null,x=It._;x;)if(v.y=l)return;if(f>h){if(i){if(i.y>=c)return}else i={x:g,y:s};r={x:g,y:c}}else{if(i){if(i.y1)if(f>h){if(i){if(i.y>=c)return}else i={x:(s-a)/n,y:s};r={x:(c-a)/n,y:c}}else{if(i){if(i.y=l)return}else i={x:o,y:n*o+a};r={x:l,y:n*l+a}}else{if(i){if(i.x0)){if(t/=f,f<0){if(t0){if(t>d)return;t>u&&(u=t)}if(t=a-s,f||!(t<0)){if(t/=f,f<0){if(t>d)return;t>u&&(u=t)}else if(f>0){if(t0)){if(t/=p,p<0){if(t0){if(t>d)return;t>u&&(u=t)}if(t=i-c,p||!(t<0)){if(t/=p,p<0){if(t>d)return;t>u&&(u=t)}else if(p>0){if(t0&&(e.a={x:s+u*f,y:c+u*p}),d<1&&(e.b={x:s+d*f,y:c+d*p}),e}}}}}),s=o.length;s--;)(!Wt(t=o[s],e)||!l(t)||y(t.a.x-t.b.x)Me||y(a-r)>Me)&&(l.splice(o,0,new $t(Qt(i.site,u,y(n-d)Me?{x:d,y:y(t-d)Me?{x:y(r-h)Me?{x:f,y:y(t-f)Me?{x:y(r-p)=r&&c.x<=a&&c.y>=n&&c.y<=o?[[r,o],[a,o],[a,n],[r,n]]:[]).point=e[l]})),t}function l(e){return e.map((function(e,t){return{x:Math.round(n(e,t)/Me)*Me,y:Math.round(a(e,t)/Me)*Me,i:t}}))}return o.links=function(e){return ir(l(e)).edges.filter((function(e){return e.l&&e.r})).map((function(t){return{source:e[t.l.i],target:e[t.r.i]}}))},o.triangles=function(e){var t=[];return ir(l(e)).cells.forEach((function(r,n){for(var a,i,o,l,s=r.site,c=r.edges.sort(Ut),u=-1,d=c.length,f=c[d-1].edge,p=f.l===s?f.r:f.l;++ui||d>o||f=_)<<1|t>=b,T=w+4;wi&&(a=t.slice(i,a),l[o]?l[o]+=a:l[++o]=a),(r=r[0])===(n=n[0])?l[o]?l[o]+=n:l[++o]=n:(l[++o]=null,s.push({i:o,x:hr(r,n)})),i=vr.lastIndex;return im&&(m=s.x),s.y>g&&(g=s.y),c.push(s.x),u.push(s.y);else for(d=0;dm&&(m=b),_>g&&(g=_),c.push(b),u.push(_)}var w=m-p,T=g-h;function M(e,t,r,n,a,i,o,l){if(!isNaN(r)&&!isNaN(n))if(e.leaf){var s=e.x,c=e.y;if(null!=s)if(y(s-r)+y(c-n)<.01)k(e,t,r,n,a,i,o,l);else{var u=e.point;e.x=e.y=e.point=null,k(e,u,s,c,a,i,o,l),k(e,t,r,n,a,i,o,l)}else e.x=r,e.y=n,e.point=t}else k(e,t,r,n,a,i,o,l)}function k(e,t,r,n,a,i,o,l){var s=.5*(a+o),c=.5*(i+l),u=r>=s,d=n>=c,f=d<<1|u;e.leaf=!1,u?a=s:o=s,d?i=c:l=c,M(e=e.nodes[f]||(e.nodes[f]={leaf:!0,nodes:[],point:null,x:null,y:null}),t,r,n,a,i,o,l)}w>T?g=h+w:m=p+T;var A={leaf:!0,nodes:[],point:null,x:null,y:null,add:function(e){M(A,e,+v(e,++d),+x(e,d),p,h,m,g)},visit:function(e){ur(e,A,p,h,m,g)},find:function(e){return dr(A,e[0],e[1],p,h,m,g)}};if(d=-1,null==t){for(;++d=0&&!(n=e.interpolators[a](t,r)););return n}function xr(e,t){var r,n=[],a=[],i=e.length,o=t.length,l=Math.min(e.length,t.length);for(r=0;r=1?1:e(t)}}function Mr(e){return function(t){return 1-e(1-t)}}function kr(e){return function(t){return.5*(t<.5?e(2*t):2-e(2-2*t))}}function Ar(e){return e*e}function Lr(e){return e*e*e}function Sr(e){if(e<=0)return 0;if(e>=1)return 1;var t=e*e,r=t*e;return 4*(e<.5?r:3*(e-t)+r-.75)}function Dr(e){return 1-Math.cos(e*Se)}function Cr(e){return Math.pow(2,10*(e-1))}function Or(e){return 1-Math.sqrt(1-e*e)}function Pr(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375}function Ir(e,t){return t-=e,function(r){return Math.round(e+t*r)}}function Rr(e){var t,r,n,a=[e.a,e.b],i=[e.c,e.d],o=Nr(a),l=zr(a,i),s=Nr(((t=i)[0]+=(n=-l)*(r=a)[0],t[1]+=n*r[1],t))||0;a[0]*i[1]=0?e.slice(0,t):e,a=t>=0?e.slice(t+1):"in";return n=_r.get(n)||br,Tr((a=wr.get(a)||C)(n.apply(null,r.call(arguments,1))))},e.interpolateHcl=function(t,r){t=e.hcl(t),r=e.hcl(r);var n=t.h,a=t.c,i=t.l,o=r.h-n,l=r.c-a,s=r.l-i;isNaN(l)&&(l=0,a=isNaN(a)?r.c:a);isNaN(o)?(o=0,n=isNaN(n)?r.h:n):o>180?o-=360:o<-180&&(o+=360);return function(e){return Ve(n+o*e,a+l*e,i+s*e)+""}},e.interpolateHsl=function(t,r){t=e.hsl(t),r=e.hsl(r);var n=t.h,a=t.s,i=t.l,o=r.h-n,l=r.s-a,s=r.l-i;isNaN(l)&&(l=0,a=isNaN(a)?r.s:a);isNaN(o)?(o=0,n=isNaN(n)?r.h:n):o>180?o-=360:o<-180&&(o+=360);return function(e){return He(n+o*e,a+l*e,i+s*e)+""}},e.interpolateLab=function(t,r){t=e.lab(t),r=e.lab(r);var n=t.l,a=t.a,i=t.b,o=r.l-n,l=r.a-a,s=r.b-i;return function(e){return Ze(n+o*e,a+l*e,i+s*e)+""}},e.interpolateRound=Ir,e.transform=function(t){var r=a.createElementNS(e.ns.prefix.svg,"g");return(e.transform=function(e){if(null!=e){r.setAttribute("transform",e);var t=r.transform.baseVal.consolidate()}return new Rr(t?t.matrix:Fr)})(t)},Rr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var Fr={a:1,b:0,c:0,d:1,e:0,f:0};function Er(e){return e.length?e.pop()+",":""}function jr(t,r){var n=[],a=[];return t=e.transform(t),r=e.transform(r),function(e,t,r,n){if(e[0]!==t[0]||e[1]!==t[1]){var a=r.push("translate(",null,",",null,")");n.push({i:a-4,x:hr(e[0],t[0])},{i:a-2,x:hr(e[1],t[1])})}else(t[0]||t[1])&&r.push("translate("+t+")")}(t.translate,r.translate,n,a),function(e,t,r,n){e!==t?(e-t>180?t+=360:t-e>180&&(e+=360),n.push({i:r.push(Er(r)+"rotate(",null,")")-2,x:hr(e,t)})):t&&r.push(Er(r)+"rotate("+t+")")}(t.rotate,r.rotate,n,a),function(e,t,r,n){e!==t?n.push({i:r.push(Er(r)+"skewX(",null,")")-2,x:hr(e,t)}):t&&r.push(Er(r)+"skewX("+t+")")}(t.skew,r.skew,n,a),function(e,t,r,n){if(e[0]!==t[0]||e[1]!==t[1]){var a=r.push(Er(r)+"scale(",null,",",null,")");n.push({i:a-4,x:hr(e[0],t[0])},{i:a-2,x:hr(e[1],t[1])})}else 1===t[0]&&1===t[1]||r.push(Er(r)+"scale("+t+")")}(t.scale,r.scale,n,a),t=r=null,function(e){for(var t,r=-1,i=a.length;++r0?n=e:(t.c=null,t.t=NaN,t=null,s.end({type:"end",alpha:n=0})):e>0&&(s.start({type:"start",alpha:n=e}),t=vt(l.tick)),l):n},l.start=function(){var e,t,r,n=v.length,s=y.length,u=c[0],h=c[1];for(e=0;e=0;)r.push(a[n])}function $r(e,t){for(var r=[e],n=[];null!=(e=r.pop());)if(n.push(e),(i=e.children)&&(a=i.length))for(var a,i,o=-1;++o=0;)o.push(u=c[s]),u.parent=i,u.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return $r(a,(function(t){var n,a;e&&(n=t.children)&&n.sort(e),r&&(a=t.parent)&&(a.value+=t.value)})),l}return n.sort=function(t){return arguments.length?(e=t,n):e},n.children=function(e){return arguments.length?(t=e,n):t},n.value=function(e){return arguments.length?(r=e,n):r},n.revalue=function(e){return r&&(Kr(e,(function(e){e.children&&(e.value=0)})),$r(e,(function(e){var t;e.children||(e.value=+r.call(n,e,e.depth)||0),(t=e.parent)&&(t.value+=e.value)}))),e},n},e.layout.partition=function(){var t=e.layout.hierarchy(),r=[1,1];function n(e,n){var a=t.call(this,e,n);return function e(t,r,n,a){var i=t.children;if(t.x=r,t.y=t.depth*a,t.dx=n,t.dy=a,i&&(o=i.length)){var o,l,s,c=-1;for(n=t.value?n/t.value:0;++cl&&(l=n),o.push(n)}for(r=0;ra&&(n=r,a=t);return n}function hn(e){return e.reduce(mn,0)}function mn(e,t){return e+t[1]}function gn(e,t){return vn(e,Math.ceil(Math.log(t.length)/Math.LN2+1))}function vn(e,t){for(var r=-1,n=+e[0],a=(e[1]-n)/t,i=[];++r<=t;)i[r]=a*r+n;return i}function yn(t){return[e.min(t),e.max(t)]}function xn(e,t){return e.value-t.value}function bn(e,t){var r=e._pack_next;e._pack_next=t,t._pack_prev=e,t._pack_next=r,r._pack_prev=t}function _n(e,t){e._pack_next=t,t._pack_prev=e}function wn(e,t){var r=t.x-e.x,n=t.y-e.y,a=e.r+t.r;return.999*a*a>r*r+n*n}function Tn(e){if((t=e.children)&&(s=t.length)){var t,r,n,a,i,o,l,s,c=1/0,u=-1/0,d=1/0,f=-1/0;if(t.forEach(Mn),(r=t[0]).x=-r.r,r.y=0,x(r),s>1&&((n=t[1]).x=n.r,n.y=0,x(n),s>2))for(An(r,n,a=t[2]),x(a),bn(r,a),r._pack_prev=a,bn(a,n),n=r._pack_next,i=3;i0)for(o=-1;++o=d[0]&&s<=d[1]&&((l=c[e.bisect(f,s,1,h)-1]).y+=m,l.push(i[o]));return c}return i.value=function(e){return arguments.length?(r=e,i):r},i.range=function(e){return arguments.length?(n=ct(e),i):n},i.bins=function(e){return arguments.length?(a="number"==typeof e?function(t){return vn(t,e)}:ct(e),i):a},i.frequency=function(e){return arguments.length?(t=!!e,i):t},i},e.layout.pack=function(){var t,r=e.layout.hierarchy().sort(xn),n=0,a=[1,1];function i(e,i){var o=r.call(this,e,i),l=o[0],s=a[0],c=a[1],u=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(l.x=l.y=0,$r(l,(function(e){e.r=+u(e.value)})),$r(l,Tn),n){var d=n*(t?1:Math.max(2*l.r/s,2*l.r/c))/2;$r(l,(function(e){e.r+=d})),$r(l,Tn),$r(l,(function(e){e.r-=d}))}return function e(t,r,n,a){var i=t.children;if(t.x=r+=a*t.x,t.y=n+=a*t.y,t.r*=a,i)for(var o=-1,l=i.length;++op.x&&(p=e),e.depth>h.depth&&(h=e)}));var m=r(f,p)/2-f.x,g=n[0]/(p.x+r(p,f)/2+m),v=n[1]/(h.depth||1);Kr(u,(function(e){e.x=(e.x+m)*g,e.y=e.depth*v}))}return c}function o(e){var t=e.children,n=e.parent.children,a=e.i?n[e.i-1]:null;if(t.length){!function(e){var t,r=0,n=0,a=e.children,i=a.length;for(;--i>=0;)(t=a[i]).z+=r,t.m+=r,r+=t.s+(n+=t.c)}(e);var i=(t[0].z+t[t.length-1].z)/2;a?(e.z=a.z+r(e._,a._),e.m=e.z-i):e.z=i}else a&&(e.z=a.z+r(e._,a._));e.parent.A=function(e,t,n){if(t){for(var a,i=e,o=e,l=t,s=i.parent.children[0],c=i.m,u=o.m,d=l.m,f=s.m;l=Dn(l),i=Sn(i),l&&i;)s=Sn(s),(o=Dn(o)).a=e,(a=l.z+d-i.z-c+r(l._,i._))>0&&(Cn(On(l,e,n),e,a),c+=a,u+=a),d+=l.m,c+=i.m,f+=s.m,u+=o.m;l&&!Dn(o)&&(o.t=l,o.m+=d-u),i&&!Sn(s)&&(s.t=i,s.m+=c-f,n=e)}return n}(e,a,e.parent.A||n[0])}function l(e){e._.x=e.z+e.parent.m,e.m+=e.parent.m}function s(e){e.x*=n[0],e.y=e.depth*n[1]}return i.separation=function(e){return arguments.length?(r=e,i):r},i.size=function(e){return arguments.length?(a=null==(n=e)?s:null,i):a?null:n},i.nodeSize=function(e){return arguments.length?(a=null==(n=e)?null:s,i):a?n:null},Qr(i,t)},e.layout.cluster=function(){var t=e.layout.hierarchy().sort(null).value(null),r=Ln,n=[1,1],a=!1;function i(i,o){var l,s=t.call(this,i,o),c=s[0],u=0;$r(c,(function(t){var n=t.children;n&&n.length?(t.x=function(e){return e.reduce((function(e,t){return e+t.x}),0)/e.length}(n),t.y=function(t){return 1+e.max(t,(function(e){return e.y}))}(n)):(t.x=l?u+=r(t,l):0,t.y=0,l=t)}));var d=function e(t){var r=t.children;return r&&r.length?e(r[0]):t}(c),f=function e(t){var r,n=t.children;return n&&(r=n.length)?e(n[r-1]):t}(c),p=d.x-r(d,f)/2,h=f.x+r(f,d)/2;return $r(c,a?function(e){e.x=(e.x-c.x)*n[0],e.y=(c.y-e.y)*n[1]}:function(e){e.x=(e.x-p)/(h-p)*n[0],e.y=(1-(c.y?e.y/c.y:1))*n[1]}),s}return i.separation=function(e){return arguments.length?(r=e,i):r},i.size=function(e){return arguments.length?(a=null==(n=e),i):a?null:n},i.nodeSize=function(e){return arguments.length?(a=null!=(n=e),i):a?n:null},Qr(i,t)},e.layout.treemap=function(){var t,r=e.layout.hierarchy(),n=Math.round,a=[1,1],i=null,o=Pn,l=!1,s="squarify",c=.5*(1+Math.sqrt(5));function u(e,t){for(var r,n,a=-1,i=e.length;++a0;)l.push(r=c[a-1]),l.area+=r.area,"squarify"!==s||(n=p(l,m))<=f?(c.pop(),f=n):(l.area-=l.pop().area,h(l,m,i,!1),m=Math.min(i.dx,i.dy),l.length=l.area=0,f=1/0);l.length&&(h(l,m,i,!0),l.length=l.area=0),t.forEach(d)}}function f(e){var t=e.children;if(t&&t.length){var r,n=o(e),a=t.slice(),i=[];for(u(a,n.dx*n.dy/e.value),i.area=0;r=a.pop();)i.push(r),i.area+=r.area,null!=r.z&&(h(i,r.z?n.dx:n.dy,n,!a.length),i.length=i.area=0);t.forEach(f)}}function p(e,t){for(var r,n=e.area,a=0,i=1/0,o=-1,l=e.length;++oa&&(a=r));return t*=t,(n*=n)?Math.max(t*a*c/n,n/(t*i*c)):1/0}function h(e,t,r,a){var i,o=-1,l=e.length,s=r.x,c=r.y,u=t?n(e.area/t):0;if(t==r.dx){for((a||u>r.dy)&&(u=r.dy);++or.dx)&&(u=r.dx);++o1);return e+t*r*Math.sqrt(-2*Math.log(a)/a)}},logNormal:function(){var t=e.random.normal.apply(e,arguments);return function(){return Math.exp(t())}},bates:function(t){var r=e.random.irwinHall(t);return function(){return r()/t}},irwinHall:function(e){return function(){for(var t=0,r=0;r2?Hn:Nn,l=a?Br:Hr;return i=e(t,r,l,n),o=e(r,t,l,yr),s}function s(e){return i(e)}return s.invert=function(e){return o(e)},s.domain=function(e){return arguments.length?(t=e.map(Number),l()):t},s.range=function(e){return arguments.length?(r=e,l()):r},s.rangeRound=function(e){return s.range(e).interpolate(Ir)},s.clamp=function(e){return arguments.length?(a=e,l()):a},s.interpolate=function(e){return arguments.length?(n=e,l()):n},s.ticks=function(e){return Un(t,e)},s.tickFormat=function(e,r){return d3_scale_linearTickFormat(t,e,r)},s.nice=function(e){return Yn(t,e),l()},s.copy=function(){return e(t,r,n,a)},l()}([0,1],[0,1],yr,!1)};e.scale.log=function(){return function e(t,r,n,a){function i(e){return(n?Math.log(e<0?0:e):-Math.log(e>0?0:-e))/Math.log(r)}function o(e){return n?Math.pow(r,e):-Math.pow(r,-e)}function l(e){return t(i(e))}return l.invert=function(e){return o(t.invert(e))},l.domain=function(e){return arguments.length?(n=e[0]>=0,t.domain((a=e.map(Number)).map(i)),l):a},l.base=function(e){return arguments.length?(r=+e,t.domain(a.map(i)),l):r},l.nice=function(){var e=Fn(a.map(i),n?Math:qn);return t.domain(e),a=e.map(o),l},l.ticks=function(){var e=Rn(a),t=[],l=e[0],s=e[1],c=Math.floor(i(l)),u=Math.ceil(i(s)),d=r%1?2:r;if(isFinite(u-c)){if(n){for(;c0;f--)t.push(o(c)*f);for(c=0;t[c]s;u--);t=t.slice(c,u)}return t},l.copy=function(){return e(t.copy(),r,n,a)},Bn(l,t)}(e.scale.linear().domain([0,1]),10,!0,[1,10])};var qn={floor:function(e){return-Math.ceil(-e)},ceil:function(e){return-Math.floor(-e)}};function Gn(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}e.scale.pow=function(){return function e(t,r,n){var a=Gn(r),i=Gn(1/r);function o(e){return t(a(e))}return o.invert=function(e){return i(t.invert(e))},o.domain=function(e){return arguments.length?(t.domain((n=e.map(Number)).map(a)),o):n},o.ticks=function(e){return Un(n,e)},o.tickFormat=function(e,t){return d3_scale_linearTickFormat(n,e,t)},o.nice=function(e){return o.domain(Yn(n,e))},o.exponent=function(e){return arguments.length?(a=Gn(r=e),i=Gn(1/r),t.domain(n.map(a)),o):r},o.copy=function(){return e(t.copy(),r,n)},Bn(o,t)}(e.scale.linear(),1,[0,1])},e.scale.sqrt=function(){return e.scale.pow().exponent(.5)},e.scale.ordinal=function(){return function t(r,n){var a,i,o;function l(e){return i[((a.get(e)||("range"===n.t?a.set(e,r.push(e)):NaN))-1)%i.length]}function s(t,n){return e.range(r.length).map((function(e){return t+n*e}))}return l.domain=function(e){if(!arguments.length)return r;r=[],a=new _;for(var t,i=-1,o=e.length;++i0?a[e-1]:r[0],ed?0:1;if(c=Le)return s(c,p)+(l?s(l,1-p):"")+"Z";var h,m,g,v,y,x,b,_,w,T,M,k,A=0,L=0,S=[];if((v=(+o.apply(this,arguments)||0)/2)&&(g=n===Kn?Math.sqrt(l*l+c*c):+n.apply(this,arguments),p||(L*=-1),c&&(L=Oe(g/c*Math.sin(v))),l&&(A=Oe(g/l*Math.sin(v)))),c){y=c*Math.cos(u+L),x=c*Math.sin(u+L),b=c*Math.cos(d-L),_=c*Math.sin(d-L);var D=Math.abs(d-u-2*L)<=ke?0:1;if(L&&aa(y,x,b,_)===p^D){var C=(u+d)/2;y=c*Math.cos(C),x=c*Math.sin(C),b=_=null}}else y=x=0;if(l){w=l*Math.cos(d-A),T=l*Math.sin(d-A),M=l*Math.cos(u+A),k=l*Math.sin(u+A);var O=Math.abs(u-d+2*A)<=ke?0:1;if(A&&aa(w,T,M,k)===1-p^O){var P=(u+d)/2;w=l*Math.cos(P),T=l*Math.sin(P),M=k=null}}else w=T=0;if(f>Me&&(h=Math.min(Math.abs(c-l)/2,+r.apply(this,arguments)))>.001){m=l0?0:1}function ia(e,t,r,n,a){var i=e[0]-t[0],o=e[1]-t[1],l=(a?n:-n)/Math.sqrt(i*i+o*o),s=l*o,c=-l*i,u=e[0]+s,d=e[1]+c,f=t[0]+s,p=t[1]+c,h=(u+f)/2,m=(d+p)/2,g=f-u,v=p-d,y=g*g+v*v,x=r-n,b=u*p-f*d,_=(v<0?-1:1)*Math.sqrt(Math.max(0,x*x*y-b*b)),w=(b*v-g*_)/y,T=(-b*g-v*_)/y,M=(b*v+g*_)/y,k=(-b*g+v*_)/y,A=w-h,L=T-m,S=M-h,D=k-m;return A*A+L*L>S*S+D*D&&(w=M,T=k),[[w-s,T-c],[w*r/x,T*r/x]]}function oa(){return!0}function la(e){var t=_t,r=wt,n=oa,a=ca,i=a.key,o=.7;function l(i){var l,s=[],c=[],u=-1,d=i.length,f=ct(t),p=ct(r);function h(){s.push("M",a(e(c),o))}for(;++u1&&a.push("H",n[0]);return a.join("")},"step-before":da,"step-after":fa,basis:ma,"basis-open":function(e){if(e.length<4)return ca(e);var t,r=[],n=-1,a=e.length,i=[0],o=[0];for(;++n<3;)t=e[n],i.push(t[0]),o.push(t[1]);r.push(ga(xa,i)+","+ga(xa,o)),--n;for(;++n9&&(a=3*t/Math.sqrt(a),o[l]=a*r,o[l+1]=a*n));l=-1;for(;++l<=s;)a=(e[Math.min(s,l+1)][0]-e[Math.max(0,l-1)][0])/(6*(1+o[l]*o[l])),i.push([a||0,o[l]*a||0]);return i}(e))}});function ca(e){return e.length>1?e.join("L"):e+"Z"}function ua(e){return e.join("L")+"Z"}function da(e){for(var t=0,r=e.length,n=e[0],a=[n[0],",",n[1]];++t1){l=t[1],i=e[s],s++,n+="C"+(a[0]+o[0])+","+(a[1]+o[1])+","+(i[0]-l[0])+","+(i[1]-l[1])+","+i[0]+","+i[1];for(var c=2;cke)+",1 "+t}function s(e,t,r,n){return"Q 0,0 "+n}return i.radius=function(e){return arguments.length?(r=ct(e),i):r},i.source=function(t){return arguments.length?(e=ct(t),i):e},i.target=function(e){return arguments.length?(t=ct(e),i):t},i.startAngle=function(e){return arguments.length?(n=ct(e),i):n},i.endAngle=function(e){return arguments.length?(a=ct(e),i):a},i},e.svg.diagonal=function(){var e=Ma,t=ka,r=La;function n(n,a){var i=e.call(this,n,a),o=t.call(this,n,a),l=(i.y+o.y)/2,s=[i,{x:i.x,y:l},{x:o.x,y:l},o];return"M"+(s=s.map(r))[0]+"C"+s[1]+" "+s[2]+" "+s[3]}return n.source=function(t){return arguments.length?(e=ct(t),n):e},n.target=function(e){return arguments.length?(t=ct(e),n):t},n.projection=function(e){return arguments.length?(r=e,n):r},n},e.svg.diagonal.radial=function(){var t=e.svg.diagonal(),r=La,n=t.projection;return t.projection=function(e){return arguments.length?n(Sa(r=e)):r},t},e.svg.symbol=function(){var e=Ca,t=Da;function r(r,n){return(Pa.get(e.call(this,r,n))||Oa)(t.call(this,r,n))}return r.type=function(t){return arguments.length?(e=ct(t),r):e},r.size=function(e){return arguments.length?(t=ct(e),r):t},r};var Pa=e.map({circle:Oa,cross:function(e){var t=Math.sqrt(e/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(e){var t=Math.sqrt(e/(2*Ra)),r=t*Ra;return"M0,"+-t+"L"+r+",0 0,"+t+" "+-r+",0Z"},square:function(e){var t=Math.sqrt(e)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(e){var t=Math.sqrt(e/Ia),r=t*Ia/2;return"M0,"+r+"L"+t+","+-r+" "+-t+","+-r+"Z"},"triangle-up":function(e){var t=Math.sqrt(e/Ia),r=t*Ia/2;return"M0,"+-r+"L"+t+","+r+" "+-t+","+r+"Z"}});e.svg.symbolTypes=Pa.keys();var Ia=Math.sqrt(3),Ra=Math.tan(30*De);G.transition=function(e){for(var t,r,n=Ea||++Ba,a=Ua(e),i=[],o=ja||{time:Date.now(),ease:Sr,delay:0,duration:250},l=-1,s=this.length;++l0;)c[--f].call(e,o);if(i>=1)return d.event&&d.event.end.call(e,e.__data__,t),--u.count?delete u[n]:delete e[r],1}d||(i=a.time,o=vt((function(e){var t=d.delay;if(o.t=t+i,t<=e)return f(e-t);o.c=f}),0,i),d=u[n]={tween:new _,time:i,timer:o,delay:a.delay,duration:a.duration,ease:a.ease,index:t},a=null,++u.count)}Ha.call=G.call,Ha.empty=G.empty,Ha.node=G.node,Ha.size=G.size,e.transition=function(t,r){return t&&t.transition?Ea?t.transition(r):t:e.selection().transition(t)},e.transition.prototype=Ha,Ha.select=function(e){var t,r,n,a=this.id,i=this.namespace,o=[];e=Z(e);for(var l=-1,s=this.length;++lrect,.s>rect").attr("width",l[1]-l[0])}function m(e){e.select(".extent").attr("y",s[0]),e.selectAll(".extent,.e>rect,.w>rect").attr("height",s[1]-s[0])}function g(){var d,g,v=this,y=e.select(e.event.target),x=n.of(v,arguments),b=e.select(v),_=y.datum(),w=!/^(n|s)$/.test(_)&&a,T=!/^(e|w)$/.test(_)&&i,M=y.classed("extent"),k=be(v),A=e.mouse(v),L=e.select(o(v)).on("keydown.brush",C).on("keyup.brush",O);if(e.event.changedTouches?L.on("touchmove.brush",P).on("touchend.brush",R):L.on("mousemove.brush",P).on("mouseup.brush",R),b.interrupt().selectAll("*").interrupt(),M)A[0]=l[0]-A[0],A[1]=s[0]-A[1];else if(_){var S=+/w$/.test(_),D=+/^n/.test(_);g=[l[1-S]-A[0],s[1-D]-A[1]],A[0]=l[S],A[1]=s[D]}else e.event.altKey&&(d=A.slice());function C(){32==e.event.keyCode&&(M||(d=null,A[0]-=l[1],A[1]-=s[1],M=2),F())}function O(){32==e.event.keyCode&&2==M&&(A[0]+=l[1],A[1]+=s[1],M=0,F())}function P(){var t=e.mouse(v),r=!1;g&&(t[0]+=g[0],t[1]+=g[1]),M||(e.event.altKey?(d||(d=[(l[0]+l[1])/2,(s[0]+s[1])/2]),A[0]=l[+(t[0]0&&o.length>a&&!o.warned){o.warned=!0;var s=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");s.name="MaxListenersExceededWarning",s.emitter=e,s.type=t,s.count=o.length,l=s,console&&console.warn&&console.warn(l)}return e}function f(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,r){var n={fired:!1,wrapFn:void 0,target:e,type:t,listener:r},a=f.bind(n);return a.listener=r,n.wrapFn=a,a}function h(e,t,r){var n=e._events;if(void 0===n)return[];var a=n[t];return void 0===a?[]:"function"==typeof a?r?[a.listener||a]:[a]:r?function(e){for(var t=new Array(e.length),r=0;r0&&(o=t[0]),o instanceof Error)throw o;var l=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw l.context=o,l}var s=a[e];if(void 0===s)return!1;if("function"==typeof s)i(s,this,t);else{var c=s.length,u=g(s,c);for(r=0;r=0;i--)if(r[i]===t||r[i].listener===t){o=r[i].listener,a=i;break}if(a<0)return this;0===a?r.shift():function(e,t){for(;t+1=0;n--)this.removeListener(e,t[n]);return this},l.prototype.listeners=function(e){return h(this,e,!0)},l.prototype.rawListeners=function(e){return h(this,e,!1)},l.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):m.call(e,t)},l.prototype.listenerCount=m,l.prototype.eventNames=function(){return this._eventsCount>0?n(this._events):[]}},{}],13:[function(e,t,r){!function(e,n){"object"==typeof r&&void 0!==t?n(r):n((e="undefined"!=typeof globalThis?globalThis:e||self).d3=e.d3||{})}(this,(function(e){"use strict";function t(e,t){if((r=(e=t?e.toExponential(t-1):e.toExponential()).indexOf("e"))<0)return null;var r,n=e.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+e.slice(r+1)]}function r(e){return(e=t(Math.abs(e)))?e[1]:NaN}var n,a=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function i(e){if(!(t=a.exec(e)))throw new Error("invalid format: "+e);var t;return new o({fill:t[1],align:t[2],sign:t[3],symbol:t[4],zero:t[5],width:t[6],comma:t[7],precision:t[8]&&t[8].slice(1),trim:t[9],type:t[10]})}function o(e){this.fill=void 0===e.fill?" ":e.fill+"",this.align=void 0===e.align?">":e.align+"",this.sign=void 0===e.sign?"-":e.sign+"",this.symbol=void 0===e.symbol?"":e.symbol+"",this.zero=!!e.zero,this.width=void 0===e.width?void 0:+e.width,this.comma=!!e.comma,this.precision=void 0===e.precision?void 0:+e.precision,this.trim=!!e.trim,this.type=void 0===e.type?"":e.type+""}function l(e,r){var n=t(e,r);if(!n)return e+"";var a=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+a:a.length>i+1?a.slice(0,i+1)+"."+a.slice(i+1):a+new Array(i-a.length+2).join("0")}i.prototype=o.prototype,o.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var s={"%":function(e,t){return(100*e).toFixed(t)},b:function(e){return Math.round(e).toString(2)},c:function(e){return e+""},d:function(e){return Math.abs(e=Math.round(e))>=1e21?e.toLocaleString("en").replace(/,/g,""):e.toString(10)},e:function(e,t){return e.toExponential(t)},f:function(e,t){return e.toFixed(t)},g:function(e,t){return e.toPrecision(t)},o:function(e){return Math.round(e).toString(8)},p:function(e,t){return l(100*e,t)},r:l,s:function(e,r){var a=t(e,r);if(!a)return e+"";var i=a[0],o=a[1],l=o-(n=3*Math.max(-8,Math.min(8,Math.floor(o/3))))+1,s=i.length;return l===s?i:l>s?i+new Array(l-s+1).join("0"):l>0?i.slice(0,l)+"."+i.slice(l):"0."+new Array(1-l).join("0")+t(e,Math.max(0,r+l-1))[0]},X:function(e){return Math.round(e).toString(16).toUpperCase()},x:function(e){return Math.round(e).toString(16)}};function c(e){return e}var u,d=Array.prototype.map,f=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function p(e){var t,a,o=void 0===e.grouping||void 0===e.thousands?c:(t=d.call(e.grouping,Number),a=e.thousands+"",function(e,r){for(var n=e.length,i=[],o=0,l=t[0],s=0;n>0&&l>0&&(s+l+1>r&&(l=Math.max(1,r-s)),i.push(e.substring(n-=l,n+l)),!((s+=l+1)>r));)l=t[o=(o+1)%t.length];return i.reverse().join(a)}),l=void 0===e.currency?"":e.currency[0]+"",u=void 0===e.currency?"":e.currency[1]+"",p=void 0===e.decimal?".":e.decimal+"",h=void 0===e.numerals?c:function(e){return function(t){return t.replace(/[0-9]/g,(function(t){return e[+t]}))}}(d.call(e.numerals,String)),m=void 0===e.percent?"%":e.percent+"",g=void 0===e.minus?"-":e.minus+"",v=void 0===e.nan?"NaN":e.nan+"";function y(e){var t=(e=i(e)).fill,r=e.align,a=e.sign,c=e.symbol,d=e.zero,y=e.width,x=e.comma,b=e.precision,_=e.trim,w=e.type;"n"===w?(x=!0,w="g"):s[w]||(void 0===b&&(b=12),_=!0,w="g"),(d||"0"===t&&"="===r)&&(d=!0,t="0",r="=");var T="$"===c?l:"#"===c&&/[boxX]/.test(w)?"0"+w.toLowerCase():"",M="$"===c?u:/[%p]/.test(w)?m:"",k=s[w],A=/[defgprs%]/.test(w);function L(e){var i,l,s,c=T,u=M;if("c"===w)u=k(e)+u,e="";else{var m=(e=+e)<0||1/e<0;if(e=isNaN(e)?v:k(Math.abs(e),b),_&&(e=function(e){e:for(var t,r=e.length,n=1,a=-1;n0&&(a=0)}return a>0?e.slice(0,a)+e.slice(t+1):e}(e)),m&&0==+e&&"+"!==a&&(m=!1),c=(m?"("===a?a:g:"-"===a||"("===a?"":a)+c,u=("s"===w?f[8+n/3]:"")+u+(m&&"("===a?")":""),A)for(i=-1,l=e.length;++i(s=e.charCodeAt(i))||s>57){u=(46===s?p+e.slice(i+1):e.slice(i))+u,e=e.slice(0,i);break}}x&&!d&&(e=o(e,1/0));var L=c.length+e.length+u.length,S=L>1)+c+e+u+S.slice(L);break;default:e=S+c+e+u}return h(e)}return b=void 0===b?6:/[gprs]/.test(w)?Math.max(1,Math.min(21,b)):Math.max(0,Math.min(20,b)),L.toString=function(){return e+""},L}return{format:y,formatPrefix:function(e,t){var n=y(((e=i(e)).type="f",e)),a=3*Math.max(-8,Math.min(8,Math.floor(r(t)/3))),o=Math.pow(10,-a),l=f[8+a/3];return function(e){return n(o*e)+l}}}}function h(t){return u=p(t),e.format=u.format,e.formatPrefix=u.formatPrefix,u}h({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"}),e.FormatSpecifier=o,e.formatDefaultLocale=h,e.formatLocale=p,e.formatSpecifier=i,e.precisionFixed=function(e){return Math.max(0,-r(Math.abs(e)))},e.precisionPrefix=function(e,t){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(r(t)/3)))-r(Math.abs(e)))},e.precisionRound=function(e,t){return e=Math.abs(e),t=Math.abs(t)-e,Math.max(0,r(t)-r(e))+1},Object.defineProperty(e,"__esModule",{value:!0})}))},{}],14:[function(e,t,r){!function(n,a){"object"==typeof r&&void 0!==t?a(r,e("d3-time")):a((n=n||self).d3=n.d3||{},n.d3)}(this,(function(e,t){"use strict";function r(e){if(0<=e.y&&e.y<100){var t=new Date(-1,e.m,e.d,e.H,e.M,e.S,e.L);return t.setFullYear(e.y),t}return new Date(e.y,e.m,e.d,e.H,e.M,e.S,e.L)}function n(e){if(0<=e.y&&e.y<100){var t=new Date(Date.UTC(-1,e.m,e.d,e.H,e.M,e.S,e.L));return t.setUTCFullYear(e.y),t}return new Date(Date.UTC(e.y,e.m,e.d,e.H,e.M,e.S,e.L))}function a(e,t,r){return{y:e,m:t,d:r,H:0,M:0,S:0,L:0}}function i(e){var i=e.dateTime,o=e.date,s=e.time,c=e.periods,u=e.days,d=e.shortDays,f=e.months,ye=e.shortMonths,xe=p(c),be=h(c),_e=p(u),we=h(u),Te=p(d),Me=h(d),ke=p(f),Ae=h(f),Le=p(ye),Se=h(ye),De={a:function(e){return d[e.getDay()]},A:function(e){return u[e.getDay()]},b:function(e){return ye[e.getMonth()]},B:function(e){return f[e.getMonth()]},c:null,d:z,e:z,f:H,H:N,I:F,j:E,L:j,m:B,M:Y,p:function(e){return c[+(e.getHours()>=12)]},q:function(e){return 1+~~(e.getMonth()/3)},Q:ge,s:ve,S:V,u:U,U:q,V:G,w:Z,W:W,x:null,X:null,y:X,Y:J,Z:Q,"%":me},Ce={a:function(e){return d[e.getUTCDay()]},A:function(e){return u[e.getUTCDay()]},b:function(e){return ye[e.getUTCMonth()]},B:function(e){return f[e.getUTCMonth()]},c:null,d:K,e:K,f:ne,H:$,I:ee,j:te,L:re,m:ae,M:ie,p:function(e){return c[+(e.getUTCHours()>=12)]},q:function(e){return 1+~~(e.getUTCMonth()/3)},Q:ge,s:ve,S:oe,u:le,U:se,V:ce,w:ue,W:de,x:null,X:null,y:fe,Y:pe,Z:he,"%":me},Oe={a:function(e,t,r){var n=Te.exec(t.slice(r));return n?(e.w=Me[n[0].toLowerCase()],r+n[0].length):-1},A:function(e,t,r){var n=_e.exec(t.slice(r));return n?(e.w=we[n[0].toLowerCase()],r+n[0].length):-1},b:function(e,t,r){var n=Le.exec(t.slice(r));return n?(e.m=Se[n[0].toLowerCase()],r+n[0].length):-1},B:function(e,t,r){var n=ke.exec(t.slice(r));return n?(e.m=Ae[n[0].toLowerCase()],r+n[0].length):-1},c:function(e,t,r){return Re(e,i,t,r)},d:k,e:k,f:O,H:L,I:L,j:A,L:C,m:M,M:S,p:function(e,t,r){var n=xe.exec(t.slice(r));return n?(e.p=be[n[0].toLowerCase()],r+n[0].length):-1},q:T,Q:I,s:R,S:D,u:g,U:v,V:y,w:m,W:x,x:function(e,t,r){return Re(e,o,t,r)},X:function(e,t,r){return Re(e,s,t,r)},y:_,Y:b,Z:w,"%":P};function Pe(e,t){return function(r){var n,a,i,o=[],s=-1,c=0,u=e.length;for(r instanceof Date||(r=new Date(+r));++s53)return null;"w"in c||(c.w=1),"Z"in c?(s=(l=n(a(c.y,0,1))).getUTCDay(),l=s>4||0===s?t.utcMonday.ceil(l):t.utcMonday(l),l=t.utcDay.offset(l,7*(c.V-1)),c.y=l.getUTCFullYear(),c.m=l.getUTCMonth(),c.d=l.getUTCDate()+(c.w+6)%7):(s=(l=r(a(c.y,0,1))).getDay(),l=s>4||0===s?t.timeMonday.ceil(l):t.timeMonday(l),l=t.timeDay.offset(l,7*(c.V-1)),c.y=l.getFullYear(),c.m=l.getMonth(),c.d=l.getDate()+(c.w+6)%7)}else("W"in c||"U"in c)&&("w"in c||(c.w="u"in c?c.u%7:"W"in c?1:0),s="Z"in c?n(a(c.y,0,1)).getUTCDay():r(a(c.y,0,1)).getDay(),c.m=0,c.d="W"in c?(c.w+6)%7+7*c.W-(s+5)%7:c.w+7*c.U-(s+6)%7);return"Z"in c?(c.H+=c.Z/100|0,c.M+=c.Z%100,n(c)):r(c)}}function Re(e,t,r,n){for(var a,i,o=0,s=t.length,c=r.length;o=c)return-1;if(37===(a=t.charCodeAt(o++))){if(a=t.charAt(o++),!(i=Oe[a in l?t.charAt(o++):a])||(n=i(e,r,n))<0)return-1}else if(a!=r.charCodeAt(n++))return-1}return n}return De.x=Pe(o,De),De.X=Pe(s,De),De.c=Pe(i,De),Ce.x=Pe(o,Ce),Ce.X=Pe(s,Ce),Ce.c=Pe(i,Ce),{format:function(e){var t=Pe(e+="",De);return t.toString=function(){return e},t},parse:function(e){var t=Ie(e+="",!1);return t.toString=function(){return e},t},utcFormat:function(e){var t=Pe(e+="",Ce);return t.toString=function(){return e},t},utcParse:function(e){var t=Ie(e+="",!0);return t.toString=function(){return e},t}}}var o,l={"-":"",_:" ",0:"0"},s=/^\s*\d+/,c=/^%/,u=/[\\^$*+?|[\]().{}]/g;function d(e,t,r){var n=e<0?"-":"",a=(n?-e:e)+"",i=a.length;return n+(i68?1900:2e3),r+n[0].length):-1}function w(e,t,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(r,r+6));return n?(e.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function T(e,t,r){var n=s.exec(t.slice(r,r+1));return n?(e.q=3*n[0]-3,r+n[0].length):-1}function M(e,t,r){var n=s.exec(t.slice(r,r+2));return n?(e.m=n[0]-1,r+n[0].length):-1}function k(e,t,r){var n=s.exec(t.slice(r,r+2));return n?(e.d=+n[0],r+n[0].length):-1}function A(e,t,r){var n=s.exec(t.slice(r,r+3));return n?(e.m=0,e.d=+n[0],r+n[0].length):-1}function L(e,t,r){var n=s.exec(t.slice(r,r+2));return n?(e.H=+n[0],r+n[0].length):-1}function S(e,t,r){var n=s.exec(t.slice(r,r+2));return n?(e.M=+n[0],r+n[0].length):-1}function D(e,t,r){var n=s.exec(t.slice(r,r+2));return n?(e.S=+n[0],r+n[0].length):-1}function C(e,t,r){var n=s.exec(t.slice(r,r+3));return n?(e.L=+n[0],r+n[0].length):-1}function O(e,t,r){var n=s.exec(t.slice(r,r+6));return n?(e.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function P(e,t,r){var n=c.exec(t.slice(r,r+1));return n?r+n[0].length:-1}function I(e,t,r){var n=s.exec(t.slice(r));return n?(e.Q=+n[0],r+n[0].length):-1}function R(e,t,r){var n=s.exec(t.slice(r));return n?(e.s=+n[0],r+n[0].length):-1}function z(e,t){return d(e.getDate(),t,2)}function N(e,t){return d(e.getHours(),t,2)}function F(e,t){return d(e.getHours()%12||12,t,2)}function E(e,r){return d(1+t.timeDay.count(t.timeYear(e),e),r,3)}function j(e,t){return d(e.getMilliseconds(),t,3)}function H(e,t){return j(e,t)+"000"}function B(e,t){return d(e.getMonth()+1,t,2)}function Y(e,t){return d(e.getMinutes(),t,2)}function V(e,t){return d(e.getSeconds(),t,2)}function U(e){var t=e.getDay();return 0===t?7:t}function q(e,r){return d(t.timeSunday.count(t.timeYear(e)-1,e),r,2)}function G(e,r){var n=e.getDay();return e=n>=4||0===n?t.timeThursday(e):t.timeThursday.ceil(e),d(t.timeThursday.count(t.timeYear(e),e)+(4===t.timeYear(e).getDay()),r,2)}function Z(e){return e.getDay()}function W(e,r){return d(t.timeMonday.count(t.timeYear(e)-1,e),r,2)}function X(e,t){return d(e.getFullYear()%100,t,2)}function J(e,t){return d(e.getFullYear()%1e4,t,4)}function Q(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+d(t/60|0,"0",2)+d(t%60,"0",2)}function K(e,t){return d(e.getUTCDate(),t,2)}function $(e,t){return d(e.getUTCHours(),t,2)}function ee(e,t){return d(e.getUTCHours()%12||12,t,2)}function te(e,r){return d(1+t.utcDay.count(t.utcYear(e),e),r,3)}function re(e,t){return d(e.getUTCMilliseconds(),t,3)}function ne(e,t){return re(e,t)+"000"}function ae(e,t){return d(e.getUTCMonth()+1,t,2)}function ie(e,t){return d(e.getUTCMinutes(),t,2)}function oe(e,t){return d(e.getUTCSeconds(),t,2)}function le(e){var t=e.getUTCDay();return 0===t?7:t}function se(e,r){return d(t.utcSunday.count(t.utcYear(e)-1,e),r,2)}function ce(e,r){var n=e.getUTCDay();return e=n>=4||0===n?t.utcThursday(e):t.utcThursday.ceil(e),d(t.utcThursday.count(t.utcYear(e),e)+(4===t.utcYear(e).getUTCDay()),r,2)}function ue(e){return e.getUTCDay()}function de(e,r){return d(t.utcMonday.count(t.utcYear(e)-1,e),r,2)}function fe(e,t){return d(e.getUTCFullYear()%100,t,2)}function pe(e,t){return d(e.getUTCFullYear()%1e4,t,4)}function he(){return"+0000"}function me(){return"%"}function ge(e){return+e}function ve(e){return Math.floor(+e/1e3)}function ye(t){return o=i(t),e.timeFormat=o.format,e.timeParse=o.parse,e.utcFormat=o.utcFormat,e.utcParse=o.utcParse,o}ye({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var xe=Date.prototype.toISOString?function(e){return e.toISOString()}:e.utcFormat("%Y-%m-%dT%H:%M:%S.%LZ");var be=+new Date("2000-01-01T00:00:00.000Z")?function(e){var t=new Date(e);return isNaN(t)?null:t}:e.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");e.isoFormat=xe,e.isoParse=be,e.timeFormatDefaultLocale=ye,e.timeFormatLocale=i,Object.defineProperty(e,"__esModule",{value:!0})}))},{"d3-time":15}],15:[function(e,t,r){!function(e,n){"object"==typeof r&&void 0!==t?n(r):n((e=e||self).d3=e.d3||{})}(this,(function(e){"use strict";var t=new Date,r=new Date;function n(e,a,i,o){function l(t){return e(t=0===arguments.length?new Date:new Date(+t)),t}return l.floor=function(t){return e(t=new Date(+t)),t},l.ceil=function(t){return e(t=new Date(t-1)),a(t,1),e(t),t},l.round=function(e){var t=l(e),r=l.ceil(e);return e-t0))return o;do{o.push(i=new Date(+t)),a(t,n),e(t)}while(i=r)for(;e(r),!t(r);)r.setTime(r-1)}),(function(e,r){if(e>=e)if(r<0)for(;++r<=0;)for(;a(e,-1),!t(e););else for(;--r>=0;)for(;a(e,1),!t(e););}))},i&&(l.count=function(n,a){return t.setTime(+n),r.setTime(+a),e(t),e(r),Math.floor(i(t,r))},l.every=function(e){return e=Math.floor(e),isFinite(e)&&e>0?e>1?l.filter(o?function(t){return o(t)%e==0}:function(t){return l.count(0,t)%e==0}):l:null}),l}var a=n((function(){}),(function(e,t){e.setTime(+e+t)}),(function(e,t){return t-e}));a.every=function(e){return e=Math.floor(e),isFinite(e)&&e>0?e>1?n((function(t){t.setTime(Math.floor(t/e)*e)}),(function(t,r){t.setTime(+t+r*e)}),(function(t,r){return(r-t)/e})):a:null};var i=a.range,o=n((function(e){e.setTime(e-e.getMilliseconds())}),(function(e,t){e.setTime(+e+1e3*t)}),(function(e,t){return(t-e)/1e3}),(function(e){return e.getUTCSeconds()})),l=o.range,s=n((function(e){e.setTime(e-e.getMilliseconds()-1e3*e.getSeconds())}),(function(e,t){e.setTime(+e+6e4*t)}),(function(e,t){return(t-e)/6e4}),(function(e){return e.getMinutes()})),c=s.range,u=n((function(e){e.setTime(e-e.getMilliseconds()-1e3*e.getSeconds()-6e4*e.getMinutes())}),(function(e,t){e.setTime(+e+36e5*t)}),(function(e,t){return(t-e)/36e5}),(function(e){return e.getHours()})),d=u.range,f=n((function(e){e.setHours(0,0,0,0)}),(function(e,t){e.setDate(e.getDate()+t)}),(function(e,t){return(t-e-6e4*(t.getTimezoneOffset()-e.getTimezoneOffset()))/864e5}),(function(e){return e.getDate()-1})),p=f.range;function h(e){return n((function(t){t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)}),(function(e,t){e.setDate(e.getDate()+7*t)}),(function(e,t){return(t-e-6e4*(t.getTimezoneOffset()-e.getTimezoneOffset()))/6048e5}))}var m=h(0),g=h(1),v=h(2),y=h(3),x=h(4),b=h(5),_=h(6),w=m.range,T=g.range,M=v.range,k=y.range,A=x.range,L=b.range,S=_.range,D=n((function(e){e.setDate(1),e.setHours(0,0,0,0)}),(function(e,t){e.setMonth(e.getMonth()+t)}),(function(e,t){return t.getMonth()-e.getMonth()+12*(t.getFullYear()-e.getFullYear())}),(function(e){return e.getMonth()})),C=D.range,O=n((function(e){e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,t){e.setFullYear(e.getFullYear()+t)}),(function(e,t){return t.getFullYear()-e.getFullYear()}),(function(e){return e.getFullYear()}));O.every=function(e){return isFinite(e=Math.floor(e))&&e>0?n((function(t){t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,r){t.setFullYear(t.getFullYear()+r*e)})):null};var P=O.range,I=n((function(e){e.setUTCSeconds(0,0)}),(function(e,t){e.setTime(+e+6e4*t)}),(function(e,t){return(t-e)/6e4}),(function(e){return e.getUTCMinutes()})),R=I.range,z=n((function(e){e.setUTCMinutes(0,0,0)}),(function(e,t){e.setTime(+e+36e5*t)}),(function(e,t){return(t-e)/36e5}),(function(e){return e.getUTCHours()})),N=z.range,F=n((function(e){e.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCDate(e.getUTCDate()+t)}),(function(e,t){return(t-e)/864e5}),(function(e){return e.getUTCDate()-1})),E=F.range;function j(e){return n((function(t){t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCDate(e.getUTCDate()+7*t)}),(function(e,t){return(t-e)/6048e5}))}var H=j(0),B=j(1),Y=j(2),V=j(3),U=j(4),q=j(5),G=j(6),Z=H.range,W=B.range,X=Y.range,J=V.range,Q=U.range,K=q.range,$=G.range,ee=n((function(e){e.setUTCDate(1),e.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCMonth(e.getUTCMonth()+t)}),(function(e,t){return t.getUTCMonth()-e.getUTCMonth()+12*(t.getUTCFullYear()-e.getUTCFullYear())}),(function(e){return e.getUTCMonth()})),te=ee.range,re=n((function(e){e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCFullYear(e.getUTCFullYear()+t)}),(function(e,t){return t.getUTCFullYear()-e.getUTCFullYear()}),(function(e){return e.getUTCFullYear()}));re.every=function(e){return isFinite(e=Math.floor(e))&&e>0?n((function(t){t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,r){t.setUTCFullYear(t.getUTCFullYear()+r*e)})):null};var ne=re.range;e.timeDay=f,e.timeDays=p,e.timeFriday=b,e.timeFridays=L,e.timeHour=u,e.timeHours=d,e.timeInterval=n,e.timeMillisecond=a,e.timeMilliseconds=i,e.timeMinute=s,e.timeMinutes=c,e.timeMonday=g,e.timeMondays=T,e.timeMonth=D,e.timeMonths=C,e.timeSaturday=_,e.timeSaturdays=S,e.timeSecond=o,e.timeSeconds=l,e.timeSunday=m,e.timeSundays=w,e.timeThursday=x,e.timeThursdays=A,e.timeTuesday=v,e.timeTuesdays=M,e.timeWednesday=y,e.timeWednesdays=k,e.timeWeek=m,e.timeWeeks=w,e.timeYear=O,e.timeYears=P,e.utcDay=F,e.utcDays=E,e.utcFriday=q,e.utcFridays=K,e.utcHour=z,e.utcHours=N,e.utcMillisecond=a,e.utcMilliseconds=i,e.utcMinute=I,e.utcMinutes=R,e.utcMonday=B,e.utcMondays=W,e.utcMonth=ee,e.utcMonths=te,e.utcSaturday=G,e.utcSaturdays=$,e.utcSecond=o,e.utcSeconds=l,e.utcSunday=H,e.utcSundays=Z,e.utcThursday=U,e.utcThursdays=Q,e.utcTuesday=Y,e.utcTuesdays=X,e.utcWednesday=V,e.utcWednesdays=J,e.utcWeek=H,e.utcWeeks=Z,e.utcYear=re,e.utcYears=ne,Object.defineProperty(e,"__esModule",{value:!0})}))},{}],16:[function(e,t,r){arguments[4][15][0].apply(r,arguments)},{dup:15}],17:[function(e,t,r){"use strict";var n=e("is-string-blank");t.exports=function(e){var t=typeof e;if("string"===t){var r=e;if(0===(e=+e)&&n(r))return!1}else if("number"!==t)return!1;return e-e<1}},{"is-string-blank":52}],18:[function(e,t,r){t.exports=function(e,t){var r=t[0],n=t[1],a=t[2],i=t[3],o=t[4],l=t[5],s=t[6],c=t[7],u=t[8],d=t[9],f=t[10],p=t[11],h=t[12],m=t[13],g=t[14],v=t[15];return e[0]=l*(f*v-p*g)-d*(s*v-c*g)+m*(s*p-c*f),e[1]=-(n*(f*v-p*g)-d*(a*v-i*g)+m*(a*p-i*f)),e[2]=n*(s*v-c*g)-l*(a*v-i*g)+m*(a*c-i*s),e[3]=-(n*(s*p-c*f)-l*(a*p-i*f)+d*(a*c-i*s)),e[4]=-(o*(f*v-p*g)-u*(s*v-c*g)+h*(s*p-c*f)),e[5]=r*(f*v-p*g)-u*(a*v-i*g)+h*(a*p-i*f),e[6]=-(r*(s*v-c*g)-o*(a*v-i*g)+h*(a*c-i*s)),e[7]=r*(s*p-c*f)-o*(a*p-i*f)+u*(a*c-i*s),e[8]=o*(d*v-p*m)-u*(l*v-c*m)+h*(l*p-c*d),e[9]=-(r*(d*v-p*m)-u*(n*v-i*m)+h*(n*p-i*d)),e[10]=r*(l*v-c*m)-o*(n*v-i*m)+h*(n*c-i*l),e[11]=-(r*(l*p-c*d)-o*(n*p-i*d)+u*(n*c-i*l)),e[12]=-(o*(d*g-f*m)-u*(l*g-s*m)+h*(l*f-s*d)),e[13]=r*(d*g-f*m)-u*(n*g-a*m)+h*(n*f-a*d),e[14]=-(r*(l*g-s*m)-o*(n*g-a*m)+h*(n*s-a*l)),e[15]=r*(l*f-s*d)-o*(n*f-a*d)+u*(n*s-a*l),e}},{}],19:[function(e,t,r){t.exports=function(e){var t=new Float32Array(16);return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t[6]=e[6],t[7]=e[7],t[8]=e[8],t[9]=e[9],t[10]=e[10],t[11]=e[11],t[12]=e[12],t[13]=e[13],t[14]=e[14],t[15]=e[15],t}},{}],20:[function(e,t,r){t.exports=function(e,t){return e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e[4]=t[4],e[5]=t[5],e[6]=t[6],e[7]=t[7],e[8]=t[8],e[9]=t[9],e[10]=t[10],e[11]=t[11],e[12]=t[12],e[13]=t[13],e[14]=t[14],e[15]=t[15],e}},{}],21:[function(e,t,r){t.exports=function(){var e=new Float32Array(16);return e[0]=1,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=1,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=1,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],22:[function(e,t,r){t.exports=function(e){var t=e[0],r=e[1],n=e[2],a=e[3],i=e[4],o=e[5],l=e[6],s=e[7],c=e[8],u=e[9],d=e[10],f=e[11],p=e[12],h=e[13],m=e[14],g=e[15];return(t*o-r*i)*(d*g-f*m)-(t*l-n*i)*(u*g-f*h)+(t*s-a*i)*(u*m-d*h)+(r*l-n*o)*(c*g-f*p)-(r*s-a*o)*(c*m-d*p)+(n*s-a*l)*(c*h-u*p)}},{}],23:[function(e,t,r){t.exports=function(e,t){var r=t[0],n=t[1],a=t[2],i=t[3],o=r+r,l=n+n,s=a+a,c=r*o,u=n*o,d=n*l,f=a*o,p=a*l,h=a*s,m=i*o,g=i*l,v=i*s;return e[0]=1-d-h,e[1]=u+v,e[2]=f-g,e[3]=0,e[4]=u-v,e[5]=1-c-h,e[6]=p+m,e[7]=0,e[8]=f+g,e[9]=p-m,e[10]=1-c-d,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],24:[function(e,t,r){t.exports=function(e,t,r){var n,a,i,o=r[0],l=r[1],s=r[2],c=Math.sqrt(o*o+l*l+s*s);if(Math.abs(c)<1e-6)return null;return o*=c=1/c,l*=c,s*=c,n=Math.sin(t),a=Math.cos(t),i=1-a,e[0]=o*o*i+a,e[1]=l*o*i+s*n,e[2]=s*o*i-l*n,e[3]=0,e[4]=o*l*i-s*n,e[5]=l*l*i+a,e[6]=s*l*i+o*n,e[7]=0,e[8]=o*s*i+l*n,e[9]=l*s*i-o*n,e[10]=s*s*i+a,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],25:[function(e,t,r){t.exports=function(e,t,r){var n=t[0],a=t[1],i=t[2],o=t[3],l=n+n,s=a+a,c=i+i,u=n*l,d=n*s,f=n*c,p=a*s,h=a*c,m=i*c,g=o*l,v=o*s,y=o*c;return e[0]=1-(p+m),e[1]=d+y,e[2]=f-v,e[3]=0,e[4]=d-y,e[5]=1-(u+m),e[6]=h+g,e[7]=0,e[8]=f+v,e[9]=h-g,e[10]=1-(u+p),e[11]=0,e[12]=r[0],e[13]=r[1],e[14]=r[2],e[15]=1,e}},{}],26:[function(e,t,r){t.exports=function(e,t){return e[0]=t[0],e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=t[1],e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=t[2],e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],27:[function(e,t,r){t.exports=function(e,t){return e[0]=1,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=1,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=1,e[11]=0,e[12]=t[0],e[13]=t[1],e[14]=t[2],e[15]=1,e}},{}],28:[function(e,t,r){t.exports=function(e,t){var r=Math.sin(t),n=Math.cos(t);return e[0]=1,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=n,e[6]=r,e[7]=0,e[8]=0,e[9]=-r,e[10]=n,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],29:[function(e,t,r){t.exports=function(e,t){var r=Math.sin(t),n=Math.cos(t);return e[0]=n,e[1]=0,e[2]=-r,e[3]=0,e[4]=0,e[5]=1,e[6]=0,e[7]=0,e[8]=r,e[9]=0,e[10]=n,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],30:[function(e,t,r){t.exports=function(e,t){var r=Math.sin(t),n=Math.cos(t);return e[0]=n,e[1]=r,e[2]=0,e[3]=0,e[4]=-r,e[5]=n,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=1,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],31:[function(e,t,r){t.exports=function(e,t,r,n,a,i,o){var l=1/(r-t),s=1/(a-n),c=1/(i-o);return e[0]=2*i*l,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=2*i*s,e[6]=0,e[7]=0,e[8]=(r+t)*l,e[9]=(a+n)*s,e[10]=(o+i)*c,e[11]=-1,e[12]=0,e[13]=0,e[14]=o*i*2*c,e[15]=0,e}},{}],32:[function(e,t,r){t.exports=function(e){return e[0]=1,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=1,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=1,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,e}},{}],33:[function(e,t,r){t.exports={create:e("./create"),clone:e("./clone"),copy:e("./copy"),identity:e("./identity"),transpose:e("./transpose"),invert:e("./invert"),adjoint:e("./adjoint"),determinant:e("./determinant"),multiply:e("./multiply"),translate:e("./translate"),scale:e("./scale"),rotate:e("./rotate"),rotateX:e("./rotateX"),rotateY:e("./rotateY"),rotateZ:e("./rotateZ"),fromRotation:e("./fromRotation"),fromRotationTranslation:e("./fromRotationTranslation"),fromScaling:e("./fromScaling"),fromTranslation:e("./fromTranslation"),fromXRotation:e("./fromXRotation"),fromYRotation:e("./fromYRotation"),fromZRotation:e("./fromZRotation"),fromQuat:e("./fromQuat"),frustum:e("./frustum"),perspective:e("./perspective"),perspectiveFromFieldOfView:e("./perspectiveFromFieldOfView"),ortho:e("./ortho"),lookAt:e("./lookAt"),str:e("./str")}},{"./adjoint":18,"./clone":19,"./copy":20,"./create":21,"./determinant":22,"./fromQuat":23,"./fromRotation":24,"./fromRotationTranslation":25,"./fromScaling":26,"./fromTranslation":27,"./fromXRotation":28,"./fromYRotation":29,"./fromZRotation":30,"./frustum":31,"./identity":32,"./invert":34,"./lookAt":35,"./multiply":36,"./ortho":37,"./perspective":38,"./perspectiveFromFieldOfView":39,"./rotate":40,"./rotateX":41,"./rotateY":42,"./rotateZ":43,"./scale":44,"./str":45,"./translate":46,"./transpose":47}],34:[function(e,t,r){t.exports=function(e,t){var r=t[0],n=t[1],a=t[2],i=t[3],o=t[4],l=t[5],s=t[6],c=t[7],u=t[8],d=t[9],f=t[10],p=t[11],h=t[12],m=t[13],g=t[14],v=t[15],y=r*l-n*o,x=r*s-a*o,b=r*c-i*o,_=n*s-a*l,w=n*c-i*l,T=a*c-i*s,M=u*m-d*h,k=u*g-f*h,A=u*v-p*h,L=d*g-f*m,S=d*v-p*m,D=f*v-p*g,C=y*D-x*S+b*L+_*A-w*k+T*M;if(!C)return null;return C=1/C,e[0]=(l*D-s*S+c*L)*C,e[1]=(a*S-n*D-i*L)*C,e[2]=(m*T-g*w+v*_)*C,e[3]=(f*w-d*T-p*_)*C,e[4]=(s*A-o*D-c*k)*C,e[5]=(r*D-a*A+i*k)*C,e[6]=(g*b-h*T-v*x)*C,e[7]=(u*T-f*b+p*x)*C,e[8]=(o*S-l*A+c*M)*C,e[9]=(n*A-r*S-i*M)*C,e[10]=(h*w-m*b+v*y)*C,e[11]=(d*b-u*w-p*y)*C,e[12]=(l*k-o*L-s*M)*C,e[13]=(r*L-n*k+a*M)*C,e[14]=(m*x-h*_-g*y)*C,e[15]=(u*_-d*x+f*y)*C,e}},{}],35:[function(e,t,r){var n=e("./identity");t.exports=function(e,t,r,a){var i,o,l,s,c,u,d,f,p,h,m=t[0],g=t[1],v=t[2],y=a[0],x=a[1],b=a[2],_=r[0],w=r[1],T=r[2];if(Math.abs(m-_)<1e-6&&Math.abs(g-w)<1e-6&&Math.abs(v-T)<1e-6)return n(e);d=m-_,f=g-w,p=v-T,h=1/Math.sqrt(d*d+f*f+p*p),i=x*(p*=h)-b*(f*=h),o=b*(d*=h)-y*p,l=y*f-x*d,(h=Math.sqrt(i*i+o*o+l*l))?(i*=h=1/h,o*=h,l*=h):(i=0,o=0,l=0);s=f*l-p*o,c=p*i-d*l,u=d*o-f*i,(h=Math.sqrt(s*s+c*c+u*u))?(s*=h=1/h,c*=h,u*=h):(s=0,c=0,u=0);return e[0]=i,e[1]=s,e[2]=d,e[3]=0,e[4]=o,e[5]=c,e[6]=f,e[7]=0,e[8]=l,e[9]=u,e[10]=p,e[11]=0,e[12]=-(i*m+o*g+l*v),e[13]=-(s*m+c*g+u*v),e[14]=-(d*m+f*g+p*v),e[15]=1,e}},{"./identity":32}],36:[function(e,t,r){t.exports=function(e,t,r){var n=t[0],a=t[1],i=t[2],o=t[3],l=t[4],s=t[5],c=t[6],u=t[7],d=t[8],f=t[9],p=t[10],h=t[11],m=t[12],g=t[13],v=t[14],y=t[15],x=r[0],b=r[1],_=r[2],w=r[3];return e[0]=x*n+b*l+_*d+w*m,e[1]=x*a+b*s+_*f+w*g,e[2]=x*i+b*c+_*p+w*v,e[3]=x*o+b*u+_*h+w*y,x=r[4],b=r[5],_=r[6],w=r[7],e[4]=x*n+b*l+_*d+w*m,e[5]=x*a+b*s+_*f+w*g,e[6]=x*i+b*c+_*p+w*v,e[7]=x*o+b*u+_*h+w*y,x=r[8],b=r[9],_=r[10],w=r[11],e[8]=x*n+b*l+_*d+w*m,e[9]=x*a+b*s+_*f+w*g,e[10]=x*i+b*c+_*p+w*v,e[11]=x*o+b*u+_*h+w*y,x=r[12],b=r[13],_=r[14],w=r[15],e[12]=x*n+b*l+_*d+w*m,e[13]=x*a+b*s+_*f+w*g,e[14]=x*i+b*c+_*p+w*v,e[15]=x*o+b*u+_*h+w*y,e}},{}],37:[function(e,t,r){t.exports=function(e,t,r,n,a,i,o){var l=1/(t-r),s=1/(n-a),c=1/(i-o);return e[0]=-2*l,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=-2*s,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=2*c,e[11]=0,e[12]=(t+r)*l,e[13]=(a+n)*s,e[14]=(o+i)*c,e[15]=1,e}},{}],38:[function(e,t,r){t.exports=function(e,t,r,n,a){var i=1/Math.tan(t/2),o=1/(n-a);return e[0]=i/r,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=i,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=(a+n)*o,e[11]=-1,e[12]=0,e[13]=0,e[14]=2*a*n*o,e[15]=0,e}},{}],39:[function(e,t,r){t.exports=function(e,t,r,n){var a=Math.tan(t.upDegrees*Math.PI/180),i=Math.tan(t.downDegrees*Math.PI/180),o=Math.tan(t.leftDegrees*Math.PI/180),l=Math.tan(t.rightDegrees*Math.PI/180),s=2/(o+l),c=2/(a+i);return e[0]=s,e[1]=0,e[2]=0,e[3]=0,e[4]=0,e[5]=c,e[6]=0,e[7]=0,e[8]=-(o-l)*s*.5,e[9]=(a-i)*c*.5,e[10]=n/(r-n),e[11]=-1,e[12]=0,e[13]=0,e[14]=n*r/(r-n),e[15]=0,e}},{}],40:[function(e,t,r){t.exports=function(e,t,r,n){var a,i,o,l,s,c,u,d,f,p,h,m,g,v,y,x,b,_,w,T,M,k,A,L,S=n[0],D=n[1],C=n[2],O=Math.sqrt(S*S+D*D+C*C);if(Math.abs(O)<1e-6)return null;S*=O=1/O,D*=O,C*=O,a=Math.sin(r),i=Math.cos(r),o=1-i,l=t[0],s=t[1],c=t[2],u=t[3],d=t[4],f=t[5],p=t[6],h=t[7],m=t[8],g=t[9],v=t[10],y=t[11],x=S*S*o+i,b=D*S*o+C*a,_=C*S*o-D*a,w=S*D*o-C*a,T=D*D*o+i,M=C*D*o+S*a,k=S*C*o+D*a,A=D*C*o-S*a,L=C*C*o+i,e[0]=l*x+d*b+m*_,e[1]=s*x+f*b+g*_,e[2]=c*x+p*b+v*_,e[3]=u*x+h*b+y*_,e[4]=l*w+d*T+m*M,e[5]=s*w+f*T+g*M,e[6]=c*w+p*T+v*M,e[7]=u*w+h*T+y*M,e[8]=l*k+d*A+m*L,e[9]=s*k+f*A+g*L,e[10]=c*k+p*A+v*L,e[11]=u*k+h*A+y*L,t!==e&&(e[12]=t[12],e[13]=t[13],e[14]=t[14],e[15]=t[15]);return e}},{}],41:[function(e,t,r){t.exports=function(e,t,r){var n=Math.sin(r),a=Math.cos(r),i=t[4],o=t[5],l=t[6],s=t[7],c=t[8],u=t[9],d=t[10],f=t[11];t!==e&&(e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e[12]=t[12],e[13]=t[13],e[14]=t[14],e[15]=t[15]);return e[4]=i*a+c*n,e[5]=o*a+u*n,e[6]=l*a+d*n,e[7]=s*a+f*n,e[8]=c*a-i*n,e[9]=u*a-o*n,e[10]=d*a-l*n,e[11]=f*a-s*n,e}},{}],42:[function(e,t,r){t.exports=function(e,t,r){var n=Math.sin(r),a=Math.cos(r),i=t[0],o=t[1],l=t[2],s=t[3],c=t[8],u=t[9],d=t[10],f=t[11];t!==e&&(e[4]=t[4],e[5]=t[5],e[6]=t[6],e[7]=t[7],e[12]=t[12],e[13]=t[13],e[14]=t[14],e[15]=t[15]);return e[0]=i*a-c*n,e[1]=o*a-u*n,e[2]=l*a-d*n,e[3]=s*a-f*n,e[8]=i*n+c*a,e[9]=o*n+u*a,e[10]=l*n+d*a,e[11]=s*n+f*a,e}},{}],43:[function(e,t,r){t.exports=function(e,t,r){var n=Math.sin(r),a=Math.cos(r),i=t[0],o=t[1],l=t[2],s=t[3],c=t[4],u=t[5],d=t[6],f=t[7];t!==e&&(e[8]=t[8],e[9]=t[9],e[10]=t[10],e[11]=t[11],e[12]=t[12],e[13]=t[13],e[14]=t[14],e[15]=t[15]);return e[0]=i*a+c*n,e[1]=o*a+u*n,e[2]=l*a+d*n,e[3]=s*a+f*n,e[4]=c*a-i*n,e[5]=u*a-o*n,e[6]=d*a-l*n,e[7]=f*a-s*n,e}},{}],44:[function(e,t,r){t.exports=function(e,t,r){var n=r[0],a=r[1],i=r[2];return e[0]=t[0]*n,e[1]=t[1]*n,e[2]=t[2]*n,e[3]=t[3]*n,e[4]=t[4]*a,e[5]=t[5]*a,e[6]=t[6]*a,e[7]=t[7]*a,e[8]=t[8]*i,e[9]=t[9]*i,e[10]=t[10]*i,e[11]=t[11]*i,e[12]=t[12],e[13]=t[13],e[14]=t[14],e[15]=t[15],e}},{}],45:[function(e,t,r){t.exports=function(e){return"mat4("+e[0]+", "+e[1]+", "+e[2]+", "+e[3]+", "+e[4]+", "+e[5]+", "+e[6]+", "+e[7]+", "+e[8]+", "+e[9]+", "+e[10]+", "+e[11]+", "+e[12]+", "+e[13]+", "+e[14]+", "+e[15]+")"}},{}],46:[function(e,t,r){t.exports=function(e,t,r){var n,a,i,o,l,s,c,u,d,f,p,h,m=r[0],g=r[1],v=r[2];t===e?(e[12]=t[0]*m+t[4]*g+t[8]*v+t[12],e[13]=t[1]*m+t[5]*g+t[9]*v+t[13],e[14]=t[2]*m+t[6]*g+t[10]*v+t[14],e[15]=t[3]*m+t[7]*g+t[11]*v+t[15]):(n=t[0],a=t[1],i=t[2],o=t[3],l=t[4],s=t[5],c=t[6],u=t[7],d=t[8],f=t[9],p=t[10],h=t[11],e[0]=n,e[1]=a,e[2]=i,e[3]=o,e[4]=l,e[5]=s,e[6]=c,e[7]=u,e[8]=d,e[9]=f,e[10]=p,e[11]=h,e[12]=n*m+l*g+d*v+t[12],e[13]=a*m+s*g+f*v+t[13],e[14]=i*m+c*g+p*v+t[14],e[15]=o*m+u*g+h*v+t[15]);return e}},{}],47:[function(e,t,r){t.exports=function(e,t){if(e===t){var r=t[1],n=t[2],a=t[3],i=t[6],o=t[7],l=t[11];e[1]=t[4],e[2]=t[8],e[3]=t[12],e[4]=r,e[6]=t[9],e[7]=t[13],e[8]=n,e[9]=i,e[11]=t[14],e[12]=a,e[13]=o,e[14]=l}else e[0]=t[0],e[1]=t[4],e[2]=t[8],e[3]=t[12],e[4]=t[1],e[5]=t[5],e[6]=t[9],e[7]=t[13],e[8]=t[2],e[9]=t[6],e[10]=t[10],e[11]=t[14],e[12]=t[3],e[13]=t[7],e[14]=t[11],e[15]=t[15];return e}},{}],48:[function(e,t,r){(function(r){(function(){"use strict";var n,a=e("is-browser");n="function"==typeof r.matchMedia?!r.matchMedia("(hover: none)").matches:a,t.exports=n}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"is-browser":50}],49:[function(e,t,r){"use strict";var n=e("is-browser");t.exports=n&&function(){var e=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener("test",null,t),window.removeEventListener("test",null,t)}catch(t){e=!1}return e}()},{"is-browser":50}],50:[function(e,t,r){t.exports=!0},{}],51:[function(e,t,r){"use strict";t.exports=i,t.exports.isMobile=i,t.exports.default=i;var n=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i,a=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk/i;function i(e){e||(e={});var t=e.ua;if(t||"undefined"==typeof navigator||(t=navigator.userAgent),t&&t.headers&&"string"==typeof t.headers["user-agent"]&&(t=t.headers["user-agent"]),"string"!=typeof t)return!1;var r=e.tablet?a.test(t):n.test(t);return!r&&e.tablet&&e.featureDetect&&navigator&&navigator.maxTouchPoints>1&&-1!==t.indexOf("Macintosh")&&-1!==t.indexOf("Safari")&&(r=!0),r}},{}],52:[function(e,t,r){"use strict";t.exports=function(e){for(var t,r=e.length,n=0;n13)&&32!==t&&133!==t&&160!==t&&5760!==t&&6158!==t&&(t<8192||t>8205)&&8232!==t&&8233!==t&&8239!==t&&8287!==t&&8288!==t&&12288!==t&&65279!==t)return!1;return!0}},{}],53:[function(e,t,r){var n={left:0,top:0};t.exports=function(e,t,r){t=t||e.currentTarget||e.srcElement,Array.isArray(r)||(r=[0,0]);var a=e.clientX||0,i=e.clientY||0,o=(l=t,l===window||l===document||l===document.body?n:l.getBoundingClientRect());var l;return r[0]=a-o.left,r[1]=i-o.top,r}},{}],54:[function(e,t,r){(function(e,r){(function(){ +/*! Native Promise Only + v0.8.1 (c) Kyle Simpson + MIT License: http://getify.mit-license.org +*/ +var n,a,i;i=function(){"use strict";var e,t,n,a=Object.prototype.toString,i=void 0!==r?function(e){return r(e)}:setTimeout;try{Object.defineProperty({},"x",{}),e=function(e,t,r,n){return Object.defineProperty(e,t,{value:r,writable:!0,configurable:!1!==n})}}catch(t){e=function(e,t,r){return e[t]=r,e}}function o(e,r){n.add(e,r),t||(t=i(n.drain))}function l(e){var t,r=typeof e;return null==e||"object"!=r&&"function"!=r||(t=e.then),"function"==typeof t&&t}function s(){for(var e=0;e0&&o(s,r))}catch(e){d.call(new p(r),e)}}}function d(e){var t=this;t.triggered||(t.triggered=!0,t.def&&(t=t.def),t.msg=e,t.state=2,t.chain.length>0&&o(s,t))}function f(e,t,r,n){for(var a=0;a2&&(t.push([r].concat(a.splice(0,2))),o="l",r="m"==r?"l":"L");;){if(a.length==n[o])return a.unshift(r),t.push(a);if(a.length=-e},pointBetween:function(t,r,n){var a=t[1]-r[1],i=n[0]-r[0],o=t[0]-r[0],l=n[1]-r[1],s=o*i+a*l;return!(s-e)},pointsSameX:function(t,r){return Math.abs(t[0]-r[0])e!=o-a>e&&(i-c)*(a-u)/(o-u)+c-n>e&&(l=!l),i=c,o=u}return l}};return t}},{}],60:[function(e,t,r){var n={toPolygon:function(e,t){function r(t){if(t.length<=0)return e.segments({inverted:!1,regions:[]});function r(t){var r=t.slice(0,t.length-1);return e.segments({inverted:!1,regions:[r]})}for(var n=r(t[0]),a=1;a0}))}function u(e,n){var a=e.seg,i=n.seg,o=a.start,l=a.end,c=i.start,u=i.end;r&&r.checkIntersection(a,i);var d=t.linesIntersect(o,l,c,u);if(!1===d){if(!t.pointsCollinear(o,l,c))return!1;if(t.pointsSame(o,u)||t.pointsSame(l,c))return!1;var f=t.pointsSame(o,c),p=t.pointsSame(l,u);if(f&&p)return n;var h=!f&&t.pointBetween(o,c,u),m=!p&&t.pointBetween(l,c,u);if(f)return m?s(n,l):s(e,u),n;h&&(p||(m?s(n,l):s(e,u)),s(n,o))}else 0===d.alongA&&(-1===d.alongB?s(e,c):0===d.alongB?s(e,d.pt):1===d.alongB&&s(e,u)),0===d.alongB&&(-1===d.alongA?s(n,o):0===d.alongA?s(n,d.pt):1===d.alongA&&s(n,l));return!1}for(var d=[];!i.isEmpty();){var f=i.getHead();if(r&&r.vert(f.pt[0]),f.isStart){r&&r.segmentNew(f.seg,f.primary);var p=c(f),h=p.before?p.before.ev:null,m=p.after?p.after.ev:null;function g(){if(h){var e=u(f,h);if(e)return e}return!!m&&u(f,m)}r&&r.tempStatus(f.seg,!!h&&h.seg,!!m&&m.seg);var v,y=g();if(y){var x;if(e)(x=null===f.seg.myFill.below||f.seg.myFill.above!==f.seg.myFill.below)&&(y.seg.myFill.above=!y.seg.myFill.above);else y.seg.otherFill=f.seg.myFill;r&&r.segmentUpdate(y.seg),f.other.remove(),f.remove()}if(i.getHead()!==f){r&&r.rewind(f.seg);continue}if(e)x=null===f.seg.myFill.below||f.seg.myFill.above!==f.seg.myFill.below,f.seg.myFill.below=m?m.seg.myFill.above:a,f.seg.myFill.above=x?!f.seg.myFill.below:f.seg.myFill.below;else if(null===f.seg.otherFill)v=m?f.primary===m.primary?m.seg.otherFill.above:m.seg.myFill.above:f.primary?o:a,f.seg.otherFill={above:v,below:v};r&&r.status(f.seg,!!h&&h.seg,!!m&&m.seg),f.other.status=p.insert(n.node({ev:f}))}else{var b=f.status;if(null===b)throw new Error("PolyBool: Zero-length segment detected; your epsilon is probably too small or too large");if(l.exists(b.prev)&&l.exists(b.next)&&u(b.prev.ev,b.next.ev),r&&r.statusRemove(b.ev.seg),b.remove(),!f.primary){var _=f.seg.myFill;f.seg.myFill=f.seg.otherFill,f.seg.otherFill=_}d.push(f.seg)}i.getHead().remove()}return r&&r.done(),d}return e?{addRegion:function(e){for(var n,a,i,o=e[e.length-1],s=0;s1)for(var r=1;r=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},r.setImmediate="function"==typeof t?t:function(e){var t=s++,n=!(arguments.length<2)&&o.call(arguments,1);return l[t]=!0,a((function(){l[t]&&(n?e.apply(null,n):e.call(null),r.clearImmediate(t))})),t},r.clearImmediate="function"==typeof n?n:function(e){delete l[e]}}).call(this)}).call(this,e("timers").setImmediate,e("timers").clearImmediate)},{"process/browser.js":65,timers:66}],67:[function(e,t,r){!function(e){var r=/^\s+/,n=/\s+$/,a=0,i=e.round,o=e.min,l=e.max,s=e.random;function c(t,s){if(s=s||{},(t=t||"")instanceof c)return t;if(!(this instanceof c))return new c(t,s);var u=function(t){var a={r:0,g:0,b:0},i=1,s=null,c=null,u=null,d=!1,f=!1;"string"==typeof t&&(t=function(e){e=e.replace(r,"").replace(n,"").toLowerCase();var t,a=!1;if(L[e])e=L[e],a=!0;else if("transparent"==e)return{r:0,g:0,b:0,a:0,format:"name"};if(t=H.rgb.exec(e))return{r:t[1],g:t[2],b:t[3]};if(t=H.rgba.exec(e))return{r:t[1],g:t[2],b:t[3],a:t[4]};if(t=H.hsl.exec(e))return{h:t[1],s:t[2],l:t[3]};if(t=H.hsla.exec(e))return{h:t[1],s:t[2],l:t[3],a:t[4]};if(t=H.hsv.exec(e))return{h:t[1],s:t[2],v:t[3]};if(t=H.hsva.exec(e))return{h:t[1],s:t[2],v:t[3],a:t[4]};if(t=H.hex8.exec(e))return{r:P(t[1]),g:P(t[2]),b:P(t[3]),a:N(t[4]),format:a?"name":"hex8"};if(t=H.hex6.exec(e))return{r:P(t[1]),g:P(t[2]),b:P(t[3]),format:a?"name":"hex"};if(t=H.hex4.exec(e))return{r:P(t[1]+""+t[1]),g:P(t[2]+""+t[2]),b:P(t[3]+""+t[3]),a:N(t[4]+""+t[4]),format:a?"name":"hex8"};if(t=H.hex3.exec(e))return{r:P(t[1]+""+t[1]),g:P(t[2]+""+t[2]),b:P(t[3]+""+t[3]),format:a?"name":"hex"};return!1}(t));"object"==typeof t&&(B(t.r)&&B(t.g)&&B(t.b)?(p=t.r,h=t.g,m=t.b,a={r:255*C(p,255),g:255*C(h,255),b:255*C(m,255)},d=!0,f="%"===String(t.r).substr(-1)?"prgb":"rgb"):B(t.h)&&B(t.s)&&B(t.v)?(s=R(t.s),c=R(t.v),a=function(t,r,n){t=6*C(t,360),r=C(r,100),n=C(n,100);var a=e.floor(t),i=t-a,o=n*(1-r),l=n*(1-i*r),s=n*(1-(1-i)*r),c=a%6;return{r:255*[n,l,o,o,s,n][c],g:255*[s,n,n,l,o,o][c],b:255*[o,o,s,n,n,l][c]}}(t.h,s,c),d=!0,f="hsv"):B(t.h)&&B(t.s)&&B(t.l)&&(s=R(t.s),u=R(t.l),a=function(e,t,r){var n,a,i;function o(e,t,r){return r<0&&(r+=1),r>1&&(r-=1),r<1/6?e+6*(t-e)*r:r<.5?t:r<2/3?e+(t-e)*(2/3-r)*6:e}if(e=C(e,360),t=C(t,100),r=C(r,100),0===t)n=a=i=r;else{var l=r<.5?r*(1+t):r+t-r*t,s=2*r-l;n=o(s,l,e+1/3),a=o(s,l,e),i=o(s,l,e-1/3)}return{r:255*n,g:255*a,b:255*i}}(t.h,s,u),d=!0,f="hsl"),t.hasOwnProperty("a")&&(i=t.a));var p,h,m;return i=D(i),{ok:d,format:t.format||f,r:o(255,l(a.r,0)),g:o(255,l(a.g,0)),b:o(255,l(a.b,0)),a:i}}(t);this._originalInput=t,this._r=u.r,this._g=u.g,this._b=u.b,this._a=u.a,this._roundA=i(100*this._a)/100,this._format=s.format||u.format,this._gradientType=s.gradientType,this._r<1&&(this._r=i(this._r)),this._g<1&&(this._g=i(this._g)),this._b<1&&(this._b=i(this._b)),this._ok=u.ok,this._tc_id=a++}function u(e,t,r){e=C(e,255),t=C(t,255),r=C(r,255);var n,a,i=l(e,t,r),s=o(e,t,r),c=(i+s)/2;if(i==s)n=a=0;else{var u=i-s;switch(a=c>.5?u/(2-i-s):u/(i+s),i){case e:n=(t-r)/u+(t>1)+720)%360;--t;)n.h=(n.h+a)%360,i.push(c(n));return i}function A(e,t){t=t||6;for(var r=c(e).toHsv(),n=r.h,a=r.s,i=r.v,o=[],l=1/t;t--;)o.push(c({h:n,s:a,v:i})),i=(i+l)%1;return o}c.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var e=this.toRgb();return(299*e.r+587*e.g+114*e.b)/1e3},getLuminance:function(){var t,r,n,a=this.toRgb();return t=a.r/255,r=a.g/255,n=a.b/255,.2126*(t<=.03928?t/12.92:e.pow((t+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:e.pow((r+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:e.pow((n+.055)/1.055,2.4))},setAlpha:function(e){return this._a=D(e),this._roundA=i(100*this._a)/100,this},toHsv:function(){var e=d(this._r,this._g,this._b);return{h:360*e.h,s:e.s,v:e.v,a:this._a}},toHsvString:function(){var e=d(this._r,this._g,this._b),t=i(360*e.h),r=i(100*e.s),n=i(100*e.v);return 1==this._a?"hsv("+t+", "+r+"%, "+n+"%)":"hsva("+t+", "+r+"%, "+n+"%, "+this._roundA+")"},toHsl:function(){var e=u(this._r,this._g,this._b);return{h:360*e.h,s:e.s,l:e.l,a:this._a}},toHslString:function(){var e=u(this._r,this._g,this._b),t=i(360*e.h),r=i(100*e.s),n=i(100*e.l);return 1==this._a?"hsl("+t+", "+r+"%, "+n+"%)":"hsla("+t+", "+r+"%, "+n+"%, "+this._roundA+")"},toHex:function(e){return f(this._r,this._g,this._b,e)},toHexString:function(e){return"#"+this.toHex(e)},toHex8:function(e){return function(e,t,r,n,a){var o=[I(i(e).toString(16)),I(i(t).toString(16)),I(i(r).toString(16)),I(z(n))];if(a&&o[0].charAt(0)==o[0].charAt(1)&&o[1].charAt(0)==o[1].charAt(1)&&o[2].charAt(0)==o[2].charAt(1)&&o[3].charAt(0)==o[3].charAt(1))return o[0].charAt(0)+o[1].charAt(0)+o[2].charAt(0)+o[3].charAt(0);return o.join("")}(this._r,this._g,this._b,this._a,e)},toHex8String:function(e){return"#"+this.toHex8(e)},toRgb:function(){return{r:i(this._r),g:i(this._g),b:i(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+i(this._r)+", "+i(this._g)+", "+i(this._b)+")":"rgba("+i(this._r)+", "+i(this._g)+", "+i(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:i(100*C(this._r,255))+"%",g:i(100*C(this._g,255))+"%",b:i(100*C(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+i(100*C(this._r,255))+"%, "+i(100*C(this._g,255))+"%, "+i(100*C(this._b,255))+"%)":"rgba("+i(100*C(this._r,255))+"%, "+i(100*C(this._g,255))+"%, "+i(100*C(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":!(this._a<1)&&(S[f(this._r,this._g,this._b,!0)]||!1)},toFilter:function(e){var t="#"+p(this._r,this._g,this._b,this._a),r=t,n=this._gradientType?"GradientType = 1, ":"";if(e){var a=c(e);r="#"+p(a._r,a._g,a._b,a._a)}return"progid:DXImageTransform.Microsoft.gradient("+n+"startColorstr="+t+",endColorstr="+r+")"},toString:function(e){var t=!!e;e=e||this._format;var r=!1,n=this._a<1&&this._a>=0;return t||!n||"hex"!==e&&"hex6"!==e&&"hex3"!==e&&"hex4"!==e&&"hex8"!==e&&"name"!==e?("rgb"===e&&(r=this.toRgbString()),"prgb"===e&&(r=this.toPercentageRgbString()),"hex"!==e&&"hex6"!==e||(r=this.toHexString()),"hex3"===e&&(r=this.toHexString(!0)),"hex4"===e&&(r=this.toHex8String(!0)),"hex8"===e&&(r=this.toHex8String()),"name"===e&&(r=this.toName()),"hsl"===e&&(r=this.toHslString()),"hsv"===e&&(r=this.toHsvString()),r||this.toHexString()):"name"===e&&0===this._a?this.toName():this.toRgbString()},clone:function(){return c(this.toString())},_applyModification:function(e,t){var r=e.apply(null,[this].concat([].slice.call(t)));return this._r=r._r,this._g=r._g,this._b=r._b,this.setAlpha(r._a),this},lighten:function(){return this._applyModification(v,arguments)},brighten:function(){return this._applyModification(y,arguments)},darken:function(){return this._applyModification(x,arguments)},desaturate:function(){return this._applyModification(h,arguments)},saturate:function(){return this._applyModification(m,arguments)},greyscale:function(){return this._applyModification(g,arguments)},spin:function(){return this._applyModification(b,arguments)},_applyCombination:function(e,t){return e.apply(null,[this].concat([].slice.call(t)))},analogous:function(){return this._applyCombination(k,arguments)},complement:function(){return this._applyCombination(_,arguments)},monochromatic:function(){return this._applyCombination(A,arguments)},splitcomplement:function(){return this._applyCombination(M,arguments)},triad:function(){return this._applyCombination(w,arguments)},tetrad:function(){return this._applyCombination(T,arguments)}},c.fromRatio=function(e,t){if("object"==typeof e){var r={};for(var n in e)e.hasOwnProperty(n)&&(r[n]="a"===n?e[n]:R(e[n]));e=r}return c(e,t)},c.equals=function(e,t){return!(!e||!t)&&c(e).toRgbString()==c(t).toRgbString()},c.random=function(){return c.fromRatio({r:s(),g:s(),b:s()})},c.mix=function(e,t,r){r=0===r?0:r||50;var n=c(e).toRgb(),a=c(t).toRgb(),i=r/100;return c({r:(a.r-n.r)*i+n.r,g:(a.g-n.g)*i+n.g,b:(a.b-n.b)*i+n.b,a:(a.a-n.a)*i+n.a})},c.readability=function(t,r){var n=c(t),a=c(r);return(e.max(n.getLuminance(),a.getLuminance())+.05)/(e.min(n.getLuminance(),a.getLuminance())+.05)},c.isReadable=function(e,t,r){var n,a,i=c.readability(e,t);switch(a=!1,(n=function(e){var t,r;t=((e=e||{level:"AA",size:"small"}).level||"AA").toUpperCase(),r=(e.size||"small").toLowerCase(),"AA"!==t&&"AAA"!==t&&(t="AA");"small"!==r&&"large"!==r&&(r="small");return{level:t,size:r}}(r)).level+n.size){case"AAsmall":case"AAAlarge":a=i>=4.5;break;case"AAlarge":a=i>=3;break;case"AAAsmall":a=i>=7}return a},c.mostReadable=function(e,t,r){var n,a,i,o,l=null,s=0;a=(r=r||{}).includeFallbackColors,i=r.level,o=r.size;for(var u=0;us&&(s=n,l=c(t[u]));return c.isReadable(e,l,{level:i,size:o})||!a?l:(r.includeFallbackColors=!1,c.mostReadable(e,["#fff","#000"],r))};var L=c.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},S=c.hexNames=function(e){var t={};for(var r in e)e.hasOwnProperty(r)&&(t[e[r]]=r);return t}(L);function D(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function C(t,r){(function(e){return"string"==typeof e&&-1!=e.indexOf(".")&&1===parseFloat(e)})(t)&&(t="100%");var n=function(e){return"string"==typeof e&&-1!=e.indexOf("%")}(t);return t=o(r,l(0,parseFloat(t))),n&&(t=parseInt(t*r,10)/100),e.abs(t-r)<1e-6?1:t%r/parseFloat(r)}function O(e){return o(1,l(0,e))}function P(e){return parseInt(e,16)}function I(e){return 1==e.length?"0"+e:""+e}function R(e){return e<=1&&(e=100*e+"%"),e}function z(t){return e.round(255*parseFloat(t)).toString(16)}function N(e){return P(e)/255}var F,E,j,H=(E="[\\s|\\(]+("+(F="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+F+")[,|\\s]+("+F+")\\s*\\)?",j="[\\s|\\(]+("+F+")[,|\\s]+("+F+")[,|\\s]+("+F+")[,|\\s]+("+F+")\\s*\\)?",{CSS_UNIT:new RegExp(F),rgb:new RegExp("rgb"+E),rgba:new RegExp("rgba"+j),hsl:new RegExp("hsl"+E),hsla:new RegExp("hsla"+j),hsv:new RegExp("hsv"+E),hsva:new RegExp("hsva"+j),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function B(e){return!!H.CSS_UNIT.exec(e)}void 0!==t&&t.exports?t.exports=c:window.tinycolor=c}(Math)},{}],68:[function(e,t,r){var n=e("../main"),a=e("object-assign"),i=n.instance();function o(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}o.prototype=new n.baseCalendar,a(o.prototype,{name:"Chinese",jdEpoch:1721425.5,hasYearZero:!1,minMonth:0,firstMonth:0,minDay:1,regionalOptions:{"":{name:"Chinese",epochs:["BEC","EC"],monthNumbers:function(e,t){if("string"==typeof e){var r=e.match(s);return r?r[0]:""}var n=this._validateYear(e),a=e.month(),i=""+this.toChineseMonth(n,a);return t&&i.length<2&&(i="0"+i),this.isIntercalaryMonth(n,a)&&(i+="i"),i},monthNames:function(e){if("string"==typeof e){var t=e.match(c);return t?t[0]:""}var r=this._validateYear(e),n=e.month(),a=["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708","\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"][this.toChineseMonth(r,n)-1];return this.isIntercalaryMonth(r,n)&&(a="\u95f0"+a),a},monthNamesShort:function(e){if("string"==typeof e){var t=e.match(u);return t?t[0]:""}var r=this._validateYear(e),n=e.month(),a=["\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341","\u5341\u4e00","\u5341\u4e8c"][this.toChineseMonth(r,n)-1];return this.isIntercalaryMonth(r,n)&&(a="\u95f0"+a),a},parseMonth:function(e,t){e=this._validateYear(e);var r,n=parseInt(t);if(isNaN(n))"\u95f0"===t[0]&&(r=!0,t=t.substring(1)),"\u6708"===t[t.length-1]&&(t=t.substring(0,t.length-1)),n=1+["\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341","\u5341\u4e00","\u5341\u4e8c"].indexOf(t);else{var a=t[t.length-1];r="i"===a||"I"===a}return this.toMonthIndex(e,n,r)},dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],digits:null,dateFormat:"yyyy/mm/dd",firstDay:1,isRTL:!1}},_validateYear:function(e,t){if(e.year&&(e=e.year()),"number"!=typeof e||e<1888||e>2111)throw t.replace(/\{0\}/,this.local.name);return e},toMonthIndex:function(e,t,r){var a=this.intercalaryMonth(e);if(r&&t!==a||t<1||t>12)throw n.local.invalidMonth.replace(/\{0\}/,this.local.name);return a?!r&&t<=a?t-1:t:t-1},toChineseMonth:function(e,t){e.year&&(t=(e=e.year()).month());var r=this.intercalaryMonth(e);if(t<0||t>(r?12:11))throw n.local.invalidMonth.replace(/\{0\}/,this.local.name);return r?t>13},isIntercalaryMonth:function(e,t){e.year&&(t=(e=e.year()).month());var r=this.intercalaryMonth(e);return!!r&&r===t},leapYear:function(e){return 0!==this.intercalaryMonth(e)},weekOfYear:function(e,t,r){var a,o=this._validateYear(e,n.local.invalidyear),l=f[o-f[0]],s=l>>9&4095,c=l>>5&15,u=31&l;(a=i.newDate(s,c,u)).add(4-(a.dayOfWeek()||7),"d");var d=this.toJD(e,t,r)-a.toJD();return 1+Math.floor(d/7)},monthsInYear:function(e){return this.leapYear(e)?13:12},daysInMonth:function(e,t){e.year&&(t=e.month(),e=e.year()),e=this._validateYear(e);var r=d[e-d[0]];if(t>(r>>13?12:11))throw n.local.invalidMonth.replace(/\{0\}/,this.local.name);return r&1<<12-t?30:29},weekDay:function(e,t,r){return(this.dayOfWeek(e,t,r)||7)<6},toJD:function(e,t,r){var a=this._validate(e,l,r,n.local.invalidDate);e=this._validateYear(a.year()),t=a.month(),r=a.day();var o=this.isIntercalaryMonth(e,t),l=this.toChineseMonth(e,t),s=function(e,t,r,n,a){var i,o,l;if("object"==typeof e)o=e,i=t||{};else{var s;if(!("number"==typeof e&&e>=1888&&e<=2111))throw new Error("Lunar year outside range 1888-2111");if(!("number"==typeof t&&t>=1&&t<=12))throw new Error("Lunar month outside range 1 - 12");if(!("number"==typeof r&&r>=1&&r<=30))throw new Error("Lunar day outside range 1 - 30");"object"==typeof n?(s=!1,i=n):(s=!!n,i=a||{}),o={year:e,month:t,day:r,isIntercalary:s}}l=o.day-1;var c,u=d[o.year-d[0]],p=u>>13;c=p&&(o.month>p||o.isIntercalary)?o.month:o.month-1;for(var h=0;h>9&4095,(m>>5&15)-1,(31&m)+l);return i.year=g.getFullYear(),i.month=1+g.getMonth(),i.day=g.getDate(),i}(e,l,r,o);return i.toJD(s.year,s.month,s.day)},fromJD:function(e){var t=i.fromJD(e),r=function(e,t,r,n){var a,i;if("object"==typeof e)a=e,i=t||{};else{if(!("number"==typeof e&&e>=1888&&e<=2111))throw new Error("Solar year outside range 1888-2111");if(!("number"==typeof t&&t>=1&&t<=12))throw new Error("Solar month outside range 1 - 12");if(!("number"==typeof r&&r>=1&&r<=31))throw new Error("Solar day outside range 1 - 31");a={year:e,month:t,day:r},i=n||{}}var o=f[a.year-f[0]],l=a.year<<9|a.month<<5|a.day;i.year=l>=o?a.year:a.year-1,o=f[i.year-f[0]];var s,c=new Date(o>>9&4095,(o>>5&15)-1,31&o),u=new Date(a.year,a.month-1,a.day);s=Math.round((u-c)/864e5);var p,h=d[i.year-d[0]];for(p=0;p<13;p++){var m=h&1<<12-p?30:29;if(s>13;!g||p=2&&n<=6},extraInfo:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return{century:o[Math.floor((a.year()-1)/100)+1]||""}},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return e=a.year()+(a.year()<0?1:0),t=a.month(),(r=a.day())+(t>1?16:0)+(t>2?32*(t-2):0)+400*(e-1)+this.jdEpoch-1},fromJD:function(e){e=Math.floor(e+.5)-Math.floor(this.jdEpoch)-1;var t=Math.floor(e/400)+1;e-=400*(t-1),e+=e>15?16:0;var r=Math.floor(e/32)+1,n=e-32*(r-1)+1;return this.newDate(t<=0?t-1:t,r,n)}});var o={20:"Fruitbat",21:"Anchovy"};n.calendars.discworld=i},{"../main":82,"object-assign":55}],71:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}i.prototype=new n.baseCalendar,a(i.prototype,{name:"Ethiopian",jdEpoch:1724220.5,daysPerMonth:[30,30,30,30,30,30,30,30,30,30,30,30,5],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Ethiopian",epochs:["BEE","EE"],monthNames:["Meskerem","Tikemet","Hidar","Tahesas","Tir","Yekatit","Megabit","Miazia","Genbot","Sene","Hamle","Nehase","Pagume"],monthNamesShort:["Mes","Tik","Hid","Tah","Tir","Yek","Meg","Mia","Gen","Sen","Ham","Neh","Pag"],dayNames:["Ehud","Segno","Maksegno","Irob","Hamus","Arb","Kidame"],dayNamesShort:["Ehu","Seg","Mak","Iro","Ham","Arb","Kid"],dayNamesMin:["Eh","Se","Ma","Ir","Ha","Ar","Ki"],digits:null,dateFormat:"dd/mm/yyyy",firstDay:0,isRTL:!1}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);return(e=t.year()+(t.year()<0?1:0))%4==3||e%4==-1},monthsInYear:function(e){return this._validate(e,this.minMonth,this.minDay,n.local.invalidYear||n.regionalOptions[""].invalidYear),13},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(-n.dayOfWeek(),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(13===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return(this.dayOfWeek(e,t,r)||7)<6},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return(e=a.year())<0&&e++,a.day()+30*(a.month()-1)+365*(e-1)+Math.floor(e/4)+this.jdEpoch-1},fromJD:function(e){var t=Math.floor(e)+.5-this.jdEpoch,r=Math.floor((t-Math.floor((t+366)/1461))/365)+1;r<=0&&r--,t=Math.floor(e)+.5-this.newDate(r,1,1).toJD();var n=Math.floor(t/30)+1,a=t-30*(n-1)+1;return this.newDate(r,n,a)}}),n.calendars.ethiopian=i},{"../main":82,"object-assign":55}],72:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}function o(e,t){return e-t*Math.floor(e/t)}i.prototype=new n.baseCalendar,a(i.prototype,{name:"Hebrew",jdEpoch:347995.5,daysPerMonth:[30,29,30,29,30,29,30,29,30,29,30,29,29],hasYearZero:!1,minMonth:1,firstMonth:7,minDay:1,regionalOptions:{"":{name:"Hebrew",epochs:["BAM","AM"],monthNames:["Nisan","Iyar","Sivan","Tammuz","Av","Elul","Tishrei","Cheshvan","Kislev","Tevet","Shevat","Adar","Adar II"],monthNamesShort:["Nis","Iya","Siv","Tam","Av","Elu","Tis","Che","Kis","Tev","She","Ada","Ad2"],dayNames:["Yom Rishon","Yom Sheni","Yom Shlishi","Yom Revi'i","Yom Chamishi","Yom Shishi","Yom Shabbat"],dayNamesShort:["Ris","She","Shl","Rev","Cha","Shi","Sha"],dayNamesMin:["Ri","She","Shl","Re","Ch","Shi","Sha"],digits:null,dateFormat:"dd/mm/yyyy",firstDay:0,isRTL:!1}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);return this._leapYear(t.year())},_leapYear:function(e){return o(7*(e=e<0?e+1:e)+1,19)<7},monthsInYear:function(e){return this._validate(e,this.minMonth,this.minDay,n.local.invalidYear),this._leapYear(e.year?e.year():e)?13:12},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(-n.dayOfWeek(),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInYear:function(e){return e=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear).year(),this.toJD(-1===e?1:e+1,7,1)-this.toJD(e,7,1)},daysInMonth:function(e,t){return e.year&&(t=e.month(),e=e.year()),this._validate(e,t,this.minDay,n.local.invalidMonth),12===t&&this.leapYear(e)||8===t&&5===o(this.daysInYear(e),10)?30:9===t&&3===o(this.daysInYear(e),10)?29:this.daysPerMonth[t-1]},weekDay:function(e,t,r){return 6!==this.dayOfWeek(e,t,r)},extraInfo:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return{yearType:(this.leapYear(a)?"embolismic":"common")+" "+["deficient","regular","complete"][this.daysInYear(a)%10-3]}},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);e=a.year(),t=a.month(),r=a.day();var i=e<=0?e+1:e,o=this.jdEpoch+this._delay1(i)+this._delay2(i)+r+1;if(t<7){for(var l=7;l<=this.monthsInYear(e);l++)o+=this.daysInMonth(e,l);for(l=1;l=this.toJD(-1===t?1:t+1,7,1);)t++;for(var r=ethis.toJD(t,r,this.daysInMonth(t,r));)r++;var n=e-this.toJD(t,r,1)+1;return this.newDate(t,r,n)}}),n.calendars.hebrew=i},{"../main":82,"object-assign":55}],73:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}i.prototype=new n.baseCalendar,a(i.prototype,{name:"Islamic",jdEpoch:1948439.5,daysPerMonth:[30,29,30,29,30,29,30,29,30,29,30,29],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Islamic",epochs:["BH","AH"],monthNames:["Muharram","Safar","Rabi' al-awwal","Rabi' al-thani","Jumada al-awwal","Jumada al-thani","Rajab","Sha'aban","Ramadan","Shawwal","Dhu al-Qi'dah","Dhu al-Hijjah"],monthNamesShort:["Muh","Saf","Rab1","Rab2","Jum1","Jum2","Raj","Sha'","Ram","Shaw","DhuQ","DhuH"],dayNames:["Yawm al-ahad","Yawm al-ithnayn","Yawm ath-thulaathaa'","Yawm al-arbi'aa'","Yawm al-kham\u012bs","Yawm al-jum'a","Yawm as-sabt"],dayNamesShort:["Aha","Ith","Thu","Arb","Kha","Jum","Sab"],dayNamesMin:["Ah","It","Th","Ar","Kh","Ju","Sa"],digits:null,dateFormat:"yyyy/mm/dd",firstDay:6,isRTL:!1}},leapYear:function(e){return(11*this._validate(e,this.minMonth,this.minDay,n.local.invalidYear).year()+14)%30<11},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(-n.dayOfWeek(),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInYear:function(e){return this.leapYear(e)?355:354},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(12===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return 5!==this.dayOfWeek(e,t,r)},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return e=a.year(),t=a.month(),e=e<=0?e+1:e,(r=a.day())+Math.ceil(29.5*(t-1))+354*(e-1)+Math.floor((3+11*e)/30)+this.jdEpoch-1},fromJD:function(e){e=Math.floor(e)+.5;var t=Math.floor((30*(e-this.jdEpoch)+10646)/10631);t=t<=0?t-1:t;var r=Math.min(12,Math.ceil((e-29-this.toJD(t,1,1))/29.5)+1),n=e-this.toJD(t,r,1)+1;return this.newDate(t,r,n)}}),n.calendars.islamic=i},{"../main":82,"object-assign":55}],74:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}i.prototype=new n.baseCalendar,a(i.prototype,{name:"Julian",jdEpoch:1721423.5,daysPerMonth:[31,28,31,30,31,30,31,31,30,31,30,31],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Julian",epochs:["BC","AD"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],digits:null,dateFormat:"mm/dd/yyyy",firstDay:0,isRTL:!1}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);return(e=t.year()<0?t.year()+1:t.year())%4==0},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(4-(n.dayOfWeek()||7),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(2===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return(this.dayOfWeek(e,t,r)||7)<6},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return e=a.year(),t=a.month(),r=a.day(),e<0&&e++,t<=2&&(e--,t+=12),Math.floor(365.25*(e+4716))+Math.floor(30.6001*(t+1))+r-1524.5},fromJD:function(e){var t=Math.floor(e+.5)+1524,r=Math.floor((t-122.1)/365.25),n=Math.floor(365.25*r),a=Math.floor((t-n)/30.6001),i=a-Math.floor(a<14?1:13),o=r-Math.floor(i>2?4716:4715),l=t-n-Math.floor(30.6001*a);return o<=0&&o--,this.newDate(o,i,l)}}),n.calendars.julian=i},{"../main":82,"object-assign":55}],75:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}function o(e,t){return e-t*Math.floor(e/t)}function l(e,t){return o(e-1,t)+1}i.prototype=new n.baseCalendar,a(i.prototype,{name:"Mayan",jdEpoch:584282.5,hasYearZero:!0,minMonth:0,firstMonth:0,minDay:0,regionalOptions:{"":{name:"Mayan",epochs:["",""],monthNames:["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17"],monthNamesShort:["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17"],dayNames:["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19"],dayNamesShort:["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19"],dayNamesMin:["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19"],digits:null,dateFormat:"YYYY.m.d",firstDay:0,isRTL:!1,haabMonths:["Pop","Uo","Zip","Zotz","Tzec","Xul","Yaxkin","Mol","Chen","Yax","Zac","Ceh","Mac","Kankin","Muan","Pax","Kayab","Cumku","Uayeb"],tzolkinMonths:["Imix","Ik","Akbal","Kan","Chicchan","Cimi","Manik","Lamat","Muluc","Oc","Chuen","Eb","Ben","Ix","Men","Cib","Caban","Etznab","Cauac","Ahau"]}},leapYear:function(e){return this._validate(e,this.minMonth,this.minDay,n.local.invalidYear),!1},formatYear:function(e){e=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear).year();var t=Math.floor(e/400);return e%=400,e+=e<0?400:0,t+"."+Math.floor(e/20)+"."+e%20},forYear:function(e){if((e=e.split(".")).length<3)throw"Invalid Mayan year";for(var t=0,r=0;r19||r>0&&n<0)throw"Invalid Mayan year";t=20*t+n}return t},monthsInYear:function(e){return this._validate(e,this.minMonth,this.minDay,n.local.invalidYear),18},weekOfYear:function(e,t,r){return this._validate(e,t,r,n.local.invalidDate),0},daysInYear:function(e){return this._validate(e,this.minMonth,this.minDay,n.local.invalidYear),360},daysInMonth:function(e,t){return this._validate(e,t,this.minDay,n.local.invalidMonth),20},daysInWeek:function(){return 5},dayOfWeek:function(e,t,r){return this._validate(e,t,r,n.local.invalidDate).day()},weekDay:function(e,t,r){return this._validate(e,t,r,n.local.invalidDate),!0},extraInfo:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate).toJD(),i=this._toHaab(a),o=this._toTzolkin(a);return{haabMonthName:this.local.haabMonths[i[0]-1],haabMonth:i[0],haabDay:i[1],tzolkinDayName:this.local.tzolkinMonths[o[0]-1],tzolkinDay:o[0],tzolkinTrecena:o[1]}},_toHaab:function(e){var t=o((e-=this.jdEpoch)+8+340,365);return[Math.floor(t/20)+1,o(t,20)]},_toTzolkin:function(e){return[l((e-=this.jdEpoch)+20,20),l(e+4,13)]},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);return a.day()+20*a.month()+360*a.year()+this.jdEpoch},fromJD:function(e){e=Math.floor(e)+.5-this.jdEpoch;var t=Math.floor(e/360);e%=360,e+=e<0?360:0;var r=Math.floor(e/20),n=e%20;return this.newDate(t,r,n)}}),n.calendars.mayan=i},{"../main":82,"object-assign":55}],76:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}i.prototype=new n.baseCalendar;var o=n.instance("gregorian");a(i.prototype,{name:"Nanakshahi",jdEpoch:2257673.5,daysPerMonth:[31,31,31,31,31,30,30,30,30,30,30,30],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Nanakshahi",epochs:["BN","AN"],monthNames:["Chet","Vaisakh","Jeth","Harh","Sawan","Bhadon","Assu","Katak","Maghar","Poh","Magh","Phagun"],monthNamesShort:["Che","Vai","Jet","Har","Saw","Bha","Ass","Kat","Mgr","Poh","Mgh","Pha"],dayNames:["Somvaar","Mangalvar","Budhvaar","Veervaar","Shukarvaar","Sanicharvaar","Etvaar"],dayNamesShort:["Som","Mangal","Budh","Veer","Shukar","Sanichar","Et"],dayNamesMin:["So","Ma","Bu","Ve","Sh","Sa","Et"],digits:null,dateFormat:"dd-mm-yyyy",firstDay:0,isRTL:!1}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear||n.regionalOptions[""].invalidYear);return o.leapYear(t.year()+(t.year()<1?1:0)+1469)},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(1-(n.dayOfWeek()||7),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(12===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return(this.dayOfWeek(e,t,r)||7)<6},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidMonth);(e=a.year())<0&&e++;for(var i=a.day(),l=1;l=this.toJD(t+1,1,1);)t++;for(var r=e-Math.floor(this.toJD(t,1,1)+.5)+1,n=1;r>this.daysInMonth(t,n);)r-=this.daysInMonth(t,n),n++;return this.newDate(t,n,r)}}),n.calendars.nanakshahi=i},{"../main":82,"object-assign":55}],77:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}i.prototype=new n.baseCalendar,a(i.prototype,{name:"Nepali",jdEpoch:1700709.5,daysPerMonth:[31,31,32,32,31,30,30,29,30,29,30,30],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,daysPerYear:365,regionalOptions:{"":{name:"Nepali",epochs:["BBS","ABS"],monthNames:["Baisakh","Jestha","Ashadh","Shrawan","Bhadra","Ashwin","Kartik","Mangsir","Paush","Mangh","Falgun","Chaitra"],monthNamesShort:["Bai","Je","As","Shra","Bha","Ash","Kar","Mang","Pau","Ma","Fal","Chai"],dayNames:["Aaitabaar","Sombaar","Manglbaar","Budhabaar","Bihibaar","Shukrabaar","Shanibaar"],dayNamesShort:["Aaita","Som","Mangl","Budha","Bihi","Shukra","Shani"],dayNamesMin:["Aai","So","Man","Bu","Bi","Shu","Sha"],digits:null,dateFormat:"dd/mm/yyyy",firstDay:1,isRTL:!1}},leapYear:function(e){return this.daysInYear(e)!==this.daysPerYear},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(-n.dayOfWeek(),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInYear:function(e){if(e=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear).year(),void 0===this.NEPALI_CALENDAR_DATA[e])return this.daysPerYear;for(var t=0,r=this.minMonth;r<=12;r++)t+=this.NEPALI_CALENDAR_DATA[e][r];return t},daysInMonth:function(e,t){return e.year&&(t=e.month(),e=e.year()),this._validate(e,t,this.minDay,n.local.invalidMonth),void 0===this.NEPALI_CALENDAR_DATA[e]?this.daysPerMonth[t-1]:this.NEPALI_CALENDAR_DATA[e][t]},weekDay:function(e,t,r){return 6!==this.dayOfWeek(e,t,r)},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);e=a.year(),t=a.month(),r=a.day();var i=n.instance(),o=0,l=t,s=e;this._createMissingCalendarData(e);var c=e-(l>9||9===l&&r>=this.NEPALI_CALENDAR_DATA[s][0]?56:57);for(9!==t&&(o=r,l--);9!==l;)l<=0&&(l=12,s--),o+=this.NEPALI_CALENDAR_DATA[s][l],l--;return 9===t?(o+=r-this.NEPALI_CALENDAR_DATA[s][0])<0&&(o+=i.daysInYear(c)):o+=this.NEPALI_CALENDAR_DATA[s][9]-this.NEPALI_CALENDAR_DATA[s][0],i.newDate(c,1,1).add(o,"d").toJD()},fromJD:function(e){var t=n.instance().fromJD(e),r=t.year(),a=t.dayOfYear(),i=r+56;this._createMissingCalendarData(i);for(var o=9,l=this.NEPALI_CALENDAR_DATA[i][0],s=this.NEPALI_CALENDAR_DATA[i][o]-l+1;a>s;)++o>12&&(o=1,i++),s+=this.NEPALI_CALENDAR_DATA[i][o];var c=this.NEPALI_CALENDAR_DATA[i][o]-(s-a);return this.newDate(i,o,c)},_createMissingCalendarData:function(e){var t=this.daysPerMonth.slice(0);t.unshift(17);for(var r=e-1;r0?474:473))%2820+474+38)%2816<682},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(-(n.dayOfWeek()+1)%7,"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(12===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return 5!==this.dayOfWeek(e,t,r)},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);e=a.year(),t=a.month(),r=a.day();var i=e-(e>=0?474:473),l=474+o(i,2820);return r+(t<=7?31*(t-1):30*(t-1)+6)+Math.floor((682*l-110)/2816)+365*(l-1)+1029983*Math.floor(i/2820)+this.jdEpoch-1},fromJD:function(e){var t=(e=Math.floor(e)+.5)-this.toJD(475,1,1),r=Math.floor(t/1029983),n=o(t,1029983),a=2820;if(1029982!==n){var i=Math.floor(n/366),l=o(n,366);a=Math.floor((2134*i+2816*l+2815)/1028522)+i+1}var s=a+2820*r+474;s=s<=0?s-1:s;var c=e-this.toJD(s,1,1)+1,u=c<=186?Math.ceil(c/31):Math.ceil((c-6)/30),d=e-this.toJD(s,u,1)+1;return this.newDate(s,u,d)}}),n.calendars.persian=i,n.calendars.jalali=i},{"../main":82,"object-assign":55}],79:[function(e,t,r){var n=e("../main"),a=e("object-assign"),i=n.instance();function o(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}o.prototype=new n.baseCalendar,a(o.prototype,{name:"Taiwan",jdEpoch:2419402.5,yearsOffset:1911,daysPerMonth:[31,28,31,30,31,30,31,31,30,31,30,31],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Taiwan",epochs:["BROC","ROC"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],digits:null,dateFormat:"yyyy/mm/dd",firstDay:1,isRTL:!1}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);e=this._t2gYear(t.year());return i.leapYear(e)},weekOfYear:function(e,t,r){var a=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);e=this._t2gYear(a.year());return i.weekOfYear(e,a.month(),a.day())},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(2===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return(this.dayOfWeek(e,t,r)||7)<6},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);e=this._t2gYear(a.year());return i.toJD(e,a.month(),a.day())},fromJD:function(e){var t=i.fromJD(e),r=this._g2tYear(t.year());return this.newDate(r,t.month(),t.day())},_t2gYear:function(e){return e+this.yearsOffset+(e>=-this.yearsOffset&&e<=-1?1:0)},_g2tYear:function(e){return e-this.yearsOffset-(e>=1&&e<=this.yearsOffset?1:0)}}),n.calendars.taiwan=o},{"../main":82,"object-assign":55}],80:[function(e,t,r){var n=e("../main"),a=e("object-assign"),i=n.instance();function o(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}o.prototype=new n.baseCalendar,a(o.prototype,{name:"Thai",jdEpoch:1523098.5,yearsOffset:543,daysPerMonth:[31,28,31,30,31,30,31,31,30,31,30,31],hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Thai",epochs:["BBE","BE"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],digits:null,dateFormat:"dd/mm/yyyy",firstDay:0,isRTL:!1}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);e=this._t2gYear(t.year());return i.leapYear(e)},weekOfYear:function(e,t,r){var a=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);e=this._t2gYear(a.year());return i.weekOfYear(e,a.month(),a.day())},daysInMonth:function(e,t){var r=this._validate(e,t,this.minDay,n.local.invalidMonth);return this.daysPerMonth[r.month()-1]+(2===r.month()&&this.leapYear(r.year())?1:0)},weekDay:function(e,t,r){return(this.dayOfWeek(e,t,r)||7)<6},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate);e=this._t2gYear(a.year());return i.toJD(e,a.month(),a.day())},fromJD:function(e){var t=i.fromJD(e),r=this._g2tYear(t.year());return this.newDate(r,t.month(),t.day())},_t2gYear:function(e){return e-this.yearsOffset-(e>=1&&e<=this.yearsOffset?1:0)},_g2tYear:function(e){return e+this.yearsOffset+(e>=-this.yearsOffset&&e<=-1?1:0)}}),n.calendars.thai=o},{"../main":82,"object-assign":55}],81:[function(e,t,r){var n=e("../main"),a=e("object-assign");function i(e){this.local=this.regionalOptions[e||""]||this.regionalOptions[""]}i.prototype=new n.baseCalendar,a(i.prototype,{name:"UmmAlQura",hasYearZero:!1,minMonth:1,firstMonth:1,minDay:1,regionalOptions:{"":{name:"Umm al-Qura",epochs:["BH","AH"],monthNames:["Al-Muharram","Safar","Rabi' al-awwal","Rabi' Al-Thani","Jumada Al-Awwal","Jumada Al-Thani","Rajab","Sha'aban","Ramadan","Shawwal","Dhu al-Qi'dah","Dhu al-Hijjah"],monthNamesShort:["Muh","Saf","Rab1","Rab2","Jum1","Jum2","Raj","Sha'","Ram","Shaw","DhuQ","DhuH"],dayNames:["Yawm al-Ahad","Yawm al-Ithnain","Yawm al-Thal\u0101th\u0101\u2019","Yawm al-Arba\u2018\u0101\u2019","Yawm al-Kham\u012bs","Yawm al-Jum\u2018a","Yawm al-Sabt"],dayNamesMin:["Ah","Ith","Th","Ar","Kh","Ju","Sa"],digits:null,dateFormat:"yyyy/mm/dd",firstDay:6,isRTL:!0}},leapYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,n.local.invalidYear);return 355===this.daysInYear(t.year())},weekOfYear:function(e,t,r){var n=this.newDate(e,t,r);return n.add(-n.dayOfWeek(),"d"),Math.floor((n.dayOfYear()-1)/7)+1},daysInYear:function(e){for(var t=0,r=1;r<=12;r++)t+=this.daysInMonth(e,r);return t},daysInMonth:function(e,t){for(var r=this._validate(e,t,this.minDay,n.local.invalidMonth).toJD()-24e5+.5,a=0,i=0;ir)return o[a]-o[a-1];a++}return 30},weekDay:function(e,t,r){return 5!==this.dayOfWeek(e,t,r)},toJD:function(e,t,r){var a=this._validate(e,t,r,n.local.invalidDate),i=12*(a.year()-1)+a.month()-15292;return a.day()+o[i-1]-1+24e5-.5},fromJD:function(e){for(var t=e-24e5+.5,r=0,n=0;nt);n++)r++;var a=r+15292,i=Math.floor((a-1)/12),l=i+1,s=a-12*i,c=t-o[r-1]+1;return this.newDate(l,s,c)},isValid:function(e,t,r){var a=n.baseCalendar.prototype.isValid.apply(this,arguments);return a&&(a=(e=null!=e.year?e.year:e)>=1276&&e<=1500),a},_validate:function(e,t,r,a){var i=n.baseCalendar.prototype._validate.apply(this,arguments);if(i.year<1276||i.year>1500)throw a.replace(/\{0\}/,this.local.name);return i}}),n.calendars.ummalqura=i;var o=[20,50,79,109,138,168,197,227,256,286,315,345,374,404,433,463,492,522,551,581,611,641,670,700,729,759,788,818,847,877,906,936,965,995,1024,1054,1083,1113,1142,1172,1201,1231,1260,1290,1320,1350,1379,1409,1438,1468,1497,1527,1556,1586,1615,1645,1674,1704,1733,1763,1792,1822,1851,1881,1910,1940,1969,1999,2028,2058,2087,2117,2146,2176,2205,2235,2264,2294,2323,2353,2383,2413,2442,2472,2501,2531,2560,2590,2619,2649,2678,2708,2737,2767,2796,2826,2855,2885,2914,2944,2973,3003,3032,3062,3091,3121,3150,3180,3209,3239,3268,3298,3327,3357,3386,3416,3446,3476,3505,3535,3564,3594,3623,3653,3682,3712,3741,3771,3800,3830,3859,3889,3918,3948,3977,4007,4036,4066,4095,4125,4155,4185,4214,4244,4273,4303,4332,4362,4391,4421,4450,4480,4509,4539,4568,4598,4627,4657,4686,4716,4745,4775,4804,4834,4863,4893,4922,4952,4981,5011,5040,5070,5099,5129,5158,5188,5218,5248,5277,5307,5336,5366,5395,5425,5454,5484,5513,5543,5572,5602,5631,5661,5690,5720,5749,5779,5808,5838,5867,5897,5926,5956,5985,6015,6044,6074,6103,6133,6162,6192,6221,6251,6281,6311,6340,6370,6399,6429,6458,6488,6517,6547,6576,6606,6635,6665,6694,6724,6753,6783,6812,6842,6871,6901,6930,6960,6989,7019,7048,7078,7107,7137,7166,7196,7225,7255,7284,7314,7344,7374,7403,7433,7462,7492,7521,7551,7580,7610,7639,7669,7698,7728,7757,7787,7816,7846,7875,7905,7934,7964,7993,8023,8053,8083,8112,8142,8171,8201,8230,8260,8289,8319,8348,8378,8407,8437,8466,8496,8525,8555,8584,8614,8643,8673,8702,8732,8761,8791,8821,8850,8880,8909,8938,8968,8997,9027,9056,9086,9115,9145,9175,9205,9234,9264,9293,9322,9352,9381,9410,9440,9470,9499,9529,9559,9589,9618,9648,9677,9706,9736,9765,9794,9824,9853,9883,9913,9943,9972,10002,10032,10061,10090,10120,10149,10178,10208,10237,10267,10297,10326,10356,10386,10415,10445,10474,10504,10533,10562,10592,10621,10651,10680,10710,10740,10770,10799,10829,10858,10888,10917,10947,10976,11005,11035,11064,11094,11124,11153,11183,11213,11242,11272,11301,11331,11360,11389,11419,11448,11478,11507,11537,11567,11596,11626,11655,11685,11715,11744,11774,11803,11832,11862,11891,11921,11950,11980,12010,12039,12069,12099,12128,12158,12187,12216,12246,12275,12304,12334,12364,12393,12423,12453,12483,12512,12542,12571,12600,12630,12659,12688,12718,12747,12777,12807,12837,12866,12896,12926,12955,12984,13014,13043,13072,13102,13131,13161,13191,13220,13250,13280,13310,13339,13368,13398,13427,13456,13486,13515,13545,13574,13604,13634,13664,13693,13723,13752,13782,13811,13840,13870,13899,13929,13958,13988,14018,14047,14077,14107,14136,14166,14195,14224,14254,14283,14313,14342,14372,14401,14431,14461,14490,14520,14550,14579,14609,14638,14667,14697,14726,14756,14785,14815,14844,14874,14904,14933,14963,14993,15021,15051,15081,15110,15140,15169,15199,15228,15258,15287,15317,15347,15377,15406,15436,15465,15494,15524,15553,15582,15612,15641,15671,15701,15731,15760,15790,15820,15849,15878,15908,15937,15966,15996,16025,16055,16085,16114,16144,16174,16204,16233,16262,16292,16321,16350,16380,16409,16439,16468,16498,16528,16558,16587,16617,16646,16676,16705,16734,16764,16793,16823,16852,16882,16912,16941,16971,17001,17030,17060,17089,17118,17148,17177,17207,17236,17266,17295,17325,17355,17384,17414,17444,17473,17502,17532,17561,17591,17620,17650,17679,17709,17738,17768,17798,17827,17857,17886,17916,17945,17975,18004,18034,18063,18093,18122,18152,18181,18211,18241,18270,18300,18330,18359,18388,18418,18447,18476,18506,18535,18565,18595,18625,18654,18684,18714,18743,18772,18802,18831,18860,18890,18919,18949,18979,19008,19038,19068,19098,19127,19156,19186,19215,19244,19274,19303,19333,19362,19392,19422,19452,19481,19511,19540,19570,19599,19628,19658,19687,19717,19746,19776,19806,19836,19865,19895,19924,19954,19983,20012,20042,20071,20101,20130,20160,20190,20219,20249,20279,20308,20338,20367,20396,20426,20455,20485,20514,20544,20573,20603,20633,20662,20692,20721,20751,20780,20810,20839,20869,20898,20928,20957,20987,21016,21046,21076,21105,21135,21164,21194,21223,21253,21282,21312,21341,21371,21400,21430,21459,21489,21519,21548,21578,21607,21637,21666,21696,21725,21754,21784,21813,21843,21873,21902,21932,21962,21991,22021,22050,22080,22109,22138,22168,22197,22227,22256,22286,22316,22346,22375,22405,22434,22464,22493,22522,22552,22581,22611,22640,22670,22700,22730,22759,22789,22818,22848,22877,22906,22936,22965,22994,23024,23054,23083,23113,23143,23173,23202,23232,23261,23290,23320,23349,23379,23408,23438,23467,23497,23527,23556,23586,23616,23645,23674,23704,23733,23763,23792,23822,23851,23881,23910,23940,23970,23999,24029,24058,24088,24117,24147,24176,24206,24235,24265,24294,24324,24353,24383,24413,24442,24472,24501,24531,24560,24590,24619,24648,24678,24707,24737,24767,24796,24826,24856,24885,24915,24944,24974,25003,25032,25062,25091,25121,25150,25180,25210,25240,25269,25299,25328,25358,25387,25416,25446,25475,25505,25534,25564,25594,25624,25653,25683,25712,25742,25771,25800,25830,25859,25888,25918,25948,25977,26007,26037,26067,26096,26126,26155,26184,26214,26243,26272,26302,26332,26361,26391,26421,26451,26480,26510,26539,26568,26598,26627,26656,26686,26715,26745,26775,26805,26834,26864,26893,26923,26952,26982,27011,27041,27070,27099,27129,27159,27188,27218,27248,27277,27307,27336,27366,27395,27425,27454,27484,27513,27542,27572,27602,27631,27661,27691,27720,27750,27779,27809,27838,27868,27897,27926,27956,27985,28015,28045,28074,28104,28134,28163,28193,28222,28252,28281,28310,28340,28369,28399,28428,28458,28488,28517,28547,28577,28607,28636,28665,28695,28724,28754,28783,28813,28843,28872,28901,28931,28960,28990,29019,29049,29078,29108,29137,29167,29196,29226,29255,29285,29315,29345,29375,29404,29434,29463,29492,29522,29551,29580,29610,29640,29669,29699,29729,29759,29788,29818,29847,29876,29906,29935,29964,29994,30023,30053,30082,30112,30141,30171,30200,30230,30259,30289,30318,30348,30378,30408,30437,30467,30496,30526,30555,30585,30614,30644,30673,30703,30732,30762,30791,30821,30850,30880,30909,30939,30968,30998,31027,31057,31086,31116,31145,31175,31204,31234,31263,31293,31322,31352,31381,31411,31441,31471,31500,31530,31559,31589,31618,31648,31676,31706,31736,31766,31795,31825,31854,31884,31913,31943,31972,32002,32031,32061,32090,32120,32150,32180,32209,32239,32268,32298,32327,32357,32386,32416,32445,32475,32504,32534,32563,32593,32622,32652,32681,32711,32740,32770,32799,32829,32858,32888,32917,32947,32976,33006,33035,33065,33094,33124,33153,33183,33213,33243,33272,33302,33331,33361,33390,33420,33450,33479,33509,33539,33568,33598,33627,33657,33686,33716,33745,33775,33804,33834,33863,33893,33922,33952,33981,34011,34040,34069,34099,34128,34158,34187,34217,34247,34277,34306,34336,34365,34395,34424,34454,34483,34512,34542,34571,34601,34631,34660,34690,34719,34749,34778,34808,34837,34867,34896,34926,34955,34985,35015,35044,35074,35103,35133,35162,35192,35222,35251,35280,35310,35340,35370,35399,35429,35458,35488,35517,35547,35576,35605,35635,35665,35694,35723,35753,35782,35811,35841,35871,35901,35930,35960,35989,36019,36048,36078,36107,36136,36166,36195,36225,36254,36284,36314,36343,36373,36403,36433,36462,36492,36521,36551,36580,36610,36639,36669,36698,36728,36757,36786,36816,36845,36875,36904,36934,36963,36993,37022,37052,37081,37111,37141,37170,37200,37229,37259,37288,37318,37347,37377,37406,37436,37465,37495,37524,37554,37584,37613,37643,37672,37701,37731,37760,37790,37819,37849,37878,37908,37938,37967,37997,38027,38056,38085,38115,38144,38174,38203,38233,38262,38292,38322,38351,38381,38410,38440,38469,38499,38528,38558,38587,38617,38646,38676,38705,38735,38764,38794,38823,38853,38882,38912,38941,38971,39001,39030,39059,39089,39118,39148,39178,39208,39237,39267,39297,39326,39355,39385,39414,39444,39473,39503,39532,39562,39592,39621,39650,39680,39709,39739,39768,39798,39827,39857,39886,39916,39946,39975,40005,40035,40064,40094,40123,40153,40182,40212,40241,40271,40300,40330,40359,40389,40418,40448,40477,40507,40536,40566,40595,40625,40655,40685,40714,40744,40773,40803,40832,40862,40892,40921,40951,40980,41009,41039,41068,41098,41127,41157,41186,41216,41245,41275,41304,41334,41364,41393,41422,41452,41481,41511,41540,41570,41599,41629,41658,41688,41718,41748,41777,41807,41836,41865,41894,41924,41953,41983,42012,42042,42072,42102,42131,42161,42190,42220,42249,42279,42308,42337,42367,42397,42426,42456,42485,42515,42545,42574,42604,42633,42662,42692,42721,42751,42780,42810,42839,42869,42899,42929,42958,42988,43017,43046,43076,43105,43135,43164,43194,43223,43253,43283,43312,43342,43371,43401,43430,43460,43489,43519,43548,43578,43607,43637,43666,43696,43726,43755,43785,43814,43844,43873,43903,43932,43962,43991,44021,44050,44080,44109,44139,44169,44198,44228,44258,44287,44317,44346,44375,44405,44434,44464,44493,44523,44553,44582,44612,44641,44671,44700,44730,44759,44788,44818,44847,44877,44906,44936,44966,44996,45025,45055,45084,45114,45143,45172,45202,45231,45261,45290,45320,45350,45380,45409,45439,45468,45498,45527,45556,45586,45615,45644,45674,45704,45733,45763,45793,45823,45852,45882,45911,45940,45970,45999,46028,46058,46088,46117,46147,46177,46206,46236,46265,46295,46324,46354,46383,46413,46442,46472,46501,46531,46560,46590,46620,46649,46679,46708,46738,46767,46797,46826,46856,46885,46915,46944,46974,47003,47033,47063,47092,47122,47151,47181,47210,47240,47269,47298,47328,47357,47387,47417,47446,47476,47506,47535,47565,47594,47624,47653,47682,47712,47741,47771,47800,47830,47860,47890,47919,47949,47978,48008,48037,48066,48096,48125,48155,48184,48214,48244,48273,48303,48333,48362,48392,48421,48450,48480,48509,48538,48568,48598,48627,48657,48687,48717,48746,48776,48805,48834,48864,48893,48922,48952,48982,49011,49041,49071,49100,49130,49160,49189,49218,49248,49277,49306,49336,49365,49395,49425,49455,49484,49514,49543,49573,49602,49632,49661,49690,49720,49749,49779,49809,49838,49868,49898,49927,49957,49986,50016,50045,50075,50104,50133,50163,50192,50222,50252,50281,50311,50340,50370,50400,50429,50459,50488,50518,50547,50576,50606,50635,50665,50694,50724,50754,50784,50813,50843,50872,50902,50931,50960,50990,51019,51049,51078,51108,51138,51167,51197,51227,51256,51286,51315,51345,51374,51403,51433,51462,51492,51522,51552,51582,51611,51641,51670,51699,51729,51758,51787,51816,51846,51876,51906,51936,51965,51995,52025,52054,52083,52113,52142,52171,52200,52230,52260,52290,52319,52349,52379,52408,52438,52467,52497,52526,52555,52585,52614,52644,52673,52703,52733,52762,52792,52822,52851,52881,52910,52939,52969,52998,53028,53057,53087,53116,53146,53176,53205,53235,53264,53294,53324,53353,53383,53412,53441,53471,53500,53530,53559,53589,53619,53648,53678,53708,53737,53767,53796,53825,53855,53884,53913,53943,53973,54003,54032,54062,54092,54121,54151,54180,54209,54239,54268,54297,54327,54357,54387,54416,54446,54476,54505,54535,54564,54593,54623,54652,54681,54711,54741,54770,54800,54830,54859,54889,54919,54948,54977,55007,55036,55066,55095,55125,55154,55184,55213,55243,55273,55302,55332,55361,55391,55420,55450,55479,55508,55538,55567,55597,55627,55657,55686,55716,55745,55775,55804,55834,55863,55892,55922,55951,55981,56011,56040,56070,56100,56129,56159,56188,56218,56247,56276,56306,56335,56365,56394,56424,56454,56483,56513,56543,56572,56601,56631,56660,56690,56719,56749,56778,56808,56837,56867,56897,56926,56956,56985,57015,57044,57074,57103,57133,57162,57192,57221,57251,57280,57310,57340,57369,57399,57429,57458,57487,57517,57546,57576,57605,57634,57664,57694,57723,57753,57783,57813,57842,57871,57901,57930,57959,57989,58018,58048,58077,58107,58137,58167,58196,58226,58255,58285,58314,58343,58373,58402,58432,58461,58491,58521,58551,58580,58610,58639,58669,58698,58727,58757,58786,58816,58845,58875,58905,58934,58964,58994,59023,59053,59082,59111,59141,59170,59200,59229,59259,59288,59318,59348,59377,59407,59436,59466,59495,59525,59554,59584,59613,59643,59672,59702,59731,59761,59791,59820,59850,59879,59909,59939,59968,59997,60027,60056,60086,60115,60145,60174,60204,60234,60264,60293,60323,60352,60381,60411,60440,60469,60499,60528,60558,60588,60618,60648,60677,60707,60736,60765,60795,60824,60853,60883,60912,60942,60972,61002,61031,61061,61090,61120,61149,61179,61208,61237,61267,61296,61326,61356,61385,61415,61445,61474,61504,61533,61563,61592,61621,61651,61680,61710,61739,61769,61799,61828,61858,61888,61917,61947,61976,62006,62035,62064,62094,62123,62153,62182,62212,62242,62271,62301,62331,62360,62390,62419,62448,62478,62507,62537,62566,62596,62625,62655,62685,62715,62744,62774,62803,62832,62862,62891,62921,62950,62980,63009,63039,63069,63099,63128,63157,63187,63216,63246,63275,63305,63334,63363,63393,63423,63453,63482,63512,63541,63571,63600,63630,63659,63689,63718,63747,63777,63807,63836,63866,63895,63925,63955,63984,64014,64043,64073,64102,64131,64161,64190,64220,64249,64279,64309,64339,64368,64398,64427,64457,64486,64515,64545,64574,64603,64633,64663,64692,64722,64752,64782,64811,64841,64870,64899,64929,64958,64987,65017,65047,65076,65106,65136,65166,65195,65225,65254,65283,65313,65342,65371,65401,65431,65460,65490,65520,65549,65579,65608,65638,65667,65697,65726,65755,65785,65815,65844,65874,65903,65933,65963,65992,66022,66051,66081,66110,66140,66169,66199,66228,66258,66287,66317,66346,66376,66405,66435,66465,66494,66524,66553,66583,66612,66641,66671,66700,66730,66760,66789,66819,66849,66878,66908,66937,66967,66996,67025,67055,67084,67114,67143,67173,67203,67233,67262,67292,67321,67351,67380,67409,67439,67468,67497,67527,67557,67587,67617,67646,67676,67705,67735,67764,67793,67823,67852,67882,67911,67941,67971,68e3,68030,68060,68089,68119,68148,68177,68207,68236,68266,68295,68325,68354,68384,68414,68443,68473,68502,68532,68561,68591,68620,68650,68679,68708,68738,68768,68797,68827,68857,68886,68916,68946,68975,69004,69034,69063,69092,69122,69152,69181,69211,69240,69270,69300,69330,69359,69388,69418,69447,69476,69506,69535,69565,69595,69624,69654,69684,69713,69743,69772,69802,69831,69861,69890,69919,69949,69978,70008,70038,70067,70097,70126,70156,70186,70215,70245,70274,70303,70333,70362,70392,70421,70451,70481,70510,70540,70570,70599,70629,70658,70687,70717,70746,70776,70805,70835,70864,70894,70924,70954,70983,71013,71042,71071,71101,71130,71159,71189,71218,71248,71278,71308,71337,71367,71397,71426,71455,71485,71514,71543,71573,71602,71632,71662,71691,71721,71751,71781,71810,71839,71869,71898,71927,71957,71986,72016,72046,72075,72105,72135,72164,72194,72223,72253,72282,72311,72341,72370,72400,72429,72459,72489,72518,72548,72577,72607,72637,72666,72695,72725,72754,72784,72813,72843,72872,72902,72931,72961,72991,73020,73050,73080,73109,73139,73168,73197,73227,73256,73286,73315,73345,73375,73404,73434,73464,73493,73523,73552,73581,73611,73640,73669,73699,73729,73758,73788,73818,73848,73877,73907,73936,73965,73995,74024,74053,74083,74113,74142,74172,74202,74231,74261,74291,74320,74349,74379,74408,74437,74467,74497,74526,74556,74586,74615,74645,74675,74704,74733,74763,74792,74822,74851,74881,74910,74940,74969,74999,75029,75058,75088,75117,75147,75176,75206,75235,75264,75294,75323,75353,75383,75412,75442,75472,75501,75531,75560,75590,75619,75648,75678,75707,75737,75766,75796,75826,75856,75885,75915,75944,75974,76003,76032,76062,76091,76121,76150,76180,76210,76239,76269,76299,76328,76358,76387,76416,76446,76475,76505,76534,76564,76593,76623,76653,76682,76712,76741,76771,76801,76830,76859,76889,76918,76948,76977,77007,77036,77066,77096,77125,77155,77185,77214,77243,77273,77302,77332,77361,77390,77420,77450,77479,77509,77539,77569,77598,77627,77657,77686,77715,77745,77774,77804,77833,77863,77893,77923,77952,77982,78011,78041,78070,78099,78129,78158,78188,78217,78247,78277,78307,78336,78366,78395,78425,78454,78483,78513,78542,78572,78601,78631,78661,78690,78720,78750,78779,78808,78838,78867,78897,78926,78956,78985,79015,79044,79074,79104,79133,79163,79192,79222,79251,79281,79310,79340,79369,79399,79428,79458,79487,79517,79546,79576,79606,79635,79665,79695,79724,79753,79783,79812,79841,79871,79900,79930,79960,79990]},{"../main":82,"object-assign":55}],82:[function(e,t,r){var n=e("object-assign");function a(){this.regionalOptions=[],this.regionalOptions[""]={invalidCalendar:"Calendar {0} not found",invalidDate:"Invalid {0} date",invalidMonth:"Invalid {0} month",invalidYear:"Invalid {0} year",differentCalendars:"Cannot mix {0} and {1} dates"},this.local=this.regionalOptions[""],this.calendars={},this._localCals={}}function i(e,t,r,n){if(this._calendar=e,this._year=t,this._month=r,this._day=n,0===this._calendar._validateLevel&&!this._calendar.isValid(this._year,this._month,this._day))throw(c.local.invalidDate||c.regionalOptions[""].invalidDate).replace(/\{0\}/,this._calendar.local.name)}function o(e,t){return"000000".substring(0,t-(e=""+e).length)+e}function l(){this.shortYearCutoff="+10"}function s(e){this.local=this.regionalOptions[e]||this.regionalOptions[""]}n(a.prototype,{instance:function(e,t){e=(e||"gregorian").toLowerCase(),t=t||"";var r=this._localCals[e+"-"+t];if(!r&&this.calendars[e]&&(r=new this.calendars[e](t),this._localCals[e+"-"+t]=r),!r)throw(this.local.invalidCalendar||this.regionalOptions[""].invalidCalendar).replace(/\{0\}/,e);return r},newDate:function(e,t,r,n,a){return(n=(null!=e&&e.year?e.calendar():"string"==typeof n?this.instance(n,a):n)||this.instance()).newDate(e,t,r)},substituteDigits:function(e){return function(t){return(t+"").replace(/[0-9]/g,(function(t){return e[t]}))}},substituteChineseDigits:function(e,t){return function(r){for(var n="",a=0;r>0;){var i=r%10;n=(0===i?"":e[i]+t[a])+n,a++,r=Math.floor(r/10)}return 0===n.indexOf(e[1]+t[1])&&(n=n.substr(1)),n||e[0]}}}),n(i.prototype,{newDate:function(e,t,r){return this._calendar.newDate(null==e?this:e,t,r)},year:function(e){return 0===arguments.length?this._year:this.set(e,"y")},month:function(e){return 0===arguments.length?this._month:this.set(e,"m")},day:function(e){return 0===arguments.length?this._day:this.set(e,"d")},date:function(e,t,r){if(!this._calendar.isValid(e,t,r))throw(c.local.invalidDate||c.regionalOptions[""].invalidDate).replace(/\{0\}/,this._calendar.local.name);return this._year=e,this._month=t,this._day=r,this},leapYear:function(){return this._calendar.leapYear(this)},epoch:function(){return this._calendar.epoch(this)},formatYear:function(){return this._calendar.formatYear(this)},monthOfYear:function(){return this._calendar.monthOfYear(this)},weekOfYear:function(){return this._calendar.weekOfYear(this)},daysInYear:function(){return this._calendar.daysInYear(this)},dayOfYear:function(){return this._calendar.dayOfYear(this)},daysInMonth:function(){return this._calendar.daysInMonth(this)},dayOfWeek:function(){return this._calendar.dayOfWeek(this)},weekDay:function(){return this._calendar.weekDay(this)},extraInfo:function(){return this._calendar.extraInfo(this)},add:function(e,t){return this._calendar.add(this,e,t)},set:function(e,t){return this._calendar.set(this,e,t)},compareTo:function(e){if(this._calendar.name!==e._calendar.name)throw(c.local.differentCalendars||c.regionalOptions[""].differentCalendars).replace(/\{0\}/,this._calendar.local.name).replace(/\{1\}/,e._calendar.local.name);var t=this._year!==e._year?this._year-e._year:this._month!==e._month?this.monthOfYear()-e.monthOfYear():this._day-e._day;return 0===t?0:t<0?-1:1},calendar:function(){return this._calendar},toJD:function(){return this._calendar.toJD(this)},fromJD:function(e){return this._calendar.fromJD(e)},toJSDate:function(){return this._calendar.toJSDate(this)},fromJSDate:function(e){return this._calendar.fromJSDate(e)},toString:function(){return(this.year()<0?"-":"")+o(Math.abs(this.year()),4)+"-"+o(this.month(),2)+"-"+o(this.day(),2)}}),n(l.prototype,{_validateLevel:0,newDate:function(e,t,r){return null==e?this.today():(e.year&&(this._validate(e,t,r,c.local.invalidDate||c.regionalOptions[""].invalidDate),r=e.day(),t=e.month(),e=e.year()),new i(this,e,t,r))},today:function(){return this.fromJSDate(new Date)},epoch:function(e){return this._validate(e,this.minMonth,this.minDay,c.local.invalidYear||c.regionalOptions[""].invalidYear).year()<0?this.local.epochs[0]:this.local.epochs[1]},formatYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,c.local.invalidYear||c.regionalOptions[""].invalidYear);return(t.year()<0?"-":"")+o(Math.abs(t.year()),4)},monthsInYear:function(e){return this._validate(e,this.minMonth,this.minDay,c.local.invalidYear||c.regionalOptions[""].invalidYear),12},monthOfYear:function(e,t){var r=this._validate(e,t,this.minDay,c.local.invalidMonth||c.regionalOptions[""].invalidMonth);return(r.month()+this.monthsInYear(r)-this.firstMonth)%this.monthsInYear(r)+this.minMonth},fromMonthOfYear:function(e,t){var r=(t+this.firstMonth-2*this.minMonth)%this.monthsInYear(e)+this.minMonth;return this._validate(e,r,this.minDay,c.local.invalidMonth||c.regionalOptions[""].invalidMonth),r},daysInYear:function(e){var t=this._validate(e,this.minMonth,this.minDay,c.local.invalidYear||c.regionalOptions[""].invalidYear);return this.leapYear(t)?366:365},dayOfYear:function(e,t,r){var n=this._validate(e,t,r,c.local.invalidDate||c.regionalOptions[""].invalidDate);return n.toJD()-this.newDate(n.year(),this.fromMonthOfYear(n.year(),this.minMonth),this.minDay).toJD()+1},daysInWeek:function(){return 7},dayOfWeek:function(e,t,r){var n=this._validate(e,t,r,c.local.invalidDate||c.regionalOptions[""].invalidDate);return(Math.floor(this.toJD(n))+2)%this.daysInWeek()},extraInfo:function(e,t,r){return this._validate(e,t,r,c.local.invalidDate||c.regionalOptions[""].invalidDate),{}},add:function(e,t,r){return this._validate(e,this.minMonth,this.minDay,c.local.invalidDate||c.regionalOptions[""].invalidDate),this._correctAdd(e,this._add(e,t,r),t,r)},_add:function(e,t,r){if(this._validateLevel++,"d"===r||"w"===r){var n=e.toJD()+t*("w"===r?this.daysInWeek():1),a=e.calendar().fromJD(n);return this._validateLevel--,[a.year(),a.month(),a.day()]}try{var i=e.year()+("y"===r?t:0),o=e.monthOfYear()+("m"===r?t:0);a=e.day();"y"===r?(e.month()!==this.fromMonthOfYear(i,o)&&(o=this.newDate(i,e.month(),this.minDay).monthOfYear()),o=Math.min(o,this.monthsInYear(i)),a=Math.min(a,this.daysInMonth(i,this.fromMonthOfYear(i,o)))):"m"===r&&(!function(e){for(;ot-1+e.minMonth;)i++,o-=t,t=e.monthsInYear(i)}(this),a=Math.min(a,this.daysInMonth(i,this.fromMonthOfYear(i,o))));var l=[i,this.fromMonthOfYear(i,o),a];return this._validateLevel--,l}catch(e){throw this._validateLevel--,e}},_correctAdd:function(e,t,r,n){if(!(this.hasYearZero||"y"!==n&&"m"!==n||0!==t[0]&&e.year()>0==t[0]>0)){var a={y:[1,1,"y"],m:[1,this.monthsInYear(-1),"m"],w:[this.daysInWeek(),this.daysInYear(-1),"d"],d:[1,this.daysInYear(-1),"d"]}[n],i=r<0?-1:1;t=this._add(e,r*a[0]+i*a[1],a[2])}return e.date(t[0],t[1],t[2])},set:function(e,t,r){this._validate(e,this.minMonth,this.minDay,c.local.invalidDate||c.regionalOptions[""].invalidDate);var n="y"===r?t:e.year(),a="m"===r?t:e.month(),i="d"===r?t:e.day();return"y"!==r&&"m"!==r||(i=Math.min(i,this.daysInMonth(n,a))),e.date(n,a,i)},isValid:function(e,t,r){this._validateLevel++;var n=this.hasYearZero||0!==e;if(n){var a=this.newDate(e,t,this.minDay);n=t>=this.minMonth&&t-this.minMonth=this.minDay&&r-this.minDay13.5?13:1),c=a-(s>2.5?4716:4715);return c<=0&&c--,this.newDate(c,s,l)},toJSDate:function(e,t,r){var n=this._validate(e,t,r,c.local.invalidDate||c.regionalOptions[""].invalidDate),a=new Date(n.year(),n.month()-1,n.day());return a.setHours(0),a.setMinutes(0),a.setSeconds(0),a.setMilliseconds(0),a.setHours(a.getHours()>12?a.getHours()+2:0),a},fromJSDate:function(e){return this.newDate(e.getFullYear(),e.getMonth()+1,e.getDate())}});var c=t.exports=new a;c.cdate=i,c.baseCalendar=l,c.calendars.gregorian=s},{"object-assign":55}],83:[function(e,t,r){var n=e("object-assign"),a=e("./main");n(a.regionalOptions[""],{invalidArguments:"Invalid arguments",invalidFormat:"Cannot format a date from another calendar",missingNumberAt:"Missing number at position {0}",unknownNameAt:"Unknown name at position {0}",unexpectedLiteralAt:"Unexpected literal at position {0}",unexpectedText:"Additional text found at end"}),a.local=a.regionalOptions[""],n(a.cdate.prototype,{formatDate:function(e,t){return"string"!=typeof e&&(t=e,e=""),this._calendar.formatDate(e||"",this,t)}}),n(a.baseCalendar.prototype,{UNIX_EPOCH:a.instance().newDate(1970,1,1).toJD(),SECS_PER_DAY:86400,TICKS_EPOCH:a.instance().jdEpoch,TICKS_PER_DAY:864e9,ATOM:"yyyy-mm-dd",COOKIE:"D, dd M yyyy",FULL:"DD, MM d, yyyy",ISO_8601:"yyyy-mm-dd",JULIAN:"J",RFC_822:"D, d M yy",RFC_850:"DD, dd-M-yy",RFC_1036:"D, d M yy",RFC_1123:"D, d M yyyy",RFC_2822:"D, d M yyyy",RSS:"D, d M yy",TICKS:"!",TIMESTAMP:"@",W3C:"yyyy-mm-dd",formatDate:function(e,t,r){if("string"!=typeof e&&(r=t,t=e,e=""),!t)return"";if(t.calendar()!==this)throw a.local.invalidFormat||a.regionalOptions[""].invalidFormat;e=e||this.local.dateFormat;for(var n,i,o,l,s=(r=r||{}).dayNamesShort||this.local.dayNamesShort,c=r.dayNames||this.local.dayNames,u=r.monthNumbers||this.local.monthNumbers,d=r.monthNamesShort||this.local.monthNamesShort,f=r.monthNames||this.local.monthNames,p=(r.calculateWeek||this.local.calculateWeek,function(t,r){for(var n=1;w+n1}),h=function(e,t,r,n){var a=""+t;if(p(e,n))for(;a.length1},x=function(e,r){var n=y(e,r),i=[2,3,n?4:2,n?4:2,10,11,20]["oyYJ@!".indexOf(e)+1],o=new RegExp("^-?\\d{1,"+i+"}"),l=t.substring(k).match(o);if(!l)throw(a.local.missingNumberAt||a.regionalOptions[""].missingNumberAt).replace(/\{0\}/,k);return k+=l[0].length,parseInt(l[0],10)},b=this,_=function(){if("function"==typeof s){y("m");var e=s.call(b,t.substring(k));return k+=e.length,e}return x("m")},w=function(e,r,n,i){for(var o=y(e,i)?n:r,l=0;l-1){p=1,h=m;for(var S=this.daysInMonth(f,p);h>S;S=this.daysInMonth(f,p))p++,h-=S}return d>-1?this.fromJD(d):this.newDate(f,p,h)},determineDate:function(e,t,r,n,a){r&&"object"!=typeof r&&(a=n,n=r,r=null),"string"!=typeof n&&(a=n,n="");var i=this;return t=t?t.newDate():null,e=null==e?t:"string"==typeof e?function(e){try{return i.parseDate(n,e,a)}catch(e){}for(var t=((e=e.toLowerCase()).match(/^c/)&&r?r.newDate():null)||i.today(),o=/([+-]?[0-9]+)\s*(d|w|m|y)?/g,l=o.exec(e);l;)t.add(parseInt(l[1],10),l[2]||"d"),l=o.exec(e);return t}(e):"number"==typeof e?isNaN(e)||e===1/0||e===-1/0?t:i.today().add(e,"d"):i.newDate(e)}})},{"./main":82,"object-assign":55}],84:[function(e,t,r){"use strict";t.exports=[{path:"",backoff:0},{path:"M-2.4,-3V3L0.6,0Z",backoff:.6},{path:"M-3.7,-2.5V2.5L1.3,0Z",backoff:1.3},{path:"M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z",backoff:1.55},{path:"M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z",backoff:1.6},{path:"M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z",backoff:2},{path:"M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z",backoff:0,noRotate:!0},{path:"M2,2V-2H-2V2Z",backoff:0,noRotate:!0}]},{}],85:[function(e,t,r){"use strict";var n=e("./arrow_paths"),a=e("../../plots/font_attributes"),i=e("../../plots/cartesian/constants"),o=e("../../plot_api/plot_template").templatedArray;e("../../constants/axis_placeable_objects");t.exports=o("annotation",{visible:{valType:"boolean",dflt:!0,editType:"calc+arraydraw"},text:{valType:"string",editType:"calc+arraydraw"},textangle:{valType:"angle",dflt:0,editType:"calc+arraydraw"},font:a({editType:"calc+arraydraw",colorEditType:"arraydraw"}),width:{valType:"number",min:1,dflt:null,editType:"calc+arraydraw"},height:{valType:"number",min:1,dflt:null,editType:"calc+arraydraw"},opacity:{valType:"number",min:0,max:1,dflt:1,editType:"arraydraw"},align:{valType:"enumerated",values:["left","center","right"],dflt:"center",editType:"arraydraw"},valign:{valType:"enumerated",values:["top","middle","bottom"],dflt:"middle",editType:"arraydraw"},bgcolor:{valType:"color",dflt:"rgba(0,0,0,0)",editType:"arraydraw"},bordercolor:{valType:"color",dflt:"rgba(0,0,0,0)",editType:"arraydraw"},borderpad:{valType:"number",min:0,dflt:1,editType:"calc+arraydraw"},borderwidth:{valType:"number",min:0,dflt:1,editType:"calc+arraydraw"},showarrow:{valType:"boolean",dflt:!0,editType:"calc+arraydraw"},arrowcolor:{valType:"color",editType:"arraydraw"},arrowhead:{valType:"integer",min:0,max:n.length,dflt:1,editType:"arraydraw"},startarrowhead:{valType:"integer",min:0,max:n.length,dflt:1,editType:"arraydraw"},arrowside:{valType:"flaglist",flags:["end","start"],extras:["none"],dflt:"end",editType:"arraydraw"},arrowsize:{valType:"number",min:.3,dflt:1,editType:"calc+arraydraw"},startarrowsize:{valType:"number",min:.3,dflt:1,editType:"calc+arraydraw"},arrowwidth:{valType:"number",min:.1,editType:"calc+arraydraw"},standoff:{valType:"number",min:0,dflt:0,editType:"calc+arraydraw"},startstandoff:{valType:"number",min:0,dflt:0,editType:"calc+arraydraw"},ax:{valType:"any",editType:"calc+arraydraw"},ay:{valType:"any",editType:"calc+arraydraw"},axref:{valType:"enumerated",dflt:"pixel",values:["pixel",i.idRegex.x.toString()],editType:"calc"},ayref:{valType:"enumerated",dflt:"pixel",values:["pixel",i.idRegex.y.toString()],editType:"calc"},xref:{valType:"enumerated",values:["paper",i.idRegex.x.toString()],editType:"calc"},x:{valType:"any",editType:"calc+arraydraw"},xanchor:{valType:"enumerated",values:["auto","left","center","right"],dflt:"auto",editType:"calc+arraydraw"},xshift:{valType:"number",dflt:0,editType:"calc+arraydraw"},yref:{valType:"enumerated",values:["paper",i.idRegex.y.toString()],editType:"calc"},y:{valType:"any",editType:"calc+arraydraw"},yanchor:{valType:"enumerated",values:["auto","top","middle","bottom"],dflt:"auto",editType:"calc+arraydraw"},yshift:{valType:"number",dflt:0,editType:"calc+arraydraw"},clicktoshow:{valType:"enumerated",values:[!1,"onoff","onout"],dflt:!1,editType:"arraydraw"},xclick:{valType:"any",editType:"arraydraw"},yclick:{valType:"any",editType:"arraydraw"},hovertext:{valType:"string",editType:"arraydraw"},hoverlabel:{bgcolor:{valType:"color",editType:"arraydraw"},bordercolor:{valType:"color",editType:"arraydraw"},font:a({editType:"arraydraw"}),editType:"arraydraw"},captureevents:{valType:"boolean",editType:"arraydraw"},editType:"calc",_deprecated:{ref:{valType:"string",editType:"calc"}}})},{"../../constants/axis_placeable_objects":208,"../../plot_api/plot_template":268,"../../plots/cartesian/constants":286,"../../plots/font_attributes":310,"./arrow_paths":84}],86:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../../plots/cartesian/axes"),i=e("./draw").draw;function o(e){var t=e._fullLayout;n.filterVisible(t.annotations).forEach((function(t){var r=a.getFromId(e,t.xref),n=a.getFromId(e,t.yref),i=a.getRefType(t.xref),o=a.getRefType(t.yref);t._extremes={},"range"===i&&l(t,r),"range"===o&&l(t,n)}))}function l(e,t){var r,n=t._id,i=n.charAt(0),o=e[i],l=e["a"+i],s=e[i+"ref"],c=e["a"+i+"ref"],u=e["_"+i+"padplus"],d=e["_"+i+"padminus"],f={x:1,y:-1}[i]*e[i+"shift"],p=3*e.arrowsize*e.arrowwidth||0,h=p+f,m=p-f,g=3*e.startarrowsize*e.arrowwidth||0,v=g+f,y=g-f;if(c===s){var x=a.findExtremes(t,[t.r2c(o)],{ppadplus:h,ppadminus:m}),b=a.findExtremes(t,[t.r2c(l)],{ppadplus:Math.max(u,v),ppadminus:Math.max(d,y)});r={min:[x.min[0],b.min[0]],max:[x.max[0],b.max[0]]}}else v=l?v+l:v,y=l?y-l:y,r=a.findExtremes(t,[t.r2c(o)],{ppadplus:Math.max(u,h,v),ppadminus:Math.max(d,m,y)});e._extremes[n]=r}t.exports=function(e){var t=e._fullLayout;if(n.filterVisible(t.annotations).length&&e._fullData.length)return n.syncOrAsync([i,o],e)}},{"../../lib":232,"../../plots/cartesian/axes":279,"./draw":91}],87:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../../registry"),i=e("../../plot_api/plot_template").arrayEditor;function o(e,t){var r,n,a,i,o,s,c,u=e._fullLayout.annotations,d=[],f=[],p=[],h=(t||[]).length;for(r=0;r0||r.explicitOff.length>0},onClick:function(e,t){var r,l,s=o(e,t),c=s.on,u=s.off.concat(s.explicitOff),d={},f=e._fullLayout.annotations;if(!c.length&&!u.length)return;for(r=0;r2/3?"right":"center"),{center:0,middle:0,left:.5,bottom:-.5,right:-.5,top:.5}[t]}for(var Z=!1,W=["x","y"],X=0;X1)&&(ne===re?((pe=ae.r2fraction(t["a"+te]))<0||pe>1)&&(Z=!0):Z=!0),J=ae._offset+ae.r2p(t[te]),$=.5}else{var he="domain"===fe;"x"===te?(K=t[te],J=he?ae._offset+ae._length*K:J=T.l+T.w*K):(K=1-t[te],J=he?ae._offset+ae._length*K:J=T.t+T.h*K),$=t.showarrow?.5:K}if(t.showarrow){de.head=J;var me=t["a"+te];if(ee=oe*U(.5,t.xanchor)-le*U(.5,t.yanchor),ne===re){var ge=s.getRefType(ne);"domain"===ge?("y"===te&&(me=1-me),de.tail=ae._offset+ae._length*me):"paper"===ge?"y"===te?(me=1-me,de.tail=T.t+T.h*me):de.tail=T.l+T.w*me:de.tail=ae._offset+ae.r2p(me),Q=ee}else de.tail=J+me,Q=ee+me;de.text=de.tail+ee;var ve=w["x"===te?"width":"height"];if("paper"===re&&(de.head=o.constrain(de.head,1,ve-1)),"pixel"===ne){var ye=-Math.max(de.tail-3,de.text),xe=Math.min(de.tail+3,de.text)-ve;ye>0?(de.tail+=ye,de.text+=ye):xe>0&&(de.tail-=xe,de.text-=xe)}de.tail+=ue,de.head+=ue}else Q=ee=se*U($,ce),de.text=J+ee;de.text+=ue,ee+=ue,Q+=ue,t["_"+te+"padplus"]=se/2+Q,t["_"+te+"padminus"]=se/2-Q,t["_"+te+"size"]=se,t["_"+te+"shift"]=ee}if(Z)N.remove();else{var be=0,_e=0;if("left"!==t.align&&(be=(k-b)*("center"===t.align?.5:1)),"top"!==t.valign&&(_e=(z-_)*("middle"===t.valign?.5:1)),d)n.select("svg").attr({x:j+be-1,y:j+_e}).call(u.setClipUrl,B?D:null,e);else{var we=j+_e-m.top,Te=j+be-m.left;q.call(f.positionText,Te,we).call(u.setClipUrl,B?D:null,e)}Y.select("rect").call(u.setRect,j,j,k,z),H.call(u.setRect,F/2,F/2,E-F,V-F),N.call(u.setTranslate,Math.round(C.x.text-E/2),Math.round(C.y.text-V/2)),I.attr({transform:"rotate("+O+","+C.x.text+","+C.y.text+")"});var Me,ke=function(r,n){P.selectAll(".annotation-arrow-g").remove();var s=C.x.head,d=C.y.head,f=C.x.tail+r,p=C.y.tail+n,m=C.x.text+r,b=C.y.text+n,_=o.rotationXYMatrix(O,m,b),w=o.apply2DTransform(_),k=o.apply2DTransform2(_),D=+H.attr("width"),R=+H.attr("height"),z=m-.5*D,F=z+D,E=b-.5*R,j=E+R,B=[[z,E,z,j],[z,j,F,j],[F,j,F,E],[F,E,z,E]].map(k);if(!B.reduce((function(e,t){return e^!!o.segmentsIntersect(s,d,s+1e6,d+1e6,t[0],t[1],t[2],t[3])}),!1)){B.forEach((function(e){var t=o.segmentsIntersect(f,p,s,d,e[0],e[1],e[2],e[3]);t&&(f=t.x,p=t.y)}));var Y=t.arrowwidth,V=t.arrowcolor,U=t.arrowside,q=P.append("g").style({opacity:c.opacity(V)}).classed("annotation-arrow-g",!0),G=q.append("path").attr("d","M"+f+","+p+"L"+s+","+d).style("stroke-width",Y+"px").call(c.stroke,c.rgb(V));if(g(G,U,t),M.annotationPosition&&G.node().parentNode&&!i){var Z=s,W=d;if(t.standoff){var X=Math.sqrt(Math.pow(s-f,2)+Math.pow(d-p,2));Z+=t.standoff*(f-s)/X,W+=t.standoff*(p-d)/X}var J,Q,K=q.append("path").classed("annotation-arrow",!0).classed("anndrag",!0).classed("cursor-move",!0).attr({d:"M3,3H-3V-3H3ZM0,0L"+(f-Z)+","+(p-W),transform:l(Z,W)}).style("stroke-width",Y+6+"px").call(c.stroke,"rgba(0,0,0,0)").call(c.fill,"rgba(0,0,0,0)");h.init({element:K.node(),gd:e,prepFn:function(){var e=u.getTranslate(N);J=e.x,Q=e.y,v&&v.autorange&&A(v._name+".autorange",!0),x&&x.autorange&&A(x._name+".autorange",!0)},moveFn:function(e,r){var n=w(J,Q),a=n[0]+e,i=n[1]+r;N.call(u.setTranslate,a,i),L("x",y(v,e,"x",T,t)),L("y",y(x,r,"y",T,t)),t.axref===t.xref&&L("ax",y(v,e,"ax",T,t)),t.ayref===t.yref&&L("ay",y(x,r,"ay",T,t)),q.attr("transform",l(e,r)),I.attr({transform:"rotate("+O+","+a+","+i+")"})},doneFn:function(){a.call("_guiRelayout",e,S());var t=document.querySelector(".js-notes-box-panel");t&&t.redraw(t.selectedObj)}})}}};if(t.showarrow&&ke(0,0),R)h.init({element:N.node(),gd:e,prepFn:function(){Me=I.attr("transform")},moveFn:function(e,r){var n="pointer";if(t.showarrow)t.axref===t.xref?L("ax",y(v,e,"ax",T,t)):L("ax",t.ax+e),t.ayref===t.yref?L("ay",y(x,r,"ay",T.w,t)):L("ay",t.ay+r),ke(e,r);else{if(i)return;var a,o;if(v)a=y(v,e,"x",T,t);else{var s=t._xsize/T.w,c=t.x+(t._xshift-t.xshift)/T.w-s/2;a=h.align(c+e/T.w,s,0,1,t.xanchor)}if(x)o=y(x,r,"y",T,t);else{var u=t._ysize/T.h,d=t.y-(t._yshift+t.yshift)/T.h-u/2;o=h.align(d-r/T.h,u,0,1,t.yanchor)}L("x",a),L("y",o),v&&x||(n=h.getCursor(v?.5:a,x?.5:o,t.xanchor,t.yanchor))}I.attr({transform:l(e,r)+Me}),p(N,n)},clickFn:function(r,n){t.captureevents&&e.emit("plotly_clickannotation",G(n))},doneFn:function(){p(N),a.call("_guiRelayout",e,S());var t=document.querySelector(".js-notes-box-panel");t&&t.redraw(t.selectedObj)}})}}}t.exports={draw:function(e){var t=e._fullLayout;t._infolayer.selectAll(".annotation").remove();for(var r=0;r=0,x=t.indexOf("end")>=0,b=h.backoff*g+r.standoff,_=m.backoff*v+r.startstandoff;if("line"===p.nodeName){o={x:+e.attr("x1"),y:+e.attr("y1")},u={x:+e.attr("x2"),y:+e.attr("y2")};var w=o.x-u.x,T=o.y-u.y;if(f=(d=Math.atan2(T,w))+Math.PI,b&&_&&b+_>Math.sqrt(w*w+T*T))return void R();if(b){if(b*b>w*w+T*T)return void R();var M=b*Math.cos(d),k=b*Math.sin(d);u.x+=M,u.y+=k,e.attr({x2:u.x,y2:u.y})}if(_){if(_*_>w*w+T*T)return void R();var A=_*Math.cos(d),L=_*Math.sin(d);o.x-=A,o.y-=L,e.attr({x1:o.x,y1:o.y})}}else if("path"===p.nodeName){var S=p.getTotalLength(),D="";if(S1){c=!0;break}}c?e.fullLayout._infolayer.select(".annotation-"+e.id+'[data-index="'+l+'"]').remove():(s._pdata=a(e.glplot.cameraParams,[t.xaxis.r2l(s.x)*r[0],t.yaxis.r2l(s.y)*r[1],t.zaxis.r2l(s.z)*r[2]]),n(e.graphDiv,s,l,e.id,s._xa,s._ya))}}},{"../../plots/gl3d/project":313,"../annotations/draw":91}],98:[function(e,t,r){"use strict";var n=e("../../registry"),a=e("../../lib");t.exports={moduleType:"component",name:"annotations3d",schema:{subplots:{scene:{annotations:e("./attributes")}}},layoutAttributes:e("./attributes"),handleDefaults:e("./defaults"),includeBasePlot:function(e,t){var r=n.subplotsRegistry.gl3d;if(!r)return;for(var i=r.attrRegex,o=Object.keys(e),l=0;l=0))return e;if(3===o)n[o]>1&&(n[o]=1);else if(n[o]>=1)return e}var l=Math.round(255*n[0])+", "+Math.round(255*n[1])+", "+Math.round(255*n[2]);return i?"rgba("+l+", "+n[3]+")":"rgb("+l+")"}o.tinyRGB=function(e){var t=e.toRgb();return"rgb("+Math.round(t.r)+", "+Math.round(t.g)+", "+Math.round(t.b)+")"},o.rgb=function(e){return o.tinyRGB(n(e))},o.opacity=function(e){return e?n(e).getAlpha():0},o.addOpacity=function(e,t){var r=n(e).toRgb();return"rgba("+Math.round(r.r)+", "+Math.round(r.g)+", "+Math.round(r.b)+", "+t+")"},o.combine=function(e,t){var r=n(e).toRgb();if(1===r.a)return n(e).toRgbString();var a=n(t||c).toRgb(),i=1===a.a?a:{r:255*(1-a.a)+a.r*a.a,g:255*(1-a.a)+a.g*a.a,b:255*(1-a.a)+a.b*a.a},o={r:i.r*(1-r.a)+r.r*r.a,g:i.g*(1-r.a)+r.g*r.a,b:i.b*(1-r.a)+r.b*r.a};return n(o).toRgbString()},o.contrast=function(e,t,r){var a=n(e);return 1!==a.getAlpha()&&(a=n(o.combine(e,c))),(a.isDark()?t?a.lighten(t):c:r?a.darken(r):s).toString()},o.stroke=function(e,t){var r=n(t);e.style({stroke:o.tinyRGB(r),"stroke-opacity":r.getAlpha()})},o.fill=function(e,t){var r=n(t);e.style({fill:o.tinyRGB(r),"fill-opacity":r.getAlpha()})},o.clean=function(e){if(e&&"object"==typeof e){var t,r,n,a,l=Object.keys(e);for(t=0;t0?n>=s:n<=s));a++)n>u&&n0?n>=s:n<=s));a++)n>r[0]&&n1){var le=Math.pow(10,Math.floor(Math.log(oe)/Math.LN10));ae*=le*c.roundUp(oe/le,[2,5,10]),(Math.abs(Y.start)/Y.size+1e-6)%1<2e-6&&(ne.tick0=0)}ne.dtick=ae}ne.domain=[te+K,te+X-K],ne.setScale(),e.attr("transform",u(Math.round(R.l),Math.round(R.t)));var se,ce=e.select("."+k.cbtitleunshift).attr("transform",u(-Math.round(R.l),-Math.round(R.t))),ue=e.select("."+k.cbaxis),de=0;function fe(n,a){var i={propContainer:ne,propName:t._propPrefix+"title",traceIndex:t._traceIndex,_meta:t._meta,placeholder:I._dfltTitle.colorbar,containerGroup:e.select("."+k.cbtitle)},o="h"===n.charAt(0)?n.substr(1):"h"+n;e.selectAll("."+o+",."+o+"-math-group").remove(),m.draw(r,n,d(i,a||{}))}return c.syncOrAsync([i.previousPromises,function(){if(-1!==["top","bottom"].indexOf(E)){var e,t=R.l+(O+J)*R.w,r=ne.title.font.size;e="top"===E?(1-(te+X-K))*R.h+R.t+3+.75*r:(1-(te+K))*R.h+R.t-3-.25*r,fe(ne._id+"title",{attributes:{x:t,y:e,"text-anchor":"start"}})}},function(){if(-1!==["top","bottom"].indexOf(E)){var i=e.select("."+k.cbtitle),o=i.select("text"),s=[-_/2,_/2],d=i.select(".h"+ne._id+"title-math-group").node(),f=15.6;if(o.node()&&(f=parseInt(o.node().style.fontSize,10)*w),d?(de=p.bBox(d).height)>f&&(s[1]-=(de-f)/2):o.node()&&!o.classed(k.jsPlaceholder)&&(de=p.bBox(o.node()).height),de){if(de+=5,"top"===E)ne.domain[1]-=de/R.h,s[1]*=-1;else{ne.domain[0]+=de/R.h;var h=g.lineCount(o);s[1]+=(1-h)*f}i.attr("transform",u(s[0],s[1])),ne.setScale()}}e.selectAll("."+k.cbfills+",."+k.cblines).attr("transform",u(0,Math.round(R.h*(1-ne.domain[1])))),ue.attr("transform",u(0,Math.round(-R.t)));var m=e.select("."+k.cbfills).selectAll("rect."+k.cbfill).attr("style","").data(U);m.enter().append("rect").classed(k.cbfill,!0).style("stroke","none"),m.exit().remove();var v=j.map(ne.c2p).map(Math.round).sort((function(e,t){return e-t}));m.each((function(e,i){var o=[0===i?j[0]:(U[i]+U[i-1])/2,i===U.length-1?j[1]:(U[i]+U[i+1])/2].map(ne.c2p).map(Math.round);o[1]=c.constrain(o[1]+(o[1]>o[0])?1:-1,v[0],v[1]);var l=n.select(this).attr({x:$,width:Math.max(G,2),y:n.min(o),height:Math.max(n.max(o)-n.min(o),2)});if(t._fillgradient)p.gradient(l,r,t._id,"vertical",t._fillgradient,"fill");else{var s=B(e).replace("e-","");l.attr("fill",a(s).toHexString())}}));var y=e.select("."+k.cblines).selectAll("path."+k.cbline).data(N.color&&N.width?q:[]);y.enter().append("path").classed(k.cbline,!0),y.exit().remove(),y.each((function(e){n.select(this).attr("d","M"+$+","+(Math.round(ne.c2p(e))+N.width/2%1)+"h"+G).call(p.lineGroupStyle,N.width,H(e),N.dash)})),ue.selectAll("g."+ne._id+"tick,path").remove();var x=$+G+(_||0)/2-("outside"===t.ticks?1:0),b=l.calcTicks(ne),T=l.getTickSigns(ne)[2];return l.drawTicks(r,ne,{vals:"inside"===ne.ticks?l.clipEnds(ne,b):b,layer:ue,path:l.makeTickPath(ne,x,T),transFn:l.makeTransTickFn(ne)}),l.drawLabels(r,ne,{vals:b,layer:ue,transFn:l.makeTransTickLabelFn(ne),labelFns:l.makeLabelFns(ne,x)})},function(){if(-1===["top","bottom"].indexOf(E)){var e=ne.title.font.size,t=ne._offset+ne._length/2,a=R.l+(ne.position||0)*R.w+("right"===ne.side?10+e*(ne.showticklabels?1:.5):-10-e*(ne.showticklabels?.5:0));fe("h"+ne._id+"title",{avoid:{selection:n.select(r).selectAll("g."+ne._id+"tick"),side:E,offsetLeft:R.l,offsetTop:0,maxShift:I.width},attributes:{x:a,y:t,"text-anchor":"middle"},transform:{rotate:"-90",offset:0}})}},i.previousPromises,function(){var n=G+_/2;if(-1===ne.ticklabelposition.indexOf("inside")&&(n+=p.bBox(ue.node()).width),(se=ce.select("text")).node()&&!se.classed(k.jsPlaceholder)){var a,l=ce.select(".h"+ne._id+"title-math-group").node();a=l&&-1!==["top","bottom"].indexOf(E)?p.bBox(l).width:p.bBox(ce.node()).right-$-R.l,n=Math.max(n,a)}var c=2*D+n+A+_/2;e.select("."+k.cbbg).attr({x:$-D-(A+_)/2,y:re-W-Q,width:Math.max(c,2),height:Math.max(W+2*Q,2)}).call(h.fill,t.bgcolor).call(h.stroke,t.bordercolor).style("stroke-width",A),e.selectAll("."+k.cboutline).attr({x:$,y:re-W+C+("top"===E?de:0),width:Math.max(G,2),height:Math.max(W-2*C-de,2)}).call(h.stroke,t.outlinecolor).style({fill:"none","stroke-width":_});var d=({center:.5,right:1}[L]||0)*c;e.attr("transform",u(R.l-d,R.t));var m={},g=T[S],y=M[S];"pixels"===s?(m.y=P,m.t=W*g,m.b=W*y):(m.t=m.b=0,m.yt=P+o*g,m.yb=P-o*y);var x=T[L],b=M[L];if("pixels"===v)m.x=O,m.l=c*x,m.r=c*b;else{var w=c-G;m.l=w*x,m.r=w*b,m.xl=O-f*x,m.xr=O+f*b}i.autoMargin(r,t._id,m)}],r)}(r,t,e);v&&v.then&&(e._promises||[]).push(v),e._context.edits.colorbarPosition&&function(e,t,r){var n,a,i,l=r._fullLayout._size;s.init({element:e.node(),gd:r,prepFn:function(){n=e.attr("transform"),f(e)},moveFn:function(r,o){e.attr("transform",n+u(r,o)),a=s.align(t._uFrac+r/l.w,t._thickFrac,0,1,t.xanchor),i=s.align(t._vFrac-o/l.h,t._lenFrac,0,1,t.yanchor);var c=s.getCursor(a,i,t.xanchor,t.yanchor);f(e,c)},doneFn:function(){if(f(e),void 0!==a&&void 0!==i){var n={};n[t._propPrefix+"x"]=a,n[t._propPrefix+"y"]=i,void 0!==t._traceIndex?o.call("_guiRestyle",r,n,t._traceIndex):o.call("_guiRelayout",r,n)}}})}(r,t,e)})),t.exit().each((function(t){i.autoMargin(e,t._id)})).remove(),t.order()}}},{"../../constants/alignment":207,"../../lib":232,"../../lib/extend":226,"../../lib/setcursor":252,"../../lib/svg_text_utils":255,"../../plots/cartesian/axes":279,"../../plots/cartesian/axis_defaults":281,"../../plots/cartesian/layout_attributes":294,"../../plots/cartesian/position_defaults":297,"../../plots/plots":316,"../../registry":318,"../color":102,"../colorscale/helpers":113,"../dragelement":121,"../drawing":124,"../titles":200,"./constants":104,"@plotly/d3":11,tinycolor2:67}],107:[function(e,t,r){"use strict";var n=e("../../lib");t.exports=function(e){return n.isPlainObject(e.colorbar)}},{"../../lib":232}],108:[function(e,t,r){"use strict";t.exports={moduleType:"component",name:"colorbar",attributes:e("./attributes"),supplyDefaults:e("./defaults"),draw:e("./draw").draw,hasColorbar:e("./has_colorbar")}},{"./attributes":103,"./defaults":105,"./draw":106,"./has_colorbar":107}],109:[function(e,t,r){"use strict";var n=e("../colorbar/attributes"),a=e("../../lib/regex").counter,i=e("../../lib/sort_object_keys"),o=e("./scales.js").scales;i(o);function l(e){return"`"+e+"`"}t.exports=function(e,t){e=e||"";var r,i=(t=t||{}).cLetter||"c",s=("onlyIfNumerical"in t?t.onlyIfNumerical:Boolean(e),"noScale"in t?t.noScale:"marker.line"===e),c="showScaleDflt"in t?t.showScaleDflt:"z"===i,u="string"==typeof t.colorscaleDflt?o[t.colorscaleDflt]:null,d=t.editTypeOverride||"",f=e?e+".":"";"colorAttr"in t?(r=t.colorAttr,t.colorAttr):l(f+(r={z:"z",c:"color"}[i]));var p=i+"auto",h=i+"min",m=i+"max",g=i+"mid",v=(l(f+p),l(f+h),l(f+m),{});v[h]=v[m]=void 0;var y={};y[p]=!1;var x={};return"color"===r&&(x.color={valType:"color",arrayOk:!0,editType:d||"style"},t.anim&&(x.color.anim=!0)),x[p]={valType:"boolean",dflt:!0,editType:"calc",impliedEdits:v},x[h]={valType:"number",dflt:null,editType:d||"plot",impliedEdits:y},x[m]={valType:"number",dflt:null,editType:d||"plot",impliedEdits:y},x[g]={valType:"number",dflt:null,editType:"calc",impliedEdits:v},x.colorscale={valType:"colorscale",editType:"calc",dflt:u,impliedEdits:{autocolorscale:!1}},x.autocolorscale={valType:"boolean",dflt:!1!==t.autoColorDflt,editType:"calc",impliedEdits:{colorscale:void 0}},x.reversescale={valType:"boolean",dflt:!1,editType:"plot"},s||(x.showscale={valType:"boolean",dflt:c,editType:"calc"},x.colorbar=n),t.noColorAxis||(x.coloraxis={valType:"subplotid",regex:a("coloraxis"),dflt:null,editType:"calc"}),x}},{"../../lib/regex":248,"../../lib/sort_object_keys":253,"../colorbar/attributes":103,"./scales.js":117}],110:[function(e,t,r){"use strict";var n=e("fast-isnumeric"),a=e("../../lib"),i=e("./helpers").extractOpts;t.exports=function(e,t,r){var o,l=e._fullLayout,s=r.vals,c=r.containerStr,u=c?a.nestedProperty(t,c).get():t,d=i(u),f=!1!==d.auto,p=d.min,h=d.max,m=d.mid,g=function(){return a.aggNums(Math.min,null,s)},v=function(){return a.aggNums(Math.max,null,s)};(void 0===p?p=g():f&&(p=u._colorAx&&n(p)?Math.min(p,g()):g()),void 0===h?h=v():f&&(h=u._colorAx&&n(h)?Math.max(h,v()):v()),f&&void 0!==m&&(h-m>m-p?p=m-(h-m):h-m=0?l.colorscale.sequential:l.colorscale.sequentialminus,d._sync("colorscale",o))}},{"../../lib":232,"./helpers":113,"fast-isnumeric":17}],111:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("./helpers").hasColorscale,i=e("./helpers").extractOpts;t.exports=function(e,t){function r(e,t){var r=e["_"+t];void 0!==r&&(e[t]=r)}function o(e,a){var o=a.container?n.nestedProperty(e,a.container).get():e;if(o)if(o.coloraxis)o._colorAx=t[o.coloraxis];else{var l=i(o),s=l.auto;(s||void 0===l.min)&&r(o,a.min),(s||void 0===l.max)&&r(o,a.max),l.autocolorscale&&r(o,"colorscale")}}for(var l=0;l=0;n--,a++){var i=e[n];r[a]=[1-i[0],i[1]]}return r}function h(e,t){t=t||{};for(var r=e.domain,o=e.range,s=o.length,c=new Array(s),u=0;u4/3-l?o:l}},{}],119:[function(e,t,r){"use strict";var n=e("../../lib"),a=[["sw-resize","s-resize","se-resize"],["w-resize","move","e-resize"],["nw-resize","n-resize","ne-resize"]];t.exports=function(e,t,r,i){return e="left"===r?0:"center"===r?1:"right"===r?2:n.constrain(Math.floor(3*e),0,2),t="bottom"===i?0:"middle"===i?1:"top"===i?2:n.constrain(Math.floor(3*t),0,2),a[t][e]}},{"../../lib":232}],120:[function(e,t,r){"use strict";r.selectMode=function(e){return"lasso"===e||"select"===e},r.drawMode=function(e){return"drawclosedpath"===e||"drawopenpath"===e||"drawline"===e||"drawrect"===e||"drawcircle"===e},r.openMode=function(e){return"drawline"===e||"drawopenpath"===e},r.rectMode=function(e){return"select"===e||"drawline"===e||"drawrect"===e||"drawcircle"===e},r.freeMode=function(e){return"lasso"===e||"drawclosedpath"===e||"drawopenpath"===e},r.selectingOrDrawing=function(e){return r.freeMode(e)||r.rectMode(e)}},{}],121:[function(e,t,r){"use strict";var n=e("mouse-event-offset"),a=e("has-hover"),i=e("has-passive-events"),o=e("../../lib").removeElement,l=e("../../plots/cartesian/constants"),s=t.exports={};s.align=e("./align"),s.getCursor=e("./cursor");var c=e("./unhover");function u(){var e=document.createElement("div");e.className="dragcover";var t=e.style;return t.position="fixed",t.left=0,t.right=0,t.top=0,t.bottom=0,t.zIndex=999999999,t.background="none",document.body.appendChild(e),e}function d(e){return n(e.changedTouches?e.changedTouches[0]:e,document.body)}s.unhover=c.wrapped,s.unhoverRaw=c.raw,s.init=function(e){var t,r,n,c,f,p,h,m,g=e.gd,v=1,y=g._context.doubleClickDelay,x=e.element;g._mouseDownTime||(g._mouseDownTime=0),x.style.pointerEvents="all",x.onmousedown=_,i?(x._ontouchstart&&x.removeEventListener("touchstart",x._ontouchstart),x._ontouchstart=_,x.addEventListener("touchstart",_,{passive:!1})):x.ontouchstart=_;var b=e.clampFn||function(e,t,r){return Math.abs(e)y&&(v=Math.max(v-1,1)),g._dragged)e.doneFn&&e.doneFn();else if(e.clickFn&&e.clickFn(v,p),!m){var r;try{r=new MouseEvent("click",t)}catch(e){var n=d(t);(r=document.createEvent("MouseEvents")).initMouseEvent("click",t.bubbles,t.cancelable,t.view,t.detail,t.screenX,t.screenY,n[0],n[1],t.ctrlKey,t.altKey,t.shiftKey,t.metaKey,t.button,t.relatedTarget)}h.dispatchEvent(r)}g._dragging=!1,g._dragged=!1}else g._dragged=!1}},s.coverSlip=u},{"../../lib":232,"../../plots/cartesian/constants":286,"./align":118,"./cursor":119,"./unhover":122,"has-hover":48,"has-passive-events":49,"mouse-event-offset":53}],122:[function(e,t,r){"use strict";var n=e("../../lib/events"),a=e("../../lib/throttle"),i=e("../../lib/dom").getGraphDiv,o=e("../fx/constants"),l=t.exports={};l.wrapped=function(e,t,r){(e=i(e))._fullLayout&&a.clear(e._fullLayout._uid+o.HOVERID),l.raw(e,t,r)},l.raw=function(e,t){var r=e._fullLayout,a=e._hoverdata;t||(t={}),t.target&&!e._dragged&&!1===n.triggerHandler(e,"plotly_beforehover",t)||(r._hoverlayer.selectAll("g").remove(),r._hoverlayer.selectAll("line").remove(),r._hoverlayer.selectAll("circle").remove(),e._hoverdata=void 0,t.target&&a&&e.emit("plotly_unhover",{event:t,points:a}))}},{"../../lib/dom":224,"../../lib/events":225,"../../lib/throttle":256,"../fx/constants":136}],123:[function(e,t,r){"use strict";r.dash={valType:"string",values:["solid","dot","dash","longdash","dashdot","longdashdot"],dflt:"solid",editType:"style"},r.pattern={shape:{valType:"enumerated",values:["","/","\\","x","-","|","+","."],dflt:"",arrayOk:!0,editType:"style"},fillmode:{valType:"enumerated",values:["replace","overlay"],dflt:"replace",editType:"style"},bgcolor:{valType:"color",arrayOk:!0,editType:"style"},fgcolor:{valType:"color",arrayOk:!0,editType:"style"},fgopacity:{valType:"number",editType:"style",min:0,max:1},size:{valType:"number",min:0,dflt:8,arrayOk:!0,editType:"style"},solidity:{valType:"number",min:0,max:1,dflt:.3,arrayOk:!0,editType:"style"},editType:"style"}},{}],124:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../../lib"),i=a.numberFormat,o=e("fast-isnumeric"),l=e("tinycolor2"),s=e("../../registry"),c=e("../color"),u=e("../colorscale"),d=a.strTranslate,f=e("../../lib/svg_text_utils"),p=e("../../constants/xmlns_namespaces"),h=e("../../constants/alignment").LINE_SPACING,m=e("../../constants/interactions").DESELECTDIM,g=e("../../traces/scatter/subtypes"),v=e("../../traces/scatter/make_bubble_size_func"),y=e("../../components/fx/helpers").appendArrayPointValue,x=t.exports={};x.font=function(e,t,r,n){a.isPlainObject(t)&&(n=t.color,r=t.size,t=t.family),t&&e.style("font-family",t),r+1&&e.style("font-size",r+"px"),n&&e.call(c.fill,n)},x.setPosition=function(e,t,r){e.attr("x",t).attr("y",r)},x.setSize=function(e,t,r){e.attr("width",t).attr("height",r)},x.setRect=function(e,t,r,n,a){e.call(x.setPosition,t,r).call(x.setSize,n,a)},x.translatePoint=function(e,t,r,n){var a=r.c2p(e.x),i=n.c2p(e.y);return!!(o(a)&&o(i)&&t.node())&&("text"===t.node().nodeName?t.attr("x",a).attr("y",i):t.attr("transform",d(a,i)),!0)},x.translatePoints=function(e,t,r){e.each((function(e){var a=n.select(this);x.translatePoint(e,a,t,r)}))},x.hideOutsideRangePoint=function(e,t,r,n,a,i){t.attr("display",r.isPtWithinRange(e,a)&&n.isPtWithinRange(e,i)?null:"none")},x.hideOutsideRangePoints=function(e,t){if(t._hasClipOnAxisFalse){var r=t.xaxis,a=t.yaxis;e.each((function(t){var i=t[0].trace,o=i.xcalendar,l=i.ycalendar,c=s.traceIs(i,"bar-like")?".bartext":".point,.textpoint";e.selectAll(c).each((function(e){x.hideOutsideRangePoint(e,n.select(this),r,a,o,l)}))}))}},x.crispRound=function(e,t,r){return t&&o(t)?e._context.staticPlot?t:t<1?1:Math.round(t):r||0},x.singleLineStyle=function(e,t,r,n,a){t.style("fill","none");var i=(((e||[])[0]||{}).trace||{}).line||{},o=r||i.width||0,l=a||i.dash||"";c.stroke(t,n||i.color),x.dashLine(t,l,o)},x.lineGroupStyle=function(e,t,r,a){e.style("fill","none").each((function(e){var i=(((e||[])[0]||{}).trace||{}).line||{},o=t||i.width||0,l=a||i.dash||"";n.select(this).call(c.stroke,r||i.color).call(x.dashLine,l,o)}))},x.dashLine=function(e,t,r){r=+r||0,t=x.dashStyle(t,r),e.style({"stroke-dasharray":t,"stroke-width":r+"px"})},x.dashStyle=function(e,t){t=+t||1;var r=Math.max(t,3);return"solid"===e?e="":"dot"===e?e=r+"px,"+r+"px":"dash"===e?e=3*r+"px,"+3*r+"px":"longdash"===e?e=5*r+"px,"+5*r+"px":"dashdot"===e?e=3*r+"px,"+r+"px,"+r+"px,"+r+"px":"longdashdot"===e&&(e=5*r+"px,"+2*r+"px,"+r+"px,"+2*r+"px"),e},x.singleFillStyle=function(e){var t=(((n.select(e.node()).data()[0]||[])[0]||{}).trace||{}).fillcolor;t&&e.call(c.fill,t)},x.fillGroupStyle=function(e){e.style("stroke-width",0).each((function(e){var t=n.select(this);e[0].trace&&t.call(c.fill,e[0].trace.fillcolor)}))};var b=e("./symbol_defs");x.symbolNames=[],x.symbolFuncs=[],x.symbolNeedLines={},x.symbolNoDot={},x.symbolNoFill={},x.symbolList=[],Object.keys(b).forEach((function(e){var t=b[e],r=t.n;x.symbolList.push(r,String(r),e,r+100,String(r+100),e+"-open"),x.symbolNames[r]=e,x.symbolFuncs[r]=t.f,t.needLine&&(x.symbolNeedLines[r]=!0),t.noDot?x.symbolNoDot[r]=!0:x.symbolList.push(r+200,String(r+200),e+"-dot",r+300,String(r+300),e+"-open-dot"),t.noFill&&(x.symbolNoFill[r]=!0)}));var _=x.symbolNames.length;function w(e,t){var r=e%100;return x.symbolFuncs[r](t)+(e>=200?"M0,0.5L0.5,0L0,-0.5L-0.5,0Z":"")}x.symbolNumber=function(e){if(o(e))e=+e;else if("string"==typeof e){var t=0;e.indexOf("-open")>0&&(t=100,e=e.replace("-open","")),e.indexOf("-dot")>0&&(t+=200,e=e.replace("-dot","")),(e=x.symbolNames.indexOf(e))>=0&&(e+=t)}return e%100>=_||e>=400?0:Math.floor(Math.max(e,0))};var T={x1:1,x2:0,y1:0,y2:0},M={x1:0,x2:0,y1:1,y2:0},k=i("~f"),A={radial:{node:"radialGradient"},radialreversed:{node:"radialGradient",reversed:!0},horizontal:{node:"linearGradient",attrs:T},horizontalreversed:{node:"linearGradient",attrs:T,reversed:!0},vertical:{node:"linearGradient",attrs:M},verticalreversed:{node:"linearGradient",attrs:M,reversed:!0}};x.gradient=function(e,t,r,i,o,s){for(var u=o.length,d=A[i],f=new Array(u),p=0;p"+v(e);h._gradientUrlQueryParts[y]=1},x.pattern=function(e,t,r,i,o,l,s,u,d,f,p,h){var m="legend"===t;u&&("overlay"===d?(f=u,p=c.contrast(f)):(f=void 0,p=u));var g,v,y,x,b,_,w,T,M,k,A,L=r._fullLayout,S="p"+L._uid+"-"+i,D={};switch(o){case"/":g=l*Math.sqrt(2),v=l*Math.sqrt(2),_="path",D={d:y="M-"+g/4+","+v/4+"l"+g/2+",-"+v/2+"M0,"+v+"L"+g+",0M"+g/4*3+","+v/4*5+"l"+g/2+",-"+v/2,opacity:h,stroke:p,"stroke-width":(x=s*l)+"px"};break;case"\\":g=l*Math.sqrt(2),v=l*Math.sqrt(2),_="path",D={d:y="M"+g/4*3+",-"+v/4+"l"+g/2+","+v/2+"M0,0L"+g+","+v+"M-"+g/4+","+v/4*3+"l"+g/2+","+v/2,opacity:h,stroke:p,"stroke-width":(x=s*l)+"px"};break;case"x":g=l*Math.sqrt(2),v=l*Math.sqrt(2),y="M-"+g/4+","+v/4+"l"+g/2+",-"+v/2+"M0,"+v+"L"+g+",0M"+g/4*3+","+v/4*5+"l"+g/2+",-"+v/2+"M"+g/4*3+",-"+v/4+"l"+g/2+","+v/2+"M0,0L"+g+","+v+"M-"+g/4+","+v/4*3+"l"+g/2+","+v/2,x=l-l*Math.sqrt(1-s),_="path",D={d:y,opacity:h,stroke:p,"stroke-width":x+"px"};break;case"|":_="path",_="path",D={d:y="M"+(g=l)/2+",0L"+g/2+","+(v=l),opacity:h,stroke:p,"stroke-width":(x=s*l)+"px"};break;case"-":_="path",_="path",D={d:y="M0,"+(v=l)/2+"L"+(g=l)+","+v/2,opacity:h,stroke:p,"stroke-width":(x=s*l)+"px"};break;case"+":_="path",y="M"+(g=l)/2+",0L"+g/2+","+(v=l)+"M0,"+v/2+"L"+g+","+v/2,x=l-l*Math.sqrt(1-s),_="path",D={d:y,opacity:h,stroke:p,"stroke-width":x+"px"};break;case".":g=l,v=l,s.pattern_filled";L._patternUrlQueryParts[P]=1},x.initGradients=function(e){var t=e._fullLayout;a.ensureSingle(t._defs,"g","gradients").selectAll("linearGradient,radialGradient").remove(),t._gradientUrlQueryParts={}},x.initPatterns=function(e){var t=e._fullLayout;a.ensureSingle(t._defs,"g","patterns").selectAll("pattern").remove(),t._patternUrlQueryParts={}},x.getPatternAttr=function(e,t,r){return e&&a.isArrayOrTypedArray(e)?t=100,t.attr("d",w(u,s))}var d,f,p,h=!1;if(e.so)p=l.outlierwidth,f=l.outliercolor,d=o.outliercolor;else{var m=(l||{}).width;p=(e.mlw+1||m+1||(e.trace?(e.trace.marker.line||{}).width:0)+1)-1||0,f="mlc"in e?e.mlcc=n.lineScale(e.mlc):a.isArrayOrTypedArray(l.color)?c.defaultLine:l.color,a.isArrayOrTypedArray(o.color)&&(d=c.defaultLine,h=!0),d="mc"in e?e.mcc=n.markerScale(e.mc):o.color||"rgba(0,0,0,0)",n.selectedColorFn&&(d=n.selectedColorFn(e))}if(e.om)t.call(c.stroke,d).style({"stroke-width":(p||1)+"px",fill:"none"});else{t.style("stroke-width",(e.isBlank?0:p)+"px");var g=o.gradient,v=e.mgt;v?h=!0:v=g&&g.type,a.isArrayOrTypedArray(v)&&(v=v[0],A[v]||(v=0));var y=o.pattern,b=y&&x.getPatternAttr(y.shape,e.i,"");if(v&&"none"!==v){var _=e.mgc;_?h=!0:_=g.color;var T=r.uid;h&&(T+="-"+e.i),x.gradient(t,i,T,v,[[0,_],[1,d]],"fill")}else if(b){var M=x.getPatternAttr(y.bgcolor,e.i,null),k=x.getPatternAttr(y.fgcolor,e.i,null),L=y.fgopacity,S=x.getPatternAttr(y.size,e.i,8),D=x.getPatternAttr(y.solidity,e.i,.3),C=e.mcc||a.isArrayOrTypedArray(y.shape)||a.isArrayOrTypedArray(y.bgcolor)||a.isArrayOrTypedArray(y.size)||a.isArrayOrTypedArray(y.solidity),O=r.uid;C&&(O+="-"+e.i),x.pattern(t,"point",i,O,b,S,D,e.mcc,y.fillmode,M,k,L)}else c.fill(t,d);p&&c.stroke(t,f)}},x.makePointStyleFns=function(e){var t={},r=e.marker;return t.markerScale=x.tryColorscale(r,""),t.lineScale=x.tryColorscale(r,"line"),s.traceIs(e,"symbols")&&(t.ms2mrc=g.isBubble(e)?v(e):function(){return(r.size||6)/2}),e.selectedpoints&&a.extendFlat(t,x.makeSelectedPointStyleFns(e)),t},x.makeSelectedPointStyleFns=function(e){var t={},r=e.selected||{},n=e.unselected||{},i=e.marker||{},o=r.marker||{},l=n.marker||{},c=i.opacity,u=o.opacity,d=l.opacity,f=void 0!==u,p=void 0!==d;(a.isArrayOrTypedArray(c)||f||p)&&(t.selectedOpacityFn=function(e){var t=void 0===e.mo?i.opacity:e.mo;return e.selected?f?u:t:p?d:m*t});var h=i.color,g=o.color,v=l.color;(g||v)&&(t.selectedColorFn=function(e){var t=e.mcc||h;return e.selected?g||t:v||t});var y=i.size,x=o.size,b=l.size,_=void 0!==x,w=void 0!==b;return s.traceIs(e,"symbols")&&(_||w)&&(t.selectedSizeFn=function(e){var t=e.mrc||y/2;return e.selected?_?x/2:t:w?b/2:t}),t},x.makeSelectedTextStyleFns=function(e){var t={},r=e.selected||{},n=e.unselected||{},a=e.textfont||{},i=r.textfont||{},o=n.textfont||{},l=a.color,s=i.color,u=o.color;return t.selectedTextColorFn=function(e){var t=e.tc||l;return e.selected?s||t:u||(s?t:c.addOpacity(t,m))},t},x.selectedPointStyle=function(e,t){if(e.size()&&t.selectedpoints){var r=x.makeSelectedPointStyleFns(t),a=t.marker||{},i=[];r.selectedOpacityFn&&i.push((function(e,t){e.style("opacity",r.selectedOpacityFn(t))})),r.selectedColorFn&&i.push((function(e,t){c.fill(e,r.selectedColorFn(t))})),r.selectedSizeFn&&i.push((function(e,t){var n=t.mx||a.symbol||0,i=r.selectedSizeFn(t);e.attr("d",w(x.symbolNumber(n),i)),t.mrc2=i})),i.length&&e.each((function(e){for(var t=n.select(this),r=0;r0?r:0}x.textPointStyle=function(e,t,r){if(e.size()){var i;if(t.selectedpoints){var o=x.makeSelectedTextStyleFns(t);i=o.selectedTextColorFn}var l=t.texttemplate,s=r._fullLayout;e.each((function(e){var o=n.select(this),c=l?a.extractOption(e,t,"txt","texttemplate"):a.extractOption(e,t,"tx","text");if(c||0===c){if(l){var u=t._module.formatLabels,d=u?u(e,t,s):{},p={};y(p,t,e.i);var h=t._meta||{};c=a.texttemplateString(c,d,s._d3locale,p,e,h)}var m=e.tp||t.textposition,g=D(e,t),v=i?i(e):e.tc||t.textfont.color;o.call(x.font,e.tf||t.textfont.family,g,v).text(c).call(f.convertToTspans,r).call(S,m,g,e.mrc)}else o.remove()}))}},x.selectedTextStyle=function(e,t){if(e.size()&&t.selectedpoints){var r=x.makeSelectedTextStyleFns(t);e.each((function(e){var a=n.select(this),i=r.selectedTextColorFn(e),o=e.tp||t.textposition,l=D(e,t);c.fill(a,i);var u=s.traceIs(t,"bar-like");S(a,o,l,e.mrc2||e.mrc,u)}))}};function C(e,t,r,a){var i=e[0]-t[0],o=e[1]-t[1],l=r[0]-t[0],s=r[1]-t[1],c=Math.pow(i*i+o*o,.25),u=Math.pow(l*l+s*s,.25),d=(u*u*i-c*c*l)*a,f=(u*u*o-c*c*s)*a,p=3*u*(c+u),h=3*c*(c+u);return[[n.round(t[0]+(p&&d/p),2),n.round(t[1]+(p&&f/p),2)],[n.round(t[0]-(h&&d/h),2),n.round(t[1]-(h&&f/h),2)]]}x.smoothopen=function(e,t){if(e.length<3)return"M"+e.join("L");var r,n="M"+e[0],a=[];for(r=1;r=1e4&&(x.savedBBoxes={},I=0),r&&(x.savedBBoxes[r]=g),I++,a.extendFlat({},g)},x.setClipUrl=function(e,t,r){e.attr("clip-path",z(t,r))},x.getTranslate=function(e){var t=(e[e.attr?"attr":"getAttribute"]("transform")||"").replace(/.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/,(function(e,t,r){return[t,r].join(" ")})).split(" ");return{x:+t[0]||0,y:+t[1]||0}},x.setTranslate=function(e,t,r){var n=e.attr?"attr":"getAttribute",a=e.attr?"attr":"setAttribute",i=e[n]("transform")||"";return t=t||0,r=r||0,i=i.replace(/(\btranslate\(.*?\);?)/,"").trim(),i=(i+=d(t,r)).trim(),e[a]("transform",i),i},x.getScale=function(e){var t=(e[e.attr?"attr":"getAttribute"]("transform")||"").replace(/.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/,(function(e,t,r){return[t,r].join(" ")})).split(" ");return{x:+t[0]||1,y:+t[1]||1}},x.setScale=function(e,t,r){var n=e.attr?"attr":"getAttribute",a=e.attr?"attr":"setAttribute",i=e[n]("transform")||"";return t=t||1,r=r||1,i=i.replace(/(\bscale\(.*?\);?)/,"").trim(),i=(i+="scale("+t+","+r+")").trim(),e[a]("transform",i),i};var N=/\s*sc.*/;x.setPointGroupScale=function(e,t,r){if(t=t||1,r=r||1,e){var n=1===t&&1===r?"":"scale("+t+","+r+")";e.each((function(){var e=(this.getAttribute("transform")||"").replace(N,"");e=(e+=n).trim(),this.setAttribute("transform",e)}))}};var F=/translate\([^)]*\)\s*$/;x.setTextPointsScale=function(e,t,r){e&&e.each((function(){var e,a=n.select(this),i=a.select("text");if(i.node()){var o=parseFloat(i.attr("x")||0),l=parseFloat(i.attr("y")||0),s=(a.attr("transform")||"").match(F);e=1===t&&1===r?[]:[d(o,l),"scale("+t+","+r+")",d(-o,-l)],s&&e.push(s),a.attr("transform",e.join(""))}}))}},{"../../components/fx/helpers":138,"../../constants/alignment":207,"../../constants/interactions":211,"../../constants/xmlns_namespaces":213,"../../lib":232,"../../lib/svg_text_utils":255,"../../registry":318,"../../traces/scatter/make_bubble_size_func":375,"../../traces/scatter/subtypes":383,"../color":102,"../colorscale":114,"./symbol_defs":125,"@plotly/d3":11,"fast-isnumeric":17,tinycolor2:67}],125:[function(e,t,r){"use strict";var n=e("@plotly/d3");t.exports={circle:{n:0,f:function(e){var t=n.round(e,2);return"M"+t+",0A"+t+","+t+" 0 1,1 0,-"+t+"A"+t+","+t+" 0 0,1 "+t+",0Z"}},square:{n:1,f:function(e){var t=n.round(e,2);return"M"+t+","+t+"H-"+t+"V-"+t+"H"+t+"Z"}},diamond:{n:2,f:function(e){var t=n.round(1.3*e,2);return"M"+t+",0L0,"+t+"L-"+t+",0L0,-"+t+"Z"}},cross:{n:3,f:function(e){var t=n.round(.4*e,2),r=n.round(1.2*e,2);return"M"+r+","+t+"H"+t+"V"+r+"H-"+t+"V"+t+"H-"+r+"V-"+t+"H-"+t+"V-"+r+"H"+t+"V-"+t+"H"+r+"Z"}},x:{n:4,f:function(e){var t=n.round(.8*e/Math.sqrt(2),2),r="l"+t+","+t,a="l"+t+",-"+t,i="l-"+t+",-"+t,o="l-"+t+","+t;return"M0,"+t+r+a+i+a+i+o+i+o+r+o+r+"Z"}},"triangle-up":{n:5,f:function(e){var t=n.round(2*e/Math.sqrt(3),2);return"M-"+t+","+n.round(e/2,2)+"H"+t+"L0,-"+n.round(e,2)+"Z"}},"triangle-down":{n:6,f:function(e){var t=n.round(2*e/Math.sqrt(3),2);return"M-"+t+",-"+n.round(e/2,2)+"H"+t+"L0,"+n.round(e,2)+"Z"}},"triangle-left":{n:7,f:function(e){var t=n.round(2*e/Math.sqrt(3),2);return"M"+n.round(e/2,2)+",-"+t+"V"+t+"L-"+n.round(e,2)+",0Z"}},"triangle-right":{n:8,f:function(e){var t=n.round(2*e/Math.sqrt(3),2);return"M-"+n.round(e/2,2)+",-"+t+"V"+t+"L"+n.round(e,2)+",0Z"}},"triangle-ne":{n:9,f:function(e){var t=n.round(.6*e,2),r=n.round(1.2*e,2);return"M-"+r+",-"+t+"H"+t+"V"+r+"Z"}},"triangle-se":{n:10,f:function(e){var t=n.round(.6*e,2),r=n.round(1.2*e,2);return"M"+t+",-"+r+"V"+t+"H-"+r+"Z"}},"triangle-sw":{n:11,f:function(e){var t=n.round(.6*e,2),r=n.round(1.2*e,2);return"M"+r+","+t+"H-"+t+"V-"+r+"Z"}},"triangle-nw":{n:12,f:function(e){var t=n.round(.6*e,2),r=n.round(1.2*e,2);return"M-"+t+","+r+"V-"+t+"H"+r+"Z"}},pentagon:{n:13,f:function(e){var t=n.round(.951*e,2),r=n.round(.588*e,2),a=n.round(-e,2),i=n.round(-.309*e,2);return"M"+t+","+i+"L"+r+","+n.round(.809*e,2)+"H-"+r+"L-"+t+","+i+"L0,"+a+"Z"}},hexagon:{n:14,f:function(e){var t=n.round(e,2),r=n.round(e/2,2),a=n.round(e*Math.sqrt(3)/2,2);return"M"+a+",-"+r+"V"+r+"L0,"+t+"L-"+a+","+r+"V-"+r+"L0,-"+t+"Z"}},hexagon2:{n:15,f:function(e){var t=n.round(e,2),r=n.round(e/2,2),a=n.round(e*Math.sqrt(3)/2,2);return"M-"+r+","+a+"H"+r+"L"+t+",0L"+r+",-"+a+"H-"+r+"L-"+t+",0Z"}},octagon:{n:16,f:function(e){var t=n.round(.924*e,2),r=n.round(.383*e,2);return"M-"+r+",-"+t+"H"+r+"L"+t+",-"+r+"V"+r+"L"+r+","+t+"H-"+r+"L-"+t+","+r+"V-"+r+"Z"}},star:{n:17,f:function(e){var t=1.4*e,r=n.round(.225*t,2),a=n.round(.951*t,2),i=n.round(.363*t,2),o=n.round(.588*t,2),l=n.round(-t,2),s=n.round(-.309*t,2),c=n.round(.118*t,2),u=n.round(.809*t,2);return"M"+r+","+s+"H"+a+"L"+i+","+c+"L"+o+","+u+"L0,"+n.round(.382*t,2)+"L-"+o+","+u+"L-"+i+","+c+"L-"+a+","+s+"H-"+r+"L0,"+l+"Z"}},hexagram:{n:18,f:function(e){var t=n.round(.66*e,2),r=n.round(.38*e,2),a=n.round(.76*e,2);return"M-"+a+",0l-"+r+",-"+t+"h"+a+"l"+r+",-"+t+"l"+r+","+t+"h"+a+"l-"+r+","+t+"l"+r+","+t+"h-"+a+"l-"+r+","+t+"l-"+r+",-"+t+"h-"+a+"Z"}},"star-triangle-up":{n:19,f:function(e){var t=n.round(e*Math.sqrt(3)*.8,2),r=n.round(.8*e,2),a=n.round(1.6*e,2),i=n.round(4*e,2),o="A "+i+","+i+" 0 0 1 ";return"M-"+t+","+r+o+t+","+r+o+"0,-"+a+o+"-"+t+","+r+"Z"}},"star-triangle-down":{n:20,f:function(e){var t=n.round(e*Math.sqrt(3)*.8,2),r=n.round(.8*e,2),a=n.round(1.6*e,2),i=n.round(4*e,2),o="A "+i+","+i+" 0 0 1 ";return"M"+t+",-"+r+o+"-"+t+",-"+r+o+"0,"+a+o+t+",-"+r+"Z"}},"star-square":{n:21,f:function(e){var t=n.round(1.1*e,2),r=n.round(2*e,2),a="A "+r+","+r+" 0 0 1 ";return"M-"+t+",-"+t+a+"-"+t+","+t+a+t+","+t+a+t+",-"+t+a+"-"+t+",-"+t+"Z"}},"star-diamond":{n:22,f:function(e){var t=n.round(1.4*e,2),r=n.round(1.9*e,2),a="A "+r+","+r+" 0 0 1 ";return"M-"+t+",0"+a+"0,"+t+a+t+",0"+a+"0,-"+t+a+"-"+t+",0Z"}},"diamond-tall":{n:23,f:function(e){var t=n.round(.7*e,2),r=n.round(1.4*e,2);return"M0,"+r+"L"+t+",0L0,-"+r+"L-"+t+",0Z"}},"diamond-wide":{n:24,f:function(e){var t=n.round(1.4*e,2),r=n.round(.7*e,2);return"M0,"+r+"L"+t+",0L0,-"+r+"L-"+t+",0Z"}},hourglass:{n:25,f:function(e){var t=n.round(e,2);return"M"+t+","+t+"H-"+t+"L"+t+",-"+t+"H-"+t+"Z"},noDot:!0},bowtie:{n:26,f:function(e){var t=n.round(e,2);return"M"+t+","+t+"V-"+t+"L-"+t+","+t+"V-"+t+"Z"},noDot:!0},"circle-cross":{n:27,f:function(e){var t=n.round(e,2);return"M0,"+t+"V-"+t+"M"+t+",0H-"+t+"M"+t+",0A"+t+","+t+" 0 1,1 0,-"+t+"A"+t+","+t+" 0 0,1 "+t+",0Z"},needLine:!0,noDot:!0},"circle-x":{n:28,f:function(e){var t=n.round(e,2),r=n.round(e/Math.sqrt(2),2);return"M"+r+","+r+"L-"+r+",-"+r+"M"+r+",-"+r+"L-"+r+","+r+"M"+t+",0A"+t+","+t+" 0 1,1 0,-"+t+"A"+t+","+t+" 0 0,1 "+t+",0Z"},needLine:!0,noDot:!0},"square-cross":{n:29,f:function(e){var t=n.round(e,2);return"M0,"+t+"V-"+t+"M"+t+",0H-"+t+"M"+t+","+t+"H-"+t+"V-"+t+"H"+t+"Z"},needLine:!0,noDot:!0},"square-x":{n:30,f:function(e){var t=n.round(e,2);return"M"+t+","+t+"L-"+t+",-"+t+"M"+t+",-"+t+"L-"+t+","+t+"M"+t+","+t+"H-"+t+"V-"+t+"H"+t+"Z"},needLine:!0,noDot:!0},"diamond-cross":{n:31,f:function(e){var t=n.round(1.3*e,2);return"M"+t+",0L0,"+t+"L-"+t+",0L0,-"+t+"ZM0,-"+t+"V"+t+"M-"+t+",0H"+t},needLine:!0,noDot:!0},"diamond-x":{n:32,f:function(e){var t=n.round(1.3*e,2),r=n.round(.65*e,2);return"M"+t+",0L0,"+t+"L-"+t+",0L0,-"+t+"ZM-"+r+",-"+r+"L"+r+","+r+"M-"+r+","+r+"L"+r+",-"+r},needLine:!0,noDot:!0},"cross-thin":{n:33,f:function(e){var t=n.round(1.4*e,2);return"M0,"+t+"V-"+t+"M"+t+",0H-"+t},needLine:!0,noDot:!0,noFill:!0},"x-thin":{n:34,f:function(e){var t=n.round(e,2);return"M"+t+","+t+"L-"+t+",-"+t+"M"+t+",-"+t+"L-"+t+","+t},needLine:!0,noDot:!0,noFill:!0},asterisk:{n:35,f:function(e){var t=n.round(1.2*e,2),r=n.round(.85*e,2);return"M0,"+t+"V-"+t+"M"+t+",0H-"+t+"M"+r+","+r+"L-"+r+",-"+r+"M"+r+",-"+r+"L-"+r+","+r},needLine:!0,noDot:!0,noFill:!0},hash:{n:36,f:function(e){var t=n.round(e/2,2),r=n.round(e,2);return"M"+t+","+r+"V-"+r+"m-"+r+",0V"+r+"M"+r+","+t+"H-"+r+"m0,-"+r+"H"+r},needLine:!0,noFill:!0},"y-up":{n:37,f:function(e){var t=n.round(1.2*e,2),r=n.round(1.6*e,2),a=n.round(.8*e,2);return"M-"+t+","+a+"L0,0M"+t+","+a+"L0,0M0,-"+r+"L0,0"},needLine:!0,noDot:!0,noFill:!0},"y-down":{n:38,f:function(e){var t=n.round(1.2*e,2),r=n.round(1.6*e,2),a=n.round(.8*e,2);return"M-"+t+",-"+a+"L0,0M"+t+",-"+a+"L0,0M0,"+r+"L0,0"},needLine:!0,noDot:!0,noFill:!0},"y-left":{n:39,f:function(e){var t=n.round(1.2*e,2),r=n.round(1.6*e,2),a=n.round(.8*e,2);return"M"+a+","+t+"L0,0M"+a+",-"+t+"L0,0M-"+r+",0L0,0"},needLine:!0,noDot:!0,noFill:!0},"y-right":{n:40,f:function(e){var t=n.round(1.2*e,2),r=n.round(1.6*e,2),a=n.round(.8*e,2);return"M-"+a+","+t+"L0,0M-"+a+",-"+t+"L0,0M"+r+",0L0,0"},needLine:!0,noDot:!0,noFill:!0},"line-ew":{n:41,f:function(e){var t=n.round(1.4*e,2);return"M"+t+",0H-"+t},needLine:!0,noDot:!0,noFill:!0},"line-ns":{n:42,f:function(e){var t=n.round(1.4*e,2);return"M0,"+t+"V-"+t},needLine:!0,noDot:!0,noFill:!0},"line-ne":{n:43,f:function(e){var t=n.round(e,2);return"M"+t+",-"+t+"L-"+t+","+t},needLine:!0,noDot:!0,noFill:!0},"line-nw":{n:44,f:function(e){var t=n.round(e,2);return"M"+t+","+t+"L-"+t+",-"+t},needLine:!0,noDot:!0,noFill:!0},"arrow-up":{n:45,f:function(e){var t=n.round(e,2);return"M0,0L-"+t+","+n.round(2*e,2)+"H"+t+"Z"},noDot:!0},"arrow-down":{n:46,f:function(e){var t=n.round(e,2);return"M0,0L-"+t+",-"+n.round(2*e,2)+"H"+t+"Z"},noDot:!0},"arrow-left":{n:47,f:function(e){var t=n.round(2*e,2),r=n.round(e,2);return"M0,0L"+t+",-"+r+"V"+r+"Z"},noDot:!0},"arrow-right":{n:48,f:function(e){var t=n.round(2*e,2),r=n.round(e,2);return"M0,0L-"+t+",-"+r+"V"+r+"Z"},noDot:!0},"arrow-bar-up":{n:49,f:function(e){var t=n.round(e,2);return"M-"+t+",0H"+t+"M0,0L-"+t+","+n.round(2*e,2)+"H"+t+"Z"},needLine:!0,noDot:!0},"arrow-bar-down":{n:50,f:function(e){var t=n.round(e,2);return"M-"+t+",0H"+t+"M0,0L-"+t+",-"+n.round(2*e,2)+"H"+t+"Z"},needLine:!0,noDot:!0},"arrow-bar-left":{n:51,f:function(e){var t=n.round(2*e,2),r=n.round(e,2);return"M0,-"+r+"V"+r+"M0,0L"+t+",-"+r+"V"+r+"Z"},needLine:!0,noDot:!0},"arrow-bar-right":{n:52,f:function(e){var t=n.round(2*e,2),r=n.round(e,2);return"M0,-"+r+"V"+r+"M0,0L-"+t+",-"+r+"V"+r+"Z"},needLine:!0,noDot:!0}}},{"@plotly/d3":11}],126:[function(e,t,r){"use strict";t.exports={visible:{valType:"boolean",editType:"calc"},type:{valType:"enumerated",values:["percent","constant","sqrt","data"],editType:"calc"},symmetric:{valType:"boolean",editType:"calc"},array:{valType:"data_array",editType:"calc"},arrayminus:{valType:"data_array",editType:"calc"},value:{valType:"number",min:0,dflt:10,editType:"calc"},valueminus:{valType:"number",min:0,dflt:10,editType:"calc"},traceref:{valType:"integer",min:0,dflt:0,editType:"style"},tracerefminus:{valType:"integer",min:0,dflt:0,editType:"style"},copy_ystyle:{valType:"boolean",editType:"plot"},copy_zstyle:{valType:"boolean",editType:"style"},color:{valType:"color",editType:"style"},thickness:{valType:"number",min:0,dflt:2,editType:"style"},width:{valType:"number",min:0,editType:"plot"},editType:"calc",_deprecated:{opacity:{valType:"number",editType:"style"}}}},{}],127:[function(e,t,r){"use strict";var n=e("fast-isnumeric"),a=e("../../registry"),i=e("../../plots/cartesian/axes"),o=e("../../lib"),l=e("./compute_error");function s(e,t,r,a){var s=t["error_"+a]||{},c=[];if(s.visible&&-1!==["linear","log"].indexOf(r.type)){for(var u=l(s),d=0;d0;t.each((function(t){var d,f=t[0].trace,p=f.error_x||{},h=f.error_y||{};f.ids&&(d=function(e){return e.id});var m=o.hasMarkers(f)&&f.marker.maxdisplayed>0;h.visible||p.visible||(t=[]);var g=n.select(this).selectAll("g.errorbar").data(t,d);if(g.exit().remove(),t.length){p.visible||g.selectAll("path.xerror").remove(),h.visible||g.selectAll("path.yerror").remove(),g.style("opacity",1);var v=g.enter().append("g").classed("errorbar",!0);u&&v.style("opacity",0).transition().duration(l.duration).style("opacity",1),i.setClipUrl(g,r.layerClipId,e),g.each((function(e){var t=n.select(this),r=function(e,t,r){var n={x:t.c2p(e.x),y:r.c2p(e.y)};void 0!==e.yh&&(n.yh=r.c2p(e.yh),n.ys=r.c2p(e.ys),a(n.ys)||(n.noYS=!0,n.ys=r.c2p(e.ys,!0)));void 0!==e.xh&&(n.xh=t.c2p(e.xh),n.xs=t.c2p(e.xs),a(n.xs)||(n.noXS=!0,n.xs=t.c2p(e.xs,!0)));return n}(e,s,c);if(!m||e.vis){var i,o=t.select("path.yerror");if(h.visible&&a(r.x)&&a(r.yh)&&a(r.ys)){var d=h.width;i="M"+(r.x-d)+","+r.yh+"h"+2*d+"m-"+d+",0V"+r.ys,r.noYS||(i+="m-"+d+",0h"+2*d),!o.size()?o=t.append("path").style("vector-effect","non-scaling-stroke").classed("yerror",!0):u&&(o=o.transition().duration(l.duration).ease(l.easing)),o.attr("d",i)}else o.remove();var f=t.select("path.xerror");if(p.visible&&a(r.y)&&a(r.xh)&&a(r.xs)){var g=(p.copy_ystyle?h:p).width;i="M"+r.xh+","+(r.y-g)+"v"+2*g+"m0,-"+g+"H"+r.xs,r.noXS||(i+="m0,-"+g+"v"+2*g),!f.size()?f=t.append("path").style("vector-effect","non-scaling-stroke").classed("xerror",!0):u&&(f=f.transition().duration(l.duration).ease(l.easing)),f.attr("d",i)}else f.remove()}}))}}))}},{"../../traces/scatter/subtypes":383,"../drawing":124,"@plotly/d3":11,"fast-isnumeric":17}],132:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../color");t.exports=function(e){e.each((function(e){var t=e[0].trace,r=t.error_y||{},i=t.error_x||{},o=n.select(this);o.selectAll("path.yerror").style("stroke-width",r.thickness+"px").call(a.stroke,r.color),i.copy_ystyle&&(i=r),o.selectAll("path.xerror").style("stroke-width",i.thickness+"px").call(a.stroke,i.color)}))}},{"../color":102,"@plotly/d3":11}],133:[function(e,t,r){"use strict";var n=e("../../plots/font_attributes"),a=e("./layout_attributes").hoverlabel,i=e("../../lib/extend").extendFlat;t.exports={hoverlabel:{bgcolor:i({},a.bgcolor,{arrayOk:!0}),bordercolor:i({},a.bordercolor,{arrayOk:!0}),font:n({arrayOk:!0,editType:"none"}),align:i({},a.align,{arrayOk:!0}),namelength:i({},a.namelength,{arrayOk:!0}),editType:"none"}}},{"../../lib/extend":226,"../../plots/font_attributes":310,"./layout_attributes":143}],134:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../../registry");function i(e,t,r,a){a=a||n.identity,Array.isArray(e)&&(t[0][r]=a(e))}t.exports=function(e){var t=e.calcdata,r=e._fullLayout;function o(e){return function(t){return n.coerceHoverinfo({hoverinfo:t},{_module:e._module},r)}}for(var l=0;l=0&&r.index_[0]._length||oe<0||oe>w[0]._length)return h.unhoverRaw(e,t)}if(t.pointerX=ie+_[0]._offset,t.pointerY=oe+w[0]._offset,Y="xval"in t?v.flat(s,t.xval):v.p2c(_,ie),V="yval"in t?v.flat(s,t.yval):v.p2c(w,oe),!a(Y[0])||!a(V[0]))return o.warn("Fx.hover failed",t,e),h.unhoverRaw(e,t)}var ce=1/0;function ue(e,r){for(q=0;qee&&(te.splice(0,ee),ce=te[0].distance),y&&0!==E&&0===te.length){$.distance=E,$.index=!1;var d=Z._module.hoverPoints($,Q,K,"closest",{hoverLayer:u._hoverlayer});if(d&&(d=d.filter((function(e){return e.spikeDistance<=E}))),d&&d.length){var f,h=d.filter((function(e){return e.xa.showspikes&&"hovered data"!==e.xa.spikesnap}));if(h.length){var m=h[0];a(m.x0)&&a(m.y0)&&(f=fe(m),(!ne.vLinePoint||ne.vLinePoint.spikeDistance>f.spikeDistance)&&(ne.vLinePoint=f))}var g=d.filter((function(e){return e.ya.showspikes&&"hovered data"!==e.ya.spikesnap}));if(g.length){var x=g[0];a(x.x0)&&a(x.y0)&&(f=fe(x),(!ne.hLinePoint||ne.hLinePoint.spikeDistance>f.spikeDistance)&&(ne.hLinePoint=f))}}}}}function de(e,t,r){for(var n,a=null,i=1/0,o=0;o0&&Math.abs(e.distance)Te-1;Ae--)Ce(te[Ae]);te=Le,ge()}var Oe=e._hoverdata,Pe=[],Ie=H(e),Re=B(e);for(U=0;U1||te.length>1)||"closest"===O&&ae&&te.length>1,Ze=p.combine(u.plot_bgcolor||p.background,u.paper_bgcolor),We=P(te,{gd:e,hovermode:O,rotateLabels:Ge,bgColor:Ze,container:u._hoverlayer,outerContainer:u._paper.node(),commonLabelOpts:u.hoverlabel,hoverdistance:u.hoverdistance});v.isUnifiedHover(O)||(!function(e,t,r){var n,a,i,o,l,s,c,u=0,d=1,f=e.size(),p=new Array(f),h=0;function m(e){var t=e[0],r=e[e.length-1];if(a=t.pmin-t.pos-t.dp+t.size,i=r.pos+r.dp+r.size-t.pmax,a>.01){for(l=e.length-1;l>=0;l--)e[l].dp+=a;n=!1}if(!(i<.01)){if(a<-.01){for(l=e.length-1;l>=0;l--)e[l].dp-=i;n=!1}if(n){var c=0;for(o=0;ot.pmax&&c++;for(o=e.length-1;o>=0&&!(c<=0);o--)(s=e[o]).pos>t.pmax-1&&(s.del=!0,c--);for(o=0;o=0;l--)e[l].dp-=i;for(o=e.length-1;o>=0&&!(c<=0);o--)(s=e[o]).pos+s.dp+s.size>t.pmax&&(s.del=!0,c--)}}}e.each((function(e){var n=e[t],a="x"===n._id.charAt(0),i=n.range;0===h&&i&&i[0]>i[1]!==a&&(d=-1),p[h++]=[{datum:e,traceIndex:e.trace.index,dp:0,pos:e.pos,posref:e.posref,size:e.by*(a?T:1)/2,pmin:0,pmax:a?r.width:r.height}]})),p.sort((function(e,t){return e[0].posref-t[0].posref||d*(t[0].traceIndex-e[0].traceIndex)}));for(;!n&&u<=f;){for(u++,n=!0,o=0;o.01&&y.pmin===x.pmin&&y.pmax===x.pmax){for(l=v.length-1;l>=0;l--)v[l].dp+=a;for(g.push.apply(g,v),p.splice(o+1,1),c=0,l=g.length-1;l>=0;l--)c+=g[l].dp;for(i=c/g.length,l=g.length-1;l>=0;l--)g[l].dp-=i;n=!1}else o++}p.forEach(m)}for(o=p.length-1;o>=0;o--){var b=p[o];for(l=b.length-1;l>=0;l--){var _=b[l],w=_.datum;w.offset=_.dp,w.del=_.del}}}(We,Ge?"xa":"ya",u),R(We,Ge,u._invScaleX,u._invScaleY));if(l&&l.tagName){var Xe=g.getComponentMethod("annotations","hasClickToShow")(e,Pe);d(n.select(l),Xe?"pointer":"")}if(!l||i||!function(e,t,r){if(!r||r.length!==e._hoverdata.length)return!0;for(var n=r.length-1;n>=0;n--){var a=r[n],i=e._hoverdata[n];if(a.curveNumber!==i.curveNumber||String(a.pointNumber)!==String(i.pointNumber)||String(a.pointNumbers)!==String(i.pointNumbers))return!0}return!1}(e,0,Oe))return;Oe&&e.emit("plotly_unhover",{event:t,points:Oe});e.emit("plotly_hover",{event:t,points:e._hoverdata,xaxes:_,yaxes:w,xvals:Y,yvals:V})}(e,t,r,i,l)}))},r.loneHover=function(e,t){var r=!0;Array.isArray(e)||(r=!1,e=[e]);var a=t.gd,i=H(a),o=B(a),l=P(e.map((function(e){var r=e._x0||e.x0||e.x||0,n=e._x1||e.x1||e.x||0,l=e._y0||e.y0||e.y||0,s=e._y1||e.y1||e.y||0,c=e.eventData;if(c){var u=Math.min(r,n),d=Math.max(r,n),f=Math.min(l,s),h=Math.max(l,s),m=e.trace;if(g.traceIs(m,"gl3d")){var v=a._fullLayout[m.scene]._scene.container,y=v.offsetLeft,x=v.offsetTop;u+=y,d+=y,f+=x,h+=x}c.bbox={x0:u+o,x1:d+o,y0:f+i,y1:h+i},t.inOut_bbox&&t.inOut_bbox.push(c.bbox)}else c=!1;return{color:e.color||p.defaultLine,x0:e.x0||e.x||0,x1:e.x1||e.x||0,y0:e.y0||e.y||0,y1:e.y1||e.y||0,xLabel:e.xLabel,yLabel:e.yLabel,zLabel:e.zLabel,text:e.text,name:e.name,idealAlign:e.idealAlign,borderColor:e.borderColor,fontFamily:e.fontFamily,fontSize:e.fontSize,fontColor:e.fontColor,nameLength:e.nameLength,textAlign:e.textAlign,trace:e.trace||{index:0,hoverinfo:""},xa:{_offset:0},ya:{_offset:0},index:0,hovertemplate:e.hovertemplate||!1,hovertemplateLabels:e.hovertemplateLabels||!1,eventData:c}})),{gd:a,hovermode:"closest",rotateLabels:!1,bgColor:t.bgColor||p.background,container:n.select(t.container),outerContainer:t.outerContainer||t.container}),s=0,c=0;return l.sort((function(e,t){return e.y0-t.y0})).each((function(e,r){var n=e.y0-e.by/2;e.offset=n-5([\s\S]*)<\/extra>/;function P(e,t){var r=t.gd,a=r._fullLayout,i=t.hovermode,c=t.rotateLabels,d=t.bgColor,h=t.container,m=t.outerContainer,w=t.commonLabelOpts||{};if(0===e.length)return[[]];var T=t.fontFamily||y.HOVERFONT,M=t.fontSize||y.HOVERFONTSIZE,k=e[0],S=k.xa,D=k.ya,O=i.charAt(0),P=k[O+"Label"],R=Y(r,m),z=R.top,N=R.width,F=R.height,E=void 0!==P&&k.distance<=t.hoverdistance&&("x"===i||"y"===i);if(E){var j,H,B=!0;for(j=0;ja.width-b?(g=a.width-b,t.attr("d","M"+(b-A)+",0L"+b+","+x+A+"v"+x+(2*L+y.height)+"H-"+b+"V"+x+A+"H"+(b-2*A)+"Z")):t.attr("d","M0,0L"+A+","+x+A+"H"+(L+y.width/2)+"v"+x+(2*L+y.height)+"H-"+(L+y.width/2)+"V"+x+A+"H-"+A+"Z")}else{var _,C,O;"right"===D.side?(_="start",C=1,O="",g=S._offset+S._length):(_="end",C=-1,O="-",g=S._offset),v=D._offset+(k.y0+k.y1)/2,s.attr("text-anchor",_),t.attr("d","M0,0L"+O+A+","+A+"V"+(L+y.height/2)+"h"+O+(2*L+y.width)+"V-"+(L+y.height/2)+"H"+O+A+"V-"+A+"Z");var I,R=y.height/2,N=z-y.top-R,F="clip"+a._uid+"commonlabel"+D._id;if(g=0?ie:oe+ce=0?oe:ve+ce=0?ne:ae+ue=0?ae:ye+ue=0,"top"!==e.idealAlign&&q||!G?q?(R+=H/2,e.anchor="start"):e.anchor="middle":(R-=H/2,e.anchor="end");else if(e.pos=R,q=O+j/2+Z<=N,G=O-j/2-Z>=0,"left"!==e.idealAlign&&q||!G)if(q)O+=j/2,e.anchor="start";else{e.anchor="middle";var W=Z/2,X=O+W-N,J=O-W;X>0&&(O-=X),J<0&&(O+=-J)}else O-=j/2,e.anchor="end";w.attr("text-anchor",e.anchor),S&&k.attr("text-anchor",e.anchor),t.attr("transform",l(O,R)+(c?s(_):""))})),xe}function I(e,t,r,n,a,i){var l="",s="";void 0!==e.nameOverride&&(e.name=e.nameOverride),e.name&&(e.trace._meta&&(e.name=o.templateString(e.name,e.trace._meta)),l=E(e.name,e.nameLength));var c=r.charAt(0),u="x"===c?"y":"x";void 0!==e.zLabel?(void 0!==e.xLabel&&(s+="x: "+e.xLabel+"
    "),void 0!==e.yLabel&&(s+="y: "+e.yLabel+"
    "),"choropleth"!==e.trace.type&&"choroplethmapbox"!==e.trace.type&&(s+=(s?"z: ":"")+e.zLabel)):t&&e[c+"Label"]===a?s=e[u+"Label"]||"":void 0===e.xLabel?void 0!==e.yLabel&&"scattercarpet"!==e.trace.type&&(s=e.yLabel):s=void 0===e.yLabel?e.xLabel:"("+e.xLabel+", "+e.yLabel+")",!e.text&&0!==e.text||Array.isArray(e.text)||(s+=(s?"
    ":"")+e.text),void 0!==e.extraText&&(s+=(s?"
    ":"")+e.extraText),i&&""===s&&!e.hovertemplate&&(""===l&&i.remove(),s=l);var d=e.hovertemplate||!1;if(d){var f=e.hovertemplateLabels||e;e[c+"Label"]!==a&&(f[c+"other"]=f[c+"Val"],f[c+"otherLabel"]=f[c+"Label"]),s=(s=o.hovertemplateString(d,f,n._d3locale,e.eventData[0]||{},e.trace._meta)).replace(O,(function(t,r){return l=E(r,e.nameLength),""}))}return[s,l]}function R(e,t,r,a){var i=function(e){return e*r},o=function(e){return e*a};e.each((function(e){var r=n.select(this);if(e.del)return r.remove();var a=r.select("text.nums"),l=e.anchor,s="end"===l?-1:1,c={start:1,end:-1,middle:0}[l],d=c*(A+L),p=d+c*(e.txwidth+L),h=0,m=e.offset,g="middle"===l;g&&(d-=e.tx2width/2,p+=e.txwidth/2+L),t&&(m*=-k,h=e.offset*M),r.select("path").attr("d",g?"M-"+i(e.bx/2+e.tx2width/2)+","+o(m-e.by/2)+"h"+i(e.bx)+"v"+o(e.by)+"h-"+i(e.bx)+"Z":"M0,0L"+i(s*A+h)+","+o(A+m)+"v"+o(e.by/2-A)+"h"+i(s*e.bx)+"v-"+o(e.by)+"H"+i(s*A+h)+"V"+o(m-A)+"Z");var v=h+d,y=m+e.ty0-e.by/2+L,x=e.textAlign||"auto";"auto"!==x&&("left"===x&&"start"!==l?(a.attr("text-anchor","start"),v=g?-e.bx/2-e.tx2width/2+L:-e.bx-L):"right"===x&&"end"!==l&&(a.attr("text-anchor","end"),v=g?e.bx/2-e.tx2width/2-L:e.bx+L)),a.call(u.positionText,i(v),o(y)),e.tx2width&&(r.select("text.name").call(u.positionText,i(p+c*L+h),o(m+e.ty0-e.by/2+L)),r.select("rect").call(f.setRect,i(p+(c-1)*e.tx2width/2+h),o(m-e.by/2-1),i(e.tx2width),o(e.by+2)))}))}function z(e,t){var r=e.index,n=e.trace||{},i=e.cd[0],l=e.cd[r]||{};function s(e){return e||a(e)&&0===e}var c=Array.isArray(r)?function(e,t){var a=o.castOption(i,r,e);return s(a)?a:o.extractOption({},n,"",t)}:function(e,t){return o.extractOption(l,n,e,t)};function u(t,r,n){var a=c(r,n);s(a)&&(e[t]=a)}if(u("hoverinfo","hi","hoverinfo"),u("bgcolor","hbg","hoverlabel.bgcolor"),u("borderColor","hbc","hoverlabel.bordercolor"),u("fontFamily","htf","hoverlabel.font.family"),u("fontSize","hts","hoverlabel.font.size"),u("fontColor","htc","hoverlabel.font.color"),u("nameLength","hnl","hoverlabel.namelength"),u("textAlign","hta","hoverlabel.align"),e.posref="y"===t||"closest"===t&&"h"===n.orientation?e.xa._offset+(e.x0+e.x1)/2:e.ya._offset+(e.y0+e.y1)/2,e.x0=o.constrain(e.x0,0,e.xa._length),e.x1=o.constrain(e.x1,0,e.xa._length),e.y0=o.constrain(e.y0,0,e.ya._length),e.y1=o.constrain(e.y1,0,e.ya._length),void 0!==e.xLabelVal&&(e.xLabel="xLabel"in e?e.xLabel:m.hoverLabelText(e.xa,e.xLabelVal,n.xhoverformat),e.xVal=e.xa.c2d(e.xLabelVal)),void 0!==e.yLabelVal&&(e.yLabel="yLabel"in e?e.yLabel:m.hoverLabelText(e.ya,e.yLabelVal,n.yhoverformat),e.yVal=e.ya.c2d(e.yLabelVal)),void 0!==e.zLabelVal&&void 0===e.zLabel&&(e.zLabel=String(e.zLabelVal)),!(isNaN(e.xerr)||"log"===e.xa.type&&e.xerr<=0)){var d=m.tickText(e.xa,e.xa.c2l(e.xerr),"hover").text;void 0!==e.xerrneg?e.xLabel+=" +"+d+" / -"+m.tickText(e.xa,e.xa.c2l(e.xerrneg),"hover").text:e.xLabel+=" \xb1 "+d,"x"===t&&(e.distance+=1)}if(!(isNaN(e.yerr)||"log"===e.ya.type&&e.yerr<=0)){var f=m.tickText(e.ya,e.ya.c2l(e.yerr),"hover").text;void 0!==e.yerrneg?e.yLabel+=" +"+f+" / -"+m.tickText(e.ya,e.ya.c2l(e.yerrneg),"hover").text:e.yLabel+=" \xb1 "+f,"y"===t&&(e.distance+=1)}var p=e.hoverinfo||e.trace.hoverinfo;return p&&"all"!==p&&(-1===(p=Array.isArray(p)?p:p.split("+")).indexOf("x")&&(e.xLabel=void 0),-1===p.indexOf("y")&&(e.yLabel=void 0),-1===p.indexOf("z")&&(e.zLabel=void 0),-1===p.indexOf("text")&&(e.text=void 0),-1===p.indexOf("name")&&(e.name=void 0)),e}function N(e,t,r){var n,a,o=r.container,l=r.fullLayout,s=l._size,c=r.event,u=!!t.hLinePoint,d=!!t.vLinePoint;if(o.selectAll(".spikeline").remove(),d||u){var h=p.combine(l.plot_bgcolor,l.paper_bgcolor);if(u){var g,v,y=t.hLinePoint;n=y&&y.xa,"cursor"===(a=y&&y.ya).spikesnap?(g=c.pointerX,v=c.pointerY):(g=n._offset+y.x,v=a._offset+y.y);var x,b,_=i.readability(y.color,h)<1.5?p.contrast(h):y.color,w=a.spikemode,T=a.spikethickness,M=a.spikecolor||_,k=m.getPxPosition(e,a);if(-1!==w.indexOf("toaxis")||-1!==w.indexOf("across")){if(-1!==w.indexOf("toaxis")&&(x=k,b=g),-1!==w.indexOf("across")){var A=a._counterDomainMin,L=a._counterDomainMax;"free"===a.anchor&&(A=Math.min(A,a.position),L=Math.max(L,a.position)),x=s.l+A*s.w,b=s.l+L*s.w}o.insert("line",":first-child").attr({x1:x,x2:b,y1:v,y2:v,"stroke-width":T,stroke:M,"stroke-dasharray":f.dashStyle(a.spikedash,T)}).classed("spikeline",!0).classed("crisp",!0),o.insert("line",":first-child").attr({x1:x,x2:b,y1:v,y2:v,"stroke-width":T+2,stroke:h}).classed("spikeline",!0).classed("crisp",!0)}-1!==w.indexOf("marker")&&o.insert("circle",":first-child").attr({cx:k+("right"!==a.side?T:-T),cy:v,r:T,fill:M}).classed("spikeline",!0)}if(d){var S,D,C=t.vLinePoint;n=C&&C.xa,a=C&&C.ya,"cursor"===n.spikesnap?(S=c.pointerX,D=c.pointerY):(S=n._offset+C.x,D=a._offset+C.y);var O,P,I=i.readability(C.color,h)<1.5?p.contrast(h):C.color,R=n.spikemode,z=n.spikethickness,N=n.spikecolor||I,F=m.getPxPosition(e,n);if(-1!==R.indexOf("toaxis")||-1!==R.indexOf("across")){if(-1!==R.indexOf("toaxis")&&(O=F,P=D),-1!==R.indexOf("across")){var E=n._counterDomainMin,j=n._counterDomainMax;"free"===n.anchor&&(E=Math.min(E,n.position),j=Math.max(j,n.position)),O=s.t+(1-j)*s.h,P=s.t+(1-E)*s.h}o.insert("line",":first-child").attr({x1:S,x2:S,y1:O,y2:P,"stroke-width":z,stroke:N,"stroke-dasharray":f.dashStyle(n.spikedash,z)}).classed("spikeline",!0).classed("crisp",!0),o.insert("line",":first-child").attr({x1:S,x2:S,y1:O,y2:P,"stroke-width":z+2,stroke:h}).classed("spikeline",!0).classed("crisp",!0)}-1!==R.indexOf("marker")&&o.insert("circle",":first-child").attr({cx:S,cy:F-("top"!==n.side?z:-z),r:z,fill:N}).classed("spikeline",!0)}}}function F(e,t){return!t||(t.vLinePoint!==e._spikepoints.vLinePoint||t.hLinePoint!==e._spikepoints.hLinePoint)}function E(e,t){return u.plainText(e||"",{len:t,allowedTags:["br","sub","sup","b","i","em"]})}function j(e,t,r){var n=t[e+"a"],a=t[e+"Val"],i=t.cd[0];if("category"===n.type)a=n._categoriesMap[a];else if("date"===n.type){var o=t.trace[e+"periodalignment"];if(o){var l=t.cd[t.index],s=l[e+"Start"];void 0===s&&(s=l[e]);var c=l[e+"End"];void 0===c&&(c=l[e]);var u=c-s;"end"===o?a+=u:"middle"===o&&(a+=u/2)}a=n.d2c(a)}return i&&i.t&&i.t.posLetter===n._id&&("group"!==r.boxmode&&"group"!==r.violinmode||(a+=i.t.dPos)),a}function H(e){return e.offsetTop+e.clientTop}function B(e){return e.offsetLeft+e.clientLeft}function Y(e,t){var r=e._fullLayout,n=t.getBoundingClientRect(),a=n.x,i=n.y,l=a+n.width,s=i+n.height,c=o.apply3DTransform(r._invTransform)(a,i),u=o.apply3DTransform(r._invTransform)(l,s),d=c[0],f=c[1],p=u[0],h=u[1];return{x:d,y:f,width:p-d,height:h-f,top:Math.min(f,h),left:Math.min(d,p),right:Math.max(d,p),bottom:Math.max(f,h)}}},{"../../lib":232,"../../lib/events":225,"../../lib/override_cursor":243,"../../lib/svg_text_utils":255,"../../plots/cartesian/axes":279,"../../registry":318,"../color":102,"../dragelement":121,"../drawing":124,"../legend/defaults":154,"../legend/draw":155,"./constants":136,"./helpers":138,"@plotly/d3":11,"fast-isnumeric":17,tinycolor2:67}],140:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../color"),i=e("./helpers").isUnifiedHover;t.exports=function(e,t,r,o){function l(e){o.font[e]||(o.font[e]=t.legend?t.legend.font[e]:t.font[e])}o=o||{},t&&i(t.hovermode)&&(o.font||(o.font={}),l("size"),l("family"),l("color"),t.legend?(o.bgcolor||(o.bgcolor=a.combine(t.legend.bgcolor,t.paper_bgcolor)),o.bordercolor||(o.bordercolor=t.legend.bordercolor)):o.bgcolor||(o.bgcolor=t.paper_bgcolor)),r("hoverlabel.bgcolor",o.bgcolor),r("hoverlabel.bordercolor",o.bordercolor),r("hoverlabel.namelength",o.namelength),n.coerceFont(r,"hoverlabel.font",o.font),r("hoverlabel.align",o.align)}},{"../../lib":232,"../color":102,"./helpers":138}],141:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("./layout_attributes");t.exports=function(e,t){function r(r,i){return void 0!==t[r]?t[r]:n.coerce(e,t,a,r,i)}return r("clickmode"),r("hovermode")}},{"../../lib":232,"./layout_attributes":143}],142:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../../lib"),i=e("../dragelement"),o=e("./helpers"),l=e("./layout_attributes"),s=e("./hover");t.exports={moduleType:"component",name:"fx",constants:e("./constants"),schema:{layout:l},attributes:e("./attributes"),layoutAttributes:l,supplyLayoutGlobalDefaults:e("./layout_global_defaults"),supplyDefaults:e("./defaults"),supplyLayoutDefaults:e("./layout_defaults"),calc:e("./calc"),getDistanceFunction:o.getDistanceFunction,getClosest:o.getClosest,inbox:o.inbox,quadrature:o.quadrature,appendArrayPointValue:o.appendArrayPointValue,castHoverOption:function(e,t,r){return a.castOption(e,t,"hoverlabel."+r)},castHoverinfo:function(e,t,r){return a.castOption(e,r,"hoverinfo",(function(r){return a.coerceHoverinfo({hoverinfo:r},{_module:e._module},t)}))},hover:s.hover,unhover:i.unhover,loneHover:s.loneHover,loneUnhover:function(e){var t=a.isD3Selection(e)?e:n.select(e);t.selectAll("g.hovertext").remove(),t.selectAll(".spikeline").remove()},click:e("./click")}},{"../../lib":232,"../dragelement":121,"./attributes":133,"./calc":134,"./click":135,"./constants":136,"./defaults":137,"./helpers":138,"./hover":139,"./layout_attributes":143,"./layout_defaults":144,"./layout_global_defaults":145,"@plotly/d3":11}],143:[function(e,t,r){"use strict";var n=e("./constants"),a=e("../../plots/font_attributes")({editType:"none"});a.family.dflt=n.HOVERFONT,a.size.dflt=n.HOVERFONTSIZE,t.exports={clickmode:{valType:"flaglist",flags:["event","select"],dflt:"event",editType:"plot",extras:["none"]},dragmode:{valType:"enumerated",values:["zoom","pan","select","lasso","drawclosedpath","drawopenpath","drawline","drawrect","drawcircle","orbit","turntable",!1],dflt:"zoom",editType:"modebar"},hovermode:{valType:"enumerated",values:["x","y","closest",!1,"x unified","y unified"],dflt:"closest",editType:"modebar"},hoverdistance:{valType:"integer",min:-1,dflt:20,editType:"none"},spikedistance:{valType:"integer",min:-1,dflt:-1,editType:"none"},hoverlabel:{bgcolor:{valType:"color",editType:"none"},bordercolor:{valType:"color",editType:"none"},font:a,align:{valType:"enumerated",values:["left","right","auto"],dflt:"auto",editType:"none"},namelength:{valType:"integer",min:-1,dflt:15,editType:"none"},editType:"none"},selectdirection:{valType:"enumerated",values:["h","v","d","any"],dflt:"any",editType:"none"}}},{"../../plots/font_attributes":310,"./constants":136}],144:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("./layout_attributes"),i=e("./hovermode_defaults"),o=e("./hoverlabel_defaults");t.exports=function(e,t){function r(r,i){return n.coerce(e,t,a,r,i)}i(e,t)&&(r("hoverdistance"),r("spikedistance")),"select"===r("dragmode")&&r("selectdirection");var l=t._has("mapbox"),s=t._has("geo"),c=t._basePlotModules.length;"zoom"===t.dragmode&&((l||s)&&1===c||l&&s&&2===c)&&(t.dragmode="pan"),o(e,t,r)}},{"../../lib":232,"./hoverlabel_defaults":140,"./hovermode_defaults":141,"./layout_attributes":143}],145:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("./hoverlabel_defaults"),i=e("./layout_attributes");t.exports=function(e,t){a(e,t,(function(r,a){return n.coerce(e,t,i,r,a)}))}},{"../../lib":232,"./hoverlabel_defaults":140,"./layout_attributes":143}],146:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../../lib/regex").counter,i=e("../../plots/domain").attributes,o=e("../../plots/cartesian/constants").idRegex,l=e("../../plot_api/plot_template"),s={rows:{valType:"integer",min:1,editType:"plot"},roworder:{valType:"enumerated",values:["top to bottom","bottom to top"],dflt:"top to bottom",editType:"plot"},columns:{valType:"integer",min:1,editType:"plot"},subplots:{valType:"info_array",freeLength:!0,dimensions:2,items:{valType:"enumerated",values:[a("xy").toString(),""],editType:"plot"},editType:"plot"},xaxes:{valType:"info_array",freeLength:!0,items:{valType:"enumerated",values:[o.x.toString(),""],editType:"plot"},editType:"plot"},yaxes:{valType:"info_array",freeLength:!0,items:{valType:"enumerated",values:[o.y.toString(),""],editType:"plot"},editType:"plot"},pattern:{valType:"enumerated",values:["independent","coupled"],dflt:"coupled",editType:"plot"},xgap:{valType:"number",min:0,max:1,editType:"plot"},ygap:{valType:"number",min:0,max:1,editType:"plot"},domain:i({name:"grid",editType:"plot",noGridCell:!0},{}),xside:{valType:"enumerated",values:["bottom","bottom plot","top plot","top"],dflt:"bottom plot",editType:"plot"},yside:{valType:"enumerated",values:["left","left plot","right plot","right"],dflt:"left plot",editType:"plot"},editType:"plot"};function c(e,t,r){var n=t[r+"axes"],a=Object.keys((e._splomAxes||{})[r]||{});return Array.isArray(n)?n:a.length?a:void 0}function u(e,t,r,n,a,i){var o=t(e+"gap",r),l=t("domain."+e);t(e+"side",n);for(var s=new Array(a),c=l[0],u=(l[1]-c)/(a-o),d=u*(1-o),f=0;f1){if(!f&&!p&&!h)"independent"===M("pattern")&&(f=!0);g._hasSubplotGrid=f;var x,b,_="top to bottom"===M("roworder"),w=f?.2:.1,T=f?.3:.1;m&&t._splomGridDflt&&(x=t._splomGridDflt.xside,b=t._splomGridDflt.yside),g._domains={x:u("x",M,w,x,y),y:u("y",M,T,b,v,_)}}else delete t.grid}function M(e,t){return n.coerce(r,g,s,e,t)}},contentDefaults:function(e,t){var r=t.grid;if(r&&r._domains){var n,a,i,o,l,s,u,f=e.grid||{},p=t._subplots,h=r._hasSubplotGrid,m=r.rows,g=r.columns,v="independent"===r.pattern,y=r._axisMap={};if(h){var x=f.subplots||[];s=r.subplots=new Array(m);var b=1;for(n=0;n1);if(!1!==m||c.uirevision){var g=i.newContainer(t,"legend");if(T("uirevision",t.uirevision),!1!==m){T("bgcolor",t.paper_bgcolor),T("bordercolor"),T("borderwidth");var v,y,x,b=a.coerceFont(T,"font",t.font),_="h"===T("orientation");if(_?(v=0,n.getComponentMethod("rangeslider","isVisible")(e.xaxis)?(y=1.1,x="bottom"):(y=-.1,x="top")):(v=1.02,y=1,x="auto"),T("traceorder",f),s.isGrouped(t.legend)&&T("tracegroupgap"),T("itemsizing"),T("itemwidth"),T("itemclick"),T("itemdoubleclick"),T("groupclick"),T("x",v),T("xanchor"),T("y",y),T("yanchor",x),T("valign"),a.noneOrAll(c,g,["x","y"]),T("title.text")){T("title.side",_?"left":"top");var w=a.extendFlat({},b,{size:a.bigFont(b.size)});a.coerceFont(T,"title.font",w)}}}function T(e,t){return a.coerce(c,g,o,e,t)}}},{"../../lib":232,"../../plot_api/plot_template":268,"../../plots/layout_attributes":314,"../../registry":318,"./attributes":152,"./helpers":158}],155:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../../lib"),i=e("../../plots/plots"),o=e("../../registry"),l=e("../../lib/events"),s=e("../dragelement"),c=e("../drawing"),u=e("../color"),d=e("../../lib/svg_text_utils"),f=e("./handle_click"),p=e("./constants"),h=e("../../constants/alignment"),m=h.LINE_SPACING,g=h.FROM_TL,v=h.FROM_BR,y=e("./get_legend_data"),x=e("./style"),b=e("./helpers");function _(e,t,r,n,a){var i=r.data()[0][0].trace,s={event:a,node:r.node(),curveNumber:i.index,expandedIndex:i._expandedIndex,data:e.data,layout:e.layout,frames:e._transitionData._frames,config:e._context,fullData:e._fullData,fullLayout:e._fullLayout};if(i._group&&(s.group=i._group),o.traceIs(i,"pie-like")&&(s.label=r.datum()[0].label),!1!==l.triggerHandler(e,"plotly_legendclick",s))if(1===n)t._clickTimeout=setTimeout((function(){e._fullLayout&&f(r,e,n)}),e._context.doubleClickDelay);else if(2===n){t._clickTimeout&&clearTimeout(t._clickTimeout),e._legendMouseDownTime=0,!1!==l.triggerHandler(e,"plotly_legenddoubleclick",s)&&f(r,e,n)}}function w(e,t,r){var n,i,l=e.data()[0][0],s=l.trace,u=o.traceIs(s,"pie-like"),f=!r._inHover&&t._context.edits.legendText&&!u,h=r._maxNameLength;l.groupTitle?(n=l.groupTitle.text,i=l.groupTitle.font):(i=r.font,r.entries?n=l.text:(n=u?l.label:s.name,s._meta&&(n=a.templateString(n,s._meta))));var m=a.ensureSingle(e,"text","legendtext");m.attr("text-anchor","start").call(c.font,i).text(f?T(n,h):n);var g=r.itemwidth+2*p.itemGap;d.positionText(m,g,0),f?m.call(d.makeEditable,{gd:t,text:n}).call(k,e,t,r).on("edit",(function(n){this.text(T(n,h)).call(k,e,t,r);var i=l.trace._fullInput||{},c={};if(o.hasTransform(i,"groupby")){var u=o.getTransformIndices(i,"groupby"),d=u[u.length-1],f=a.keyedContainer(i,"transforms["+d+"].styles","target","value.name");f.set(l.trace._group,n),c=f.constructUpdate()}else c.name=n;return o.call("_guiRestyle",t,c,s.index)})):k(m,e,t,r)}function T(e,t){var r=Math.max(4,t);if(e&&e.trim().length>=r/2)return e;for(var n=r-(e=e||"").length;n>0;n--)e+=" ";return e}function M(e,t){var r,i=t._context.doubleClickDelay,o=1,l=a.ensureSingle(e,"rect","legendtoggle",(function(e){t._context.staticPlot||e.style("cursor","pointer").attr("pointer-events","all"),e.call(u.fill,"rgba(0,0,0,0)")}));t._context.staticPlot||(l.on("mousedown",(function(){(r=(new Date).getTime())-t._legendMouseDownTimei&&(o=Math.max(o-1,1)),_(t,r,e,o,n.event)}})))}function k(e,t,r,n,a){n._inHover&&e.attr("data-notex",!0),d.convertToTspans(e,r,(function(){!function(e,t,r,n){var a=e.data()[0][0];if(!r._inHover&&a&&!a.trace.showlegend)return void e.remove();var i=e.select("g[class*=math-group]"),o=i.node();r||(r=t._fullLayout.legend);var l,s=r.borderwidth;l=1===n?r.title.font:a.groupTitle?a.groupTitle.font:r.font;var u,f,h=l.size*m;if(o){var g=c.bBox(o);u=g.height,f=g.width,1===n?c.setTranslate(i,s,s+.75*u):c.setTranslate(i,0,.25*u)}else{var v=e.select(1===n?".legendtitletext":".legendtext"),y=d.lineCount(v),x=v.node();if(u=h*y,f=x?c.bBox(x).width:0,1===n)"left"===r.title.side&&(f+=2*p.itemGap),d.positionText(v,s+p.titlePad,s+h);else{var b=2*p.itemGap+r.itemwidth;a.groupTitle&&(b=p.itemGap,f-=r.itemwidth),d.positionText(v,b,-h*((y-1)/2-.3))}}1===n?(r._titleWidth=f,r._titleHeight=u):(a.lineHeight=h,a.height=Math.max(u,16)+3,a.width=f)}(t,r,n,a)}))}function A(e){return a.isRightAnchor(e)?"right":a.isCenterAnchor(e)?"center":"left"}function L(e){return a.isBottomAnchor(e)?"bottom":a.isMiddleAnchor(e)?"middle":"top"}t.exports=function(e,t){return t||(t=e._fullLayout.legend||{}),function(e,t){var r,l,d=e._fullLayout,f="legend"+d._uid,h=t._inHover;h?(r=t.layer,f+="-hover"):r=d._infolayer;if(!r)return;e._legendMouseDownTime||(e._legendMouseDownTime=0);if(h){if(!t.entries)return;l=y(t.entries,t)}else{if(!e.calcdata)return;l=d.showlegend&&y(e.calcdata,t)}var m=d.hiddenlabels||[];if(!(h||d.showlegend&&l.length))return r.selectAll(".legend").remove(),d._topdefs.select("#"+f).remove(),i.autoMargin(e,"legend");var T=a.ensureSingle(r,"g","legend",(function(e){h||e.attr("pointer-events","all")})),S=a.ensureSingleById(d._topdefs,"clipPath",f,(function(e){e.append("rect")})),D=a.ensureSingle(T,"rect","bg",(function(e){e.attr("shape-rendering","crispEdges")}));D.call(u.stroke,t.bordercolor).call(u.fill,t.bgcolor).style("stroke-width",t.borderwidth+"px");var C=a.ensureSingle(T,"g","scrollbox"),O=t.title;if(t._titleWidth=0,t._titleHeight=0,O.text){var P=a.ensureSingle(C,"text","legendtitletext");P.attr("text-anchor","start").call(c.font,O.font).text(O.text),k(P,C,e,t,1)}else C.selectAll(".legendtitletext").remove();var I=a.ensureSingle(T,"rect","scrollbar",(function(e){e.attr(p.scrollBarEnterAttrs).call(u.fill,p.scrollBarColor)})),R=C.selectAll("g.groups").data(l);R.enter().append("g").attr("class","groups"),R.exit().remove();var z=R.selectAll("g.traces").data(a.identity);z.enter().append("g").attr("class","traces"),z.exit().remove(),z.style("opacity",(function(e){var t=e[0].trace;return o.traceIs(t,"pie-like")?-1!==m.indexOf(e[0].label)?.5:1:"legendonly"===t.visible?.5:1})).each((function(){n.select(this).call(w,e,t)})).call(x,e,t).each((function(){h||n.select(this).call(M,e)})),a.syncOrAsync([i.previousPromises,function(){return function(e,t,r,a){var i=e._fullLayout;a||(a=i.legend);var o=i._size,l=b.isVertical(a),s=b.isGrouped(a),u=a.borderwidth,d=2*u,f=p.itemGap,h=a.itemwidth+2*f,m=2*(u+f),g=L(a),v=a.y<0||0===a.y&&"top"===g,y=a.y>1||1===a.y&&"bottom"===g,x=a.tracegroupgap;a._maxHeight=Math.max(v||y?i.height/2:o.h,30);var _=0;a._width=0,a._height=0;var w=function(e){var t=0,r=0,n=e.title.side;n&&(-1!==n.indexOf("left")&&(t=e._titleWidth),-1!==n.indexOf("top")&&(r=e._titleHeight));return[t,r]}(a);if(l)r.each((function(e){var t=e[0].height;c.setTranslate(this,u+w[0],u+w[1]+a._height+t/2+f),a._height+=t,a._width=Math.max(a._width,e[0].width)})),_=h+a._width,a._width+=f+h+d,a._height+=m,s&&(t.each((function(e,t){c.setTranslate(this,0,t*a.tracegroupgap)})),a._height+=(a._lgroupsLength-1)*a.tracegroupgap);else{var T=A(a),M=a.x<0||0===a.x&&"right"===T,k=a.x>1||1===a.x&&"left"===T,S=y||v,D=i.width/2;a._maxWidth=Math.max(M?S&&"left"===T?o.l+o.w:D:k?S&&"right"===T?o.r+o.w:D:o.w,2*h);var C=0,O=0;r.each((function(e){var t=e[0].width+h;C=Math.max(C,t),O+=t})),_=null;var P=0;if(s){var I=0,R=0,z=0;t.each((function(){var e=0,t=0;n.select(this).selectAll("g.traces").each((function(r){var n=r[0].width,a=r[0].height;c.setTranslate(this,w[0],w[1]+u+f+a/2+t),t+=a,e=Math.max(e,h+n)})),I=Math.max(I,t);var r=e+f;R>0&&r+u+R>a._maxWidth&&(P=Math.max(P,R),R=0,z+=I+x,I=t),c.setTranslate(this,R,z),R+=r})),a._width=Math.max(P,R)+u,a._height=z+I+m}else{var N=r.size(),F=O+d+(N-1)*f=a._maxWidth&&(P=Math.max(P,B),j=0,H+=E,a._height+=E,E=0),c.setTranslate(this,w[0]+u+j,w[1]+u+H+t/2+f),B=j+r+f,j+=n,E=Math.max(E,t)})),F?(a._width=j+d,a._height=E+m):(a._width=Math.max(P,B)+d,a._height+=E+m)}}a._width=Math.ceil(Math.max(a._width+w[0],a._titleWidth+2*(u+p.titlePad))),a._height=Math.ceil(Math.max(a._height+w[1],a._titleHeight+2*(u+p.itemGap))),a._effHeight=Math.min(a._height,a._maxHeight);var Y=e._context.edits,V=Y.legendText||Y.legendPosition;r.each((function(e){var t=n.select(this).select(".legendtoggle"),r=e[0].height,a=V?h:_||h+e[0].width;l||(a+=f/2),c.setRect(t,0,-r/2,a,r)}))}(e,R,z,t)},function(){var l,u,m,y,x=d._size,b=t.borderwidth;if(!h){if(function(e){var t=e._fullLayout.legend,r=A(t),n=L(t);return i.autoMargin(e,"legend",{x:t.x,y:t.y,l:t._width*g[r],r:t._width*v[r],b:t._effHeight*v[n],t:t._effHeight*g[n]})}(e))return;var w=x.l+x.w*t.x-g[A(t)]*t._width,M=x.t+x.h*(1-t.y)-g[L(t)]*t._effHeight;if(d.margin.autoexpand){var k=w,O=M;w=a.constrain(w,0,d.width-t._width),M=a.constrain(M,0,d.height-t._effHeight),w!==k&&a.log("Constrain legend.x to make legend fit inside graph"),M!==O&&a.log("Constrain legend.y to make legend fit inside graph")}c.setTranslate(T,w,M)}if(I.on(".drag",null),T.on("wheel",null),h||t._height<=t._maxHeight||e._context.staticPlot){var P=t._effHeight;h&&(P=t._height),D.attr({width:t._width-b,height:P-b,x:b/2,y:b/2}),c.setTranslate(C,0,0),S.select("rect").attr({width:t._width-2*b,height:P-2*b,x:b,y:b}),c.setClipUrl(C,f,e),c.setRect(I,0,0,0,0),delete t._scrollY}else{var R,z,N,F=Math.max(p.scrollBarMinHeight,t._effHeight*t._effHeight/t._height),E=t._effHeight-F-2*p.scrollBarMargin,j=t._height-t._effHeight,H=E/j,B=Math.min(t._scrollY||0,j);D.attr({width:t._width-2*b+p.scrollBarWidth+p.scrollBarMargin,height:t._effHeight-b,x:b/2,y:b/2}),S.select("rect").attr({width:t._width-2*b+p.scrollBarWidth+p.scrollBarMargin,height:t._effHeight-2*b,x:b,y:b+B}),c.setClipUrl(C,f,e),U(B,F,H),T.on("wheel",(function(){U(B=a.constrain(t._scrollY+n.event.deltaY/E*j,0,j),F,H),0!==B&&B!==j&&n.event.preventDefault()}));var Y=n.behavior.drag().on("dragstart",(function(){var e=n.event.sourceEvent;R="touchstart"===e.type?e.changedTouches[0].clientY:e.clientY,N=B})).on("drag",(function(){var e=n.event.sourceEvent;2===e.buttons||e.ctrlKey||(z="touchmove"===e.type?e.changedTouches[0].clientY:e.clientY,U(B=function(e,t,r){var n=(r-t)/H+e;return a.constrain(n,0,j)}(N,R,z),F,H))}));I.call(Y);var V=n.behavior.drag().on("dragstart",(function(){var e=n.event.sourceEvent;"touchstart"===e.type&&(R=e.changedTouches[0].clientY,N=B)})).on("drag",(function(){var e=n.event.sourceEvent;"touchmove"===e.type&&(z=e.changedTouches[0].clientY,U(B=function(e,t,r){var n=(t-r)/H+e;return a.constrain(n,0,j)}(N,R,z),F,H))}));C.call(V)}function U(r,n,a){t._scrollY=e._fullLayout.legend._scrollY=r,c.setTranslate(C,0,-r),c.setRect(I,t._width,p.scrollBarMargin+r*a,p.scrollBarWidth,n),S.select("rect").attr("y",b+r)}e._context.edits.legendPosition&&(T.classed("cursor-move",!0),s.init({element:T.node(),gd:e,prepFn:function(){var e=c.getTranslate(T);m=e.x,y=e.y},moveFn:function(e,r){var n=m+e,a=y+r;c.setTranslate(T,n,a),l=s.align(n,0,x.l,x.l+x.w,t.xanchor),u=s.align(a,0,x.t+x.h,x.t,t.yanchor)},doneFn:function(){void 0!==l&&void 0!==u&&o.call("_guiRelayout",e,{"legend.x":l,"legend.y":u})},clickFn:function(t,n){var a=r.selectAll("g.traces").filter((function(){var e=this.getBoundingClientRect();return n.clientX>=e.left&&n.clientX<=e.right&&n.clientY>=e.top&&n.clientY<=e.bottom}));a.size()>0&&_(e,T,a,t,n)}}))}],e)}(e,t)}},{"../../constants/alignment":207,"../../lib":232,"../../lib/events":225,"../../lib/svg_text_utils":255,"../../plots/plots":316,"../../registry":318,"../color":102,"../dragelement":121,"../drawing":124,"./constants":153,"./get_legend_data":156,"./handle_click":157,"./helpers":158,"./style":160,"@plotly/d3":11}],156:[function(e,t,r){"use strict";var n=e("../../registry"),a=e("./helpers");t.exports=function(e,t){var r,i,o=t._inHover,l=a.isGrouped(t),s=a.isReversed(t),c={},u=[],d=!1,f={},p=0,h=0;function m(e,r){if(""!==e&&a.isGrouped(t))-1===u.indexOf(e)?(u.push(e),d=!0,c[e]=[r]):c[e].push(r);else{var n="~~i"+p;u.push(n),c[n]=[r],p++}}for(r=0;rk&&(M=k)}w[r][0]._groupMinRank=M,w[r][0]._preGroupSort=r}var A=function(e,t){return e.trace.legendrank-t.trace.legendrank||e._preSort-t._preSort};for(w.forEach((function(e,t){e[0]._preGroupSort=t})),w.sort((function(e,t){return e[0]._groupMinRank-t[0]._groupMinRank||e[0]._preGroupSort-t[0]._preGroupSort})),r=0;rr?r:e}t.exports=function(e,t,r){var v=t._fullLayout;r||(r=v.legend);var y="constant"===r.itemsizing,x=r.itemwidth,b=(x+2*p.itemGap)/2,_=o(b,0),w=function(e,t,r,n){var a;if(e+1)a=e;else{if(!(t&&t.width>0))return 0;a=t.width}return y?n:Math.min(a,r)};function T(e,i,o){var u=e[0].trace,d=u.marker||{},f=d.line||{},p=o?u.visible&&u.type===o:a.traceIs(u,"bar"),h=n.select(i).select("g.legendpoints").selectAll("path.legend"+o).data(p?[e]:[]);h.enter().append("path").classed("legend"+o,!0).attr("d","M6,6H-6V-6H6Z").attr("transform",_),h.exit().remove(),h.each((function(e){var a=n.select(this),i=e[0],o=w(i.mlw,d.line,5,2);a.style("stroke-width",o+"px");var p=i.mcc;if(!r._inHover&&"mc"in i){var h=c(d),m=h.mid;void 0===m&&(m=(h.max+h.min)/2),p=l.tryColorscale(d,"")(m)}var v=p||i.mc||d.color,y=d.pattern,x=y&&l.getPatternAttr(y.shape,0,"");if(x){var b=l.getPatternAttr(y.bgcolor,0,null),_=l.getPatternAttr(y.fgcolor,0,null),T=y.fgopacity,M=g(y.size,8,10),k=g(y.solidity,.5,1),A="legend-"+u.uid;a.call(l.pattern,"legend",t,A,x,M,k,p,y.fillmode,b,_,T)}else a.call(s.fill,v);o&&s.stroke(a,i.mlc||f.color)}))}function M(e,t,r){var o=e[0],l=o.trace,s=r?l.visible&&l.type===r:a.traceIs(l,r),c=n.select(t).select("g.legendpoints").selectAll("path.legend"+r).data(s?[e]:[]);if(c.enter().append("path").classed("legend"+r,!0).attr("d","M6,6H-6V-6H6Z").attr("transform",_),c.exit().remove(),c.size()){var u=(l.marker||{}).line,p=w(f(u.width,o.pts),u,5,2),h=i.minExtend(l,{marker:{line:{width:p}}});h.marker.line.color=u.color;var m=i.minExtend(o,{trace:h});d(c,m,h)}}e.each((function(e){var t=n.select(this),a=i.ensureSingle(t,"g","layers");a.style("opacity",e[0].trace.opacity);var l=r.valign,s=e[0].lineHeight,c=e[0].height;if("middle"!==l&&s&&c){var u={top:1,bottom:-1}[l]*(.5*(s-c+3));a.attr("transform",o(0,u))}else a.attr("transform",null);a.selectAll("g.legendfill").data([e]).enter().append("g").classed("legendfill",!0),a.selectAll("g.legendlines").data([e]).enter().append("g").classed("legendlines",!0);var d=a.selectAll("g.legendsymbols").data([e]);d.enter().append("g").classed("legendsymbols",!0),d.selectAll("g.legendpoints").data([e]).enter().append("g").classed("legendpoints",!0)})).each((function(e){var r,a=e[0].trace,o=[];if(a.visible)switch(a.type){case"histogram2d":case"heatmap":o=[["M-15,-2V4H15V-2Z"]],r=!0;break;case"choropleth":case"choroplethmapbox":o=[["M-6,-6V6H6V-6Z"]],r=!0;break;case"densitymapbox":o=[["M-6,0 a6,6 0 1,0 12,0 a 6,6 0 1,0 -12,0"]],r="radial";break;case"cone":o=[["M-6,2 A2,2 0 0,0 -6,6 V6L6,4Z"],["M-6,-6 A2,2 0 0,0 -6,-2 L6,-4Z"],["M-6,-2 A2,2 0 0,0 -6,2 L6,0Z"]],r=!1;break;case"streamtube":o=[["M-6,2 A2,2 0 0,0 -6,6 H6 A2,2 0 0,1 6,2 Z"],["M-6,-6 A2,2 0 0,0 -6,-2 H6 A2,2 0 0,1 6,-6 Z"],["M-6,-2 A2,2 0 0,0 -6,2 H6 A2,2 0 0,1 6,-2 Z"]],r=!1;break;case"surface":o=[["M-6,-6 A2,3 0 0,0 -6,0 H6 A2,3 0 0,1 6,-6 Z"],["M-6,1 A2,3 0 0,1 -6,6 H6 A2,3 0 0,0 6,0 Z"]],r=!0;break;case"mesh3d":o=[["M-6,6H0L-6,-6Z"],["M6,6H0L6,-6Z"],["M-6,-6H6L0,6Z"]],r=!1;break;case"volume":o=[["M-6,6H0L-6,-6Z"],["M6,6H0L6,-6Z"],["M-6,-6H6L0,6Z"]],r=!0;break;case"isosurface":o=[["M-6,6H0L-6,-6Z"],["M6,6H0L6,-6Z"],["M-6,-6 A12,24 0 0,0 6,-6 L0,6Z"]],r=!1}var u=n.select(this).select("g.legendpoints").selectAll("path.legend3dandfriends").data(o);u.enter().append("path").classed("legend3dandfriends",!0).attr("transform",_).style("stroke-miterlimit",1),u.exit().remove(),u.each((function(e,o){var u,d=n.select(this),f=c(a),p=f.colorscale,m=f.reversescale;if(p){if(!r){var g=p.length;u=0===o?p[m?g-1:0][1]:1===o?p[m?0:g-1][1]:p[Math.floor((g-1)/2)][1]}}else{var v=a.vertexcolor||a.facecolor||a.color;u=i.isArrayOrTypedArray(v)?v[o]||v[0]:v}d.attr("d",e[0]),u?d.call(s.fill,u):d.call((function(e){if(e.size()){var n="legendfill-"+a.uid;l.gradient(e,t,n,h(m,"radial"===r),p,"fill")}}))}))})).each((function(e){var t=e[0].trace,r="waterfall"===t.type;if(e[0]._distinct&&r){var a=e[0].trace[e[0].dir].marker;return e[0].mc=a.color,e[0].mlw=a.line.width,e[0].mlc=a.line.color,T(e,this,"waterfall")}var i=[];t.visible&&r&&(i=e[0].hasTotals?[["increasing","M-6,-6V6H0Z"],["totals","M6,6H0L-6,-6H-0Z"],["decreasing","M6,6V-6H0Z"]]:[["increasing","M-6,-6V6H6Z"],["decreasing","M6,6V-6H-6Z"]]);var o=n.select(this).select("g.legendpoints").selectAll("path.legendwaterfall").data(i);o.enter().append("path").classed("legendwaterfall",!0).attr("transform",_).style("stroke-miterlimit",1),o.exit().remove(),o.each((function(e){var r=n.select(this),a=t[e[0]].marker,i=w(void 0,a.line,5,2);r.attr("d",e[1]).style("stroke-width",i+"px").call(s.fill,a.color),i&&r.call(s.stroke,a.line.color)}))})).each((function(e){T(e,this,"funnel")})).each((function(e){T(e,this)})).each((function(e){var r=e[0].trace,o=n.select(this).select("g.legendpoints").selectAll("path.legendbox").data(r.visible&&a.traceIs(r,"box-violin")?[e]:[]);o.enter().append("path").classed("legendbox",!0).attr("d","M6,6H-6V-6H6Z").attr("transform",_),o.exit().remove(),o.each((function(){var e=n.select(this);if("all"!==r.boxpoints&&"all"!==r.points||0!==s.opacity(r.fillcolor)||0!==s.opacity((r.line||{}).color)){var a=w(void 0,r.line,5,2);e.style("stroke-width",a+"px").call(s.fill,r.fillcolor),a&&s.stroke(e,r.line.color)}else{var c=i.minExtend(r,{marker:{size:y?12:i.constrain(r.marker.size,2,16),sizeref:1,sizemin:1,sizemode:"diameter"}});o.call(l.pointStyle,c,t)}}))})).each((function(e){M(e,this,"funnelarea")})).each((function(e){M(e,this,"pie")})).each((function(e){var r,a,o=m(e),s=o.showFill,d=o.showLine,f=o.showGradientLine,p=o.showGradientFill,g=o.anyFill,v=o.anyLine,y=e[0],b=y.trace,_=c(b),T=_.colorscale,M=_.reversescale,k=u.hasMarkers(b)||!g?"M5,0":v?"M5,-2":"M5,-3",A=n.select(this),L=A.select(".legendfill").selectAll("path").data(s||p?[e]:[]);if(L.enter().append("path").classed("js-fill",!0),L.exit().remove(),L.attr("d",k+"h"+x+"v6h-"+x+"z").call(s?l.fillGroupStyle:function(e){if(e.size()){var r="legendfill-"+b.uid;l.gradient(e,t,r,h(M),T,"fill")}}),d||f){var S=w(void 0,b.line,10,5);a=i.minExtend(b,{line:{width:S}}),r=[i.minExtend(y,{trace:a})]}var D=A.select(".legendlines").selectAll("path").data(d||f?[r]:[]);D.enter().append("path").classed("js-line",!0),D.exit().remove(),D.attr("d",k+(f?"l"+x+",0.0001":"h"+x)).call(d?l.lineGroupStyle:function(e){if(e.size()){var r="legendline-"+b.uid;l.lineGroupStyle(e),l.gradient(e,t,r,h(M),T,"stroke")}})})).each((function(e){var r,a,o=m(e),s=o.anyFill,c=o.anyLine,d=o.showLine,f=o.showMarker,p=e[0],h=p.trace,g=!f&&!c&&!s&&u.hasText(h);function v(e,t,r,n){var a=i.nestedProperty(h,e).get(),o=i.isArrayOrTypedArray(a)&&t?t(a):a;if(y&&o&&void 0!==n&&(o=n),r){if(or[1])return r[1]}return o}function x(e){return p._distinct&&p.index&&e[p.index]?e[p.index]:e[0]}if(f||g||d){var b={},w={};if(f){b.mc=v("marker.color",x),b.mx=v("marker.symbol",x),b.mo=v("marker.opacity",i.mean,[.2,1]),b.mlc=v("marker.line.color",x),b.mlw=v("marker.line.width",i.mean,[0,5],2),w.marker={sizeref:1,sizemin:1,sizemode:"diameter"};var T=v("marker.size",i.mean,[2,16],12);b.ms=T,w.marker.size=T}d&&(w.line={width:v("line.width",x,[0,10],5)}),g&&(b.tx="Aa",b.tp=v("textposition",x),b.ts=10,b.tc=v("textfont.color",x),b.tf=v("textfont.family",x)),r=[i.minExtend(p,b)],(a=i.minExtend(h,w)).selectedpoints=null,a.texttemplate=null}var M=n.select(this).select("g.legendpoints"),k=M.selectAll("path.scatterpts").data(f?r:[]);k.enter().insert("path",":first-child").classed("scatterpts",!0).attr("transform",_),k.exit().remove(),k.call(l.pointStyle,a,t),f&&(r[0].mrc=3);var A=M.selectAll("g.pointtext").data(g?r:[]);A.enter().append("g").classed("pointtext",!0).append("text").attr("transform",_),A.exit().remove(),A.selectAll("text").call(l.textPointStyle,a,t)})).each((function(e){var t=e[0].trace,r=n.select(this).select("g.legendpoints").selectAll("path.legendcandle").data(t.visible&&"candlestick"===t.type?[e,e]:[]);r.enter().append("path").classed("legendcandle",!0).attr("d",(function(e,t){return t?"M-15,0H-8M-8,6V-6H8Z":"M15,0H8M8,-6V6H-8Z"})).attr("transform",_).style("stroke-miterlimit",1),r.exit().remove(),r.each((function(e,r){var a=n.select(this),i=t[r?"increasing":"decreasing"],o=w(void 0,i.line,5,2);a.style("stroke-width",o+"px").call(s.fill,i.fillcolor),o&&s.stroke(a,i.line.color)}))})).each((function(e){var t=e[0].trace,r=n.select(this).select("g.legendpoints").selectAll("path.legendohlc").data(t.visible&&"ohlc"===t.type?[e,e]:[]);r.enter().append("path").classed("legendohlc",!0).attr("d",(function(e,t){return t?"M-15,0H0M-8,-6V0":"M15,0H0M8,6V0"})).attr("transform",_).style("stroke-miterlimit",1),r.exit().remove(),r.each((function(e,r){var a=n.select(this),i=t[r?"increasing":"decreasing"],o=w(void 0,i.line,5,2);a.style("fill","none").call(l.dashLine,i.line.dash,o),o&&s.stroke(a,i.line.color)}))}))}},{"../../lib":232,"../../registry":318,"../../traces/pie/helpers":350,"../../traces/pie/style_one":356,"../../traces/scatter/subtypes":383,"../color":102,"../colorscale/helpers":113,"../drawing":124,"./constants":153,"@plotly/d3":11}],161:[function(e,t,r){"use strict";e("./constants");t.exports={editType:"modebar",orientation:{valType:"enumerated",values:["v","h"],dflt:"h",editType:"modebar"},bgcolor:{valType:"color",editType:"modebar"},color:{valType:"color",editType:"modebar"},activecolor:{valType:"color",editType:"modebar"},uirevision:{valType:"any",editType:"none"},add:{valType:"string",arrayOk:!0,dflt:"",editType:"modebar"},remove:{valType:"string",arrayOk:!0,dflt:"",editType:"modebar"}}},{"./constants":163}],162:[function(e,t,r){"use strict";var n=e("../../registry"),a=e("../../plots/plots"),i=e("../../plots/cartesian/axis_ids"),o=e("../../fonts/ploticon"),l=e("../shapes/draw").eraseActiveShape,s=e("../../lib"),c=s._,u=t.exports={};function d(e,t){var r,a,o=t.currentTarget,l=o.getAttribute("data-attr"),s=o.getAttribute("data-val")||!0,c=e._fullLayout,u={},d=i.list(e,null,!0),f=c._cartesianSpikesEnabled;if("zoom"===l){var p,h="in"===s?.5:2,m=(1+h)/2,g=(1-h)/2;for(a=0;a1?(P=["toggleHover"],I=["resetViews"]):v?(O=["zoomInGeo","zoomOutGeo"],P=["hoverClosestGeo"],I=["resetGeo"]):g?(P=["hoverClosest3d"],I=["resetCameraDefault3d","resetCameraLastSave3d"]):w?(O=["zoomInMapbox","zoomOutMapbox"],P=["toggleHover"],I=["resetViewMapbox"]):b?P=["hoverClosestGl2d"]:y?P=["hoverClosestPie"]:k?(P=["hoverClosestCartesian","hoverCompareCartesian"],I=["resetViewSankey"]):P=["toggleHover"];m&&(P=["toggleSpikelines","hoverClosestCartesian","hoverCompareCartesian"]);(function(e){for(var t=0;t0)){var m=function(e,t,r){for(var n=r.filter((function(r){return t[r].anchor===e._id})),a=0,i=0;i=n.max)t=F[r+1];else if(e=n.pmax)t=F[r+1];else if(e0?f+c:c;return{ppad:c,ppadplus:u?h:m,ppadminus:u?m:h}}return{ppad:c}}function u(e,t,r,n,a){var l="category"===e.type||"multicategory"===e.type?e.r2c:e.d2c;if(void 0!==t)return[l(t),l(r)];if(n){var s,c,u,d,f=1/0,p=-1/0,h=n.match(i.segmentRE);for("date"===e.type&&(l=o.decodeDate(l)),s=0;sp&&(p=d)));return p>=f?[f,p]:void 0}}t.exports=function(e){var t=e._fullLayout,r=n.filterVisible(t.shapes);if(r.length&&e._fullData.length)for(var o=0;oy?(M=d,S="y0",k=y,D="y1"):(M=y,S="y1",k=d,D="y0");X(n),K(l,r),function(e,t,r){var n=t.xref,a=t.yref,o=i.getFromId(r,n),l=i.getFromId(r,a),s="";"paper"===n||o.autorange||(s+=n);"paper"===a||l.autorange||(s+=a);u.setClipUrl(e,s?"clip"+r._fullLayout._uid+s:null,r)}(t,r,e),W.moveFn="move"===I?J:Q,W.altKey=n.altKey},doneFn:function(){if(v(e))return;p(t),$(l),b(t,e,r),n.call("_guiRelayout",e,s.getUpdateObj())},clickFn:function(){if(v(e))return;$(l)}};function X(r){if(v(e))I=null;else if(N)I="path"===r.target.tagName?"move":"start-point"===r.target.attributes["data-line-point"].value?"resize-over-start-point":"resize-over-end-point";else{var n=W.element.getBoundingClientRect(),a=n.right-n.left,i=n.bottom-n.top,o=r.clientX-n.left,l=r.clientY-n.top,s=!F&&a>10&&i>10&&!r.shiftKey?f.getCursor(o/a,1-l/i):"move";p(t,s),I=s.split("-")[0]}}function J(n,a){if("path"===r.type){var i=function(e){return e},o=i,s=i;R?E("xanchor",r.xanchor=q(x+n)):(o=function(e){return q(V(e)+n)},j&&"date"===j.type&&(o=m.encodeDate(o))),z?E("yanchor",r.yanchor=G(T+a)):(s=function(e){return G(U(e)+a)},B&&"date"===B.type&&(s=m.encodeDate(s))),E("path",r.path=w(P,o,s))}else R?E("xanchor",r.xanchor=q(x+n)):(E("x0",r.x0=q(c+n)),E("x1",r.x1=q(g+n))),z?E("yanchor",r.yanchor=G(T+a)):(E("y0",r.y0=G(d+a)),E("y1",r.y1=G(y+a)));t.attr("d",_(e,r)),K(l,r)}function Q(n,a){if(F){var i=function(e){return e},o=i,s=i;R?E("xanchor",r.xanchor=q(x+n)):(o=function(e){return q(V(e)+n)},j&&"date"===j.type&&(o=m.encodeDate(o))),z?E("yanchor",r.yanchor=G(T+a)):(s=function(e){return G(U(e)+a)},B&&"date"===B.type&&(s=m.encodeDate(s))),E("path",r.path=w(P,o,s))}else if(N){if("resize-over-start-point"===I){var u=c+n,f=z?d-a:d+a;E("x0",r.x0=R?u:q(u)),E("y0",r.y0=z?f:G(f))}else if("resize-over-end-point"===I){var p=g+n,h=z?y-a:y+a;E("x1",r.x1=R?p:q(p)),E("y1",r.y1=z?h:G(h))}}else{var v=function(e){return-1!==I.indexOf(e)},b=v("n"),H=v("s"),Y=v("w"),Z=v("e"),W=b?M+a:M,X=H?k+a:k,J=Y?A+n:A,Q=Z?L+n:L;z&&(b&&(W=M-a),H&&(X=k-a)),(!z&&X-W>10||z&&W-X>10)&&(E(S,r[S]=z?W:G(W)),E(D,r[D]=z?X:G(X))),Q-J>10&&(E(C,r[C]=R?J:q(J)),E(O,r[O]=R?Q:q(Q)))}t.attr("d",_(e,r)),K(l,r)}function K(e,t){(R||z)&&function(){var r="path"!==t.type,n=e.selectAll(".visual-cue").data([0]);n.enter().append("path").attr({fill:"#fff","fill-rule":"evenodd",stroke:"#000","stroke-width":1}).classed("visual-cue",!0);var i=V(R?t.xanchor:a.midRange(r?[t.x0,t.x1]:m.extractPathCoords(t.path,h.paramIsX))),o=U(z?t.yanchor:a.midRange(r?[t.y0,t.y1]:m.extractPathCoords(t.path,h.paramIsY)));if(i=m.roundPositionForSharpStrokeRendering(i,1),o=m.roundPositionForSharpStrokeRendering(o,1),R&&z){var l="M"+(i-1-1)+","+(o-1-1)+"h-8v2h8 v8h2v-8 h8v-2h-8 v-8h-2 Z";n.attr("d",l)}else if(R){var s="M"+(i-1-1)+","+(o-9-1)+"v18 h2 v-18 Z";n.attr("d",s)}else{var c="M"+(i-9-1)+","+(o-1-1)+"h18 v2 h-18 Z";n.attr("d",c)}}()}function $(e){e.selectAll(".visual-cue").remove()}f.init(W),Z.node().onmousemove=X}(e,R,s,t,r,I):!0===s.editable&&R.style("pointer-events",O||c.opacity(L)*A<=.5?"stroke":"all");R.node().addEventListener("click",(function(){return function(e,t){if(!y(e))return;var r=+t.node().getAttribute("data-index");if(r>=0){if(r===e._fullLayout._activeShapeIndex)return void T(e);e._fullLayout._activeShapeIndex=r,e._fullLayout._deactivateShape=T,g(e)}}(e,R)}))}}function b(e,t,r){var n=(r.xref+r.yref).replace(/paper/g,"").replace(/[xyz][1-9]* *domain/g,"");u.setClipUrl(e,n?"clip"+t._fullLayout._uid+n:null,t)}function _(e,t){var r,n,o,l,s,c,u,d,f=t.type,p=i.getRefType(t.xref),g=i.getRefType(t.yref),v=i.getFromId(e,t.xref),y=i.getFromId(e,t.yref),x=e._fullLayout._size;if(v?"domain"===p?n=function(e){return v._offset+v._length*e}:(r=m.shapePositionToRange(v),n=function(e){return v._offset+v.r2p(r(e,!0))}):n=function(e){return x.l+x.w*e},y?"domain"===g?l=function(e){return y._offset+y._length*(1-e)}:(o=m.shapePositionToRange(y),l=function(e){return y._offset+y.r2p(o(e,!0))}):l=function(e){return x.t+x.h*(1-e)},"path"===f)return v&&"date"===v.type&&(n=m.decodeDate(n)),y&&"date"===y.type&&(l=m.decodeDate(l)),function(e,t,r){var n=e.path,i=e.xsizemode,o=e.ysizemode,l=e.xanchor,s=e.yanchor;return n.replace(h.segmentRE,(function(e){var n=0,c=e.charAt(0),u=h.paramIsX[c],d=h.paramIsY[c],f=h.numParams[c],p=e.substr(1).replace(h.paramRE,(function(e){return u[n]?e="pixel"===i?t(l)+Number(e):t(e):d[n]&&(e="pixel"===o?r(s)-Number(e):r(e)),++n>f&&(e="X"),e}));return n>f&&(p=p.replace(/[\s,]*X.*/,""),a.log("Ignoring extra params in segment "+e)),c+p}))}(t,n,l);if("pixel"===t.xsizemode){var b=n(t.xanchor);s=b+t.x0,c=b+t.x1}else s=n(t.x0),c=n(t.x1);if("pixel"===t.ysizemode){var _=l(t.yanchor);u=_-t.y0,d=_-t.y1}else u=l(t.y0),d=l(t.y1);if("line"===f)return"M"+s+","+u+"L"+c+","+d;if("rect"===f)return"M"+s+","+u+"H"+c+"V"+d+"H"+s+"Z";var w=(s+c)/2,T=(u+d)/2,M=Math.abs(w-s),k=Math.abs(T-u),A="A"+M+","+k,L=w+M+","+T;return"M"+L+A+" 0 1,1 "+(w+","+(T-k))+A+" 0 0,1 "+L+"Z"}function w(e,t,r){return e.replace(h.segmentRE,(function(e){var n=0,a=e.charAt(0),i=h.paramIsX[a],o=h.paramIsY[a],l=h.numParams[a];return a+e.substr(1).replace(h.paramRE,(function(e){return n>=l||(i[n]?e=t(e):o[n]&&(e=r(e)),n++),e}))}))}function T(e){y(e)&&(e._fullLayout._activeShapeIndex>=0&&(s(e),delete e._fullLayout._activeShapeIndex,g(e)))}t.exports={draw:g,drawOne:x,eraseActiveShape:function(e){if(!y(e))return;s(e);var t=e._fullLayout._activeShapeIndex,r=(e.layout||{}).shapes||[];if(t=0&&d(v),r.attr("d",m(t)),k&&!f)&&(M=function(e,t){for(var r=0;r1&&(2!==e.length||"Z"!==e[1][0])&&(0===T&&(e[0][0]="M"),t[w]=e,y(),x())}}()}}function P(e,r){!function(e,r){if(t.length)for(var n=0;n0&&s0&&(l=l.transition().duration(t.transition.duration).ease(t.transition.easing)),l.attr("transform",s(o-.5*d.gripWidth,t._dims.currentValueTotalHeight))}}function S(e,t){var r=e._dims;return r.inputAreaStart+d.stepInset+(r.inputAreaLength-2*d.stepInset)*Math.min(1,Math.max(0,t))}function D(e,t){var r=e._dims;return Math.min(1,Math.max(0,(t-d.stepInset-r.inputAreaStart)/(r.inputAreaLength-2*d.stepInset-2*r.inputAreaStart)))}function C(e,t,r){var n=r._dims,a=l.ensureSingle(e,"rect",d.railTouchRectClass,(function(n){n.call(k,t,e,r).style("pointer-events","all")}));a.attr({width:n.inputAreaLength,height:Math.max(n.inputAreaWidth,d.tickOffset+r.ticklen+n.labelHeight)}).call(i.fill,r.bgcolor).attr("opacity",0),o.setTranslate(a,0,n.currentValueTotalHeight)}function O(e,t){var r=t._dims,n=r.inputAreaLength-2*d.railInset,a=l.ensureSingle(e,"rect",d.railRectClass);a.attr({width:n,height:d.railWidth,rx:d.railRadius,ry:d.railRadius,"shape-rendering":"crispEdges"}).call(i.stroke,t.bordercolor).call(i.fill,t.bgcolor).style("stroke-width",t.borderwidth+"px"),o.setTranslate(a,d.railInset,.5*(r.inputAreaWidth-d.railWidth)+r.currentValueTotalHeight)}t.exports=function(e){var t=e._fullLayout,r=function(e,t){for(var r=e[d.name],n=[],a=0;a0?[0]:[]);function l(t){t._commandObserver&&(t._commandObserver.remove(),delete t._commandObserver),a.autoMargin(e,g(t))}if(i.enter().append("g").classed(d.containerClassName,!0).style("cursor","ew-resize"),i.exit().each((function(){n.select(this).selectAll("g."+d.groupClassName).each(l)})).remove(),0!==r.length){var s=i.selectAll("g."+d.groupClassName).data(r,v);s.enter().append("g").classed(d.groupClassName,!0),s.exit().each(l).remove();for(var c=0;c0||f<0){var v={left:[-h,0],right:[h,0],top:[0,-h],bottom:[0,h]}[b.side];t.attr("transform",s(v[0],v[1]))}}}return z.call(N),I&&(S?z.on(".opacity",null):(k=0,A=!0,z.text(y).on("mouseover.opacity",(function(){n.select(this).transition().duration(f.SHOW_PLACEHOLDER).style("opacity",1)})).on("mouseout.opacity",(function(){n.select(this).transition().duration(f.HIDE_PLACEHOLDER).style("opacity",0)}))),z.call(d.makeEditable,{gd:e}).on("edit",(function(t){void 0!==x?o.call("_guiRestyle",e,v,t,x):o.call("_guiRelayout",e,v,t)})).on("cancel",(function(){this.text(this.attr("data-unformatted")).call(N)})).on("input",(function(e){this.text(e||" ").call(d.positionText,_.x,_.y)}))),z.classed("js-placeholder",A),T}}},{"../../constants/alignment":207,"../../constants/interactions":211,"../../lib":232,"../../lib/svg_text_utils":255,"../../plots/plots":316,"../../registry":318,"../color":102,"../drawing":124,"@plotly/d3":11,"fast-isnumeric":17}],201:[function(e,t,r){"use strict";var n=e("../../plots/font_attributes"),a=e("../color/attributes"),i=e("../../lib/extend").extendFlat,o=e("../../plot_api/edit_types").overrideAll,l=e("../../plots/pad_attributes"),s=e("../../plot_api/plot_template").templatedArray,c=s("button",{visible:{valType:"boolean"},method:{valType:"enumerated",values:["restyle","relayout","animate","update","skip"],dflt:"restyle"},args:{valType:"info_array",freeLength:!0,items:[{valType:"any"},{valType:"any"},{valType:"any"}]},args2:{valType:"info_array",freeLength:!0,items:[{valType:"any"},{valType:"any"},{valType:"any"}]},label:{valType:"string",dflt:""},execute:{valType:"boolean",dflt:!0}});t.exports=o(s("updatemenu",{_arrayAttrRegexps:[/^updatemenus\[(0|[1-9][0-9]+)\]\.buttons/],visible:{valType:"boolean"},type:{valType:"enumerated",values:["dropdown","buttons"],dflt:"dropdown"},direction:{valType:"enumerated",values:["left","right","up","down"],dflt:"down"},active:{valType:"integer",min:-1,dflt:0},showactive:{valType:"boolean",dflt:!0},buttons:c,x:{valType:"number",min:-2,max:3,dflt:-.05},xanchor:{valType:"enumerated",values:["auto","left","center","right"],dflt:"right"},y:{valType:"number",min:-2,max:3,dflt:1},yanchor:{valType:"enumerated",values:["auto","top","middle","bottom"],dflt:"top"},pad:i(l({editType:"arraydraw"}),{}),font:n({}),bgcolor:{valType:"color"},bordercolor:{valType:"color",dflt:a.borderLine},borderwidth:{valType:"number",min:0,dflt:1,editType:"arraydraw"}}),"arraydraw","from-root")},{"../../lib/extend":226,"../../plot_api/edit_types":261,"../../plot_api/plot_template":268,"../../plots/font_attributes":310,"../../plots/pad_attributes":315,"../color/attributes":101}],202:[function(e,t,r){"use strict";t.exports={name:"updatemenus",containerClassName:"updatemenu-container",headerGroupClassName:"updatemenu-header-group",headerClassName:"updatemenu-header",headerArrowClassName:"updatemenu-header-arrow",dropdownButtonGroupClassName:"updatemenu-dropdown-button-group",dropdownButtonClassName:"updatemenu-dropdown-button",buttonClassName:"updatemenu-button",itemRectClassName:"updatemenu-item-rect",itemTextClassName:"updatemenu-item-text",menuIndexAttrName:"updatemenu-active-index",autoMarginIdRoot:"updatemenu-",blankHeaderOpts:{label:" "},minWidth:30,minHeight:30,textPadX:24,arrowPadX:16,rx:2,ry:2,textOffsetX:12,textOffsetY:3,arrowOffsetX:4,gapButtonHeader:5,gapButton:2,activeColor:"#F4FAFF",hoverColor:"#F4FAFF",arrowSymbol:{left:"\u25c4",right:"\u25ba",up:"\u25b2",down:"\u25bc"}}},{}],203:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../../plots/array_container_defaults"),i=e("./attributes"),o=e("./constants").name,l=i.buttons;function s(e,t,r){function o(r,a){return n.coerce(e,t,i,r,a)}o("visible",a(e,t,{name:"buttons",handleItemDefaults:c}).length>0)&&(o("active"),o("direction"),o("type"),o("showactive"),o("x"),o("y"),n.noneOrAll(e,t,["x","y"]),o("xanchor"),o("yanchor"),o("pad.t"),o("pad.r"),o("pad.b"),o("pad.l"),n.coerceFont(o,"font",r.font),o("bgcolor",r.paper_bgcolor),o("bordercolor"),o("borderwidth"))}function c(e,t){function r(r,a){return n.coerce(e,t,l,r,a)}r("visible","skip"===e.method||Array.isArray(e.args))&&(r("method"),r("args"),r("args2"),r("label"),r("execute"))}t.exports=function(e,t){a(e,t,{name:o,handleItemDefaults:s})}},{"../../lib":232,"../../plots/array_container_defaults":274,"./attributes":201,"./constants":202}],204:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../../plots/plots"),i=e("../color"),o=e("../drawing"),l=e("../../lib"),s=e("../../lib/svg_text_utils"),c=e("../../plot_api/plot_template").arrayEditor,u=e("../../constants/alignment").LINE_SPACING,d=e("./constants"),f=e("./scrollbox");function p(e){return e._index}function h(e,t){return+e.attr(d.menuIndexAttrName)===t._index}function m(e,t,r,n,a,i,o,l){t.active=o,c(e.layout,d.name,t).applyUpdate("active",o),"buttons"===t.type?v(e,n,null,null,t):"dropdown"===t.type&&(a.attr(d.menuIndexAttrName,"-1"),g(e,n,a,i,t),l||v(e,n,a,i,t))}function g(e,t,r,n,a){var i=l.ensureSingle(t,"g",d.headerClassName,(function(e){e.style("pointer-events","all")})),s=a._dims,c=a.active,u=a.buttons[c]||d.blankHeaderOpts,f={y:a.pad.t,yPad:0,x:a.pad.l,xPad:0,index:0},p={width:s.headerWidth,height:s.headerHeight};i.call(y,a,u,e).call(A,a,f,p),l.ensureSingle(t,"text",d.headerArrowClassName,(function(e){e.attr("text-anchor","end").call(o.font,a.font).text(d.arrowSymbol[a.direction])})).attr({x:s.headerWidth-d.arrowOffsetX+a.pad.l,y:s.headerHeight/2+d.textOffsetY+a.pad.t}),i.on("click",(function(){r.call(L,String(h(r,a)?-1:a._index)),v(e,t,r,n,a)})),i.on("mouseover",(function(){i.call(w)})),i.on("mouseout",(function(){i.call(T,a)})),o.setTranslate(t,s.lx,s.ly)}function v(e,t,r,i,o){r||(r=t).attr("pointer-events","all");var s=function(e){return-1==+e.attr(d.menuIndexAttrName)}(r)&&"buttons"!==o.type?[]:o.buttons,c="dropdown"===o.type?d.dropdownButtonClassName:d.buttonClassName,u=r.selectAll("g."+c).data(l.filterVisible(s)),f=u.enter().append("g").classed(c,!0),p=u.exit();"dropdown"===o.type?(f.attr("opacity","0").transition().attr("opacity","1"),p.transition().attr("opacity","0").remove()):p.remove();var h=0,g=0,v=o._dims,x=-1!==["up","down"].indexOf(o.direction);"dropdown"===o.type&&(x?g=v.headerHeight+d.gapButtonHeader:h=v.headerWidth+d.gapButtonHeader),"dropdown"===o.type&&"up"===o.direction&&(g=-d.gapButtonHeader+d.gapButton-v.openHeight),"dropdown"===o.type&&"left"===o.direction&&(h=-d.gapButtonHeader+d.gapButton-v.openWidth);var b={x:v.lx+h+o.pad.l,y:v.ly+g+o.pad.t,yPad:d.gapButton,xPad:d.gapButton,index:0},M={l:b.x+o.borderwidth,t:b.y+o.borderwidth};u.each((function(l,s){var c=n.select(this);c.call(y,o,l,e).call(A,o,b),c.on("click",(function(){n.event.defaultPrevented||(l.execute&&(l.args2&&o.active===s?(m(e,o,0,t,r,i,-1),a.executeAPICommand(e,l.method,l.args2)):(m(e,o,0,t,r,i,s),a.executeAPICommand(e,l.method,l.args))),e.emit("plotly_buttonclicked",{menu:o,button:l,active:o.active}))})),c.on("mouseover",(function(){c.call(w)})),c.on("mouseout",(function(){c.call(T,o),u.call(_,o)}))})),u.call(_,o),x?(M.w=Math.max(v.openWidth,v.headerWidth),M.h=b.y-M.t):(M.w=b.x-M.l,M.h=Math.max(v.openHeight,v.headerHeight)),M.direction=o.direction,i&&(u.size()?function(e,t,r,n,a,i){var o,l,s,c=a.direction,u="up"===c||"down"===c,f=a._dims,p=a.active;if(u)for(l=0,s=0;s0?[0]:[]);if(o.enter().append("g").classed(d.containerClassName,!0).style("cursor","pointer"),o.exit().each((function(){n.select(this).selectAll("g."+d.headerGroupClassName).each(i)})).remove(),0!==r.length){var s=o.selectAll("g."+d.headerGroupClassName).data(r,p);s.enter().append("g").classed(d.headerGroupClassName,!0);for(var c=l.ensureSingle(o,"g",d.dropdownButtonGroupClassName,(function(e){e.style("pointer-events","all")})),u=0;uw,k=l.barLength+2*l.barPad,A=l.barWidth+2*l.barPad,L=h,S=g+v;S+A>c&&(S=c-A);var D=this.container.selectAll("rect.scrollbar-horizontal").data(M?[0]:[]);D.exit().on(".drag",null).remove(),D.enter().append("rect").classed("scrollbar-horizontal",!0).call(a.fill,l.barColor),M?(this.hbar=D.attr({rx:l.barRadius,ry:l.barRadius,x:L,y:S,width:k,height:A}),this._hbarXMin=L+k/2,this._hbarTranslateMax=w-k):(delete this.hbar,delete this._hbarXMin,delete this._hbarTranslateMax);var C=v>T,O=l.barWidth+2*l.barPad,P=l.barLength+2*l.barPad,I=h+m,R=g;I+O>s&&(I=s-O);var z=this.container.selectAll("rect.scrollbar-vertical").data(C?[0]:[]);z.exit().on(".drag",null).remove(),z.enter().append("rect").classed("scrollbar-vertical",!0).call(a.fill,l.barColor),C?(this.vbar=z.attr({rx:l.barRadius,ry:l.barRadius,x:I,y:R,width:O,height:P}),this._vbarYMin=R+P/2,this._vbarTranslateMax=T-P):(delete this.vbar,delete this._vbarYMin,delete this._vbarTranslateMax);var N=this.id,F=u-.5,E=C?d+O+.5:d+.5,j=f-.5,H=M?p+A+.5:p+.5,B=o._topdefs.selectAll("#"+N).data(M||C?[0]:[]);if(B.exit().remove(),B.enter().append("clipPath").attr("id",N).append("rect"),M||C?(this._clipRect=B.select("rect").attr({x:Math.floor(F),y:Math.floor(j),width:Math.ceil(E)-Math.floor(F),height:Math.ceil(H)-Math.floor(j)}),this.container.call(i.setClipUrl,N,this.gd),this.bg.attr({x:h,y:g,width:m,height:v})):(this.bg.attr({width:0,height:0}),this.container.on("wheel",null).on(".drag",null).call(i.setClipUrl,null),delete this._clipRect),M||C){var Y=n.behavior.drag().on("dragstart",(function(){n.event.sourceEvent.preventDefault()})).on("drag",this._onBoxDrag.bind(this));this.container.on("wheel",null).on("wheel",this._onBoxWheel.bind(this)).on(".drag",null).call(Y);var V=n.behavior.drag().on("dragstart",(function(){n.event.sourceEvent.preventDefault(),n.event.sourceEvent.stopPropagation()})).on("drag",this._onBarDrag.bind(this));M&&this.hbar.on(".drag",null).call(V),C&&this.vbar.on(".drag",null).call(V)}this.setTranslate(t,r)},l.prototype.disable=function(){(this.hbar||this.vbar)&&(this.bg.attr({width:0,height:0}),this.container.on("wheel",null).on(".drag",null).call(i.setClipUrl,null),delete this._clipRect),this.hbar&&(this.hbar.on(".drag",null),this.hbar.remove(),delete this.hbar,delete this._hbarXMin,delete this._hbarTranslateMax),this.vbar&&(this.vbar.on(".drag",null),this.vbar.remove(),delete this.vbar,delete this._vbarYMin,delete this._vbarTranslateMax)},l.prototype._onBoxDrag=function(){var e=this.translateX,t=this.translateY;this.hbar&&(e-=n.event.dx),this.vbar&&(t-=n.event.dy),this.setTranslate(e,t)},l.prototype._onBoxWheel=function(){var e=this.translateX,t=this.translateY;this.hbar&&(e+=n.event.deltaY),this.vbar&&(t+=n.event.deltaY),this.setTranslate(e,t)},l.prototype._onBarDrag=function(){var e=this.translateX,t=this.translateY;if(this.hbar){var r=e+this._hbarXMin,a=r+this._hbarTranslateMax;e=(o.constrain(n.event.x,r,a)-r)/(a-r)*(this.position.w-this._box.w)}if(this.vbar){var i=t+this._vbarYMin,l=i+this._vbarTranslateMax;t=(o.constrain(n.event.y,i,l)-i)/(l-i)*(this.position.h-this._box.h)}this.setTranslate(e,t)},l.prototype.setTranslate=function(e,t){var r=this.position.w-this._box.w,n=this.position.h-this._box.h;if(e=o.constrain(e||0,0,r),t=o.constrain(t||0,0,n),this.translateX=e,this.translateY=t,this.container.call(i.setTranslate,this._box.l-this.position.l-e,this._box.t-this.position.t-t),this._clipRect&&this._clipRect.attr({x:Math.floor(this.position.l+e-.5),y:Math.floor(this.position.t+t-.5)}),this.hbar){var a=e/r;this.hbar.call(i.setTranslate,e+a*this._hbarTranslateMax,t)}if(this.vbar){var l=t/n;this.vbar.call(i.setTranslate,e,t+l*this._vbarTranslateMax)}}},{"../../lib":232,"../color":102,"../drawing":124,"@plotly/d3":11}],207:[function(e,t,r){"use strict";t.exports={FROM_BL:{left:0,center:.5,right:1,bottom:0,middle:.5,top:1},FROM_TL:{left:0,center:.5,right:1,bottom:1,middle:.5,top:0},FROM_BR:{left:1,center:.5,right:0,bottom:0,middle:.5,top:1},LINE_SPACING:1.3,CAP_SHIFT:.7,MID_SHIFT:.35,OPPOSITE_SIDE:{left:"right",right:"left",top:"bottom",bottom:"top"}}},{}],208:[function(e,t,r){"use strict";t.exports={axisRefDescription:function(e,t,r){return["If set to a",e,"axis id (e.g. *"+e+"* or","*"+e+"2*), the `"+e+"` position refers to a",e,"coordinate. If set to *paper*, the `"+e+"`","position refers to the distance from the",t,"of the plotting","area in normalized coordinates where *0* (*1*) corresponds to the",t,"("+r+"). If set to a",e,"axis ID followed by","*domain* (separated by a space), the position behaves like for","*paper*, but refers to the distance in fractions of the domain","length from the",t,"of the domain of that axis: e.g.,","*"+e+"2 domain* refers to the domain of the second",e," axis and a",e,"position of 0.5 refers to the","point between the",t,"and the",r,"of the domain of the","second",e,"axis."].join(" ")}}},{}],209:[function(e,t,r){"use strict";t.exports={FORMAT_LINK:"https://github.com/d3/d3-format/tree/v1.4.5#d3-format",DATE_FORMAT_LINK:"https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format"}},{}],210:[function(e,t,r){"use strict";t.exports={COMPARISON_OPS:["=","!=","<",">=",">","<="],COMPARISON_OPS2:["=","<",">=",">","<="],INTERVAL_OPS:["[]","()","[)","(]","][",")(","](",")["],SET_OPS:["{}","}{"],CONSTRAINT_REDUCTION:{"=":"=","<":"<","<=":"<",">":">",">=":">","[]":"[]","()":"[]","[)":"[]","(]":"[]","][":"][",")(":"][","](":"][",")[":"]["}}},{}],211:[function(e,t,r){"use strict";t.exports={SHOW_PLACEHOLDER:100,HIDE_PLACEHOLDER:1e3,DESELECTDIM:.2}},{}],212:[function(e,t,r){"use strict";t.exports={BADNUM:void 0,FP_SAFE:1e-4*Number.MAX_VALUE,ONEMAXYEAR:316224e5,ONEAVGYEAR:315576e5,ONEMINYEAR:31536e6,ONEMAXQUARTER:79488e5,ONEAVGQUARTER:78894e5,ONEMINQUARTER:76896e5,ONEMAXMONTH:26784e5,ONEAVGMONTH:26298e5,ONEMINMONTH:24192e5,ONEWEEK:6048e5,ONEDAY:864e5,ONEHOUR:36e5,ONEMIN:6e4,ONESEC:1e3,EPOCHJD:2440587.5,ALMOST_EQUAL:.999999,LOG_CLIP:10,MINUS_SIGN:"\u2212"}},{}],213:[function(e,t,r){"use strict";r.xmlns="http://www.w3.org/2000/xmlns/",r.svg="http://www.w3.org/2000/svg",r.xlink="http://www.w3.org/1999/xlink",r.svgAttrs={xmlns:r.svg,"xmlns:xlink":r.xlink}},{}],214:[function(e,t,r){"use strict";r.version=e("./version").version,e("native-promise-only"),e("../build/plotcss");for(var n=e("./registry"),a=r.register=n.register,i=e("./plot_api"),o=Object.keys(i),l=0;lplotly-logomark"}}},{}],216:[function(e,t,r){"use strict";r.isLeftAnchor=function(e){return"left"===e.xanchor||"auto"===e.xanchor&&e.x<=1/3},r.isCenterAnchor=function(e){return"center"===e.xanchor||"auto"===e.xanchor&&e.x>1/3&&e.x<2/3},r.isRightAnchor=function(e){return"right"===e.xanchor||"auto"===e.xanchor&&e.x>=2/3},r.isTopAnchor=function(e){return"top"===e.yanchor||"auto"===e.yanchor&&e.y>=2/3},r.isMiddleAnchor=function(e){return"middle"===e.yanchor||"auto"===e.yanchor&&e.y>1/3&&e.y<2/3},r.isBottomAnchor=function(e){return"bottom"===e.yanchor||"auto"===e.yanchor&&e.y<=1/3}},{}],217:[function(e,t,r){"use strict";var n=e("./mod"),a=n.mod,i=n.modHalf,o=Math.PI,l=2*o;function s(e){return Math.abs(e[1]-e[0])>l-1e-14}function c(e,t){return i(t-e,l)}function u(e,t){if(s(t))return!0;var r,n;t[0](n=a(n,l))&&(n+=l);var i=a(e,l),o=i+l;return i>=r&&i<=n||o>=r&&o<=n}function d(e,t,r,n,a,i,c){a=a||0,i=i||0;var u,d,f,p,h,m=s([r,n]);function g(e,t){return[e*Math.cos(t)+a,i-e*Math.sin(t)]}m?(u=0,d=o,f=l):r=a&&e<=i);var a,i},pathArc:function(e,t,r,n,a){return d(null,e,t,r,n,a,0)},pathSector:function(e,t,r,n,a){return d(null,e,t,r,n,a,1)},pathAnnulus:function(e,t,r,n,a,i){return d(e,t,r,n,a,i,1)}}},{"./mod":239}],218:[function(e,t,r){"use strict";var n=Array.isArray,a=ArrayBuffer,i=DataView;function o(e){return a.isView(e)&&!(e instanceof i)}function l(e){return n(e)||o(e)}function s(e,t,r){if(l(e)){if(l(e[0])){for(var n=r,a=0;aa.max?t.set(r):t.set(+e)}},integer:{coerceFunction:function(e,t,r,a){e%1||!n(e)||void 0!==a.min&&ea.max?t.set(r):t.set(+e)}},string:{coerceFunction:function(e,t,r,n){if("string"!=typeof e){var a="number"==typeof e;!0!==n.strict&&a?t.set(String(e)):t.set(r)}else n.noBlank&&!e?t.set(r):t.set(e)}},color:{coerceFunction:function(e,t,r){a(e).isValid()?t.set(e):t.set(r)}},colorlist:{coerceFunction:function(e,t,r){Array.isArray(e)&&e.length&&e.every((function(e){return a(e).isValid()}))?t.set(e):t.set(r)}},colorscale:{coerceFunction:function(e,t,r){t.set(o.get(e,r))}},angle:{coerceFunction:function(e,t,r){"auto"===e?t.set("auto"):n(e)?t.set(d(+e,360)):t.set(r)}},subplotid:{coerceFunction:function(e,t,r,n){var a=n.regex||u(r);"string"==typeof e&&a.test(e)?t.set(e):t.set(r)},validateFunction:function(e,t){var r=t.dflt;return e===r||"string"==typeof e&&!!u(r).test(e)}},flaglist:{coerceFunction:function(e,t,r,n){if("string"==typeof e)if(-1===(n.extras||[]).indexOf(e)){for(var a=e.split("+"),i=0;i=n&&e<=a?e:u}if("string"!=typeof e&&"number"!=typeof e)return u;e=String(e);var c=_(t),v=e.charAt(0);!c||"G"!==v&&"g"!==v||(e=e.substr(1),t="");var w=c&&"chinese"===t.substr(0,7),T=e.match(w?x:y);if(!T)return u;var M=T[1],k=T[3]||"1",A=Number(T[5]||1),L=Number(T[7]||0),S=Number(T[9]||0),D=Number(T[11]||0);if(c){if(2===M.length)return u;var C;M=Number(M);try{var O=g.getComponentMethod("calendars","getCal")(t);if(w){var P="i"===k.charAt(k.length-1);k=parseInt(k,10),C=O.newDate(M,O.toMonthIndex(M,k,P),A)}else C=O.newDate(M,Number(k),A)}catch(e){return u}return C?(C.toJD()-m)*d+L*f+S*p+D*h:u}M=2===M.length?(Number(M)+2e3-b)%100+b:Number(M),k-=1;var I=new Date(Date.UTC(2e3,k,A,L,S));return I.setUTCFullYear(M),I.getUTCMonth()!==k||I.getUTCDate()!==A?u:I.getTime()+D*h},n=r.MIN_MS=r.dateTime2ms("-9999"),a=r.MAX_MS=r.dateTime2ms("9999-12-31 23:59:59.9999"),r.isDateTime=function(e,t){return r.dateTime2ms(e,t)!==u};var T=90*d,M=3*f,k=5*p;function A(e,t,r,n,a){if((t||r||n||a)&&(e+=" "+w(t,2)+":"+w(r,2),(n||a)&&(e+=":"+w(n,2),a))){for(var i=4;a%10==0;)i-=1,a/=10;e+="."+w(a,i)}return e}r.ms2DateTime=function(e,t,r){if("number"!=typeof e||!(e>=n&&e<=a))return u;t||(t=0);var i,o,l,c,y,x,b=Math.floor(10*s(e+.05,1)),w=Math.round(e-b/10);if(_(r)){var L=Math.floor(w/d)+m,S=Math.floor(s(e,d));try{i=g.getComponentMethod("calendars","getCal")(r).fromJD(L).formatDate("yyyy-mm-dd")}catch(e){i=v("G%Y-%m-%d")(new Date(w))}if("-"===i.charAt(0))for(;i.length<11;)i="-0"+i.substr(1);else for(;i.length<10;)i="0"+i;o=t=n+d&&e<=a-d))return u;var t=Math.floor(10*s(e+.05,1)),r=new Date(Math.round(e-t/10));return A(i("%Y-%m-%d")(r),r.getHours(),r.getMinutes(),r.getSeconds(),10*r.getUTCMilliseconds()+t)},r.cleanDate=function(e,t,n){if(e===u)return t;if(r.isJSDate(e)||"number"==typeof e&&isFinite(e)){if(_(n))return l.error("JS Dates and milliseconds are incompatible with world calendars",e),t;if(!(e=r.ms2DateTimeLocal(+e))&&void 0!==t)return t}else if(!r.isDateTime(e,n))return l.error("unrecognized date",e),t;return e};var L=/%\d?f/g,S=/%h/g,D={1:"1",2:"1",3:"2",4:"2"};function C(e,t,r,n){e=e.replace(L,(function(e){var r=Math.min(+e.charAt(1)||6,6);return(t/1e3%1+2).toFixed(r).substr(2).replace(/0+$/,"")||"0"}));var a=new Date(Math.floor(t+.05));if(e=e.replace(S,(function(){return D[r("%q")(a)]})),_(n))try{e=g.getComponentMethod("calendars","worldCalFmt")(e,t,n)}catch(e){return"Invalid"}return r(e)(a)}var O=[59,59.9,59.99,59.999,59.9999];r.formatDate=function(e,t,r,n,a,i){if(a=_(a)&&a,!t)if("y"===r)t=i.year;else if("m"===r)t=i.month;else{if("d"!==r)return function(e,t){var r=s(e+.05,d),n=w(Math.floor(r/f),2)+":"+w(s(Math.floor(r/p),60),2);if("M"!==t){o(t)||(t=0);var a=(100+Math.min(s(e/h,60),O[t])).toFixed(t).substr(1);t>0&&(a=a.replace(/0+$/,"").replace(/[\.]$/,"")),n+=":"+a}return n}(e,r)+"\n"+C(i.dayMonthYear,e,n,a);t=i.dayMonth+"\n"+i.year}return C(t,e,n,a)};var P=3*d;r.incrementMonth=function(e,t,r){r=_(r)&&r;var n=s(e,d);if(e=Math.round(e-n),r)try{var a=Math.round(e/d)+m,i=g.getComponentMethod("calendars","getCal")(r),o=i.fromJD(a);return t%12?i.add(o,t,"m"):i.add(o,t/12,"y"),(o.toJD()-m)*d+n}catch(t){l.error("invalid ms "+e+" in calendar "+r)}var c=new Date(e+P);return c.setUTCMonth(c.getUTCMonth()+t)+n-P},r.findExactDates=function(e,t){for(var r,n,a=0,i=0,l=0,s=0,c=_(t)&&g.getComponentMethod("calendars","getCal")(t),u=0;u1||m<0||m>1?null:{x:e+s*m,y:t+d*m}}function s(e,t,r,n,a){var i=n*e+a*t;if(i<0)return n*n+a*a;if(i>r){var o=n-e,l=a-t;return o*o+l*l}var s=n*t-a*e;return s*s/r}r.segmentsIntersect=l,r.segmentDistance=function(e,t,r,n,a,i,o,c){if(l(e,t,r,n,a,i,o,c))return 0;var u=r-e,d=n-t,f=o-a,p=c-i,h=u*u+d*d,m=f*f+p*p,g=Math.min(s(u,d,h,a-e,i-t),s(u,d,h,o-e,c-t),s(f,p,m,e-a,t-i),s(f,p,m,r-a,n-i));return Math.sqrt(g)},r.getTextLocation=function(e,t,r,l){if(e===a&&l===i||(n={},a=e,i=l),n[r])return n[r];var s=e.getPointAtLength(o(r-l/2,t)),c=e.getPointAtLength(o(r+l/2,t)),u=Math.atan((c.y-s.y)/(c.x-s.x)),d=e.getPointAtLength(o(r,t)),f={x:(4*d.x+s.x+c.x)/6,y:(4*d.y+s.y+c.y)/6,theta:u};return n[r]=f,f},r.clearLocationCache=function(){a=null},r.getVisibleSegment=function(e,t,r){var n,a,i=t.left,o=t.right,l=t.top,s=t.bottom,c=0,u=e.getTotalLength(),d=u;function f(t){var r=e.getPointAtLength(t);0===t?n=r:t===u&&(a=r);var c=r.xo?r.x-o:0,d=r.ys?r.y-s:0;return Math.sqrt(c*c+d*d)}for(var p=f(c);p;){if((c+=p+r)>d)return;p=f(c)}for(p=f(d);p;){if(c>(d-=p+r))return;p=f(d)}return{min:c,max:d,len:d-c,total:u,isClosed:0===c&&d===u&&Math.abs(n.x-a.x)<.1&&Math.abs(n.y-a.y)<.1}},r.findPointOnPath=function(e,t,r,n){for(var a,i,o,l=(n=n||{}).pathLength||e.getTotalLength(),s=n.tolerance||.001,c=n.iterationLimit||30,u=e.getPointAtLength(0)[r]>e.getPointAtLength(l)[r]?-1:1,d=0,f=0,p=l;d0?p=a:f=a,d++}return i}},{"./mod":239}],230:[function(e,t,r){"use strict";t.exports=function(e){return e}},{}],231:[function(e,t,r){"use strict";t.exports=function(e,t){if(!t)return e;var r=1/Math.abs(t),n=r>1?(r*e+r*t)/r:e+t,a=String(n).length;if(a>16){var i=String(t).length;if(a>=String(e).length+i){var o=parseFloat(n).toPrecision(12);-1===o.indexOf("e+")&&(n=+o)}}return n}},{}],232:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("d3-time-format").utcFormat,i=e("d3-format").format,o=e("fast-isnumeric"),l=e("../constants/numerical"),s=l.FP_SAFE,c=-s,u=l.BADNUM,d=t.exports={};d.adjustFormat=function(e){return!e||/^\d[.]\df/.test(e)||/[.]\d%/.test(e)?e:"0.f"===e?"~f":/^\d%/.test(e)?"~%":/^\ds/.test(e)?"~s":!/^[~,.0$]/.test(e)&&/[&fps]/.test(e)?"~"+e:e};var f={};d.warnBadFormat=function(e){var t=String(e);f[t]||(f[t]=1,d.warn('encountered bad format: "'+t+'"'))},d.noFormat=function(e){return String(e)},d.numberFormat=function(e){var t;try{t=i(d.adjustFormat(e))}catch(t){return d.warnBadFormat(e),d.noFormat}return t},d.nestedProperty=e("./nested_property"),d.keyedContainer=e("./keyed_container"),d.relativeAttr=e("./relative_attr"),d.isPlainObject=e("./is_plain_object"),d.toLogRange=e("./to_log_range"),d.relinkPrivateKeys=e("./relink_private");var p=e("./array");d.isTypedArray=p.isTypedArray,d.isArrayOrTypedArray=p.isArrayOrTypedArray,d.isArray1D=p.isArray1D,d.ensureArray=p.ensureArray,d.concat=p.concat,d.maxRowLength=p.maxRowLength,d.minRowLength=p.minRowLength;var h=e("./mod");d.mod=h.mod,d.modHalf=h.modHalf;var m=e("./coerce");d.valObjectMeta=m.valObjectMeta,d.coerce=m.coerce,d.coerce2=m.coerce2,d.coerceFont=m.coerceFont,d.coercePattern=m.coercePattern,d.coerceHoverinfo=m.coerceHoverinfo,d.coerceSelectionMarkerOpacity=m.coerceSelectionMarkerOpacity,d.validate=m.validate;var g=e("./dates");d.dateTime2ms=g.dateTime2ms,d.isDateTime=g.isDateTime,d.ms2DateTime=g.ms2DateTime,d.ms2DateTimeLocal=g.ms2DateTimeLocal,d.cleanDate=g.cleanDate,d.isJSDate=g.isJSDate,d.formatDate=g.formatDate,d.incrementMonth=g.incrementMonth,d.dateTick0=g.dateTick0,d.dfltRange=g.dfltRange,d.findExactDates=g.findExactDates,d.MIN_MS=g.MIN_MS,d.MAX_MS=g.MAX_MS;var v=e("./search");d.findBin=v.findBin,d.sorterAsc=v.sorterAsc,d.sorterDes=v.sorterDes,d.distinctVals=v.distinctVals,d.roundUp=v.roundUp,d.sort=v.sort,d.findIndexOfMin=v.findIndexOfMin,d.sortObjectKeys=e("./sort_object_keys");var y=e("./stats");d.aggNums=y.aggNums,d.len=y.len,d.mean=y.mean,d.median=y.median,d.midRange=y.midRange,d.variance=y.variance,d.stdev=y.stdev,d.interp=y.interp;var x=e("./matrix");d.init2dArray=x.init2dArray,d.transposeRagged=x.transposeRagged,d.dot=x.dot,d.translationMatrix=x.translationMatrix,d.rotationMatrix=x.rotationMatrix,d.rotationXYMatrix=x.rotationXYMatrix,d.apply3DTransform=x.apply3DTransform,d.apply2DTransform=x.apply2DTransform,d.apply2DTransform2=x.apply2DTransform2,d.convertCssMatrix=x.convertCssMatrix,d.inverseTransformMatrix=x.inverseTransformMatrix;var b=e("./angles");d.deg2rad=b.deg2rad,d.rad2deg=b.rad2deg,d.angleDelta=b.angleDelta,d.angleDist=b.angleDist,d.isFullCircle=b.isFullCircle,d.isAngleInsideSector=b.isAngleInsideSector,d.isPtInsideSector=b.isPtInsideSector,d.pathArc=b.pathArc,d.pathSector=b.pathSector,d.pathAnnulus=b.pathAnnulus;var _=e("./anchor_utils");d.isLeftAnchor=_.isLeftAnchor,d.isCenterAnchor=_.isCenterAnchor,d.isRightAnchor=_.isRightAnchor,d.isTopAnchor=_.isTopAnchor,d.isMiddleAnchor=_.isMiddleAnchor,d.isBottomAnchor=_.isBottomAnchor;var w=e("./geometry2d");d.segmentsIntersect=w.segmentsIntersect,d.segmentDistance=w.segmentDistance,d.getTextLocation=w.getTextLocation,d.clearLocationCache=w.clearLocationCache,d.getVisibleSegment=w.getVisibleSegment,d.findPointOnPath=w.findPointOnPath;var T=e("./extend");d.extendFlat=T.extendFlat,d.extendDeep=T.extendDeep,d.extendDeepAll=T.extendDeepAll,d.extendDeepNoArrays=T.extendDeepNoArrays;var M=e("./loggers");d.log=M.log,d.warn=M.warn,d.error=M.error;var k=e("./regex");d.counterRegex=k.counter;var A=e("./throttle");d.throttle=A.throttle,d.throttleDone=A.done,d.clearThrottle=A.clear;var L=e("./dom");function S(e){var t={};for(var r in e)for(var n=e[r],a=0;as||e=t)&&(o(e)&&e>=0&&e%1==0)},d.noop=e("./noop"),d.identity=e("./identity"),d.repeat=function(e,t){for(var r=new Array(t),n=0;nr?Math.max(r,Math.min(t,e)):Math.max(t,Math.min(r,e))},d.bBoxIntersect=function(e,t,r){return r=r||0,e.left<=t.right+r&&t.left<=e.right+r&&e.top<=t.bottom+r&&t.top<=e.bottom+r},d.simpleMap=function(e,t,r,n,a){for(var i=e.length,o=new Array(i),l=0;l=Math.pow(2,r)?a>10?(d.warn("randstr failed uniqueness"),s):e(t,r,n,(a||0)+1):s},d.OptionControl=function(e,t){e||(e={}),t||(t="opt");var r={optionList:[],_newoption:function(n){n[t]=e,r[n.name]=n,r.optionList.push(n)}};return r["_"+t]=e,r},d.smooth=function(e,t){if((t=Math.round(t)||0)<2)return e;var r,n,a,i,o=e.length,l=2*o,s=2*t-1,c=new Array(s),u=new Array(o);for(r=0;r=l&&(a-=l*Math.floor(a/l)),a<0?a=-1-a:a>=o&&(a=l-1-a),i+=e[a]*c[n];u[r]=i}return u},d.syncOrAsync=function(e,t,r){var n;function a(){return d.syncOrAsync(e,t,r)}for(;e.length;)if((n=(0,e.splice(0,1)[0])(t))&&n.then)return n.then(a);return r&&r(t)},d.stripTrailingSlash=function(e){return"/"===e.substr(-1)?e.substr(0,e.length-1):e},d.noneOrAll=function(e,t,r){if(e){var n,a=!1,i=!0;for(n=0;n0?t:0}))},d.fillArray=function(e,t,r,n){if(n=n||d.identity,d.isArrayOrTypedArray(e))for(var a=0;a1?a+o[1]:"";if(i&&(o.length>1||l.length>4||r))for(;n.test(l);)l=l.replace(n,"$1"+i+"$2");return l+s},d.TEMPLATE_STRING_REGEX=/%{([^\s%{}:]*)([:|\|][^}]*)?}/g;var R=/^\w*$/;d.templateString=function(e,t){var r={};return e.replace(d.TEMPLATE_STRING_REGEX,(function(e,n){var a;return R.test(n)?a=t[n]:(r[n]=r[n]||d.nestedProperty(t,n).get,a=r[n]()),d.isValidTextValue(a)?a:""}))};var z={max:10,count:0,name:"hovertemplate"};d.hovertemplateString=function(){return E.apply(z,arguments)};var N={max:10,count:0,name:"texttemplate"};d.texttemplateString=function(){return E.apply(N,arguments)};var F=/^[:|\|]/;function E(e,t,r){var n=this,i=arguments;t||(t={});var o={};return e.replace(d.TEMPLATE_STRING_REGEX,(function(e,l,s){var c,u,f,p="_xother"===l||"_yother"===l,h="_xother_"===l||"_yother_"===l,m="xother_"===l||"yother_"===l,g="xother"===l||"yother"===l||p||m||h,v=l;if((p||h)&&(v=v.substring(1)),(m||h)&&(v=v.substring(0,v.length-1)),g){if(void 0===(c=t[v]))return""}else for(f=3;f=48&&o<=57,c=l>=48&&l<=57;if(s&&(n=10*n+o-48),c&&(a=10*a+l-48),!s||!c){if(n!==a)return n-a;if(o!==l)return o-l}}return a-n};var j=2e9;d.seedPseudoRandom=function(){j=2e9},d.pseudoRandom=function(){var e=j;return j=(69069*j+1)%4294967296,Math.abs(j-e)<429496729?d.pseudoRandom():j/4294967296},d.fillText=function(e,t,r){var n=Array.isArray(r)?function(e){r.push(e)}:function(e){r.text=e},a=d.extractOption(e,t,"htx","hovertext");if(d.isValidTextValue(a))return n(a);var i=d.extractOption(e,t,"tx","text");return d.isValidTextValue(i)?n(i):void 0},d.isValidTextValue=function(e){return e||0===e},d.formatPercent=function(e,t){t=t||0;for(var r=(Math.round(100*e*Math.pow(10,t))*Math.pow(.1,t)).toFixed(t)+"%",n=0;n1&&(c=1):c=0,d.strTranslate(a-c*(r+o),i-c*(n+l))+d.strScale(c)+(s?"rotate("+s+(t?"":" "+r+" "+n)+")":"")},d.ensureUniformFontSize=function(e,t){var r=d.extendFlat({},t);return r.size=Math.max(t.size,e._fullLayout.uniformtext.minsize||0),r},d.join2=function(e,t,r){var n=e.length;return n>1?e.slice(0,-1).join(t)+r+e[n-1]:e.join(t)},d.bigFont=function(e){return Math.round(1.2*e)};var H=d.getFirefoxVersion(),B=null!==H&&H<86;d.getPositionFromD3Event=function(){return B?[n.event.layerX,n.event.layerY]:[n.event.offsetX,n.event.offsetY]}},{"../constants/numerical":212,"./anchor_utils":216,"./angles":217,"./array":218,"./clean_number":219,"./clear_responsive":221,"./coerce":222,"./dates":223,"./dom":224,"./extend":226,"./filter_unique":227,"./filter_visible":228,"./geometry2d":229,"./identity":230,"./increment":231,"./is_plain_object":233,"./keyed_container":234,"./localize":235,"./loggers":236,"./make_trace_groups":237,"./matrix":238,"./mod":239,"./nested_property":240,"./noop":241,"./notifier":242,"./preserve_drawing_buffer":245,"./push_unique":246,"./regex":248,"./relative_attr":249,"./relink_private":250,"./search":251,"./sort_object_keys":253,"./stats":254,"./throttle":256,"./to_log_range":257,"@plotly/d3":11,"d3-format":13,"d3-time-format":14,"fast-isnumeric":17}],233:[function(e,t,r){"use strict";t.exports=function(e){return window&&window.process&&window.process.versions?"[object Object]"===Object.prototype.toString.call(e):"[object Object]"===Object.prototype.toString.call(e)&&Object.getPrototypeOf(e).hasOwnProperty("hasOwnProperty")}},{}],234:[function(e,t,r){"use strict";var n=e("./nested_property"),a=/^\w*$/;t.exports=function(e,t,r,i){var o,l,s;r=r||"name",i=i||"value";var c={};t&&t.length?(s=n(e,t),l=s.get()):l=e,t=t||"";var u={};if(l)for(o=0;o2)return c[t]=2|c[t],f.set(e,null);if(d){for(o=t;o1){var t=["LOG:"];for(e=0;e1){var r=[];for(e=0;e"),"long")}},i.warn=function(){var e;if(n.logging>0){var t=["WARN:"];for(e=0;e0){var r=[];for(e=0;e"),"stick")}},i.error=function(){var e;if(n.logging>0){var t=["ERROR:"];for(e=0;e0){var r=[];for(e=0;e"),"stick")}}},{"../plot_api/plot_config":266,"./notifier":242}],237:[function(e,t,r){"use strict";var n=e("@plotly/d3");t.exports=function(e,t,r){var a=e.selectAll("g."+r.replace(/\s/g,".")).data(t,(function(e){return e[0].trace.uid}));a.exit().remove(),a.enter().append("g").attr("class",r),a.order();var i=e.classed("rangeplot")?"nodeRangePlot3":"node3";return a.each((function(e){e[0][i]=n.select(this)})),a}},{"@plotly/d3":11}],238:[function(e,t,r){"use strict";var n=e("gl-mat4");r.init2dArray=function(e,t){for(var r=new Array(e),n=0;nt/2?e-Math.round(e/t)*t:e}}},{}],240:[function(e,t,r){"use strict";var n=e("fast-isnumeric"),a=e("./array").isArrayOrTypedArray;function i(e,t){return function(){var r,n,o,l,s,c=e;for(l=0;l/g),s=0;si||c===a||cl)&&(!t||!s(e))}:function(e,t){var s=e[0],c=e[1];if(s===a||si||c===a||cl)return!1;var u,d,f,p,h,m=r.length,g=r[0][0],v=r[0][1],y=0;for(u=1;uMath.max(d,g)||c>Math.max(f,v)))if(cu||Math.abs(n(o,f))>a)return!0;return!1},i.filter=function(e,t){var r=[e[0]],n=0,a=0;function o(o){e.push(o);var l=r.length,s=n;r.splice(a+1);for(var c=s+1;c1&&o(e.pop());return{addPt:o,raw:e,filtered:r}}},{"../constants/numerical":212,"./matrix":238}],245:[function(e,t,r){"use strict";var n=e("fast-isnumeric"),a=e("is-mobile");t.exports=function(e){var t;if("string"!=typeof(t=e&&e.hasOwnProperty("userAgent")?e.userAgent:function(){var e;"undefined"!=typeof navigator&&(e=navigator.userAgent);e&&e.headers&&"string"==typeof e.headers["user-agent"]&&(e=e.headers["user-agent"]);return e}()))return!0;var r=a({ua:{headers:{"user-agent":t}},tablet:!0,featureDetect:!1});if(!r)for(var i=t.split(" "),o=1;o-1;l--){var s=i[l];if("Version/"===s.substr(0,8)){var c=s.substr(8).split(".")[0];if(n(c)&&(c=+c),c>=13)return!0}}}return r}},{"fast-isnumeric":17,"is-mobile":51}],246:[function(e,t,r){"use strict";t.exports=function(e,t){if(t instanceof RegExp){for(var r=t.toString(),n=0;na.queueLength&&(e.undoQueue.queue.shift(),e.undoQueue.index--))},startSequence:function(e){e.undoQueue=e.undoQueue||{index:0,queue:[],sequence:!1},e.undoQueue.sequence=!0,e.undoQueue.beginSequence=!0},stopSequence:function(e){e.undoQueue=e.undoQueue||{index:0,queue:[],sequence:!1},e.undoQueue.sequence=!1,e.undoQueue.beginSequence=!1},undo:function(e){var t,r;if(!(void 0===e.undoQueue||isNaN(e.undoQueue.index)||e.undoQueue.index<=0)){for(e.undoQueue.index--,t=e.undoQueue.queue[e.undoQueue.index],e.undoQueue.inSequence=!0,r=0;r=e.undoQueue.queue.length)){for(t=e.undoQueue.queue[e.undoQueue.index],e.undoQueue.inSequence=!0,r=0;rt}function u(e,t){return e>=t}r.findBin=function(e,t,r){if(n(t.start))return r?Math.ceil((e-t.start)/t.size-1e-9)-1:Math.floor((e-t.start)/t.size+1e-9);var i,o,d=0,f=t.length,p=0,h=f>1?(t[f-1]-t[0])/(f-1):1;for(o=h>=0?r?l:s:r?u:c,e+=1e-9*h*(r?-1:1)*(h>=0?1:-1);d90&&a.log("Long binary search..."),d-1},r.sorterAsc=function(e,t){return e-t},r.sorterDes=function(e,t){return t-e},r.distinctVals=function(e){var t,n=e.slice();for(n.sort(r.sorterAsc),t=n.length-1;t>-1&&n[t]===o;t--);for(var a,i=n[t]-n[0]||1,l=i/(t||1)/1e4,s=[],c=0;c<=t;c++){var u=n[c],d=u-a;void 0===a?(s.push(u),a=u):d>l&&(i=Math.min(i,d),s.push(u),a=u)}return{vals:s,minDiff:i}},r.roundUp=function(e,t,r){for(var n,a=0,i=t.length-1,o=0,l=r?0:1,s=r?1:0,c=r?Math.ceil:Math.floor;a0&&(n=1),r&&n)return e.sort(t)}return n?e:e.reverse()},r.findIndexOfMin=function(e,t){t=t||i;for(var r,n=1/0,a=0;ai.length)&&(o=i.length),n(t)||(t=!1),a(i[0])){for(s=new Array(o),l=0;le.length-1)return e[e.length-1];var r=t%1;return r*e[Math.ceil(t)]+(1-r)*e[Math.floor(t)]}},{"./array":218,"fast-isnumeric":17}],255:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../lib"),i=a.strTranslate,o=e("../constants/xmlns_namespaces"),l=e("../constants/alignment").LINE_SPACING,s=/([^$]*)([$]+[^$]*[$]+)([^$]*)/;r.convertToTspans=function(e,t,h){var k=e.text(),A=!e.attr("data-notex")&&"undefined"!=typeof MathJax&&k.match(s),D=n.select(e.node().parentNode);if(!D.empty()){var C=e.attr("class")?e.attr("class").split(" ")[0]:"text";return C+="-math",D.selectAll("svg."+C).remove(),D.selectAll("g."+C+"-group").remove(),e.style("display",null).attr({"data-unformatted":k,"data-math":"N"}),A?(t&&t._promises||[]).push(new Promise((function(t){e.style("display","none");var r=parseInt(e.node().style.fontSize,10),o={fontSize:r};!function(e,t,r){var i,o,l,s;MathJax.Hub.Queue((function(){return o=a.extendDeepAll({},MathJax.Hub.config),l=MathJax.Hub.processSectionDelay,void 0!==MathJax.Hub.processSectionDelay&&(MathJax.Hub.processSectionDelay=0),MathJax.Hub.Config({messageStyle:"none",tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]]},displayAlign:"left"})}),(function(){if("SVG"!==(i=MathJax.Hub.config.menuSettings.renderer))return MathJax.Hub.setRenderer("SVG")}),(function(){var r="math-output-"+a.randstr({},64);return s=n.select("body").append("div").attr({id:r}).style({visibility:"hidden",position:"absolute"}).style({"font-size":t.fontSize+"px"}).text(e.replace(c,"\\lt ").replace(u,"\\gt ")),MathJax.Hub.Typeset(s.node())}),(function(){var t=n.select("body").select("#MathJax_SVG_glyphs");if(s.select(".MathJax_SVG").empty()||!s.select("svg").node())a.log("There was an error in the tex syntax.",e),r();else{var o=s.select("svg").node().getBoundingClientRect();r(s.select(".MathJax_SVG"),t,o)}if(s.remove(),"SVG"!==i)return MathJax.Hub.setRenderer(i)}),(function(){return void 0!==l&&(MathJax.Hub.processSectionDelay=l),MathJax.Hub.Config(o)}))}(A[2],o,(function(n,a,o){D.selectAll("svg."+C).remove(),D.selectAll("g."+C+"-group").remove();var l=n&&n.select("svg");if(!l||!l.node())return O(),void t();var s=D.append("g").classed(C+"-group",!0).attr({"pointer-events":"none","data-unformatted":k,"data-math":"Y"});s.node().appendChild(l.node()),a&&a.node()&&l.node().insertBefore(a.node().cloneNode(!0),l.node().firstChild);var c=o.width,u=o.height;l.attr({class:C,height:u,preserveAspectRatio:"xMinYMin meet"}).style({overflow:"visible","pointer-events":"none"});var d=e.node().style.fill||"black",f=l.select("g");f.attr({fill:d,stroke:d});var p=f.node().getBoundingClientRect(),m=p.width,g=p.height;(m>c||g>u)&&(l.style("overflow","hidden"),m=(p=l.node().getBoundingClientRect()).width,g=p.height);var v=+e.attr("x"),y=+e.attr("y"),x=-(r||e.node().getBoundingClientRect().height)/4;if("y"===C[0])s.attr({transform:"rotate("+[-90,v,y]+")"+i(-m/2,x-g/2)});else if("l"===C[0])y=x-g/2;else if("a"===C[0]&&0!==C.indexOf("atitle"))v=0,y=x;else{var b=e.attr("text-anchor");v-=m*("middle"===b?.5:"end"===b?1:0),y=y+x-g/2}l.attr({x:v,y:y}),h&&h.call(e,s),t(s)}))}))):O(),e}function O(){D.empty()||(C=e.attr("class")+"-math",D.select("svg."+C).remove()),e.text("").style("white-space","pre"),function(e,t){t=t.replace(m," ");var r,i=!1,s=[],c=-1;function u(){c++;var t=document.createElementNS(o.svg,"tspan");n.select(t).attr({class:"line",dy:c*l+"em"}),e.appendChild(t),r=t;var a=s;if(s=[{node:t}],a.length>1)for(var i=1;i doesnt match end tag <"+e+">. Pretending it did match.",t),r=s[s.length-1].node}else a.log("Ignoring unexpected end tag .",t)}y.test(t)?u():(r=e,s=[{node:e}]);for(var D=t.split(g),C=0;C|>|>)/g;var d={sup:"font-size:70%",sub:"font-size:70%",b:"font-weight:bold",i:"font-style:italic",a:"cursor:pointer",span:"",em:"font-style:italic;font-weight:bold"},f={sub:"0.3em",sup:"-0.6em"},p={sub:"-0.21em",sup:"0.42em"},h=["http:","https:","mailto:","",void 0,":"],m=r.NEWLINES=/(\r\n?|\n)/g,g=/(<[^<>]*>)/,v=/<(\/?)([^ >]*)(\s+(.*))?>/i,y=//i;r.BR_TAG_ALL=//gi;var x=/(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i,b=/(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i,_=/(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i,w=/(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i;function T(e,t){if(!e)return null;var r=e.match(t),n=r&&(r[3]||r[4]);return n&&L(n)}var M=/(^|;)\s*color:/;r.plainText=function(e,t){for(var r=void 0!==(t=t||{}).len&&-1!==t.len?t.len:1/0,n=void 0!==t.allowedTags?t.allowedTags:["br"],a="...".length,i=e.split(g),o=[],l="",s=0,c=0;ca?o.push(u.substr(0,h-a)+"..."):o.push(u.substr(0,h));break}l=""}}return o.join("")};var k={mu:"\u03bc",amp:"&",lt:"<",gt:">",nbsp:"\xa0",times:"\xd7",plusmn:"\xb1",deg:"\xb0"},A=/&(#\d+|#x[\da-fA-F]+|[a-z]+);/g;function L(e){return e.replace(A,(function(e,t){return("#"===t.charAt(0)?function(e){if(e>1114111)return;var t=String.fromCodePoint;if(t)return t(e);var r=String.fromCharCode;return e<=65535?r(e):r(55232+(e>>10),e%1024+56320)}("x"===t.charAt(1)?parseInt(t.substr(2),16):parseInt(t.substr(1),10)):k[t])||e}))}function S(e){var t=encodeURI(decodeURI(e)),r=document.createElement("a"),n=document.createElement("a");r.href=e,n.href=t;var a=r.protocol,i=n.protocol;return-1!==h.indexOf(a)&&-1!==h.indexOf(i)?t:""}function D(e,t,r){var n,i,o,l=r.horizontalAlign,s=r.verticalAlign||"top",c=e.node().getBoundingClientRect(),u=t.node().getBoundingClientRect();return i="bottom"===s?function(){return c.bottom-n.height}:"middle"===s?function(){return c.top+(c.height-n.height)/2}:function(){return c.top},o="right"===l?function(){return c.right-n.width}:"center"===l?function(){return c.left+(c.width-n.width)/2}:function(){return c.left},function(){n=this.node().getBoundingClientRect();var e=o()-u.left,t=i()-u.top,l=r.gd||{};if(r.gd){l._fullLayout._calcInverseTransform(l);var s=a.apply3DTransform(l._fullLayout._invTransform)(e,t);e=s[0],t=s[1]}return this.style({top:t+"px",left:e+"px","z-index":1e3}),this}}r.convertEntities=L,r.sanitizeHTML=function(e){e=e.replace(m," ");for(var t=document.createElement("p"),r=t,a=[],i=e.split(g),o=0;oi.ts+t?s():i.timer=setTimeout((function(){s(),i.timer=null}),t)},r.done=function(e){var t=n[e];return t&&t.timer?new Promise((function(e){var r=t.onDone;t.onDone=function(){r&&r(),e(),t.onDone=null}})):Promise.resolve()},r.clear=function(e){if(e)a(n[e]),delete n[e];else for(var t in n)r.clear(t)}},{}],257:[function(e,t,r){"use strict";var n=e("fast-isnumeric");t.exports=function(e,t){if(e>0)return Math.log(e)/Math.LN10;var r=Math.log(Math.min(t[0],t[1]))/Math.LN10;return n(r)||(r=Math.log(Math.max(t[0],t[1]))/Math.LN10-6),r}},{"fast-isnumeric":17}],258:[function(e,t,r){"use strict";t.exports={moduleType:"locale",name:"en-US",dictionary:{"Click to enter Colorscale title":"Click to enter Colorscale title"},format:{date:"%m/%d/%Y"}}},{}],259:[function(e,t,r){"use strict";t.exports={moduleType:"locale",name:"en",dictionary:{"Click to enter Colorscale title":"Click to enter Colourscale title"},format:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],periods:["AM","PM"],dateTime:"%a %b %e %X %Y",date:"%d/%m/%Y",time:"%H:%M:%S",decimal:".",thousands:",",grouping:[3],currency:["$",""],year:"%Y",month:"%b %Y",dayMonth:"%b %-d",dayMonthYear:"%b %-d, %Y"}}},{}],260:[function(e,t,r){"use strict";var n=e("../registry");t.exports=function(e){for(var t,r,a=n.layoutArrayContainers,i=n.layoutArrayRegexes,o=e.split("[")[0],l=0;l0&&o.log("Clearing previous rejected promises from queue."),e._promises=[]},r.cleanLayout=function(e){var t,n;e||(e={}),e.xaxis1&&(e.xaxis||(e.xaxis=e.xaxis1),delete e.xaxis1),e.yaxis1&&(e.yaxis||(e.yaxis=e.yaxis1),delete e.yaxis1),e.scene1&&(e.scene||(e.scene=e.scene1),delete e.scene1);var i=(l.subplotsRegistry.cartesian||{}).attrRegex,s=(l.subplotsRegistry.polar||{}).attrRegex,d=(l.subplotsRegistry.ternary||{}).attrRegex,f=(l.subplotsRegistry.gl3d||{}).attrRegex,m=Object.keys(e);for(t=0;t3?(I.x=1.02,I.xanchor="left"):I.x<-2&&(I.x=-.02,I.xanchor="right"),I.y>3?(I.y=1.02,I.yanchor="bottom"):I.y<-2&&(I.y=-.02,I.yanchor="top")),h(e),"rotate"===e.dragmode&&(e.dragmode="orbit"),c.clean(e),e.template&&e.template.layout&&r.cleanLayout(e.template.layout),e},r.cleanData=function(e){for(var t=0;t0)return e.substr(0,t)}r.hasParent=function(e,t){for(var r=b(t);r;){if(r in e)return!0;r=b(r)}return!1};var _=["x","y","z"];r.clearAxisTypes=function(e,t,r){for(var n=0;n1&&i.warn("Full array edits are incompatible with other edits",d);var y=r[""][""];if(c(y))t.set(null);else{if(!Array.isArray(y))return i.warn("Unrecognized full array edit value",d,y),!0;t.set(y)}return!m&&(f(g,v),p(e),!0)}var x,b,_,w,T,M,k,A,L=Object.keys(r).map(Number).sort(o),S=t.get(),D=S||[],C=u(v,d).get(),O=[],P=-1,I=D.length;for(x=0;xD.length-(k?0:1))i.warn("index out of range",d,_);else if(void 0!==M)T.length>1&&i.warn("Insertion & removal are incompatible with edits to the same index.",d,_),c(M)?O.push(_):k?("add"===M&&(M={}),D.splice(_,0,M),C&&C.splice(_,0,{})):i.warn("Unrecognized full object edit value",d,_,M),-1===P&&(P=_);else for(b=0;b=0;x--)D.splice(O[x],1),C&&C.splice(O[x],1);if(D.length?S||t.set(D):t.set(null),m)return!1;if(f(g,v),h!==a){var R;if(-1===P)R=L;else{for(I=Math.max(D.length,I),R=[],x=0;x=P);x++)R.push(_);for(x=P;x=e.data.length||a<-e.data.length)throw new Error(r+" must be valid indices for gd.data.");if(t.indexOf(a,n+1)>-1||a>=0&&t.indexOf(-e.data.length+a)>-1||a<0&&t.indexOf(e.data.length+a)>-1)throw new Error("each index in "+r+" must be unique.")}}function P(e,t,r){if(!Array.isArray(e.data))throw new Error("gd.data must be an array.");if(void 0===t)throw new Error("currentIndices is a required argument.");if(Array.isArray(t)||(t=[t]),O(e,t,"currentIndices"),void 0===r||Array.isArray(r)||(r=[r]),void 0!==r&&O(e,r,"newIndices"),void 0!==r&&t.length!==r.length)throw new Error("current and new indices must be of equal length.")}function I(e,t,r,n,i){!function(e,t,r,n){var a=o.isPlainObject(n);if(!Array.isArray(e.data))throw new Error("gd.data must be an array");if(!o.isPlainObject(t))throw new Error("update must be a key:value object");if(void 0===r)throw new Error("indices must be an integer or array of integers");for(var i in O(e,r,"indices"),t){if(!Array.isArray(t[i])||t[i].length!==r.length)throw new Error("attribute "+i+" must be an array of length equal to indices array length");if(a&&(!(i in n)||!Array.isArray(n[i])||n[i].length!==t[i].length))throw new Error("when maxPoints is set as a key:value object it must contain a 1:1 corrispondence with the keys and number of traces in the update object")}}(e,t,r,n);for(var s=function(e,t,r,n){var i,s,c,u,d,f=o.isPlainObject(n),p=[];for(var h in Array.isArray(r)||(r=[r]),r=C(r,e.data.length-1),t)for(var m=0;m-1?s(r,r.replace("titlefont","title.font")):r.indexOf("titleposition")>-1?s(r,r.replace("titleposition","title.position")):r.indexOf("titleside")>-1?s(r,r.replace("titleside","title.side")):r.indexOf("titleoffset")>-1&&s(r,r.replace("titleoffset","title.offset")):s(r,r.replace("title","title.text"));function s(t,r){e[r]=e[t],delete e[t]}}function B(e,t,r){e=o.getGraphDiv(e),_.clearPromiseQueue(e);var n={};if("string"==typeof t)n[t]=r;else{if(!o.isPlainObject(t))return o.warn("Relayout fail.",t,r),Promise.reject();n=o.extendFlat({},t)}Object.keys(n).length&&(e.changed=!0);var a=Z(e,n),i=a.flags;i.calc&&(e.calcdata=void 0);var l=[f.previousPromises];i.layoutReplot?l.push(w.layoutReplot):Object.keys(n).length&&(Y(e,i,a)||f.supplyDefaults(e),i.legend&&l.push(w.doLegend),i.layoutstyle&&l.push(w.layoutStyles),i.axrange&&V(l,a.rangesAltered),i.ticks&&l.push(w.doTicksRelayout),i.modebar&&l.push(w.doModeBar),i.camera&&l.push(w.doCamera),i.colorbars&&l.push(w.doColorBars),l.push(A)),l.push(f.rehover,f.redrag),c.add(e,B,[e,a.undoit],B,[e,a.redoit]);var s=o.syncOrAsync(l,e);return s&&s.then||(s=Promise.resolve(e)),s.then((function(){return e.emit("plotly_relayout",a.eventData),e}))}function Y(e,t,r){var n=e._fullLayout;if(!t.axrange)return!1;for(var a in t)if("axrange"!==a&&t[a])return!1;for(var i in r.rangesAltered){var o=p.id2name(i),l=e.layout[o],s=n[o];if(s.autorange=l.autorange,l.range&&(s.range=l.range.slice()),s.cleanRange(),s._matchGroup)for(var c in s._matchGroup)if(c!==i){var u=n[p.id2name(c)];u.autorange=s.autorange,u.range=s.range.slice(),u._input.range=s.range.slice()}}return!0}function V(e,t){var r=t?function(e){var r=[],n=!0;for(var a in t){var i=p.getFromId(e,a);if(r.push(a),-1!==(i.ticklabelposition||"").indexOf("inside")&&i._anchorAxis&&r.push(i._anchorAxis._id),i._matchGroup)for(var o in i._matchGroup)t[o]||r.push(o);i.automargin&&(n=!1)}return p.draw(e,r,{skipTitle:n})}:function(e){return p.draw(e,"redraw")};e.push(y,w.doAutoRangeAndConstraints,r,w.drawData,w.finalDraw)}var U=/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/,q=/^[xyz]axis[0-9]*\.autorange$/,G=/^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/;function Z(e,t){var r,n,a,i=e.layout,s=e._fullLayout,c=s._guiEditing,f=F(s._preGUI,c),h=Object.keys(t),m=p.list(e),g=o.extendDeepAll({},t),v={};for(H(t),h=Object.keys(t),n=0;n0&&"string"!=typeof I.parts[z];)z--;var E=I.parts[z],j=I.parts[z-1]+"."+E,B=I.parts.slice(0,z).join("."),Y=l(e.layout,B).get(),V=l(s,B).get(),Z=I.get();if(void 0!==R){A[P]=R,L[P]="reverse"===E?R:N(Z);var X=d.getLayoutValObject(s,I.parts);if(X&&X.impliedEdits&&null!==R)for(var J in X.impliedEdits)S(o.relativeAttr(P,J),X.impliedEdits[J]);if(-1!==["width","height"].indexOf(P))if(R){S("autosize",null);var Q="height"===P?"width":"height";S(Q,s[Q])}else s[P]=e._initialAutoSize[P];else if("autosize"===P)S("width",R?null:s.width),S("height",R?null:s.height);else if(j.match(U))O(j),l(s,B+"._inputRange").set(null);else if(j.match(q)){O(j),l(s,B+"._inputRange").set(null);var K=l(s,B).get();K._inputDomain&&(K._input.domain=K._inputDomain.slice())}else j.match(G)&&l(s,B+"._inputDomain").set(null);if("type"===E){D=Y;var $="linear"===V.type&&"log"===R,ee="log"===V.type&&"linear"===R;if($||ee){if(D&&D.range)if(V.autorange)$&&(D.range=D.range[1]>D.range[0]?[1,2]:[2,1]);else{var te=D.range[0],re=D.range[1];$?(te<=0&&re<=0&&S(B+".autorange",!0),te<=0?te=re/1e6:re<=0&&(re=te/1e6),S(B+".range[0]",Math.log(te)/Math.LN10),S(B+".range[1]",Math.log(re)/Math.LN10)):(S(B+".range[0]",Math.pow(10,te)),S(B+".range[1]",Math.pow(10,re)))}else S(B+".autorange",!0);Array.isArray(s._subplots.polar)&&s._subplots.polar.length&&s[I.parts[0]]&&"radialaxis"===I.parts[1]&&delete s[I.parts[0]]._subplot.viewInitial["radialaxis.range"],u.getComponentMethod("annotations","convertCoords")(e,V,R,S),u.getComponentMethod("images","convertCoords")(e,V,R,S)}else S(B+".autorange",!0),S(B+".range",null);l(s,B+"._inputRange").set(null)}else if(E.match(M)){var ne=l(s,P).get(),ae=(R||{}).type;ae&&"-"!==ae||(ae="linear"),u.getComponentMethod("annotations","convertCoords")(e,ne,ae,S),u.getComponentMethod("images","convertCoords")(e,ne,ae,S)}var ie=b.containerArrayMatch(P);if(ie){r=ie.array,n=ie.index;var oe=ie.property,le=X||{editType:"calc"};""!==n&&""===oe&&(b.isAddVal(R)?L[P]=null:b.isRemoveVal(R)?L[P]=(l(i,r).get()||[])[n]:o.warn("unrecognized full object value",t)),T.update(k,le),v[r]||(v[r]={});var se=v[r][n];se||(se=v[r][n]={}),se[oe]=R,delete t[P]}else"reverse"===E?(Y.range?Y.range.reverse():(S(B+".autorange",!0),Y.range=[1,0]),V.autorange?k.calc=!0:k.plot=!0):(s._has("scatter-like")&&s._has("regl")&&"dragmode"===P&&("lasso"===R||"select"===R)&&"lasso"!==Z&&"select"!==Z||s._has("gl2d")?k.plot=!0:X?T.update(k,X):k.calc=!0,I.set(R))}}for(r in v){b.applyContainerArrayChanges(e,f(i,r),v[r],k,f)||(k.plot=!0)}for(var ce in C){var ue=(D=p.getFromId(e,ce))&&D._constraintGroup;if(ue)for(var de in k.calc=!0,ue)C[de]||(p.getFromId(e,de)._constraintShrinkable=!0)}return(W(e)||t.height||t.width)&&(k.plot=!0),(k.plot||k.calc)&&(k.layoutReplot=!0),{flags:k,rangesAltered:C,undoit:L,redoit:A,eventData:g}}function W(e){var t=e._fullLayout,r=t.width,n=t.height;return e.layout.autosize&&f.plotAutoSize(e,e.layout,t),t.width!==r||t.height!==n}function X(e,t,n,a){e=o.getGraphDiv(e),_.clearPromiseQueue(e),o.isPlainObject(t)||(t={}),o.isPlainObject(n)||(n={}),Object.keys(t).length&&(e.changed=!0),Object.keys(n).length&&(e.changed=!0);var i=_.coerceTraceIndices(e,a),l=j(e,o.extendFlat({},t),i),s=l.flags,u=Z(e,o.extendFlat({},n)),d=u.flags;(s.calc||d.calc)&&(e.calcdata=void 0),s.clearAxisTypes&&_.clearAxisTypes(e,i,n);var p=[];d.layoutReplot?p.push(w.layoutReplot):s.fullReplot?p.push(r._doPlot):(p.push(f.previousPromises),Y(e,d,u)||f.supplyDefaults(e),s.style&&p.push(w.doTraceStyle),(s.colorbars||d.colorbars)&&p.push(w.doColorBars),d.legend&&p.push(w.doLegend),d.layoutstyle&&p.push(w.layoutStyles),d.axrange&&V(p,u.rangesAltered),d.ticks&&p.push(w.doTicksRelayout),d.modebar&&p.push(w.doModeBar),d.camera&&p.push(w.doCamera),p.push(A)),p.push(f.rehover,f.redrag),c.add(e,X,[e,l.undoit,u.undoit,l.traces],X,[e,l.redoit,u.redoit,l.traces]);var h=o.syncOrAsync(p,e);return h&&h.then||(h=Promise.resolve(e)),h.then((function(){return e.emit("plotly_update",{data:l.eventData,layout:u.eventData}),e}))}function J(e){return function(t){t._fullLayout._guiEditing=!0;var r=e.apply(null,arguments);return t._fullLayout._guiEditing=!1,r}}var Q=[{pattern:/^hiddenlabels/,attr:"legend.uirevision"},{pattern:/^((x|y)axis\d*)\.((auto)?range|title\.text)/},{pattern:/axis\d*\.showspikes$/,attr:"modebar.uirevision"},{pattern:/(hover|drag)mode$/,attr:"modebar.uirevision"},{pattern:/^(scene\d*)\.camera/},{pattern:/^(geo\d*)\.(projection|center|fitbounds)/},{pattern:/^(ternary\d*\.[abc]axis)\.(min|title\.text)$/},{pattern:/^(polar\d*\.radialaxis)\.((auto)?range|angle|title\.text)/},{pattern:/^(polar\d*\.angularaxis)\.rotation/},{pattern:/^(mapbox\d*)\.(center|zoom|bearing|pitch)/},{pattern:/^legend\.(x|y)$/,attr:"editrevision"},{pattern:/^(shapes|annotations)/,attr:"editrevision"},{pattern:/^title\.text$/,attr:"editrevision"}],K=[{pattern:/^selectedpoints$/,attr:"selectionrevision"},{pattern:/(^|value\.)visible$/,attr:"legend.uirevision"},{pattern:/^dimensions\[\d+\]\.constraintrange/},{pattern:/^node\.(x|y|groups)/},{pattern:/^level$/},{pattern:/(^|value\.)name$/},{pattern:/colorbar\.title\.text$/},{pattern:/colorbar\.(x|y)$/,attr:"editrevision"}];function $(e,t){for(var r=0;r1;)if(n.pop(),void 0!==(r=l(t,n.join(".")+".uirevision").get()))return r;return t.uirevision}function te(e,t){for(var r=0;r=a.length?a[0]:a[e]:a}function s(e){return Array.isArray(i)?e>=i.length?i[0]:i[e]:i}function c(e,t){var r=0;return function(){if(e&&++r===t)return e()}}return void 0===n._frameWaitingCnt&&(n._frameWaitingCnt=0),new Promise((function(i,u){function d(){n._currentFrame&&n._currentFrame.onComplete&&n._currentFrame.onComplete();var t=n._currentFrame=n._frameQueue.shift();if(t){var r=t.name?t.name.toString():null;e._fullLayout._currentFrame=r,n._lastFrameAt=Date.now(),n._timeToNext=t.frameOpts.duration,f.transition(e,t.frame.data,t.frame.layout,_.coerceTraceIndices(e,t.frame.traces),t.frameOpts,t.transitionOpts).then((function(){t.onComplete&&t.onComplete()})),e.emit("plotly_animatingframe",{name:r,frame:t.frame,animation:{frame:t.frameOpts,transition:t.transitionOpts}})}else e.emit("plotly_animated"),window.cancelAnimationFrame(n._animationRaf),n._animationRaf=null}function p(){e.emit("plotly_animating"),n._lastFrameAt=-1/0,n._timeToNext=0,n._runningTransitions=0,n._currentFrame=null;var t=function(){n._animationRaf=window.requestAnimationFrame(t),Date.now()-n._lastFrameAt>n._timeToNext&&d()};t()}var h,m,g=0;function v(e){return Array.isArray(a)?g>=a.length?e.transitionOpts=a[g]:e.transitionOpts=a[0]:e.transitionOpts=a,g++,e}var y=[],x=null==t,b=Array.isArray(t);if(!x&&!b&&o.isPlainObject(t))y.push({type:"object",data:v(o.extendFlat({},t))});else if(x||-1!==["string","number"].indexOf(typeof t))for(h=0;h0&&MM)&&k.push(m);y=k}}y.length>0?function(t){if(0!==t.length){for(var a=0;a=0;n--)if(o.isPlainObject(t[n])){var m=t[n].name,g=(u[m]||h[m]||{}).name,v=t[n].name,y=u[g]||h[g];g&&v&&"number"==typeof v&&y&&k<5&&(k++,o.warn('addFrames: overwriting frame "'+(u[g]||h[g]).name+'" with a frame whose name of type "number" also equates to "'+g+'". This is valid but may potentially lead to unexpected behavior since all plotly.js frame names are stored internally as strings.'),5===k&&o.warn("addFrames: This API call has yielded too many of these warnings. For the rest of this call, further warnings about numeric frame names will be suppressed.")),h[m]={name:m},p.push({frame:f.supplyFrameDefaults(t[n]),index:r&&void 0!==r[n]&&null!==r[n]?r[n]:d+n})}p.sort((function(e,t){return e.index>t.index?-1:e.index=0;n--){if("number"==typeof(a=p[n].frame).name&&o.warn("Warning: addFrames accepts frames with numeric names, but the numbers areimplicitly cast to strings"),!a.name)for(;u[a.name="frame "+e._transitionData._counter++];);if(u[a.name]){for(i=0;i=0;r--)n=t[r],i.push({type:"delete",index:n}),l.unshift({type:"insert",index:n,value:a[n]});var s=f.modifyFrames,u=f.modifyFrames,d=[e,l],p=[e,i];return c&&c.add(e,s,d,u,p),f.modifyFrames(e,i)},r.addTraces=function e(t,n,a){t=o.getGraphDiv(t);var i,l,s=[],u=r.deleteTraces,d=e,f=[t,s],p=[t,n];for(function(e,t,r){var n,a;if(!Array.isArray(e.data))throw new Error("gd.data must be an array.");if(void 0===t)throw new Error("traces must be defined.");for(Array.isArray(t)||(t=[t]),n=0;n=0&&r=0&&r=i.length)return!1;if(2===e.dimensions){if(r++,t.length===r)return e;var o=t[r];if(!y(o))return!1;e=i[a][o]}else e=i[a]}else e=i}}return e}function y(e){return e===Math.round(e)&&e>=0}function x(){var e,t,r={};for(e in d(r,o),n.subplotsRegistry){if((t=n.subplotsRegistry[e]).layoutAttributes)if(Array.isArray(t.attr))for(var a=0;a=s.length)return!1;a=(r=(n.transformsRegistry[s[c].type]||{}).attributes)&&r[t[2]],l=3}else{var u=e._module;if(u||(u=(n.modules[e.type||i.type.dflt]||{})._module),!u)return!1;if(!(a=(r=u.attributes)&&r[o])){var d=u.basePlotModule;d&&d.attributes&&(a=d.attributes[o])}a||(a=i[o])}return v(a,t,l)},r.getLayoutValObject=function(e,t){return v(function(e,t){var r,a,i,l,s=e._basePlotModules;if(s){var c;for(r=0;r=a&&(r._input||{})._templateitemname;o&&(i=a);var l,s=t+"["+i+"]";function c(){l={},o&&(l[s]={},l[s].templateitemname=o)}function u(e,t){o?n.nestedProperty(l[s],e).set(t):l[s+"."+e]=t}function d(){var e=l;return c(),e}return c(),{modifyBase:function(e,t){l[e]=t},modifyItem:u,getUpdateObj:d,applyUpdate:function(t,r){t&&u(t,r);var a=d();for(var i in a)n.nestedProperty(e,i).set(a[i])}}}},{"../lib":232,"../plots/attributes":275}],269:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../registry"),i=e("../plots/plots"),o=e("../lib"),l=e("../lib/clear_gl_canvases"),s=e("../components/color"),c=e("../components/drawing"),u=e("../components/titles"),d=e("../components/modebar"),f=e("../plots/cartesian/axes"),p=e("../constants/alignment"),h=e("../plots/cartesian/constraints"),m=h.enforce,g=h.clean,v=e("../plots/cartesian/autorange").doAutoRange;function y(e,t,r){for(var n=0;n=e[1]||a[1]<=e[0])&&(i[0]t[0]))return!0}return!1}function x(e){var t,a,l,u,h,m,g=e._fullLayout,v=g._size,x=v.p,_=f.list(e,"",!0);if(g._paperdiv.style({width:e._context.responsive&&g.autosize&&!e._context._hasZeroWidth&&!e.layout.width?"100%":g.width+"px",height:e._context.responsive&&g.autosize&&!e._context._hasZeroHeight&&!e.layout.height?"100%":g.height+"px"}).selectAll(".main-svg").call(c.setSize,g.width,g.height),e._context.setBackground(e,g.paper_bgcolor),r.drawMainTitle(e),d.manage(e),!g._has("cartesian"))return i.previousPromises(e);function T(e,t,r){var n=e._lw/2;return"x"===e._id.charAt(0)?t?"top"===r?t._offset-x-n:t._offset+t._length+x+n:v.t+v.h*(1-(e.position||0))+n%1:t?"right"===r?t._offset+t._length+x+n:t._offset-x-n:v.l+v.w*(e.position||0)+n%1}for(t=0;t<_.length;t++){var M=(u=_[t])._anchorAxis;u._linepositions={},u._lw=c.crispRound(e,u.linewidth,1),u._mainLinePosition=T(u,M,u.side),u._mainMirrorPosition=u.mirror&&M?T(u,M,p.OPPOSITE_SIDE[u.side]):null}var k=[],A=[],L=[],S=1===s.opacity(g.paper_bgcolor)&&1===s.opacity(g.plot_bgcolor)&&g.paper_bgcolor===g.plot_bgcolor;for(a in g._plots)if((l=g._plots[a]).mainplot)l.bg&&l.bg.remove(),l.bg=void 0;else{var D=l.xaxis.domain,C=l.yaxis.domain,O=l.plotgroup;if(y(D,C,L)){var P=O.node(),I=l.bg=o.ensureSingle(O,"rect","bg");P.insertBefore(I.node(),P.childNodes[0]),A.push(a)}else O.select("rect.bg").remove(),L.push([D,C]),S||(k.push(a),A.push(a))}var R,z,N,F,E,j,H,B,Y,V,U,q,G,Z=g._bgLayer.selectAll(".bg").data(k);for(Z.enter().append("rect").classed("bg",!0),Z.exit().remove(),Z.each((function(e){g._plots[e].bg=n.select(this)})),t=0;tT?u.push({code:"unused",traceType:y,templateCount:w,dataCount:T}):T>w&&u.push({code:"reused",traceType:y,templateCount:w,dataCount:T})}}else u.push({code:"data"});if(function e(t,r){for(var n in t)if("_"!==n.charAt(0)){var i=t[n],o=m(t,n,r);a(i)?(Array.isArray(t)&&!1===i._template&&i.templateitemname&&u.push({code:"missing",path:o,templateitemname:i.templateitemname}),e(i,o)):Array.isArray(i)&&g(i)&&e(i,o)}}({data:p,layout:f},""),u.length)return u.map(v)}},{"../lib":232,"../plots/attributes":275,"../plots/plots":316,"./plot_config":266,"./plot_schema":267,"./plot_template":268}],271:[function(e,t,r){"use strict";var n=e("fast-isnumeric"),a=e("./plot_api"),i=e("../plots/plots"),o=e("../lib"),l=e("../snapshot/helpers"),s=e("../snapshot/tosvg"),c=e("../snapshot/svgtoimg"),u=e("../version").version,d={format:{valType:"enumerated",values:["png","jpeg","webp","svg","full-json"],dflt:"png"},width:{valType:"number",min:1},height:{valType:"number",min:1},scale:{valType:"number",min:0,dflt:1},setBackground:{valType:"any",dflt:!1},imageDataOnly:{valType:"boolean",dflt:!1}};t.exports=function(e,t){var r,f,p,h;function m(e){return!(e in t)||o.validate(t[e],d[e])}if(t=t||{},o.isPlainObject(e)?(r=e.data||[],f=e.layout||{},p=e.config||{},h={}):(e=o.getGraphDiv(e),r=o.extendDeep([],e.data),f=o.extendDeep({},e.layout),p=e._context,h=e._fullLayout||{}),!m("width")&&null!==t.width||!m("height")&&null!==t.height)throw new Error("Height and width should be pixel values.");if(!m("format"))throw new Error("Export format is not "+o.join2(d.format.values,", "," or ")+".");var g={};function v(e,r){return o.coerce(t,g,d,e,r)}var y=v("format"),x=v("width"),b=v("height"),_=v("scale"),w=v("setBackground"),T=v("imageDataOnly"),M=document.createElement("div");M.style.position="absolute",M.style.left="-5000px",document.body.appendChild(M);var k=o.extendFlat({},f);x?k.width=x:null===t.width&&n(h.width)&&(k.width=h.width),b?k.height=b:null===t.height&&n(h.height)&&(k.height=h.height);var A=o.extendFlat({},p,{_exportedPlot:!0,staticPlot:!0,setBackground:w}),L=l.getRedrawFunc(M);function S(){return new Promise((function(e){setTimeout(e,l.getDelay(M._fullLayout))}))}function D(){return new Promise((function(e,t){var r=s(M,y,_),n=M._fullLayout.width,d=M._fullLayout.height;function f(){a.purge(M),document.body.removeChild(M)}if("full-json"===y){var p=i.graphJson(M,!1,"keepdata","object",!0,!0);return p.version=u,p=JSON.stringify(p),f(),e(T?p:l.encodeJSON(p))}if(f(),"svg"===y)return e(T?r:l.encodeSVG(r));var h=document.createElement("canvas");h.id=o.randstr(),c({format:y,width:n,height:d,scale:_,canvas:h,svg:r,promise:!0}).then(e).catch(t)}))}return new Promise((function(e,t){a.newPlot(M,r,k,A).then(L).then(S).then(D).then((function(t){e(function(e){return T?e.replace(l.IMAGE_URL_PREFIX,""):e}(t))})).catch((function(e){t(e)}))}))}},{"../lib":232,"../plots/plots":316,"../snapshot/helpers":322,"../snapshot/svgtoimg":324,"../snapshot/tosvg":326,"../version":391,"./plot_api":265,"fast-isnumeric":17}],272:[function(e,t,r){"use strict";var n=e("../lib"),a=e("../plots/plots"),i=e("./plot_schema"),o=e("./plot_config").dfltConfig,l=n.isPlainObject,s=Array.isArray,c=n.isArrayOrTypedArray;function u(e,t,r,a,i,o){o=o||[];for(var d=Object.keys(e),f=0;fx.length&&a.push(h("unused",i,v.concat(x.length)));var k,A,L,S,D,C=x.length,O=Array.isArray(M);if(O&&(C=Math.min(C,M.length)),2===b.dimensions)for(A=0;Ax[A].length&&a.push(h("unused",i,v.concat(A,x[A].length)));var P=x[A].length;for(k=0;k<(O?Math.min(P,M[A].length):P);k++)L=O?M[A][k]:M,S=y[A][k],D=x[A][k],n.validate(S,L)?D!==S&&D!==+S&&a.push(h("dynamic",i,v.concat(A,k),S,D)):a.push(h("value",i,v.concat(A,k),S))}else a.push(h("array",i,v.concat(A),y[A]));else for(A=0;A1&&p.push(h("object","layout"))),a.supplyDefaults(m);for(var g=m._fullData,v=r.length,y=0;y0&&Math.round(d)===d))return{vals:a};c=d}for(var f=t.calendar,p="start"===s,h="end"===s,m=e[r+"period0"],g=i(m,f)||0,v=[],y=[],x=[],b=a.length,_=0;_k;)M=o(M,-c,f);for(;M<=k;)M=o(M,c,f);T=o(M,-c,f)}else{for(M=g+(w=Math.round((k-g)/u))*u;M>k;)M-=u;for(;M<=k;)M+=u;T=M-u}v[_]=p?T:h?M:(T+M)/2,y[_]=T,x[_]=M}return{vals:v,starts:y,ends:x}}},{"../../constants/numerical":212,"../../lib":232,"fast-isnumeric":17}],277:[function(e,t,r){"use strict";t.exports={xaxis:{valType:"subplotid",dflt:"x",editType:"calc+clearAxisTypes"},yaxis:{valType:"subplotid",dflt:"y",editType:"calc+clearAxisTypes"}}},{}],278:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("fast-isnumeric"),i=e("../../lib"),o=e("../../constants/numerical").FP_SAFE,l=e("../../registry"),s=e("../../components/drawing"),c=e("./axis_ids"),u=c.getFromId,d=c.isLinked;function f(e,t){var r,n,a=[],o=e._fullLayout,l=h(o,t,0),s=h(o,t,1),c=m(e,t),u=c.min,d=c.max;if(0===u.length||0===d.length)return i.simpleMap(t.range,t.r2l);var f=u[0].val,g=d[0].val;for(r=1;r0&&((T=S-l(x)-s(b))>D?M/T>C&&(_=x,w=b,C=M/T):M/S>C&&(_={val:x.val,nopad:1},w={val:b.val,nopad:1},C=M/S));if(f===g){var O=f-1,P=f+1;if(A)if(0===f)a=[0,1];else{var I=(f>0?d:u).reduce((function(e,t){return Math.max(e,s(t))}),0),R=f/(1-Math.min(.5,I/S));a=f>0?[0,R]:[R,0]}else a=L?[Math.max(0,O),Math.max(1,P)]:[O,P]}else A?(_.val>=0&&(_={val:0,nopad:1}),w.val<=0&&(w={val:0,nopad:1})):L&&(_.val-C*l(_)<0&&(_={val:0,nopad:1}),w.val<=0&&(w={val:1,nopad:1})),C=(w.val-_.val-p(t,x.val,b.val))/(S-l(_)-s(w)),a=[_.val-C*l(_),w.val+C*s(w)];return v&&a.reverse(),i.simpleMap(a,t.l2r||Number)}function p(e,t,r){var n=0;if(e.rangebreaks)for(var a=e.locateBreaks(t,r),i=0;i0?r.ppadplus:r.ppadminus)||r.ppad||0),L=k((e._m>0?r.ppadminus:r.ppadplus)||r.ppad||0),S=k(r.vpadplus||r.vpad),D=k(r.vpadminus||r.vpad);if(!T){if(f=1/0,p=-1/0,w)for(n=0;n0&&(f=i),i>p&&i-o&&(f=i),i>p&&i=P;n--)O(n);return{min:h,max:m,opts:r}},concatExtremes:m};function m(e,t,r){var n,a,i,o=t._id,l=e._fullData,s=e._fullLayout,c=[],d=[];function f(e,t){for(n=0;n=r&&(c.extrapad||!o)){l=!1;break}a(t,c.val)&&c.pad<=r&&(o||!c.extrapad)&&(e.splice(s,1),s--)}if(l){var u=i&&0===t;e.push({val:t,pad:u?0:r,extrapad:!u&&o})}}function x(e){return a(e)&&Math.abs(e)=t}},{"../../components/drawing":124,"../../constants/numerical":212,"../../lib":232,"../../registry":318,"./axis_ids":283,"@plotly/d3":11,"fast-isnumeric":17}],279:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("fast-isnumeric"),i=e("../../plots/plots"),o=e("../../registry"),l=e("../../lib"),s=l.strTranslate,c=e("../../lib/svg_text_utils"),u=e("../../components/titles"),d=e("../../components/color"),f=e("../../components/drawing"),p=e("./layout_attributes"),h=e("./clean_ticks"),m=e("../../constants/numerical"),g=m.ONEMAXYEAR,v=m.ONEAVGYEAR,y=m.ONEMINYEAR,x=m.ONEMAXQUARTER,b=m.ONEAVGQUARTER,_=m.ONEMINQUARTER,w=m.ONEMAXMONTH,T=m.ONEAVGMONTH,M=m.ONEMINMONTH,k=m.ONEWEEK,A=m.ONEDAY,L=A/2,S=m.ONEHOUR,D=m.ONEMIN,C=m.ONESEC,O=m.MINUS_SIGN,P=m.BADNUM,I={K:"zeroline"},R={K:"gridline",L:"path"},z={K:"tick",L:"path"},N={K:"tick",L:"text"},F=e("../../constants/alignment"),E=F.MID_SHIFT,j=F.CAP_SHIFT,H=F.LINE_SPACING,B=F.OPPOSITE_SIDE,Y=t.exports={};Y.setConvert=e("./set_convert");var V=e("./axis_autotype"),U=e("./axis_ids"),q=U.idSort,G=U.isLinked;Y.id2name=U.id2name,Y.name2id=U.name2id,Y.cleanId=U.cleanId,Y.list=U.list,Y.listIds=U.listIds,Y.getFromId=U.getFromId,Y.getFromTrace=U.getFromTrace;var Z=e("./autorange");Y.getAutoRange=Z.getAutoRange,Y.findExtremes=Z.findExtremes;function W(e){var t=1e-4*(e[1]-e[0]);return[e[0]-t,e[1]+t]}Y.coerceRef=function(e,t,r,n,a,i){var o=n.charAt(n.length-1),s=r._fullLayout._subplots[o+"axis"],c=n+"ref",u={};return a||(a=s[0]||("string"==typeof i?i:i[0])),i||(i=a),s=s.concat(s.map((function(e){return e+" domain"}))),u[c]={valType:"enumerated",values:s.concat(i?"string"==typeof i?[i]:i:[]),dflt:a},l.coerce(e,t,u,c)},Y.getRefType=function(e){return void 0===e?e:"paper"===e?"paper":"pixel"===e?"pixel":/( domain)$/.test(e)?"domain":"range"},Y.coercePosition=function(e,t,r,n,a,i){var o,s;if("range"!==Y.getRefType(n))o=l.ensureNumber,s=r(a,i);else{var c=Y.getFromId(t,n);s=r(a,i=c.fraction2r(i)),o=c.cleanPos}e[a]=o(s)},Y.cleanPosition=function(e,t,r){return("paper"===r||"pixel"===r?l.ensureNumber:Y.getFromId(t,r).cleanPos)(e)},Y.redrawComponents=function(e,t){t=t||Y.listIds(e);var r=e._fullLayout;function n(n,a,i,l){for(var s=o.getComponentMethod(n,a),c={},u=0;u2e-6||((r-e._forceTick0)/e._minDtick%1+1.000001)%1>2e-6)&&(e._minDtick=0)):e._minDtick=0},Y.saveRangeInitial=function(e,t){for(var r=Y.list(e,"",!0),n=!1,a=0;a.3*f||u(n)||u(i))){var p=r.dtick/2;e+=e+p.8){var o=Number(r.substr(1));i.exactYears>.8&&o%12==0?e=Y.tickIncrement(e,"M6","reverse")+1.5*A:i.exactMonths>.8?e=Y.tickIncrement(e,"M1","reverse")+15.5*A:e-=L;var s=Y.tickIncrement(e,r);if(s<=n)return s}return e}(y,e,v,c,i)),g=y,0;g<=u;)g=Y.tickIncrement(g,v,!1,i);return{start:t.c2r(y,0,i),end:t.c2r(g,0,i),size:v,_dataSpan:u-c}},Y.prepTicks=function(e,t){var r=l.simpleMap(e.range,e.r2l,void 0,void 0,t);if(e._dtickInit=e.dtick,e._tick0Init=e.tick0,"auto"===e.tickmode||!e.dtick){var n,i=e.nticks;i||("category"===e.type||"multicategory"===e.type?(n=e.tickfont?l.bigFont(e.tickfont.size||12):15,i=e._length/n):(n="y"===e._id.charAt(0)?40:80,i=l.constrain(e._length/n,4,9)+1),"radialaxis"===e._name&&(i*=2)),"array"===e.tickmode&&(i*=100),e._roughDTick=Math.abs(r[1]-r[0])/i,Y.autoTicks(e,e._roughDTick),e._minDtick>0&&e.dtick<2*e._minDtick&&(e.dtick=e._minDtick,e.tick0=e.l2r(e._forceTick0))}"period"===e.ticklabelmode&&function(e){var t;function r(){return!(a(e.dtick)||"M"!==e.dtick.charAt(0))}var n=r(),i=Y.getTickFormat(e);if(i){var o=e._dtickInit!==e.dtick;/%[fLQsSMX]/.test(i)||(/%[HI]/.test(i)?(t=S,o&&!n&&e.dticki&&d=o:p<=o;p=Y.tickIncrement(p,e.dtick,s,e.calendar)){if(e.rangebreaks&&!s){if(p=u)break}if(D.length>m||p===C)break;C=p;var O=!1;d&&p!==(0|p)&&(O=!0),D.push({minor:O,value:p})}if(f&&function(e,t,r){for(var n=0;n0?(i=n-1,o=n):(i=n,o=n);var l,s=e[i].value,c=e[o].value,u=Math.abs(c-s),d=r||u,f=0;d>=y?f=u>=y&&u<=g?u:v:r===b&&d>=_?f=u>=_&&u<=x?u:b:d>=M?f=u>=M&&u<=w?u:T:r===k&&d>=k?f=k:d>=A?f=A:r===L&&d>=L?f=L:r===S&&d>=S&&(f=S),f>=u&&(f=u,l=!0);var p=a+f;if(t.rangebreaks&&f>0){for(var h=0,m=0;m<84;m++){var D=(m+.5)/84;t.maskBreaks(a*(1-D)+D*p)!==P&&h++}(f*=h/84)||(e[n].drop=!0),l&&u>k&&(f=u)}(f>0||0===n)&&(e[n].periodX=a+f/2)}}(D,e,e._definedDelta),e.rangebreaks){var I="y"===e._id.charAt(0),R=1;"auto"===e.tickmode&&(R=e.tickfont?e.tickfont.size:12);var z=NaN;for(h=D.length-1;h>-1;h--)if(D[h].drop)D.splice(h,1);else{D[h].value=Ae(D[h].value,e);var N=e.c2p(D[h].value);(I?z>N-R:zu||Eu&&(F.periodX=u),E10||"01-01"!==n.substr(5)?e._tickround="d":e._tickround=+t.substr(1)%12==0?"y":"m";else if(t>=A&&i<=10||t>=15*A)e._tickround="d";else if(t>=D&&i<=16||t>=S)e._tickround="M";else if(t>=C&&i<=19||t>=D)e._tickround="S";else{var o=e.l2r(r+t).replace(/^-/,"").length;e._tickround=Math.max(i,o)-20,e._tickround<0&&(e._tickround=4)}}else if(a(t)||"L"===t.charAt(0)){var l=e.range.map(e.r2d||Number);a(t)||(t=Number(t.substr(1))),e._tickround=2-Math.floor(Math.log(t)/Math.LN10+.01);var s=Math.max(Math.abs(l[0]),Math.abs(l[1])),c=Math.floor(Math.log(s)/Math.LN10+.01),u=void 0===e.minexponent?3:e.minexponent;Math.abs(c)>u&&(ue(e.exponentformat)&&!de(c)?e._tickexponent=3*Math.round((c-1)/3):e._tickexponent=c)}else e._tickround=null}function se(e,t,r){var n=e.tickfont||{};return{x:t,dx:0,dy:0,text:r||"",fontSize:n.size,font:n.family,fontColor:n.color}}Y.autoTicks=function(e,t){var r;function n(e){return Math.pow(e,Math.floor(Math.log(t)/Math.LN10))}if("date"===e.type){e.tick0=l.dateTick0(e.calendar,0);var i=2*t;if(i>v)t/=v,r=n(10),e.dtick="M"+12*oe(t,r,$);else if(i>T)t/=T,e.dtick="M"+oe(t,1,ee);else if(i>A){e.dtick=oe(t,A,e._hasDayOfWeekBreaks?[1,2,7,14]:re);var o=Y.getTickFormat(e),s="period"===e.ticklabelmode;s&&(e._rawTick0=e.tick0),/%[uVW]/.test(o)?e.tick0=l.dateTick0(e.calendar,2):e.tick0=l.dateTick0(e.calendar,1),s&&(e._dowTick0=e.tick0)}else i>S?e.dtick=oe(t,S,ee):i>D?e.dtick=oe(t,D,te):i>C?e.dtick=oe(t,C,te):(r=n(10),e.dtick=oe(t,r,$))}else if("log"===e.type){e.tick0=0;var c=l.simpleMap(e.range,e.r2l);if(t>.7)e.dtick=Math.ceil(t);else if(Math.abs(c[1]-c[0])<1){var u=1.5*Math.abs((c[1]-c[0])/t);t=Math.abs(Math.pow(10,c[1])-Math.pow(10,c[0]))/u,r=n(10),e.dtick="L"+oe(t,r,$)}else e.dtick=t>.3?"D2":"D1"}else"category"===e.type||"multicategory"===e.type?(e.tick0=0,e.dtick=Math.ceil(Math.max(t,1))):ke(e)?(e.tick0=0,r=1,e.dtick=oe(t,r,ie)):(e.tick0=0,r=n(10),e.dtick=oe(t,r,$));if(0===e.dtick&&(e.dtick=1),!a(e.dtick)&&"string"!=typeof e.dtick){var d=e.dtick;throw e.dtick=1,"ax.dtick error: "+String(d)}},Y.tickIncrement=function(e,t,r,i){var o=r?-1:1;if(a(t))return l.increment(e,o*t);var s=t.charAt(0),c=o*Number(t.substr(1));if("M"===s)return l.incrementMonth(e,c,i);if("L"===s)return Math.log(Math.pow(10,e)+c)/Math.LN10;if("D"===s){var u="D2"===t?ae:ne,d=e+.01*o,f=l.roundUp(l.mod(d,1),u,r);return Math.floor(d)+Math.log(n.round(Math.pow(10,f),1))/Math.LN10}throw"unrecognized dtick "+String(t)},Y.tickFirst=function(e,t){var r=e.r2l||Number,i=l.simpleMap(e.range,r,void 0,void 0,t),o=i[1] ")}else e._prevDateHead=s,c+="
    "+s;t.text=c}(e,o,r,c):"log"===u?function(e,t,r,n,i){var o=e.dtick,s=t.x,c=e.tickformat,u="string"==typeof o&&o.charAt(0);"never"===i&&(i="");n&&"L"!==u&&(o="L3",u="L");if(c||"L"===u)t.text=fe(Math.pow(10,s),e,i,n);else if(a(o)||"D"===u&&l.mod(s+.01,1)<.1){var d=Math.round(s),f=Math.abs(d),p=e.exponentformat;"power"===p||ue(p)&&de(d)?(t.text=0===d?1:1===d?"10":"10"+(d>1?"":O)+f+"",t.fontSize*=1.25):("e"===p||"E"===p)&&f>2?t.text="1"+p+(d>0?"+":O)+f:(t.text=fe(Math.pow(10,s),e,"","fakehover"),"D1"===o&&"y"===e._id.charAt(0)&&(t.dy-=t.fontSize/6))}else{if("D"!==u)throw"unrecognized dtick "+String(o);t.text=String(Math.round(Math.pow(10,l.mod(s,1)))),t.fontSize*=.75}if("D1"===e.dtick){var h=String(t.text).charAt(0);"0"!==h&&"1"!==h||("y"===e._id.charAt(0)?t.dx-=t.fontSize/4:(t.dy+=t.fontSize/2,t.dx+=(e.range[1]>e.range[0]?1:-1)*t.fontSize*(s<0?.5:.25)))}}(e,o,0,c,m):"category"===u?function(e,t){var r=e._categories[Math.round(t.x)];void 0===r&&(r="");t.text=String(r)}(e,o):"multicategory"===u?function(e,t,r){var n=Math.round(t.x),a=e._categories[n]||[],i=void 0===a[1]?"":String(a[1]),o=void 0===a[0]?"":String(a[0]);r?t.text=o+" - "+i:(t.text=i,t.text2=o)}(e,o,r):ke(e)?function(e,t,r,n,a){if("radians"!==e.thetaunit||r)t.text=fe(t.x,e,a,n);else{var i=t.x/180;if(0===i)t.text="0";else{var o=function(e){function t(e,t){return Math.abs(e-t)<=1e-6}var r=function(e){for(var r=1;!t(Math.round(e*r)/r,e);)r*=10;return r}(e),n=e*r,a=Math.abs(function e(r,n){return t(n,0)?r:e(n,r%n)}(n,r));return[Math.round(n/a),Math.round(r/a)]}(i);if(o[1]>=100)t.text=fe(l.deg2rad(t.x),e,a,n);else{var s=t.x<0;1===o[1]?1===o[0]?t.text="\u03c0":t.text=o[0]+"\u03c0":t.text=["",o[0],"","\u2044","",o[1],"","\u03c0"].join(""),s&&(t.text=O+t.text)}}}}(e,o,r,c,m):function(e,t,r,n,a){"never"===a?a="":"all"===e.showexponent&&Math.abs(t.x/e.dtick)<1e-6&&(a="hide");t.text=fe(t.x,e,a,n)}(e,o,0,c,m),n||(e.tickprefix&&!h(e.showtickprefix)&&(o.text=e.tickprefix+o.text),e.ticksuffix&&!h(e.showticksuffix)&&(o.text+=e.ticksuffix)),"boundaries"===e.tickson||e.showdividers){var g=function(t){var r=e.l2p(t);return r>=0&&r<=e._length?t:null};o.xbnd=[g(o.x-.5),g(o.x+e.dtick-.5)]}return o},Y.hoverLabelText=function(e,t,r){r&&(e=l.extendFlat({},e,{hoverformat:r}));var n=Array.isArray(t)?t[0]:t,a=Array.isArray(t)?t[1]:void 0;if(void 0!==a&&a!==n)return Y.hoverLabelText(e,n,r)+" - "+Y.hoverLabelText(e,a,r);var i="log"===e.type&&n<=0,o=Y.tickText(e,e.c2l(i?-n:n),"hover").text;return i?0===n?"0":O+o:o};var ce=["f","p","n","\u03bc","m","","k","M","G","T"];function ue(e){return"SI"===e||"B"===e}function de(e){return e>14||e<-15}function fe(e,t,r,n){var i=e<0,o=t._tickround,s=r||t.exponentformat||"B",c=t._tickexponent,u=Y.getTickFormat(t),d=t.separatethousands;if(n){var f={exponentformat:s,minexponent:t.minexponent,dtick:"none"===t.showexponent?t.dtick:a(e)&&Math.abs(e)||1,range:"none"===t.showexponent?t.range.map(t.r2d):[0,e||1]};le(f),o=(Number(f._tickround)||0)+4,c=f._tickexponent,t.hoverformat&&(u=t.hoverformat)}if(u)return t._numFormat(u)(e).replace(/-/g,O);var p,h=Math.pow(10,-o)/2;if("none"===s&&(c=0),(e=Math.abs(e))"+p+"":"B"===s&&9===c?e+="B":ue(s)&&(e+=ce[c/3+5]));return i?O+e:e}function pe(e,t){for(var r=[],n={},a=0;a1&&r=a.min&&e=0,i=u(e,t[1])<=0;return(r||a)&&(n||i)}if(e.tickformatstops&&e.tickformatstops.length>0)switch(e.type){case"date":case"linear":for(t=0;t=o(a)))){r=n;break}break;case"log":for(t=0;t0?r.bottom-d:0,f)))),t.automargin){n={x:0,y:0,r:0,l:0,t:0,b:0};var p=[0,1];if("x"===h){if("b"===s?n[s]=t._depth:(n[s]=t._depth=Math.max(r.width>0?d-r.top:0,f),p.reverse()),r.width>0){var g=r.right-(t._offset+t._length);g>0&&(n.xr=1,n.r=g);var v=t._offset-r.left;v>0&&(n.xl=0,n.l=v)}}else if("l"===s?n[s]=t._depth=Math.max(r.height>0?d-r.left:0,f):(n[s]=t._depth=Math.max(r.height>0?r.right-d:0,f),p.reverse()),r.height>0){var y=r.bottom-(t._offset+t._length);y>0&&(n.yb=0,n.b=y);var x=t._offset-r.top;x>0&&(n.yt=1,n.t=x)}n[m]="free"===t.anchor?t.position:t._anchorAxis.domain[p[0]],t.title.text!==c._dfltTitle[h]&&(n[s]+=ge(t)+(t.title.standoff||0)),t.mirror&&"free"!==t.anchor&&((a={x:0,y:0,r:0,l:0,t:0,b:0})[u]=t.linewidth,t.mirror&&!0!==t.mirror&&(a[u]+=f),!0===t.mirror||"ticks"===t.mirror?a[m]=t._anchorAxis.domain[p[1]]:"all"!==t.mirror&&"allticks"!==t.mirror||(a[m]=[t._counterDomainMin,t._counterDomainMax][p[1]]))}Q&&(l=o.getComponentMethod("rangeslider","autoMarginOpts")(e,t)),i.autoMargin(e,xe(t),n),i.autoMargin(e,be(t),a),i.autoMargin(e,_e(t),l)})),r.skipTitle||Q&&"bottom"===t.side||X.push((function(){return function(e,t){var r,n=e._fullLayout,a=t._id,i=a.charAt(0),o=t.title.font.size;if(t.title.hasOwnProperty("standoff"))r=t._depth+t.title.standoff+ge(t);else{var l=Le(t);if("multicategory"===t.type)r=t._depth;else{var s=1.5*o;l&&(s=.5*o,"outside"===t.ticks&&(s+=t.ticklen)),r=10+s+(t.linewidth?t.linewidth-1:0)}l||(r+="x"===i?"top"===t.side?o*(t.showticklabels?1:0):o*(t.showticklabels?1.5:.5):"right"===t.side?o*(t.showticklabels?1:.5):o*(t.showticklabels?.5:0))}var c,d,p,h,m=Y.getPxPosition(e,t);"x"===i?(d=t._offset+t._length/2,p="top"===t.side?m-r:m+r):(p=t._offset+t._length/2,d="right"===t.side?m+r:m-r,c={rotate:"-90",offset:0});if("multicategory"!==t.type){var g=t._selections[t._id+"tick"];if(h={selection:g,side:t.side},g&&g.node()&&g.node().parentNode){var v=f.getTranslate(g.node().parentNode);h.offsetLeft=v.x,h.offsetTop=v.y}t.title.hasOwnProperty("standoff")&&(h.pad=0)}return u.draw(e,a+"title",{propContainer:t,propName:t._name+".title.text",placeholder:n._dfltTitle[i],avoid:h,transform:c,attributes:{x:d,y:p,"text-anchor":"middle"}})}(e,t)})),l.syncOrAsync(X)}}function K(e){var r=p+(e||"tick");return w[r]||(w[r]=function(e,t){var r,n,a,i;e._selections[t].size()?(r=1/0,n=-1/0,a=1/0,i=-1/0,e._selections[t].each((function(){var e=ye(this),t=f.bBox(e.node().parentNode);r=Math.min(r,t.top),n=Math.max(n,t.bottom),a=Math.min(a,t.left),i=Math.max(i,t.right)}))):(r=0,n=0,a=0,i=0);return{top:r,bottom:n,left:a,right:i,height:n-r,width:i-a}}(t,r)),w[r]}},Y.getTickSigns=function(e){var t=e._id.charAt(0),r={x:"top",y:"right"}[t],n=e.side===r?1:-1,a=[-1,1,n,-n];return"inside"!==e.ticks==("x"===t)&&(a=a.map((function(e){return-e}))),e.side&&a.push({l:-1,t:-1,r:1,b:1}[e.side.charAt(0)]),a},Y.makeTransTickFn=function(e){return"x"===e._id.charAt(0)?function(t){return s(e._offset+e.l2p(t.x),0)}:function(t){return s(0,e._offset+e.l2p(t.x))}},Y.makeTransTickLabelFn=function(e){var t=function(e){var t=e.ticklabelposition||"",r=function(e){return-1!==t.indexOf(e)},n=r("top"),a=r("left"),i=r("right"),o=r("bottom"),l=r("inside"),s=o||a||n||i;if(!s&&!l)return[0,0];var c=e.side,u=s?(e.tickwidth||0)/2:0,d=3,f=e.tickfont?e.tickfont.size:12;(o||n)&&(u+=f*j,d+=(e.linewidth||0)/2);(a||i)&&(u+=(e.linewidth||0)/2,d+=3);l&&"top"===c&&(d-=f*(1-j));(a||n)&&(u=-u);"bottom"!==c&&"right"!==c||(d=-d);return[s?u:0,l?d:0]}(e),r=t[0],n=t[1];return"x"===e._id.charAt(0)?function(t){return s(r+e._offset+e.l2p(he(t)),n)}:function(t){return s(n,r+e._offset+e.l2p(he(t)))}},Y.makeTickPath=function(e,t,r,n){n=void 0!==n?n:e.ticklen;var a=e._id.charAt(0),i=(e.linewidth||1)/2;return"x"===a?"M0,"+(t+i*r)+"v"+n*r:"M"+(t+i*r)+",0h"+n*r},Y.makeLabelFns=function(e,t,r){var n=e.ticklabelposition||"",i=function(e){return-1!==n.indexOf(e)},o=i("top"),s=i("left"),c=i("right"),u=i("bottom")||s||o||c,d=i("inside"),f="inside"===n&&"inside"===e.ticks||!d&&"outside"===e.ticks&&"boundaries"!==e.tickson,p=0,h=0,m=f?e.ticklen:0;if(d?m*=-1:u&&(m=0),f&&(p+=m,r)){var g=l.deg2rad(r);p=m*Math.cos(g)+1,h=m*Math.sin(g)}e.showticklabels&&(f||e.showline)&&(p+=.2*e.tickfont.size);var v,y,x,b,_,w={labelStandoff:p+=(e.linewidth||1)/2*(d?-1:1),labelShift:h},T=0,M=e.side,k=e._id.charAt(0),A=e.tickangle;if("x"===k)b=(_=!d&&"bottom"===M||d&&"top"===M)?1:-1,d&&(b*=-1),v=h*b,y=t+p*b,x=_?1:-.2,90===Math.abs(A)&&(d?x+=E:x=-90===A&&"bottom"===M?j:90===A&&"top"===M?E:.5,T=E/2*(A/90)),w.xFn=function(e){return e.dx+v+T*e.fontSize},w.yFn=function(e){return e.dy+y+e.fontSize*x},w.anchorFn=function(e,t){if(u){if(s)return"end";if(c)return"start"}return a(t)&&0!==t&&180!==t?t*b<0!==d?"end":"start":"middle"},w.heightFn=function(t,r,n){return r<-60||r>60?-.5*n:"top"===e.side!==d?-n:0};else if("y"===k){if(b=(_=!d&&"left"===M||d&&"right"===M)?1:-1,d&&(b*=-1),v=p,y=h*b,x=0,d||90!==Math.abs(A)||(x=-90===A&&"left"===M||90===A&&"right"===M?j:.5),d){var L=a(A)?+A:0;if(0!==L){var S=l.deg2rad(L);T=Math.abs(Math.sin(S))*j*b,x=0}}w.xFn=function(e){return e.dx+t-(v+e.fontSize*x)*b+T*e.fontSize},w.yFn=function(e){return e.dy+y+e.fontSize*E},w.anchorFn=function(e,t){return a(t)&&90===Math.abs(t)?"middle":_?"end":"start"},w.heightFn=function(t,r,n){return"right"===e.side&&(r*=-1),r<-30?-n:r<30?-.5*n:0}}return w},Y.drawTicks=function(e,t,r){r=r||{};var n=t._id+"tick",a=r.vals;"period"===t.ticklabelmode&&(a=a.slice()).shift();var i=r.layer.selectAll("path."+n).data(t.ticks?a:[],me);i.exit().remove(),i.enter().append("path").classed(n,1).classed("ticks",1).classed("crisp",!1!==r.crisp).call(d.stroke,t.tickcolor).style("stroke-width",f.crispRound(e,t.tickwidth,1)+"px").attr("d",r.path).style("display",null),Se(t,[z]),i.attr("transform",r.transFn)},Y.drawGrid=function(e,t,r){r=r||{};var n=t._id+"grid",a=r.vals,i=r.counterAxis;if(!1===t.showgrid)a=[];else if(i&&Y.shouldShowZeroLine(e,t,i))for(var o="array"===t.tickmode,l=0;lp||i.leftp||i.top+(t.tickangle?0:e.fontSize/4)t["_visibleLabelMin_"+r._id]?s.style("display","none"):"tick"!==e.K||a||s.style("display",null)}))}))}))}))},x(v,g+1?g:m);var b=null;t._selections&&(t._selections[d]=v);var _=[function(){return y.length&&Promise.all(y)}];t.automargin&&i._redrawFromAutoMarginCount&&90===g?(b=90,_.push((function(){x(v,g)}))):_.push((function(){if(x(v,m),p.length&&"x"===u&&!a(m)&&("log"!==t.type||"D"!==String(t.dtick).charAt(0))){b=0;var e,n=0,i=[];if(v.each((function(e){n=Math.max(n,e.fontSize);var r=t.l2p(e.x),a=ye(this),o=f.bBox(a.node());i.push({top:0,bottom:10,height:10,left:r-o.width/2,right:r+o.width/2+2,width:o.width+2})})),"boundaries"!==t.tickson&&!t.showdividers||r.secondary){var o=p.length,s=Math.abs((p[o-1].x-p[0].x)*t._m)/(o-1),c=t.ticklabelposition||"",d=function(e){return-1!==c.indexOf(e)},h=d("top"),g=d("left"),y=d("right"),_=d("bottom")||g||h||y?(t.tickwidth||0)+6:0,w=s<2.5*n||"multicategory"===t.type||"realaxis"===t._name;for(e=0;e1)for(n=1;n2*o}(a,t))return"date";var g="strict"!==r.autotypenumbers;return function(e,t){for(var r=e.length,n=d(r),a=0,o=0,l={},u=0;u2*a}(a,g)?"category":function(e,t){for(var r=e.length,n=0;n=2){var s,c,u="";if(2===o.length)for(s=0;s<2;s++)if(c=x(o[s])){u=m;break}var d=a("pattern",u);if(d===m)for(s=0;s<2;s++)(c=x(o[s]))&&(t.bounds[s]=o[s]=c-1);if(d)for(s=0;s<2;s++)switch(c=o[s],d){case m:if(!n(c))return void(t.enabled=!1);if((c=+c)!==Math.floor(c)||c<0||c>=7)return void(t.enabled=!1);t.bounds[s]=o[s]=c;break;case g:if(!n(c))return void(t.enabled=!1);if((c=+c)<0||c>24)return void(t.enabled=!1);t.bounds[s]=o[s]=c}if(!1===r.autorange){var f=r.range;if(f[0]f[1])return void(t.enabled=!1)}else if(o[0]>f[0]&&o[1]n?1:-1:+(e.substr(1)||1)-+(t.substr(1)||1)},r.ref2id=function(e){return!!/^[xyz]/.test(e)&&e.split(" ")[0]},r.isLinked=function(e,t){return i(t,e._axisMatchGroups)||i(t,e._axisConstraintGroups)}},{"../../registry":318,"./constants":286}],284:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){if("category"===t.type){var a,i=e.categoryarray,o=Array.isArray(i)&&i.length>0;o&&(a="array");var l,s=r("categoryorder",a);"array"===s&&(l=r("categoryarray")),o||"array"!==s||(s=t.categoryorder="trace"),"trace"===s?t._initialCategories=[]:"array"===s?t._initialCategories=l.slice():(l=function(e,t){var r,n,a,i=t.dataAttr||e._id.charAt(0),o={};if(t.axData)r=t.axData;else for(r=[],n=0;nn?a.substr(n):i.substr(r))+o:a+i+e*t:o}function g(e,t){for(var r=t._size,n=r.h/r.w,a={},i=Object.keys(e),o=0;oc*x)||T)for(r=0;rI&&FO&&(O=F);f/=(O-C)/(2*P),C=s.l2r(C),O=s.l2r(O),s.range=s._input.range=L=0?Math.min(e,.9):1/(1/Math.max(e,-.3)+3.222))}function j(e,t,r,n,a){return e.append("path").attr("class","zoombox").style({fill:t>.2?"rgba(0,0,0,0)":"rgba(255,255,255,0)","stroke-width":0}).attr("transform",c(r,n)).attr("d",a+"Z")}function H(e,t,r){return e.append("path").attr("class","zoombox-corners").style({fill:d.background,stroke:d.defaultLine,"stroke-width":1,opacity:0}).attr("transform",c(t,r)).attr("d","M0,0Z")}function B(e,t,r,n,a,i){e.attr("d",n+"M"+r.l+","+r.t+"v"+r.h+"h"+r.w+"v-"+r.h+"h-"+r.w+"Z"),Y(e,t,a,i)}function Y(e,t,r,n){r||(e.transition().style("fill",n>.2?"rgba(0,0,0,0.4)":"rgba(255,255,255,0.3)").duration(200),t.transition().style("opacity",1).duration(200))}function V(e){n.select(e).selectAll(".zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners").remove()}function U(e){P&&e.data&&e._context.showTips&&(a.notifier(a._(e,"Double-click to zoom back out"),"long"),P=!1)}function q(e){var t=Math.floor(Math.min(e.b-e.t,e.r-e.l,O)/2);return"M"+(e.l-3.5)+","+(e.t-.5+t)+"h3v"+-t+"h"+t+"v-3h-"+(t+3)+"ZM"+(e.r+3.5)+","+(e.t-.5+t)+"h-3v"+-t+"h"+-t+"v-3h"+(t+3)+"ZM"+(e.r+3.5)+","+(e.b+.5-t)+"h-3v"+t+"h"+-t+"v3h"+(t+3)+"ZM"+(e.l-3.5)+","+(e.b+.5-t)+"h3v"+t+"h"+t+"v3h-"+(t+3)+"Z"}function G(e,t,r,n,i){for(var o,l,s,c,u=!1,d={},f={},p=(i||{}).xaHash,h=(i||{}).yaHash,m=0;m=0)a._fullLayout._deactivateShape(a);else{var o=a._fullLayout.clickmode;if(V(a),2!==e||ve||Ue(),ge)o.indexOf("select")>-1&&L(r,a,J,Q,t.id,Oe),o.indexOf("event")>-1&&p.click(a,r,t.id);else if(1===e&&ve){var l=m?I:P,c="s"===m||"w"===v?0:1,d=l._name+".range["+c+"]",f=function(e,t){var r,n=e.range[t],a=Math.abs(n-e.range[1-t]);return"date"===e.type?n:"log"===e.type?(r=Math.ceil(Math.max(0,-Math.log(a)/Math.LN10))+3,i("."+r+"g")(Math.pow(10,n))):(r=Math.floor(Math.log(Math.abs(n))/Math.LN10)-Math.floor(Math.log(a)/Math.LN10)+4,i("."+String(r)+"g")(n))}(l,c),h="left",g="middle";if(l.fixedrange)return;m?(g="n"===m?"top":"bottom","right"===l.side&&(h="right")):"e"===v&&(h="right"),a._context.showAxisRangeEntryBoxes&&n.select(be).call(u.makeEditable,{gd:a,immediate:!0,background:a._fullLayout.paper_bgcolor,text:String(f),fill:l.tickfont?l.tickfont.color:"#444",horizontalAlign:h,verticalAlign:g}).on("edit",(function(e){var t=l.d2r(e);void 0!==t&&s.call("_guiRelayout",a,d,t)}))}}}function Re(t,r){if(e._transitioningWithDuration)return!1;var n=Math.max(0,Math.min(ee,pe*t+_e)),a=Math.max(0,Math.min(te,he*r+we)),i=Math.abs(n-_e),o=Math.abs(a-we);function l(){Le="",Te.r=Te.l,Te.t=Te.b,De.attr("d","M0,0Z")}if(Te.l=Math.min(_e,n),Te.r=Math.max(_e,n),Te.t=Math.min(we,a),Te.b=Math.max(we,a),re.isSubplotConstrained)i>O||o>O?(Le="xy",i/ee>o/te?(o=i*te/ee,we>a?Te.t=we-o:Te.b=we+o):(i=o*ee/te,_e>n?Te.l=_e-i:Te.r=_e+i),De.attr("d",q(Te))):l();else if(ne.isSubplotConstrained)if(i>O||o>O){Le="xy";var s=Math.min(Te.l/ee,(te-Te.b)/te),c=Math.max(Te.r/ee,(te-Te.t)/te);Te.l=s*ee,Te.r=c*ee,Te.b=(1-s)*te,Te.t=(1-c)*te,De.attr("d",q(Te))}else l();else!ie||o0){var u;if(ne.isSubplotConstrained||!ae&&1===ie.length){for(u=0;um[1]-1/4096&&(t.domain=l),a.noneOrAll(e.domain,t.domain,l)}return r("layer"),t}},{"../../lib":232,"fast-isnumeric":17}],298:[function(e,t,r){"use strict";var n=e("./show_dflt");t.exports=function(e,t,r,a,i){i||(i={});var o=i.tickSuffixDflt,l=n(e);r("tickprefix")&&r("showtickprefix",l),r("ticksuffix",o)&&r("showticksuffix",l)}},{"./show_dflt":302}],299:[function(e,t,r){"use strict";var n=e("../../constants/alignment").FROM_BL;t.exports=function(e,t,r){void 0===r&&(r=n[e.constraintoward||"center"]);var a=[e.r2l(e.range[0]),e.r2l(e.range[1])],i=a[0]+(a[1]-a[0])*r;e.range=e._input.range=[e.l2r(i+(a[0]-i)*t),e.l2r(i+(a[1]-i)*t)],e.setScale()}},{"../../constants/alignment":207}],300:[function(e,t,r){"use strict";var n=e("polybooljs"),a=e("../../registry"),i=e("../../components/drawing").dashStyle,o=e("../../components/color"),l=e("../../components/fx"),s=e("../../components/fx/helpers").makeEventData,c=e("../../components/dragelement/helpers"),u=c.freeMode,d=c.rectMode,f=c.drawMode,p=c.openMode,h=c.selectMode,m=e("../../components/shapes/draw_newshape/display_outlines"),g=e("../../components/shapes/draw_newshape/helpers").handleEllipse,v=e("../../components/shapes/draw_newshape/newshapes"),y=e("../../lib"),x=e("../../lib/polygon"),b=e("../../lib/throttle"),_=e("./axis_ids").getFromId,w=e("../../lib/clear_gl_canvases"),T=e("../../plot_api/subroutines").redrawReglTraces,M=e("./constants"),k=M.MINSELECT,A=x.filter,L=x.tester,S=e("./handle_outline").clearSelect,D=e("./helpers"),C=D.p2r,O=D.axValue,P=D.getTransform;function I(e,t,r,n,a,i,o){var l,s,c,u,d,f,h,g,v,y=t._hoverdata,x=t._fullLayout.clickmode.indexOf("event")>-1,b=[];if(function(e){return e&&Array.isArray(e)&&!0!==e[0].hoverOnBox}(y)){F(e,t,i);var _=function(e,t){var r,n,a=e[0],i=-1,o=[];for(n=0;n0?function(e,t){var r,n,a,i=[];for(a=0;a0&&i.push(r);if(1===i.length&&i[0]===t.searchInfo&&(n=t.searchInfo.cd[0].trace).selectedpoints.length===t.pointNumbers.length){for(a=0;a1)return!1;if((a+=r.selectedpoints.length)>1)return!1}return 1===a}(l)&&(f=H(_))){for(o&&o.remove(),v=0;v=0&&n._fullLayout._deactivateShape(n),f(t)){var i=n._fullLayout._zoomlayer.selectAll(".select-outline-"+r.id);if(i&&n._fullLayout._drawing){var o=v(i,e);o&&a.call("_guiRelayout",n,{shapes:o}),n._fullLayout._drawing=!1}}r.selection={},r.selection.selectionDefs=e.selectionDefs=[],r.selection.mergedPolygons=e.mergedPolygons=[]}function j(e,t,r,n){var a,i,o,l=[],s=t.map((function(e){return e._id})),c=r.map((function(e){return e._id}));for(o=0;o0?n[0]:r;return!!t.selectedpoints&&t.selectedpoints.indexOf(a)>-1}function B(e,t,r){var n,i,o,l;for(n=0;n=0)D._fullLayout._deactivateShape(D);else if(!_){var r=R.clickmode;b.done(me).then((function(){if(b.clear(me),2===e){for(de.remove(),$=0;$-1&&I(t,D,a.xaxes,a.yaxes,a.subplot,a,de),"event"===r&&D.emit("plotly_selected",void 0);l.click(D,t)})).catch(y.error)}},a.doneFn=function(){he.remove(),b.done(me).then((function(){b.clear(me),a.gd.emit("plotly_selected",te),K&&a.selectionDefs&&(K.subtract=ue,a.selectionDefs.push(K),a.mergedPolygons.length=0,[].push.apply(a.mergedPolygons,Q)),a.doneFnCompleted&&a.doneFnCompleted(ge)})).catch(y.error),_&&E(a)}},clearSelect:S,clearSelectionsCache:E,selectOnClick:I}},{"../../components/color":102,"../../components/dragelement/helpers":120,"../../components/drawing":124,"../../components/fx":142,"../../components/fx/helpers":138,"../../components/shapes/draw_newshape/display_outlines":190,"../../components/shapes/draw_newshape/helpers":191,"../../components/shapes/draw_newshape/newshapes":192,"../../lib":232,"../../lib/clear_gl_canvases":220,"../../lib/polygon":244,"../../lib/throttle":256,"../../plot_api/subroutines":269,"../../registry":318,"./axis_ids":283,"./constants":286,"./handle_outline":290,"./helpers":291,polybooljs:57}],301:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("d3-time-format").utcFormat,i=e("../../lib"),o=i.numberFormat,l=e("fast-isnumeric"),s=i.cleanNumber,c=i.ms2DateTime,u=i.dateTime2ms,d=i.ensureNumber,f=i.isArrayOrTypedArray,p=e("../../constants/numerical"),h=p.FP_SAFE,m=p.BADNUM,g=p.LOG_CLIP,v=p.ONEWEEK,y=p.ONEDAY,x=p.ONEHOUR,b=p.ONEMIN,_=p.ONESEC,w=e("./axis_ids"),T=e("./constants"),M=T.HOUR_PATTERN,k=T.WEEKDAY_PATTERN;function A(e){return Math.pow(10,e)}function L(e){return null!=e}t.exports=function(e,t){t=t||{};var r=e._id||"x",p=r.charAt(0);function S(t,r){if(t>0)return Math.log(t)/Math.LN10;if(t<=0&&r&&e.range&&2===e.range.length){var n=e.range[0],a=e.range[1];return.5*(n+a-2*g*Math.abs(n-a))}return m}function D(t,r,n,a){if((a||{}).msUTC&&l(t))return+t;var o=u(t,n||e.calendar);if(o===m){if(!l(t))return m;t=+t;var s=Math.floor(10*i.mod(t+.05,1)),c=Math.round(t-s/10);o=u(new Date(c))+s/10}return o}function C(t,r,n){return c(t,r,n||e.calendar)}function O(t){return e._categories[Math.round(t)]}function P(t){if(L(t)){if(void 0===e._categoriesMap&&(e._categoriesMap={}),void 0!==e._categoriesMap[t])return e._categoriesMap[t];e._categories.push("number"==typeof t?String(t):t);var r=e._categories.length-1;return e._categoriesMap[t]=r,r}return m}function I(t){if(e._categoriesMap)return e._categoriesMap[t]}function R(e){var t=I(e);return void 0!==t?t:l(e)?+e:void 0}function z(e){return l(e)?+e:I(e)}function N(e,t,r){return n.round(r+t*e,2)}function F(e,t,r){return(e-r)/t}var E=function(t){return l(t)?N(t,e._m,e._b):m},j=function(t){return F(t,e._m,e._b)};if(e.rangebreaks){var H="y"===p;E=function(t){if(!l(t))return m;var r=e._rangebreaks.length;if(!r)return N(t,e._m,e._b);var n=H;e.range[0]>e.range[1]&&(n=!n);for(var a=n?-1:1,i=a*t,o=0,s=0;su)){o=i<(c+u)/2?s:s+1;break}o=s+1}var d=e._B[o]||0;return isFinite(d)?N(t,e._m2,d):0},j=function(t){var r=e._rangebreaks.length;if(!r)return F(t,e._m,e._b);for(var n=0,a=0;ae._rangebreaks[a].pmax&&(n=a+1);return F(t,e._m2,e._B[n])}}e.c2l="log"===e.type?S:d,e.l2c="log"===e.type?A:d,e.l2p=E,e.p2l=j,e.c2p="log"===e.type?function(e,t){return E(S(e,t))}:E,e.p2c="log"===e.type?function(e){return A(j(e))}:j,-1!==["linear","-"].indexOf(e.type)?(e.d2r=e.r2d=e.d2c=e.r2c=e.d2l=e.r2l=s,e.c2d=e.c2r=e.l2d=e.l2r=d,e.d2p=e.r2p=function(t){return e.l2p(s(t))},e.p2d=e.p2r=j,e.cleanPos=d):"log"===e.type?(e.d2r=e.d2l=function(e,t){return S(s(e),t)},e.r2d=e.r2c=function(e){return A(s(e))},e.d2c=e.r2l=s,e.c2d=e.l2r=d,e.c2r=S,e.l2d=A,e.d2p=function(t,r){return e.l2p(e.d2r(t,r))},e.p2d=function(e){return A(j(e))},e.r2p=function(t){return e.l2p(s(t))},e.p2r=j,e.cleanPos=d):"date"===e.type?(e.d2r=e.r2d=i.identity,e.d2c=e.r2c=e.d2l=e.r2l=D,e.c2d=e.c2r=e.l2d=e.l2r=C,e.d2p=e.r2p=function(t,r,n){return e.l2p(D(t,0,n))},e.p2d=e.p2r=function(e,t,r){return C(j(e),t,r)},e.cleanPos=function(t){return i.cleanDate(t,m,e.calendar)}):"category"===e.type?(e.d2c=e.d2l=P,e.r2d=e.c2d=e.l2d=O,e.d2r=e.d2l_noadd=R,e.r2c=function(t){var r=z(t);return void 0!==r?r:e.fraction2r(.5)},e.l2r=e.c2r=d,e.r2l=z,e.d2p=function(t){return e.l2p(e.r2c(t))},e.p2d=function(e){return O(j(e))},e.r2p=e.d2p,e.p2r=j,e.cleanPos=function(e){return"string"==typeof e&&""!==e?e:d(e)}):"multicategory"===e.type&&(e.r2d=e.c2d=e.l2d=O,e.d2r=e.d2l_noadd=R,e.r2c=function(t){var r=R(t);return void 0!==r?r:e.fraction2r(.5)},e.r2c_just_indices=I,e.l2r=e.c2r=d,e.r2l=R,e.d2p=function(t){return e.l2p(e.r2c(t))},e.p2d=function(e){return O(j(e))},e.r2p=e.d2p,e.p2r=j,e.cleanPos=function(e){return Array.isArray(e)||"string"==typeof e&&""!==e?e:d(e)},e.setupMultiCategory=function(n){var a,o,l=e._traceIndices,s=e._matchGroup;if(s&&0===e._categories.length)for(var c in s)if(c!==r){var u=t[w.id2name(c)];l=l.concat(u._traceIndices)}var d=[[0,{}],[0,{}]],h=[];for(a=0;ah&&(o[n]=h),o[0]===o[1]){var c=Math.max(1,Math.abs(1e-6*o[0]));o[0]-=c,o[1]+=c}}else i.nestedProperty(e,t).set(a)},e.setScale=function(r){var n=t._size;if(e.overlaying){var a=w.getFromId({_fullLayout:t},e.overlaying);e.domain=a.domain}var i=r&&e._r?"_r":"range",o=e.calendar;e.cleanRange(i);var l,s,c=e.r2l(e[i][0],o),u=e.r2l(e[i][1],o),d="y"===p;if((d?(e._offset=n.t+(1-e.domain[1])*n.h,e._length=n.h*(e.domain[1]-e.domain[0]),e._m=e._length/(c-u),e._b=-e._m*u):(e._offset=n.l+e.domain[0]*n.w,e._length=n.w*(e.domain[1]-e.domain[0]),e._m=e._length/(u-c),e._b=-e._m*c),e._rangebreaks=[],e._lBreaks=0,e._m2=0,e._B=[],e.rangebreaks)&&(e._rangebreaks=e.locateBreaks(Math.min(c,u),Math.max(c,u)),e._rangebreaks.length)){for(l=0;lu&&(f=!f),f&&e._rangebreaks.reverse();var h=f?-1:1;for(e._m2=h*e._length/(Math.abs(u-c)-e._lBreaks),e._B.push(-e._m2*(d?u:c)),l=0;la&&(a+=7,oa&&(a+=24,o=n&&o=n&&t=l.min&&(el.max&&(l.max=n),a=!1)}a&&c.push({min:e,max:n})}};for(n=0;nr.duration?(!function(){for(var r={},n=0;n rect").call(o.setTranslate,0,0).call(o.setScale,1,1),e.plot.call(o.setTranslate,t._offset,r._offset).call(o.setScale,1,1);var n=e.plot.selectAll(".scatterlayer .trace");n.selectAll(".point").call(o.setPointGroupScale,1,1),n.selectAll(".textpoint").call(o.setTextPointsScale,1,1),n.call(o.hideOutsideRangePoints,e)}function g(t,r){var n=t.plotinfo,a=n.xaxis,s=n.yaxis,c=a._length,u=s._length,d=!!t.xr1,f=!!t.yr1,p=[];if(d){var h=i.simpleMap(t.xr0,a.r2l),m=i.simpleMap(t.xr1,a.r2l),g=h[1]-h[0],v=m[1]-m[0];p[0]=(h[0]*(1-r)+r*m[0]-h[0])/(h[1]-h[0])*c,p[2]=c*(1-r+r*v/g),a.range[0]=a.l2r(h[0]*(1-r)+r*m[0]),a.range[1]=a.l2r(h[1]*(1-r)+r*m[1])}else p[0]=0,p[2]=c;if(f){var y=i.simpleMap(t.yr0,s.r2l),x=i.simpleMap(t.yr1,s.r2l),b=y[1]-y[0],_=x[1]-x[0];p[1]=(y[1]*(1-r)+r*x[1]-y[1])/(y[0]-y[1])*u,p[3]=u*(1-r+r*_/b),s.range[0]=a.l2r(y[0]*(1-r)+r*x[0]),s.range[1]=s.l2r(y[1]*(1-r)+r*x[1])}else p[1]=0,p[3]=u;l.drawOne(e,a,{skipTitle:!0}),l.drawOne(e,s,{skipTitle:!0}),l.redrawComponents(e,[a._id,s._id]);var w=d?c/p[2]:1,T=f?u/p[3]:1,M=d?p[0]:0,k=f?p[1]:0,A=d?p[0]/p[2]*c:0,L=f?p[1]/p[3]*u:0,S=a._offset-A,D=s._offset-L;n.clipRect.call(o.setTranslate,M,k).call(o.setScale,1/w,1/T),n.plot.call(o.setTranslate,S,D).call(o.setScale,w,T),o.setPointGroupScale(n.zoomScalePts,1/w,1/T),o.setTextPointsScale(n.zoomScaleTxt,1/w,1/T)}l.redrawComponents(e)}},{"../../components/drawing":124,"../../lib":232,"../../registry":318,"./axes":279,"@plotly/d3":11}],307:[function(e,t,r){"use strict";var n=e("../../registry").traceIs,a=e("./axis_autotype");function i(e){return{v:"x",h:"y"}[e.orientation||"v"]}function o(e,t){var r=i(e),a=n(e,"box-violin"),o=n(e._fullInput||{},"candlestick");return a&&!o&&t===r&&void 0===e[r]&&void 0===e[r+"0"]}t.exports=function(e,t,r,l){r("autotypenumbers",l.autotypenumbersDflt),"-"===r("type",(l.splomStash||{}).type)&&(!function(e,t){if("-"!==e.type)return;var r,l=e._id,s=l.charAt(0);-1!==l.indexOf("scene")&&(l=s);var c=function(e,t,r){for(var n=0;n0&&(a["_"+r+"axes"]||{})[t])return a;if((a[r+"axis"]||r)===t){if(o(a,r))return a;if((a[r]||[]).length||a[r+"0"])return a}}}(t,l,s);if(!c)return;if("histogram"===c.type&&s==={v:"y",h:"x"}[c.orientation||"v"])return void(e.type="linear");var u=s+"calendar",d=c[u],f={noMultiCategory:!n(c,"cartesian")||n(c,"noMultiCategory")};"box"===c.type&&c._hasPreCompStats&&s==={h:"x",v:"y"}[c.orientation||"v"]&&(f.noMultiCategory=!0);if(f.autotypenumbers=e.autotypenumbers,o(c,s)){var p=i(c),h=[];for(r=0;r0?".":"")+i;a.isPlainObject(o)?s(o,t,l,n+1):t(l,i,o)}}))}r.manageCommandObserver=function(e,t,n,o){var l={},s=!0;t&&t._commandObserver&&(l=t._commandObserver),l.cache||(l.cache={}),l.lookupTable={};var c=r.hasSimpleAPICommandBindings(e,n,l.lookupTable);if(t&&t._commandObserver){if(c)return l;if(t._commandObserver.remove)return t._commandObserver.remove(),t._commandObserver=null,l}if(c){i(e,c,l.cache),l.check=function(){if(s){var t=i(e,c,l.cache);return t.changed&&o&&void 0!==l.lookupTable[t.value]&&(l.disable(),Promise.resolve(o({value:t.value,type:c.type,prop:c.prop,traces:c.traces,index:l.lookupTable[t.value]})).then(l.enable,l.enable)),t.changed}};for(var u=["plotly_relayout","plotly_redraw","plotly_restyle","plotly_update","plotly_animatingframe","plotly_afterplot"],d=0;d=t.width-20?(i["text-anchor"]="start",i.x=5):(i["text-anchor"]="end",i.x=t._paper.attr("width")-7),r.attr(i);var o=r.select(".js-link-to-tool"),l=r.select(".js-link-spacer"),s=r.select(".js-sourcelinks");e._context.showSources&&e._context.showSources(e),e._context.showLink&&function(e,t){t.text("");var r=t.append("a").attr({"xlink:xlink:href":"#",class:"link--impt link--embedview","font-weight":"bold"}).text(e._context.linkText+" "+String.fromCharCode(187));if(e._context.sendData)r.on("click",(function(){b.sendDataToCloud(e)}));else{var n=window.location.pathname.split("/"),a=window.location.search;r.attr({"xlink:xlink:show":"new","xlink:xlink:href":"/"+n[2].split(".")[0]+"/"+n[1]+a})}}(e,o),l.text(o.text()&&s.text()?" - ":"")}},b.sendDataToCloud=function(e){var t=(window.PLOTLYENV||{}).BASE_URL||e._context.plotlyServerURL;if(t){e.emit("plotly_beforeexport");var r=n.select(e).append("div").attr("id","hiddenform").style("display","none"),a=r.append("form").attr({action:t+"/external",method:"post",target:"_blank"});return a.append("input").attr({type:"text",name:"data"}).node().value=b.graphJson(e,!1,"keepdata"),a.node().submit(),r.remove(),e.emit("plotly_afterexport"),!1}};var T=["days","shortDays","months","shortMonths","periods","dateTime","date","time","decimal","thousands","grouping","currency"],M=["year","month","dayMonth","dayMonthYear"];function k(e,t){var r=e._context.locale;r||(r="en-US");var n=!1,a={};function i(e){for(var r=!0,i=0;i1&&R.length>1){for(l.getComponentMethod("grid","sizeDefaults")(c,s),o=0;o15&&R.length>15&&0===s.shapes.length&&0===s.images.length,b.linkSubplots(f,s,d,n),b.cleanPlot(f,s,d,n);var j=!(!n._has||!n._has("gl2d")),H=!(!s._has||!s._has("gl2d")),B=!(!n._has||!n._has("cartesian"))||j,Y=!(!s._has||!s._has("cartesian"))||H;B&&!Y?n._bgLayer.remove():Y&&!B&&(s._shouldCreateBgLayer=!0),n._zoomlayer&&!e._dragging&&h({_fullLayout:n}),function(e,t){var r,n=[];t.meta&&(r=t._meta={meta:t.meta,layout:{meta:t.meta}});for(var a=0;a0){var d=1-2*l;n=Math.round(d*n),a=Math.round(d*a)}}var f=b.layoutAttributes.width.min,p=b.layoutAttributes.height.min;n1,m=!t.height&&Math.abs(r.height-a)>1;(m||h)&&(h&&(r.width=n),m&&(r.height=a)),e._initialAutoSize||(e._initialAutoSize={width:n,height:a}),b.sanitizeMargins(r)},b.supplyLayoutModuleDefaults=function(e,t,r,n){var a,i,o,s=l.componentsRegistry,c=t._basePlotModules,d=l.subplotsRegistry.cartesian;for(a in s)(o=s[a]).includeBasePlot&&o.includeBasePlot(e,t);for(var f in c.length||c.push(d),t._has("cartesian")&&(l.getComponentMethod("grid","contentDefaults")(e,t),d.finalizeSubplots(e,t)),t._subplots)t._subplots[f].sort(u.subplotSort);for(i=0;i1&&(r.l/=m,r.r/=m)}if(d){var g=(r.t+r.b)/d;g>1&&(r.t/=g,r.b/=g)}var v=void 0!==r.xl?r.xl:r.x,y=void 0!==r.xr?r.xr:r.x,x=void 0!==r.yt?r.yt:r.y,_=void 0!==r.yb?r.yb:r.y;f[t]={l:{val:v,size:r.l+h},r:{val:y,size:r.r+h},b:{val:_,size:r.b+h},t:{val:x,size:r.t+h}},p[t]=1}else delete f[t],delete p[t];if(!n._replotting)return b.doAutoMargin(e)}},b.doAutoMargin=function(e){var t=e._fullLayout,r=t.width,n=t.height;t._size||(t._size={}),C(t);var a=t._size,i=t.margin,s=u.extendFlat({},a),c=i.l,d=i.r,f=i.t,h=i.b,m=t._pushmargin,g=t._pushmarginIds;if(!1!==t.margin.autoexpand){for(var v in m)g[v]||delete m[v];for(var y in m.base={l:{val:0,size:c},r:{val:1,size:d},t:{val:1,size:f},b:{val:0,size:h}},m){var x=m[y].l||{},_=m[y].b||{},w=x.val,T=x.size,M=_.val,k=_.size;for(var A in m){if(o(T)&&m[A].r){var L=m[A].r.val,S=m[A].r.size;if(L>w){var D=(T*L+(S-r)*w)/(L-w),O=(S*(1-w)+(T-r)*(1-L))/(L-w);D+O>c+d&&(c=D,d=O)}}if(o(k)&&m[A].t){var P=m[A].t.val,I=m[A].t.size;if(P>M){var R=(k*P+(I-n)*M)/(P-M),z=(I*(1-M)+(k-n)*(1-P))/(P-M);R+z>h+f&&(h=R,f=z)}}}}}var N=u.constrain(r-i.l-i.r,2,64),F=u.constrain(n-i.t-i.b,2,64),E=Math.max(0,r-N),j=Math.max(0,n-F);if(E){var H=(c+d)/E;H>1&&(c/=H,d/=H)}if(j){var B=(h+f)/j;B>1&&(h/=B,f/=B)}if(a.l=Math.round(c),a.r=Math.round(d),a.t=Math.round(f),a.b=Math.round(h),a.p=Math.round(i.pad),a.w=Math.round(r)-a.l-a.r,a.h=Math.round(n)-a.t-a.b,!t._replotting&&b.didMarginChange(s,a)){"_redrawFromAutoMarginCount"in t?t._redrawFromAutoMarginCount++:t._redrawFromAutoMarginCount=1;var Y=3*(1+Object.keys(g).length);if(t._redrawFromAutoMarginCount0&&(e._transitioningWithDuration=!0),e._transitionData._interruptCallbacks.push((function(){n=!0})),r.redraw&&e._transitionData._interruptCallbacks.push((function(){return l.call("redraw",e)})),e._transitionData._interruptCallbacks.push((function(){e.emit("plotly_transitioninterrupted",[])}));var i=0,o=0;function s(){return i++,function(){o++,n||o!==i||function(t){if(!e._transitionData)return;(function(e){if(e)for(;e.length;)e.shift()})(e._transitionData._interruptCallbacks),Promise.resolve().then((function(){if(r.redraw)return l.call("redraw",e)})).then((function(){e._transitioning=!1,e._transitioningWithDuration=!1,e.emit("plotly_transitioned",[])})).then(t)}(a)}}r.runFn(s),setTimeout(s())}))}],i=u.syncOrAsync(a,e);return i&&i.then||(i=Promise.resolve()),i.then((function(){return e}))}b.didMarginChange=function(e,t){for(var r=0;r1)return!0}return!1},b.graphJson=function(e,t,r,n,a,i){(a&&t&&!e._fullData||a&&!t&&!e._fullLayout)&&b.supplyDefaults(e);var o=a?e._fullData:e.data,l=a?e._fullLayout:e.layout,s=(e._transitionData||{})._frames;function c(e,t){if("function"==typeof e)return t?"_function_":null;if(u.isPlainObject(e)){var n,a={};return Object.keys(e).sort().forEach((function(i){if(-1===["_","["].indexOf(i.charAt(0)))if("function"!=typeof e[i]){if("keepdata"===r){if("src"===i.substr(i.length-3))return}else if("keepstream"===r){if("string"==typeof(n=e[i+"src"])&&n.indexOf(":")>0&&!u.isPlainObject(e.stream))return}else if("keepall"!==r&&"string"==typeof(n=e[i+"src"])&&n.indexOf(":")>0)return;a[i]=c(e[i],t)}else t&&(a[i]="_function")})),a}return Array.isArray(e)?e.map((function(e){return c(e,t)})):u.isTypedArray(e)?u.simpleMap(e,u.identity):u.isJSDate(e)?u.ms2DateTimeLocal(+e):e}var d={data:(o||[]).map((function(e){var r=c(e);return t&&delete r.fit,r}))};if(!t&&(d.layout=c(l),a)){var f=l._size;d.layout.computed={margin:{b:f.b,l:f.l,r:f.r,t:f.t}}}return s&&(d.frames=c(s)),i&&(d.config=c(e._context,!0)),"object"===n?d:JSON.stringify(d)},b.modifyFrames=function(e,t){var r,n,a,i=e._transitionData._frames,o=e._transitionData._frameHash;for(r=0;r=0;i--)if(l[i].enabled){r._indexToPoints=l[i]._indexToPoints;break}n&&n.calc&&(o=n.calc(e,r))}Array.isArray(o)&&o[0]||(o=[{x:f,y:f}]),o[0].t||(o[0].t={}),o[0].trace=r,h[t]=o}}for(R(o,c,d),a=0;a0){for(var n=[],a=0;a-1&&(d[p[r]].title={text:""});for(r=0;r")?"":t.html(e).text()}));return t.remove(),r}(T),T=(T=T.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g,"&")).replace(c,"'"),a.isIE()&&(T=(T=(T=T.replace(/"/gi,"'")).replace(/(\('#)([^']*)('\))/gi,'("#$2")')).replace(/(\\')/gi,'"')),T}},{"../components/color":102,"../components/drawing":124,"../constants/xmlns_namespaces":213,"../lib":232,"@plotly/d3":11}],327:[function(e,t,r){"use strict";var n=e("../../lib");t.exports=function(e,t){for(var r=0;rd+c||!n(u))}for(var p=0;pi))return t}return void 0!==r?r:e.dflt},r.coerceColor=function(e,t,r){return a(t).isValid()?t:void 0!==r?r:e.dflt},r.coerceEnumerated=function(e,t,r){return e.coerceNumber&&(t=+t),-1!==e.values.indexOf(t)?t:void 0!==r?r:e.dflt},r.getValue=function(e,t){var r;return Array.isArray(e)?t0?t+=r:u<0&&(t-=r)}return t}function R(e){var t=u,r=e.b,a=I(e);return n.inbox(r-t,a-t,_+(a-t)/(a-r)-1)}var z=e[d+"a"],N=e[f+"a"];m=Math.abs(z.r2c(z.range[1])-z.r2c(z.range[0]));var F=n.getDistanceFunction(a,p,h,(function(e){return(p(e)+h(e))/2}));if(n.getClosest(g,F,e),!1!==e.index&&g[e.index].p!==c){M||(D=function(e){return Math.min(k(e),e.p-y.bargroupwidth/2)},C=function(e){return Math.max(A(e),e.p+y.bargroupwidth/2)});var E=g[e.index],j=v.base?E.b+E.s:E.s;e[f+"0"]=e[f+"1"]=N.c2p(E[f],!0),e[f+"LabelVal"]=j;var H=y.extents[y.extents.round(E.p)];e[d+"0"]=z.c2p(x?D(E):H[0],!0),e[d+"1"]=z.c2p(x?C(E):H[1],!0);var B=void 0!==E.orig_p;return e[d+"LabelVal"]=B?E.orig_p:E.p,e.labelLabel=s(z,e[d+"LabelVal"],v[d+"hoverformat"]),e.valueLabel=s(N,e[f+"LabelVal"],v[f+"hoverformat"]),e.baseLabel=s(N,E.b,v[f+"hoverformat"]),e.spikeDistance=(function(e){var t=u,r=e.b,a=I(e);return n.inbox(r-t,a-t,w+(a-t)/(a-r)-1)}(E)+function(e){return O(k(e),A(e),w)}(E))/2,e[d+"Spike"]=z.c2p(E.p,!0),o(E,v,e),e.hovertemplate=v.hovertemplate,e}}function d(e,t){var r=t.mcc||e.marker.color,n=t.mlcc||e.marker.line.color,a=l(e,t);return i.opacity(r)?r:i.opacity(n)&&a?n:void 0}t.exports={hoverPoints:function(e,t,r,n,i){var o=u(e,t,r,n,i);if(o){var l=o.cd,s=l[0].trace,c=l[o.index];return o.color=d(s,c),a.getComponentMethod("errorbars","hoverInfo")(c,s,o),[o]}},hoverOnBars:u,getTraceColor:d}},{"../../components/color":102,"../../components/fx":142,"../../constants/numerical":212,"../../lib":232,"../../plots/cartesian/axes":279,"../../registry":318,"./helpers":334}],336:[function(e,t,r){"use strict";t.exports={attributes:e("./attributes"),layoutAttributes:e("./layout_attributes"),supplyDefaults:e("./defaults").supplyDefaults,crossTraceDefaults:e("./defaults").crossTraceDefaults,supplyLayoutDefaults:e("./layout_defaults"),calc:e("./calc"),crossTraceCalc:e("./cross_trace_calc").crossTraceCalc,colorbar:e("../scatter/marker_colorbar"),arraysToCalcdata:e("./arrays_to_calcdata"),plot:e("./plot").plot,style:e("./style").style,styleOnSelect:e("./style").styleOnSelect,hoverPoints:e("./hover").hoverPoints,eventData:e("./event_data"),selectPoints:e("./select"),moduleType:"trace",name:"bar",basePlotModule:e("../../plots/cartesian"),categories:["bar-like","cartesian","svg","bar","oriented","errorBarsOK","showLegend","zoomScale"],animatable:!0,meta:{}}},{"../../plots/cartesian":293,"../scatter/marker_colorbar":376,"./arrays_to_calcdata":327,"./attributes":328,"./calc":329,"./cross_trace_calc":331,"./defaults":332,"./event_data":333,"./hover":335,"./layout_attributes":337,"./layout_defaults":338,"./plot":339,"./select":340,"./style":342}],337:[function(e,t,r){"use strict";t.exports={barmode:{valType:"enumerated",values:["stack","group","overlay","relative"],dflt:"group",editType:"calc"},barnorm:{valType:"enumerated",values:["","fraction","percent"],dflt:"",editType:"calc"},bargap:{valType:"number",min:0,max:1,editType:"calc"},bargroupgap:{valType:"number",min:0,max:1,dflt:0,editType:"calc"}}},{}],338:[function(e,t,r){"use strict";var n=e("../../registry"),a=e("../../plots/cartesian/axes"),i=e("../../lib"),o=e("./layout_attributes");t.exports=function(e,t,r){function l(r,n){return i.coerce(e,t,o,r,n)}for(var s=!1,c=!1,u=!1,d={},f=l("barmode"),p=0;p0}function L(e){return"auto"===e?0:e}function S(e,t){var r=Math.PI/180*t,n=Math.abs(Math.sin(r)),a=Math.abs(Math.cos(r));return{x:e.width*a+e.height*n,y:e.width*n+e.height*a}}function D(e,t,r,n,a,i){var o=!!i.isHorizontal,l=!!i.constrained,s=i.angle||0,c=i.anchor||"end",u="end"===c,d="start"===c,f=((i.leftToRight||0)+1)/2,p=1-f,h=a.width,m=a.height,g=Math.abs(t-e),v=Math.abs(n-r),y=g>2*_&&v>2*_?_:0;g-=2*y,v-=2*y;var x=L(s);"auto"!==s||h<=g&&m<=v||!(h>g||m>v)||(h>v||m>g)&&h.01?U:function(e,t,r){return r&&e===t?e:Math.abs(e-t)>=2?U(e):e>t?Math.ceil(e):Math.floor(e)};E=q(E,j,z),j=q(j,E,z),H=q(H,B,!z),B=q(B,H,!z)}var G=k(i.ensureSingle(P,"path"),O,g,v);if(G.style("vector-effect","non-scaling-stroke").attr("d",isNaN((j-E)*(B-H))||Y&&e._context.staticPlot?"M0,0Z":"M"+E+","+H+"V"+B+"H"+j+"V"+H+"Z").call(s.setClipUrl,t.layerClipId,e),!O.uniformtext.mode&&N){var Z=s.makePointStyleFns(d);s.singlePointStyle(c,G,d,Z,e)}!function(e,t,r,n,a,l,c,d,p,g,v){var w,T=t.xaxis,A=t.yaxis,C=e._fullLayout;function O(t,r,n){return i.ensureSingle(t,"text").text(r).attr({class:"bartext bartext-"+w,"text-anchor":"middle","data-notex":1}).call(s.font,n).call(o.convertToTspans,e)}var P=n[0].trace,I="h"===P.orientation,R=function(e,t,r,n,a){var o,l=t[0].trace;o=l.texttemplate?function(e,t,r,n,a){var o=t[0].trace,l=i.castOption(o,r,"texttemplate");if(!l)return"";var s,c,d,f,p="waterfall"===o.type,h="funnel"===o.type;"h"===o.orientation?(s="y",c=a,d="x",f=n):(s="x",c=n,d="y",f=a);function m(e){return u(f,f.c2l(e),!0).text}var g=t[r],v={};v.label=g.p,v.labelLabel=v[s+"Label"]=(y=g.p,u(c,c.c2l(y),!0).text);var y;var x=i.castOption(o,g.i,"text");(0===x||x)&&(v.text=x);v.value=g.s,v.valueLabel=v[d+"Label"]=m(g.s);var _={};b(_,o,g.i),p&&(v.delta=+g.rawS||g.s,v.deltaLabel=m(v.delta),v.final=g.v,v.finalLabel=m(v.final),v.initial=v.final-v.delta,v.initialLabel=m(v.initial));h&&(v.value=g.s,v.valueLabel=m(v.value),v.percentInitial=g.begR,v.percentInitialLabel=i.formatPercent(g.begR),v.percentPrevious=g.difR,v.percentPreviousLabel=i.formatPercent(g.difR),v.percentTotal=g.sumR,v.percenTotalLabel=i.formatPercent(g.sumR));var w=i.castOption(o,g.i,"customdata");w&&(v.customdata=w);return i.texttemplateString(l,v,e._d3locale,_,v,o._meta||{})}(e,t,r,n,a):l.textinfo?function(e,t,r,n){var a=e[0].trace,o="h"===a.orientation,l="waterfall"===a.type,s="funnel"===a.type;function c(e){return u(o?r:n,+e,!0).text}var d,f=a.textinfo,p=e[t],h=f.split("+"),m=[],g=function(e){return-1!==h.indexOf(e)};g("label")&&m.push((v=e[t].p,u(o?n:r,v,!0).text));var v;g("text")&&(0===(d=i.castOption(a,p.i,"text"))||d)&&m.push(d);if(l){var y=+p.rawS||p.s,x=p.v,b=x-y;g("initial")&&m.push(c(b)),g("delta")&&m.push(c(y)),g("final")&&m.push(c(x))}if(s){g("value")&&m.push(c(p.s));var _=0;g("percent initial")&&_++,g("percent previous")&&_++,g("percent total")&&_++;var w=_>1;g("percent initial")&&(d=i.formatPercent(p.begR),w&&(d+=" of initial"),m.push(d)),g("percent previous")&&(d=i.formatPercent(p.difR),w&&(d+=" of previous"),m.push(d)),g("percent total")&&(d=i.formatPercent(p.sumR),w&&(d+=" of total"),m.push(d))}return m.join("
    ")}(t,r,n,a):m.getValue(l.text,r);return m.coerceString(y,o)}(C,n,a,T,A);w=function(e,t){var r=m.getValue(e.textposition,t);return m.coerceEnumerated(x,r)}(P,a);var z="stack"===g.mode||"relative"===g.mode,N=n[a],F=!z||N._outmost;if(!R||"none"===w||(N.isBlank||l===c||d===p)&&("auto"===w||"inside"===w))return void r.select("text").remove();var E=C.font,j=h.getBarColor(n[a],P),H=h.getInsideTextFont(P,a,E,j),B=h.getOutsideTextFont(P,a,E),Y=r.datum();I?"log"===T.type&&Y.s0<=0&&(l=T.range[0]=q*(X/G):X>=G*(W/q);q>0&&G>0&&(J||Q||K)?w="inside":(w="outside",V.remove(),V=null)}else w="inside";if(!V){Z=i.ensureUniformFontSize(e,"outside"===w?B:H);var $=(V=O(r,R,Z)).attr("transform");if(V.attr("transform",""),U=s.bBox(V.node()),q=U.width,G=U.height,V.attr("transform",$),q<=0||G<=0)return void V.remove()}var ee,te,re=P.textangle;"outside"===w?(te="both"===P.constraintext||"outside"===P.constraintext,ee=function(e,t,r,n,a,i){var o,l=!!i.isHorizontal,s=!!i.constrained,c=i.angle||0,u=a.width,d=a.height,f=Math.abs(t-e),p=Math.abs(n-r);o=l?p>2*_?_:0:f>2*_?_:0;var h=1;s&&(h=l?Math.min(1,p/d):Math.min(1,f/u));var m=L(c),g=S(a,m),v=(l?g.x:g.y)/2,y=(a.left+a.right)/2,x=(a.top+a.bottom)/2,b=(e+t)/2,w=(r+n)/2,T=0,k=0,A=l?M(t,e):M(r,n);l?(b=t-A*o,T=A*v):(w=n+A*o,k=-A*v);return{textX:y,textY:x,targetX:b,targetY:w,anchorX:T,anchorY:k,scale:h,rotate:m}}(l,c,d,p,U,{isHorizontal:I,constrained:te,angle:re})):(te="both"===P.constraintext||"inside"===P.constraintext,ee=D(l,c,d,p,U,{isHorizontal:I,constrained:te,angle:re,anchor:P.insidetextanchor}));ee.fontSize=Z.size,f("histogram"===P.type?"bar":P.type,ee,C),N.transform=ee,k(V,C,g,v).attr("transform",i.getTextTransform(ee))}(e,t,P,r,p,E,j,H,B,g,v),t.layerClipId&&s.hideOutsideRangePoint(c,P.select("text"),w,C,d.xcalendar,d.ycalendar)}));var H=!1===d.cliponaxis;s.setClipUrl(c,H?null:t.layerClipId,e)}));c.getComponentMethod("errorbars","plot")(e,P,t,g)},toMoveInsideBar:D}},{"../../components/color":102,"../../components/drawing":124,"../../components/fx/helpers":138,"../../lib":232,"../../lib/svg_text_utils":255,"../../plots/cartesian/axes":279,"../../registry":318,"./attributes":328,"./constants":330,"./helpers":334,"./style":342,"./uniform_text":344,"@plotly/d3":11,"fast-isnumeric":17}],340:[function(e,t,r){"use strict";function n(e,t,r,n,a){var i=t.c2p(n?e.s0:e.p0,!0),o=t.c2p(n?e.s1:e.p1,!0),l=r.c2p(n?e.p0:e.s0,!0),s=r.c2p(n?e.p1:e.s1,!0);return a?[(i+o)/2,(l+s)/2]:n?[o,(l+s)/2]:[(i+o)/2,s]}t.exports=function(e,t){var r,a=e.cd,i=e.xaxis,o=e.yaxis,l=a[0].trace,s="funnel"===l.type,c="h"===l.orientation,u=[];if(!1===t)for(r=0;r1||0===a.bargap&&0===a.bargroupgap&&!e[0].trace.marker.line.width)&&n.select(this).attr("shape-rendering","crispEdges")})),t.selectAll("g.points").each((function(t){h(n.select(this),t[0].trace,e)})),l.getComponentMethod("errorbars","style")(t)},styleTextPoints:m,styleOnSelect:function(e,t,r){var a=t[0].trace;a.selectedpoints?function(e,t,r){i.selectedPointStyle(e.selectAll("path"),t),function(e,t,r){e.each((function(e){var a,l=n.select(this);if(e.selected){a=o.ensureUniformFontSize(r,g(l,e,t,r));var s=t.selected.textfont&&t.selected.textfont.color;s&&(a.color=s),i.font(l,a)}else i.selectedTextStyle(l,t)}))}(e.selectAll("text"),t,r)}(r,a,e):(h(r,a,e),l.getComponentMethod("errorbars","style")(r))},getInsideTextFont:y,getOutsideTextFont:x,getBarColor:_,resizeText:s}},{"../../components/color":102,"../../components/drawing":124,"../../lib":232,"../../registry":318,"./attributes":328,"./helpers":334,"./uniform_text":344,"@plotly/d3":11}],343:[function(e,t,r){"use strict";var n=e("../../components/color"),a=e("../../components/colorscale/helpers").hasColorscale,i=e("../../components/colorscale/defaults"),o=e("../../lib").coercePattern;t.exports=function(e,t,r,l,s){var c=r("marker.color",l),u=a(e,"marker");u&&i(e,t,s,r,{prefix:"marker.",cLetter:"c"}),r("marker.line.color",n.defaultLine),a(e,"marker.line")&&i(e,t,s,r,{prefix:"marker.line.",cLetter:"c"}),r("marker.line.width"),r("marker.opacity"),o(r,"marker.pattern",c,u),r("selected.marker.color"),r("unselected.marker.color")}},{"../../components/color":102,"../../components/colorscale/defaults":112,"../../components/colorscale/helpers":113,"../../lib":232}],344:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../../lib");function i(e){return"_"+e+"Text_minsize"}t.exports={recordMinTextSize:function(e,t,r){if(r.uniformtext.mode){var n=i(e),a=r.uniformtext.minsize,o=t.scale*t.fontSize;t.hide=o0){l=!0;break}}l||(o=0)}return{hasLabels:r,hasValues:i,len:o}}t.exports={handleLabelsAndValues:s,supplyDefaults:function(e,t,r,n){function c(r,n){return a.coerce(e,t,i,r,n)}var u=s(c("labels"),c("values")),d=u.len;if(t._hasLabels=u.hasLabels,t._hasValues=u.hasValues,!t._hasLabels&&t._hasValues&&(c("label0"),c("dlabel")),d){t._length=d,c("marker.line.width")&&c("marker.line.color"),c("marker.colors"),c("scalegroup");var f,p=c("text"),h=c("texttemplate");if(h||(f=c("textinfo",Array.isArray(p)?"text+percent":"percent")),c("hovertext"),c("hovertemplate"),h||f&&"none"!==f){var m=c("textposition");l(e,t,n,c,m,{moduleHasSelected:!1,moduleHasUnselected:!1,moduleHasConstrain:!1,moduleHasCliponaxis:!1,moduleHasTextangle:!1,moduleHasInsideanchor:!1}),(Array.isArray(m)||"auto"===m||"outside"===m)&&c("automargin"),("inside"===m||"auto"===m||Array.isArray(m))&&c("insidetextorientation")}o(t,n,c);var g=c("hole");if(c("title.text")){var v=c("title.position",g?"middle center":"top center");g||"middle center"!==v||(t.title.position="top center"),a.coerceFont(c,"title.font",n.font)}c("sort"),c("direction"),c("rotation"),c("pull")}else t.visible=!1}}},{"../../lib":232,"../../plots/domain":309,"../bar/defaults":332,"./attributes":345,"fast-isnumeric":17}],349:[function(e,t,r){"use strict";var n=e("../../components/fx/helpers").appendArrayMultiPointValues;t.exports=function(e,t){var r={curveNumber:t.index,pointNumbers:e.pts,data:t._input,fullData:t,label:e.label,color:e.color,value:e.v,percent:e.percent,text:e.text,bbox:e.bbox,v:e.v};return 1===e.pts.length&&(r.pointNumber=r.i=e.pts[0]),n(r,t,e.pts),"funnelarea"===t.type&&(delete r.v,delete r.i),r}},{"../../components/fx/helpers":138}],350:[function(e,t,r){"use strict";var n=e("../../lib");function a(e){return-1!==e.indexOf("e")?e.replace(/[.]?0+e/,"e"):-1!==e.indexOf(".")?e.replace(/[.]?0+$/,""):e}r.formatPiePercent=function(e,t){var r=a((100*e).toPrecision(3));return n.numSeparate(r,t)+"%"},r.formatPieValue=function(e,t){var r=a(e.toPrecision(10));return n.numSeparate(r,t)},r.getFirstFilled=function(e,t){if(Array.isArray(e))for(var r=0;r"),name:d.hovertemplate||-1!==f.indexOf("name")?d.name:void 0,idealAlign:e.pxmid[0]<0?"left":"right",color:g.castOption(_.bgcolor,e.pts)||e.color,borderColor:g.castOption(_.bordercolor,e.pts),fontFamily:g.castOption(w.family,e.pts),fontSize:g.castOption(w.size,e.pts),fontColor:g.castOption(w.color,e.pts),nameLength:g.castOption(_.namelength,e.pts),textAlign:g.castOption(_.align,e.pts),hovertemplate:g.castOption(d.hovertemplate,e.pts),hovertemplateLabels:e,eventData:[v(e,d)]},{container:r._hoverlayer.node(),outerContainer:r._paper.node(),gd:t,inOut_bbox:T}),e.bbox=T[0],c._hasHoverLabel=!0}c._hasHoverEvent=!0,t.emit("plotly_hover",{points:[v(e,d)],event:n.event})}})),e.on("mouseout",(function(e){var r=t._fullLayout,a=t._fullData[c.index],o=n.select(this).datum();c._hasHoverEvent&&(e.originalEvent=n.event,t.emit("plotly_unhover",{points:[v(o,a)],event:n.event}),c._hasHoverEvent=!1),c._hasHoverLabel&&(i.loneUnhover(r._hoverlayer.node()),c._hasHoverLabel=!1)})),e.on("click",(function(e){var r=t._fullLayout,a=t._fullData[c.index];t._dragging||!1===r.hovermode||(t._hoverdata=[v(e,a)],i.click(t,n.event))}))}function b(e,t,r){var n=g.castOption(e.insidetextfont.color,t.pts);!n&&e._input.textfont&&(n=g.castOption(e._input.textfont.color,t.pts));var a=g.castOption(e.insidetextfont.family,t.pts)||g.castOption(e.textfont.family,t.pts)||r.family,i=g.castOption(e.insidetextfont.size,t.pts)||g.castOption(e.textfont.size,t.pts)||r.size;return{color:n||o.contrast(t.color),family:a,size:i}}function _(e,t){for(var r,n,a=0;at&&t>n||r=-4;g-=2)v(Math.PI*g,"tan");for(g=4;g>=-4;g-=2)v(Math.PI*(g+1),"tan")}if(d||p){for(g=4;g>=-4;g-=2)v(Math.PI*(g+1.5),"rad");for(g=4;g>=-4;g-=2)v(Math.PI*(g+.5),"rad")}}if(l||h||d){var y=Math.sqrt(e.width*e.width+e.height*e.height);if((i={scale:a*n*2/y,rCenter:1-a,rotate:0}).textPosAngle=(t.startangle+t.stopangle)/2,i.scale>=1)return i;m.push(i)}(h||p)&&((i=T(e,n,o,s,c)).textPosAngle=(t.startangle+t.stopangle)/2,m.push(i)),(h||f)&&((i=M(e,n,o,s,c)).textPosAngle=(t.startangle+t.stopangle)/2,m.push(i));for(var x=0,b=0,_=0;_=1)break}return m[x]}function T(e,t,r,n,a){t=Math.max(0,t-2*m);var i=e.width/e.height,o=L(i,n,t,r);return{scale:2*o/e.height,rCenter:k(i,o/t),rotate:A(a)}}function M(e,t,r,n,a){t=Math.max(0,t-2*m);var i=e.height/e.width,o=L(i,n,t,r);return{scale:2*o/e.width,rCenter:k(i,o/t),rotate:A(a+Math.PI/2)}}function k(e,t){return Math.cos(t)-e*t}function A(e){return(180/Math.PI*e+720)%180-90}function L(e,t,r,n){var a=e+1/(2*Math.tan(t));return r*Math.min(1/(Math.sqrt(a*a+.5)+a),n/(Math.sqrt(e*e+n/2)+e))}function S(e,t){return e.v!==t.vTotal||t.trace.hole?Math.min(1/(1+1/Math.sin(e.halfangle)),e.ring/2):1}function D(e,t){var r=t.pxmid[0],n=t.pxmid[1],a=e.width/2,i=e.height/2;return r<0&&(a*=-1),n<0&&(i*=-1),{scale:1,rCenter:1,rotate:0,x:a+Math.abs(i)*(a>0?1:-1)/2,y:i/(1+r*r/(n*n)),outside:!0}}function C(e,t){var r,n,a,i=e.trace,o={x:e.cx,y:e.cy},l={tx:0,ty:0};l.ty+=i.title.font.size,a=P(i),-1!==i.title.position.indexOf("top")?(o.y-=(1+a)*e.r,l.ty-=e.titleBox.height):-1!==i.title.position.indexOf("bottom")&&(o.y+=(1+a)*e.r);var s,c,u=(s=e.r,c=e.trace.aspectratio,s/(void 0===c?1:c)),d=t.w*(i.domain.x[1]-i.domain.x[0])/2;return-1!==i.title.position.indexOf("left")?(d+=u,o.x-=(1+a)*u,l.tx+=e.titleBox.width/2):-1!==i.title.position.indexOf("center")?d*=2:-1!==i.title.position.indexOf("right")&&(d+=u,o.x+=(1+a)*u,l.tx-=e.titleBox.width/2),r=d/e.titleBox.width,n=O(e,t)/e.titleBox.height,{x:o.x,y:o.y,scale:Math.min(r,n),tx:l.tx,ty:l.ty}}function O(e,t){var r=e.trace,n=t.h*(r.domain.y[1]-r.domain.y[0]);return Math.min(e.titleBox.height,n/2)}function P(e){var t,r=e.pull;if(!r)return 0;if(Array.isArray(r))for(r=0,t=0;tr&&(r=e.pull[t]);return r}function I(e,t){for(var r=[],n=0;n1?(c=r.r,u=c/a.aspectratio):(u=r.r,c=u*a.aspectratio),c*=(1+a.baseratio)/2,s=c*u}o=Math.min(o,s/r.vTotal)}for(n=0;n")}if(i){var x=s.castOption(a,t.i,"texttemplate");if(x){var b=function(e){return{label:e.label,value:e.v,valueLabel:g.formatPieValue(e.v,n.separators),percent:e.v/r.vTotal,percentLabel:g.formatPiePercent(e.v/r.vTotal,n.separators),color:e.color,text:e.text,customdata:s.castOption(a,e.i,"customdata")}}(t),_=g.getFirstFilled(a.text,t.pts);(y(_)||""===_)&&(b.text=_),t.text=s.texttemplateString(x,b,e._fullLayout._d3locale,b,a._meta||{})}else t.text=""}}function N(e,t){var r=e.rotate*Math.PI/180,n=Math.cos(r),a=Math.sin(r),i=(t.left+t.right)/2,o=(t.top+t.bottom)/2;e.textX=i*n-o*a,e.textY=i*a+o*n,e.noCenter=!0}t.exports={plot:function(e,t){var r=e._fullLayout,i=r._size;h("pie",r),_(t,e),I(t,i);var f=s.makeTraceGroups(r._pielayer,t,"trace").each((function(t){var f=n.select(this),h=t[0],m=h.trace;!function(e){var t,r,n,a=e[0],i=a.r,o=a.trace,l=g.getRotationAngle(o.rotation),s=2*Math.PI/a.vTotal,c="px0",u="px1";if("counterclockwise"===o.direction){for(t=0;ta.vTotal/2?1:0,r.halfangle=Math.PI*Math.min(r.v/a.vTotal,.5),r.ring=1-o.hole,r.rInscribed=S(r,a))}(t),f.attr("stroke-linejoin","round"),f.each((function(){var v=n.select(this).selectAll("g.slice").data(t);v.enter().append("g").classed("slice",!0),v.exit().remove();var y=[[[],[]],[[],[]]],_=!1;v.each((function(a,i){if(a.hidden)n.select(this).selectAll("path,g").remove();else{a.pointNumber=a.i,a.curveNumber=m.index,y[a.pxmid[1]<0?0:1][a.pxmid[0]<0?0:1].push(a);var o=h.cx,c=h.cy,u=n.select(this),f=u.selectAll("path.surface").data([a]);if(f.enter().append("path").classed("surface",!0).style({"pointer-events":"all"}),u.call(x,e,t),m.pull){var v=+g.castOption(m.pull,a.pts)||0;v>0&&(o+=v*a.pxmid[0],c+=v*a.pxmid[1])}a.cxFinal=o,a.cyFinal=c;var T=m.hole;if(a.v===h.vTotal){var M="M"+(o+a.px0[0])+","+(c+a.px0[1])+C(a.px0,a.pxmid,!0,1)+C(a.pxmid,a.px0,!0,1)+"Z";T?f.attr("d","M"+(o+T*a.px0[0])+","+(c+T*a.px0[1])+C(a.px0,a.pxmid,!1,T)+C(a.pxmid,a.px0,!1,T)+"Z"+M):f.attr("d",M)}else{var k=C(a.px0,a.px1,!0,1);if(T){var A=1-T;f.attr("d","M"+(o+T*a.px1[0])+","+(c+T*a.px1[1])+C(a.px1,a.px0,!1,T)+"l"+A*a.px0[0]+","+A*a.px0[1]+k+"Z")}else f.attr("d","M"+o+","+c+"l"+a.px0[0]+","+a.px0[1]+k+"Z")}z(e,a,h);var L=g.castOption(m.textposition,a.pts),S=u.selectAll("g.slicetext").data(a.text&&"none"!==L?[0]:[]);S.enter().append("g").classed("slicetext",!0),S.exit().remove(),S.each((function(){var u=s.ensureSingle(n.select(this),"text","",(function(e){e.attr("data-notex",1)})),f=s.ensureUniformFontSize(e,"outside"===L?function(e,t,r){var n=g.castOption(e.outsidetextfont.color,t.pts)||g.castOption(e.textfont.color,t.pts)||r.color,a=g.castOption(e.outsidetextfont.family,t.pts)||g.castOption(e.textfont.family,t.pts)||r.family,i=g.castOption(e.outsidetextfont.size,t.pts)||g.castOption(e.textfont.size,t.pts)||r.size;return{color:n,family:a,size:i}}(m,a,r.font):b(m,a,r.font));u.text(a.text).attr({class:"slicetext",transform:"","text-anchor":"middle"}).call(l.font,f).call(d.convertToTspans,e);var v,y=l.bBox(u.node());if("outside"===L)v=D(y,a);else if(v=w(y,a,h),"auto"===L&&v.scale<1){var x=s.ensureUniformFontSize(e,m.outsidetextfont);u.call(l.font,x),v=D(y=l.bBox(u.node()),a)}var T=v.textPosAngle,M=void 0===T?a.pxmid:R(h.r,T);if(v.targetX=o+M[0]*v.rCenter+(v.x||0),v.targetY=c+M[1]*v.rCenter+(v.y||0),N(v,y),v.outside){var k=v.targetY;a.yLabelMin=k-y.height/2,a.yLabelMid=k,a.yLabelMax=k+y.height/2,a.labelExtraX=0,a.labelExtraY=0,_=!0}v.fontSize=f.size,p(m.type,v,r),t[i].transform=v,u.attr("transform",s.getTextTransform(v))}))}function C(e,t,r,n){var i=n*(t[0]-e[0]),o=n*(t[1]-e[1]);return"a"+n*h.r+","+n*h.r+" 0 "+a.largeArc+(r?" 1 ":" 0 ")+i+","+o}}));var T=n.select(this).selectAll("g.titletext").data(m.title.text?[0]:[]);if(T.enter().append("g").classed("titletext",!0),T.exit().remove(),T.each((function(){var t,r=s.ensureSingle(n.select(this),"text","",(function(e){e.attr("data-notex",1)})),a=m.title.text;m._meta&&(a=s.templateString(a,m._meta)),r.text(a).attr({class:"titletext",transform:"","text-anchor":"middle"}).call(l.font,m.title.font).call(d.convertToTspans,e),t="middle center"===m.title.position?function(e){var t=Math.sqrt(e.titleBox.width*e.titleBox.width+e.titleBox.height*e.titleBox.height);return{x:e.cx,y:e.cy,scale:e.trace.hole*e.r*2/t,tx:0,ty:-e.titleBox.height/2+e.trace.title.font.size}}(h):C(h,i),r.attr("transform",u(t.x,t.y)+c(Math.min(1,t.scale))+u(t.tx,t.ty))})),_&&function(e,t){var r,n,a,i,o,l,s,c,u,d,f,p,h;function m(e,t){return e.pxmid[1]-t.pxmid[1]}function v(e,t){return t.pxmid[1]-e.pxmid[1]}function y(e,r){r||(r={});var a,c,u,f,p=r.labelExtraY+(n?r.yLabelMax:r.yLabelMin),h=n?e.yLabelMin:e.yLabelMax,m=n?e.yLabelMax:e.yLabelMin,v=e.cyFinal+o(e.px0[1],e.px1[1]),y=p-h;if(y*s>0&&(e.labelExtraY=y),Array.isArray(t.pull))for(c=0;c=(g.castOption(t.pull,u.pts)||0)||((e.pxmid[1]-u.pxmid[1])*s>0?(y=u.cyFinal+o(u.px0[1],u.px1[1])-h-e.labelExtraY)*s>0&&(e.labelExtraY+=y):(m+e.labelExtraY-v)*s>0&&(a=3*l*Math.abs(c-d.indexOf(e)),(f=u.cxFinal+i(u.px0[0],u.px1[0])+a-(e.cxFinal+e.pxmid[0])-e.labelExtraX)*l>0&&(e.labelExtraX+=f)))}for(n=0;n<2;n++)for(a=n?m:v,o=n?Math.max:Math.min,s=n?1:-1,r=0;r<2;r++){for(i=r?Math.max:Math.min,l=r?1:-1,(c=e[n][r]).sort(a),u=e[1-n][r],d=u.concat(c),p=[],f=0;fMath.abs(d)?l+="l"+d*e.pxmid[0]/e.pxmid[1]+","+d+"H"+(i+e.labelExtraX+c):l+="l"+e.labelExtraX+","+u+"v"+(d-u)+"h"+c}else l+="V"+(e.yLabelMid+e.labelExtraY)+"h"+c;s.ensureSingle(r,"path","textline").call(o.stroke,t.outsidetextfont.color).attr({"stroke-width":Math.min(2,t.outsidetextfont.size/8),d:l,fill:"none"})}else r.select("path.textline").remove()}))}(v,m),_&&m.automargin){var M=l.bBox(f.node()),k=m.domain,A=i.w*(k.x[1]-k.x[0]),L=i.h*(k.y[1]-k.y[0]),S=(.5*A-h.r)/i.w,O=(.5*L-h.r)/i.h;a.autoMargin(e,"pie."+m.uid+".automargin",{xl:k.x[0]-S,xr:k.x[1]+S,yb:k.y[0]-O,yt:k.y[1]+O,l:Math.max(h.cx-h.r-M.left,0),r:Math.max(M.right-(h.cx+h.r),0),b:Math.max(M.bottom-(h.cy+h.r),0),t:Math.max(h.cy-h.r-M.top,0),pad:5})}}))}));setTimeout((function(){f.selectAll("tspan").each((function(){var e=n.select(this);e.attr("dy")&&e.attr("dy",e.attr("dy"))}))}),0)},formatSliceLabel:z,transformInsideText:w,determineInsideTextFont:b,positionTitleOutside:C,prerenderTitles:_,layoutAreas:I,attachFxHandlers:x,computeTransform:N}},{"../../components/color":102,"../../components/drawing":124,"../../components/fx":142,"../../lib":232,"../../lib/svg_text_utils":255,"../../plots/plots":316,"../bar/constants":330,"../bar/uniform_text":344,"./event_data":349,"./helpers":350,"@plotly/d3":11}],355:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("./style_one"),i=e("../bar/uniform_text").resizeText;t.exports=function(e){var t=e._fullLayout._pielayer.selectAll(".trace");i(e,t,"pie"),t.each((function(e){var t=e[0].trace,r=n.select(this);r.style({opacity:t.opacity}),r.selectAll("path.surface").each((function(e){n.select(this).call(a,e,t)}))}))}},{"../bar/uniform_text":344,"./style_one":356,"@plotly/d3":11}],356:[function(e,t,r){"use strict";var n=e("../../components/color"),a=e("./helpers").castOption;t.exports=function(e,t,r){var i=r.marker.line,o=a(i.color,t.pts)||n.defaultLine,l=a(i.width,t.pts)||0;e.style("stroke-width",l).call(n.fill,t.color).call(n.stroke,o)}},{"../../components/color":102,"./helpers":350}],357:[function(e,t,r){"use strict";var n=e("../../lib");t.exports=function(e,t){for(var r=0;rs&&C[v].gap;)v--;for(x=C[v].s,m=C.length-1;m>v;m--)C[m].s=x;for(;sA[u]&&u=0;a--){var i=e[a];if("scatter"===i.type&&i.xaxis===r.xaxis&&i.yaxis===r.yaxis){i.opacity=void 0;break}}}}}},{}],365:[function(e,t,r){"use strict";var n=e("../../lib"),a=e("../../registry"),i=e("./attributes"),o=e("./constants"),l=e("./subtypes"),s=e("./xy_defaults"),c=e("./period_defaults"),u=e("./stack_defaults"),d=e("./marker_defaults"),f=e("./line_defaults"),p=e("./line_shape_defaults"),h=e("./text_defaults"),m=e("./fillcolor_defaults");t.exports=function(e,t,r,g){function v(r,a){return n.coerce(e,t,i,r,a)}var y=s(e,t,g,v);if(y||(t.visible=!1),t.visible){c(e,t,g,v),v("xhoverformat"),v("yhoverformat");var x=u(e,t,g,v),b=!x&&y=Math.min(t,r)&&h<=Math.max(t,r)?0:1/0}var n=Math.max(3,e.mrc||0),a=1-1/n,i=Math.abs(f.c2p(e.x)-h);return i=Math.min(t,r)&&m<=Math.max(t,r)?0:1/0}var n=Math.max(3,e.mrc||0),a=1-1/n,i=Math.abs(p.c2p(e.y)-m);return iZ!=(j=R[P][1])>=Z&&(N=R[P-1][0],F=R[P][0],j-E&&(z=N+(F-N)*(Z-E)/(j-E),V=Math.min(V,z),U=Math.max(U,z)));V=Math.max(V,0),U=Math.min(U,f._length);var W=l.defaultLine;return l.opacity(d.fillcolor)?W=d.fillcolor:l.opacity((d.line||{}).color)&&(W=d.line.color),n.extendFlat(e,{distance:e.maxHoverDistance,x0:V,x1:U,y0:Z,y1:Z,color:W,hovertemplate:!1}),delete e.index,d.text&&!Array.isArray(d.text)?e.text=String(d.text):e.text=d.name,[e]}}}},{"../../components/color":102,"../../components/fx":142,"../../lib":232,"../../registry":318,"./get_trace_color":368}],370:[function(e,t,r){"use strict";var n=e("./subtypes");t.exports={hasLines:n.hasLines,hasMarkers:n.hasMarkers,hasText:n.hasText,isBubble:n.isBubble,attributes:e("./attributes"),supplyDefaults:e("./defaults"),crossTraceDefaults:e("./cross_trace_defaults"),calc:e("./calc").calc,crossTraceCalc:e("./cross_trace_calc"),arraysToCalcdata:e("./arrays_to_calcdata"),plot:e("./plot"),colorbar:e("./marker_colorbar"),formatLabels:e("./format_labels"),style:e("./style").style,styleOnSelect:e("./style").styleOnSelect,hoverPoints:e("./hover"),selectPoints:e("./select"),animatable:!0,moduleType:"trace",name:"scatter",basePlotModule:e("../../plots/cartesian"),categories:["cartesian","svg","symbols","errorBarsOK","showLegend","scatter-like","zoomScale"],meta:{}}},{"../../plots/cartesian":293,"./arrays_to_calcdata":357,"./attributes":358,"./calc":359,"./cross_trace_calc":363,"./cross_trace_defaults":364,"./defaults":365,"./format_labels":367,"./hover":369,"./marker_colorbar":376,"./plot":379,"./select":380,"./style":382,"./subtypes":383}],371:[function(e,t,r){"use strict";var n=e("../../lib").isArrayOrTypedArray,a=e("../../components/colorscale/helpers").hasColorscale,i=e("../../components/colorscale/defaults");t.exports=function(e,t,r,o,l,s){var c=(e.marker||{}).color;(l("line.color",r),a(e,"line"))?i(e,t,o,l,{prefix:"line.",cLetter:"c"}):l("line.color",!n(c)&&c||r);l("line.width"),(s||{}).noDash||l("line.dash")}},{"../../components/colorscale/defaults":112,"../../components/colorscale/helpers":113,"../../lib":232}],372:[function(e,t,r){"use strict";var n=e("../../constants/numerical"),a=n.BADNUM,i=n.LOG_CLIP,o=i+.5,l=i-.5,s=e("../../lib"),c=s.segmentsIntersect,u=s.constrain,d=e("./constants");t.exports=function(e,t){var r,n,i,f,p,h,m,g,v,y,x,b,_,w,T,M,k,A,L=t.xaxis,S=t.yaxis,D="log"===L.type,C="log"===S.type,O=L._length,P=S._length,I=t.connectGaps,R=t.baseTolerance,z=t.shape,N="linear"===z,F=t.fill&&"none"!==t.fill,E=[],j=d.minTolerance,H=e.length,B=new Array(H),Y=0;function V(r){var n=e[r];if(!n)return!1;var i=t.linearized?L.l2p(n.x):L.c2p(n.x),s=t.linearized?S.l2p(n.y):S.c2p(n.y);if(i===a){if(D&&(i=L.c2p(n.x,!0)),i===a)return!1;C&&s===a&&(i*=Math.abs(L._m*P*(L._m>0?o:l)/(S._m*O*(S._m>0?o:l)))),i*=1e3}if(s===a){if(C&&(s=S.c2p(n.y,!0)),s===a)return!1;s*=1e3}return[i,s]}function U(e,t,r,n){var a=r-e,i=n-t,o=.5-e,l=.5-t,s=a*a+i*i,c=a*o+i*l;if(c>0&&cre||e[1]ae)return[u(e[0],te,re),u(e[1],ne,ae)]}function le(e,t){return e[0]===t[0]&&(e[0]===te||e[0]===re)||(e[1]===t[1]&&(e[1]===ne||e[1]===ae)||void 0)}function se(e,t,r){return function(n,a){var i=oe(n),o=oe(a),l=[];if(i&&o&&le(i,o))return l;i&&l.push(i),o&&l.push(o);var c=2*s.constrain((n[e]+a[e])/2,t,r)-((i||n)[e]+(o||a)[e]);c&&((i&&o?c>0==i[e]>o[e]?i:o:i||o)[e]+=c);return l}}function ce(e){var t=e[0],r=e[1],n=t===B[Y-1][0],a=r===B[Y-1][1];if(!n||!a)if(Y>1){var i=t===B[Y-2][0],o=r===B[Y-2][1];n&&(t===te||t===re)&&i?o?Y--:B[Y-1]=e:a&&(r===ne||r===ae)&&o?i?Y--:B[Y-1]=e:B[Y++]=e}else B[Y++]=e}function ue(e){B[Y-1][0]!==e[0]&&B[Y-1][1]!==e[1]&&ce([X,J]),ce(e),Q=null,X=J=0}function de(e){if(k=e[0]/O,A=e[1]/P,Z=e[0]re?re:0,W=e[1]ae?ae:0,Z||W){if(Y)if(Q){var t=$(Q,e);t.length>1&&(ue(t[0]),B[Y++]=t[1])}else K=$(B[Y-1],e)[0],B[Y++]=K;else B[Y++]=[Z||e[0],W||e[1]];var r=B[Y-1];Z&&W&&(r[0]!==Z||r[1]!==W)?(Q&&(X!==Z&&J!==W?ce(X&&J?(n=Q,i=(a=e)[0]-n[0],o=(a[1]-n[1])/i,(n[1]*a[0]-a[1]*n[0])/i>0?[o>0?te:re,ae]:[o>0?re:te,ne]):[X||Z,J||W]):X&&J&&ce([X,J])),ce([Z,W])):X-Z&&J-W&&ce([Z||X,W||J]),Q=e,X=Z,J=W}else Q&&ue($(Q,e)[0]),B[Y++]=e;var n,a,i,o}for("linear"===z||"spline"===z?$=function(e,t){for(var r=[],n=0,a=0;a<4;a++){var i=ie[a],o=c(e[0],e[1],t[0],t[1],i[0],i[1],i[2],i[3]);o&&(!n||Math.abs(o.x-r[0][0])>1||Math.abs(o.y-r[0][1])>1)&&(o=[o.x,o.y],n&&G(o,e)q(h,fe))break;i=h,(_=v[0]*g[0]+v[1]*g[1])>x?(x=_,f=h,m=!1):_=e.length||!h)break;de(h),n=h}}else de(f)}Q&&ce([X||Q[0],J||Q[1]]),E.push(B.slice(0,Y))}return E}},{"../../constants/numerical":212,"../../lib":232,"./constants":362}],373:[function(e,t,r){"use strict";t.exports=function(e,t,r){"spline"===r("line.shape")&&r("line.smoothing")}},{}],374:[function(e,t,r){"use strict";var n={tonextx:1,tonexty:1,tonext:1};t.exports=function(e,t,r){var a,i,o,l,s,c={},u=!1,d=-1,f=0,p=-1;for(i=0;i=0?s=p:(s=p=f,f++),s0?Math.max(r,i):0}}},{"fast-isnumeric":17}],376:[function(e,t,r){"use strict";t.exports={container:"marker",min:"cmin",max:"cmax"}},{}],377:[function(e,t,r){"use strict";var n=e("../../components/color"),a=e("../../components/colorscale/helpers").hasColorscale,i=e("../../components/colorscale/defaults"),o=e("./subtypes");t.exports=function(e,t,r,l,s,c){var u=o.isBubble(e),d=(e.line||{}).color;(c=c||{},d&&(r=d),s("marker.symbol"),s("marker.opacity",u?.7:1),s("marker.size"),s("marker.color",r),a(e,"marker")&&i(e,t,l,s,{prefix:"marker.",cLetter:"c"}),c.noSelect||(s("selected.marker.color"),s("unselected.marker.color"),s("selected.marker.size"),s("unselected.marker.size")),c.noLine||(s("marker.line.color",d&&!Array.isArray(d)&&t.marker.color!==d?d:u?n.background:n.defaultLine),a(e,"marker.line")&&i(e,t,l,s,{prefix:"marker.line.",cLetter:"c"}),s("marker.line.width",u?1:0)),u&&(s("marker.sizeref"),s("marker.sizemin"),s("marker.sizemode")),c.gradient)&&("none"!==s("marker.gradient.type")&&s("marker.gradient.color"))}},{"../../components/color":102,"../../components/colorscale/defaults":112,"../../components/colorscale/helpers":113,"./subtypes":383}],378:[function(e,t,r){"use strict";var n=e("../../lib").dateTick0,a=e("../../constants/numerical").ONEWEEK;function i(e,t){return n(t,e%a==0?1:0)}t.exports=function(e,t,r,n,a){if(a||(a={x:!0,y:!0}),a.x){var o=n("xperiod");o&&(n("xperiod0",i(o,t.xcalendar)),n("xperiodalignment"))}if(a.y){var l=n("yperiod");l&&(n("yperiod0",i(l,t.ycalendar)),n("yperiodalignment"))}}},{"../../constants/numerical":212,"../../lib":232}],379:[function(e,t,r){"use strict";var n=e("@plotly/d3"),a=e("../../registry"),i=e("../../lib"),o=i.ensureSingle,l=i.identity,s=e("../../components/drawing"),c=e("./subtypes"),u=e("./line_points"),d=e("./link_traces"),f=e("../../lib/polygon").tester;function p(e,t,r,d,p,h,m){var g;!function(e,t,r,a,o){var l=r.xaxis,s=r.yaxis,u=n.extent(i.simpleMap(l.range,l.r2c)),d=n.extent(i.simpleMap(s.range,s.r2c)),f=a[0].trace;if(!c.hasMarkers(f))return;var p=f.marker.maxdisplayed;if(0===p)return;var h=a.filter((function(e){return e.x>=u[0]&&e.x<=u[1]&&e.y>=d[0]&&e.y<=d[1]})),m=Math.ceil(h.length/p),g=0;o.forEach((function(e,r){var n=e[0].trace;c.hasMarkers(n)&&n.marker.maxdisplayed>0&&r0;function y(e){return v?e.transition():e}var x=r.xaxis,b=r.yaxis,_=d[0].trace,w=_.line,T=n.select(h),M=o(T,"g","errorbars"),k=o(T,"g","lines"),A=o(T,"g","points"),L=o(T,"g","text");if(a.getComponentMethod("errorbars","plot")(e,M,r,m),!0===_.visible){var S,D;y(T).style("opacity",_.opacity);var C=_.fill.charAt(_.fill.length-1);"x"!==C&&"y"!==C&&(C=""),d[0][r.isRangePlot?"nodeRangePlot3":"node3"]=T;var O,P,I="",R=[],z=_._prevtrace;z&&(I=z._prevRevpath||"",D=z._nextFill,R=z._polygons);var N,F,E,j,H,B,Y,V="",U="",q=[],G=i.noop;if(S=_._ownFill,c.hasLines(_)||"none"!==_.fill){for(D&&D.datum(d),-1!==["hv","vh","hvh","vhv"].indexOf(w.shape)?(N=s.steps(w.shape),F=s.steps(w.shape.split("").reverse().join(""))):N=F="spline"===w.shape?function(e){var t=e[e.length-1];return e.length>1&&e[0][0]===t[0]&&e[0][1]===t[1]?s.smoothclosed(e.slice(1),w.smoothing):s.smoothopen(e,w.smoothing)}:function(e){return"M"+e.join("L")},E=function(e){return F(e.reverse())},q=u(d,{xaxis:x,yaxis:b,connectGaps:_.connectgaps,baseTolerance:Math.max(w.width||1,3)/4,shape:w.shape,simplify:w.simplify,fill:_.fill}),Y=_._polygons=new Array(q.length),g=0;g1){var r=n.select(this);if(r.datum(d),e)y(r.style("opacity",0).attr("d",O).call(s.lineGroupStyle)).style("opacity",1);else{var a=y(r);a.attr("d",O),s.singleLineStyle(d,a)}}}}}var Z=k.selectAll(".js-line").data(q);y(Z.exit()).style("opacity",0).remove(),Z.each(G(!1)),Z.enter().append("path").classed("js-line",!0).style("vector-effect","non-scaling-stroke").call(s.lineGroupStyle).each(G(!0)),s.setClipUrl(Z,r.layerClipId,e),q.length?(S?(S.datum(d),j&&B&&(C?("y"===C?j[1]=B[1]=b.c2p(0,!0):"x"===C&&(j[0]=B[0]=x.c2p(0,!0)),y(S).attr("d","M"+B+"L"+j+"L"+V.substr(1)).call(s.singleFillStyle)):y(S).attr("d",V+"Z").call(s.singleFillStyle))):D&&("tonext"===_.fill.substr(0,6)&&V&&I?("tonext"===_.fill?y(D).attr("d",V+"Z"+I+"Z").call(s.singleFillStyle):y(D).attr("d",V+"L"+I.substr(1)+"Z").call(s.singleFillStyle),_._polygons=_._polygons.concat(R)):(X(D),_._polygons=null)),_._prevRevpath=U,_._prevPolygons=Y):(S?X(S):D&&X(D),_._polygons=_._prevRevpath=_._prevPolygons=null),A.datum(d),L.datum(d),function(t,a,i){var o,u=i[0].trace,d=c.hasMarkers(u),f=c.hasText(u),p=ee(u),h=te,m=te;if(d||f){var g=l,_=u.stackgroup,w=_&&"infer zero"===e._fullLayout._scatterStackOpts[x._id+b._id][_].stackgaps;u.marker.maxdisplayed||u._needsCull?g=w?Q:J:_&&!w&&(g=K),d&&(h=g),f&&(m=g)}var T,M=(o=t.selectAll("path.point").data(h,p)).enter().append("path").classed("point",!0);v&&M.call(s.pointStyle,u,e).call(s.translatePoints,x,b).style("opacity",0).transition().style("opacity",1),o.order(),d&&(T=s.makePointStyleFns(u)),o.each((function(t){var a=n.select(this),i=y(a);s.translatePoint(t,i,x,b)?(s.singlePointStyle(t,i,u,T,e),r.layerClipId&&s.hideOutsideRangePoint(t,i,x,b,u.xcalendar,u.ycalendar),u.customdata&&a.classed("plotly-customdata",null!==t.data&&void 0!==t.data)):i.remove()})),v?o.exit().transition().style("opacity",0).remove():o.exit().remove(),(o=a.selectAll("g").data(m,p)).enter().append("g").classed("textpoint",!0).append("text"),o.order(),o.each((function(e){var t=n.select(this),a=y(t.select("text"));s.translatePoint(e,a,x,b)?r.layerClipId&&s.hideOutsideRangePoint(e,t,x,b,u.xcalendar,u.ycalendar):t.remove()})),o.selectAll("text").call(s.textPointStyle,u,e).each((function(e){var t=x.c2p(e.x),r=b.c2p(e.y);n.select(this).selectAll("tspan.line").each((function(){y(n.select(this)).attr({x:t,y:r})}))})),o.exit().remove()}(A,L,d);var W=!1===_.cliponaxis?null:r.layerClipId;s.setClipUrl(A,W,e),s.setClipUrl(L,W,e)}function X(e){y(e).attr("d","M0,0Z")}function J(e){return e.filter((function(e){return!e.gap&&e.vis}))}function Q(e){return e.filter((function(e){return e.vis}))}function K(e){return e.filter((function(e){return!e.gap}))}function $(e){return e.id}function ee(e){if(e.ids)return $}function te(){return!1}}t.exports=function(e,t,r,a,i,c){var u,f,h=!i,m=!!i&&i.duration>0,g=d(e,t,r);((u=a.selectAll("g.trace").data(g,(function(e){return e[0].trace.uid}))).enter().append("g").attr("class",(function(e){return"trace scatter trace"+e[0].trace.uid})).style("stroke-miterlimit",2),u.order(),function(e,t,r){t.each((function(t){var a=o(n.select(this),"g","fills");s.setClipUrl(a,r.layerClipId,e);var i=t[0].trace,c=[];i._ownfill&&c.push("_ownFill"),i._nexttrace&&c.push("_nextFill");var u=a.selectAll("g").data(c,l);u.enter().append("g"),u.exit().each((function(e){i[e]=null})).remove(),u.order().each((function(e){i[e]=o(n.select(this),"path","js-fill")}))}))}(e,u,t),m)?(c&&(f=c()),n.transition().duration(i.duration).ease(i.easing).each("end",(function(){f&&f()})).each("interrupt",(function(){f&&f()})).each((function(){a.selectAll("g.trace").each((function(r,n){p(e,n,t,r,g,this,i)}))}))):u.each((function(r,n){p(e,n,t,r,g,this,i)}));h&&u.exit().remove(),a.selectAll("path:not([d])").remove()}},{"../../components/drawing":124,"../../lib":232,"../../lib/polygon":244,"../../registry":318,"./line_points":372,"./link_traces":374,"./subtypes":383,"@plotly/d3":11}],380:[function(e,t,r){"use strict";var n=e("./subtypes");t.exports=function(e,t){var r,a,i,o,l=e.cd,s=e.xaxis,c=e.yaxis,u=[],d=l[0].trace;if(!n.hasMarkers(d)&&!n.hasText(d))return[];if(!1===t)for(r=0;ra&&(a=u,o=c)}}return a?i(o):l};case"rms":return function(e,t){for(var r=0,a=0,o=0;o":return function(e){return f(e)>l};case">=":return function(e){return f(e)>=l};case"[]":return function(e){var t=f(e);return t>=l[0]&&t<=l[1]};case"()":return function(e){var t=f(e);return t>l[0]&&t=l[0]&&tl[0]&&t<=l[1]};case"][":return function(e){var t=f(e);return t<=l[0]||t>=l[1]};case")(":return function(e){var t=f(e);return tl[1]};case"](":return function(e){var t=f(e);return t<=l[0]||t>l[1]};case")[":return function(e){var t=f(e);return t=l[1]};case"{}":return function(e){return-1!==l.indexOf(f(e))};case"}{":return function(e){return-1===l.indexOf(f(e))}}}(r,i.getDataToCoordFunc(e,t,l,a),f),x={},b={},_=0;h?(g=function(e){x[e.astr]=n.extendDeep([],e.get()),e.set(new Array(d))},v=function(e,t){var r=x[e.astr][t];e.get()[t]=r}):(g=function(e){x[e.astr]=n.extendDeep([],e.get()),e.set([])},v=function(e,t){var r=x[e.astr][t];e.get().push(r)}),M(g);for(var w=o(t.transforms,r),T=0;T1?"%{group} (%{trace})":"%{group}");var s=e.styles,c=o.styles=[];if(s)for(i=0;i