diff --git a/CHANGELOG.rst b/CHANGELOG.rst index df3b2e05..50e1d286 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,18 @@ Not yet released ---------------- \- +v3.5.2 (released August 12, 2020) +----------------------------- + +* Fixed missing version numbers. +* See https://github.com/phpservermon/phpservermon/compare/v3.5.1...v3.5.2 + +v3.5.1 (released August 12, 2020) +----------------------------- + +* Security update regaring jQuery, see #972. +* See https://github.com/phpservermon/phpservermon/compare/v3.5.0...v3.5.1 + v3.5.0 (released May 1, 2020) ----------------------------- diff --git a/README.rst b/README.rst index b6a29294..832f23e3 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ PHP Server Monitor :alt: Join the chat at https://gitter.im/erickrf/nlpnet :target: https://gitter.im/phpservermon/phpservermon -Version 3.5.0 +Version 3.5.2 PHP Server Monitor is a script that checks whether your websites and servers are up and running. It comes with a web based user interface where you can manage your services and websites, @@ -60,6 +60,8 @@ The following SMS gateways are currently available: * SolutionsInfini - * Plivo - * Callr - +* SMSAPI - +* OVH SMS PRO - @@ -77,12 +79,21 @@ Requirements * Web server * MySQL database -* For PHP5: 5.6.0+ +* For PHP5: 5.5.9+ * For PHP7: 7.0.8+ -* PHP cURL package -* PHP PDO mysql driver -* PHP-XML +* PHP Extensions (modules) + * ext-curl + * ext-ctype + * ext-filter + * ext-hash + * ext-json + * ext-libxml + * ext-openssl + * ext-pdo + * ext-pcre + * ext-sockets + * ext-xml Install ------- diff --git a/composer.json b/composer.json index 88c4658d..0498c033 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "ext-curl": "*", + "ext-json": "*", "ext-pdo": "*", "ext-xml": "*", "phpmailer/phpmailer": ">=6.0.6 ~6.0", diff --git a/composer.lock b/composer.lock index 1ffbcc76..6ce79bb8 100644 --- a/composer.lock +++ b/composer.lock @@ -160,16 +160,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.1.3", + "version": "v6.1.6", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "a25ae38e03de4ee4031725498a600012364787c7" + "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a25ae38e03de4ee4031725498a600012364787c7", - "reference": "a25ae38e03de4ee4031725498a600012364787c7", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3", + "reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3", "shasum": "" }, "require": { @@ -218,7 +218,7 @@ } ], "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "time": "2019-11-21T09:37:46+00:00" + "time": "2020-05-27T12:24:03+00:00" }, { "name": "psr/container", diff --git a/docs/conf.py b/docs/conf.py index 20f6ef0c..13b77a40 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ copyright = u'2008-2017, Pepijn Over' # built documents. # # The short X.Y version. -version = '3.5.0' +version = '3.5' # The full version, including alpha/beta/rc tags. -release = '3.5.0' +release = '3.5.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/credits.rst b/docs/credits.rst index 6786fb1d..e1c89471 100644 --- a/docs/credits.rst +++ b/docs/credits.rst @@ -85,6 +85,10 @@ The following people have contributed to the development of PHP Server Monitor: * Nexmo SMS gateway +* Mateusz Małek - https://github.com/mateuszmalek + + * SMSAPI gateway + Translators +++++++++++ @@ -192,3 +196,4 @@ The following libraries are being used by PHP Server Monitor: * PHP-Pushover - https://github.com/kryap/php-pushover * Symfony - https://symfony.com * Random_compat - https://github.com/paragonie/random_compat +* Hammer.js - https://github.com/hammerjs/hammer.js \ No newline at end of file diff --git a/docs/faq.rst b/docs/faq.rst index 19a0db2b..04f6e05a 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -51,7 +51,7 @@ The other way is to parse the access logs created by your webserver software, wh When using tools such as Google Analytics, the monitor requests will not show up in your statistics, because the monitor does not execute any Javascript. Tools that parse your raw access logs like Awstats, will include the requests made by the monitor. -To make sure these requests can be identified, the monitor uses a custom user agent, which you can usually filter out. The user agent of the monitor looks like:: +To make sure these requests can be identified, the monitor uses a custom user agent, which you can usually filter out. The user agent can be modified in the config section, but bij default looks like:: Mozilla/5.0 (compatible; phpservermon/3.0.1; +http://www.phpservermonitor.org) diff --git a/docs/requirements.rst b/docs/requirements.rst index 0a0f9838..636dde40 100644 --- a/docs/requirements.rst +++ b/docs/requirements.rst @@ -5,7 +5,7 @@ Requirements * Web server * MySQL database -* For PHP5: 5.6.0+ +* For PHP5: 5.5.9+ * For PHP7: 7.0.8+ * PHP cURL package * PHP PDO mysql driver diff --git a/src/includes/functions.inc.php b/src/includes/functions.inc.php index 493bded9..59717599 100644 --- a/src/includes/functions.inc.php +++ b/src/includes/functions.inc.php @@ -34,21 +34,21 @@ namespace { # ############################################### -/** - * Retrieve language settings from the selected language file - * Return false if arg is not found - * - * @return string|bool - * @see psm_load_lang() - */ + /** + * Retrieve language settings from the selected language file + * Return false if arg is not found + * + * @return string|bool + * @see psm_load_lang() + */ function psm_get_lang() { $args = func_get_args(); - + if (empty($args)) { return isset($GLOBALS['sm_lang']) ? $GLOBALS['sm_lang'] : $GLOBALS['sm_lang_default']; } - + if (isset($GLOBALS['sm_lang'])) { $lang = $GLOBALS['sm_lang']; $not_found = false; @@ -72,13 +72,13 @@ namespace { return $lang; } -/** - * Load default language from the English (en_US) language file to the $GLOBALS['sm_lang_default'] variable - * Load language from the language file to the $GLOBALS['sm_lang'] variable if language is different from default - * - * @param string $lang language - * @see psm_get_lang() - */ + /** + * Load default language from the English (en_US) language file to the $GLOBALS['sm_lang_default'] variable + * Load language from the language file to the $GLOBALS['sm_lang'] variable if language is different from default + * + * @param string $lang language + * @see psm_get_lang() + */ function psm_load_lang($lang) { // load default language - English (en_US) @@ -86,29 +86,29 @@ namespace { $default_lang_file = PSM_PATH_LANG . 'en_US.lang.php'; file_exists($default_lang_file) ? require $default_lang_file : - trigger_error("English translation needs to be installed at all time!", E_USER_ERROR); + trigger_error("English translation needs to be installed at all time!", E_USER_ERROR); isset($sm_lang) ? $GLOBALS['sm_lang_default'] = $sm_lang : - trigger_error("\$sm_lang not found in English translation!", E_USER_ERROR); + trigger_error("\$sm_lang not found in English translation!", E_USER_ERROR); unset($sm_lang); // load translation is the selected language is not English (en_US) if ($lang != "en_US") { $lang_file = PSM_PATH_LANG . $lang . '.lang.php'; file_exists($lang_file) ? require $lang_file : - trigger_error("Translation file could not be found! Default language will be used.", E_USER_WARNING); - + trigger_error("Translation file could not be found! Default language will be used.", E_USER_WARNING); + isset($sm_lang) ? $GLOBALS['sm_lang'] = $sm_lang : - trigger_error("\$sm_lang not found in translation file! Default language will be used.", E_USER_WARNING); + trigger_error("\$sm_lang not found in translation file! Default language will be used.", E_USER_WARNING); isset($sm_lang['locale']) ? setlocale(LC_TIME, $sm_lang['locale']) : - trigger_error("locale could not ben found in translation file.", E_USER_WARNING); + trigger_error("locale could not ben found in translation file.", E_USER_WARNING); } } -/** - * Retrieve a list with keys of the available languages - * - * @return array - * @see psm_load_lang() - */ + /** + * Retrieve a list with keys of the available languages + * + * @return array + * @see psm_load_lang() + */ function psm_get_langs() { $fn_ext = '.lang.php'; @@ -133,11 +133,11 @@ namespace { return $langs; } -/** - * Retrieve a list with available sms gateways - * - * @return array - */ + /** + * Retrieve a list with available sms gateways + * + * @return array + */ function psm_get_sms_gateways() { $sms_gateway_files = glob(PSM_PATH_SMS_GATEWAY . '*.php'); @@ -155,14 +155,14 @@ namespace { return $sms_gateways; } -/** - * Get a setting from the config. - * - * @param string $key - * @param mixed $alt if not set, return this alternative - * @return string - * @see psm_load_conf() - */ + /** + * Get a setting from the config. + * + * @param string $key + * @param mixed $alt if not set, return this alternative + * @return string + * @see psm_load_conf() + */ function psm_get_conf($key, $alt = null) { if (!isset($GLOBALS['sm_config'])) { @@ -173,13 +173,13 @@ namespace { return $result; } -/** - * Load config from the database to the $GLOBALS['sm_config'] variable - * - * @global object $db - * @return boolean - * @see psm_get_conf() - */ + /** + * Load config from the database to the $GLOBALS['sm_config'] variable + * + * @return boolean + * @global object $db + * @see psm_get_conf() + */ function psm_load_conf() { global $db; @@ -204,14 +204,14 @@ namespace { } } -/** - * Update a config setting. - * - * If the key does not exist yet it will be created. - * @global \psm\Service\Database $db - * @param string $key - * @param string $value - */ + /** + * Update a config setting. + * + * If the key does not exist yet it will be created. + * @param string $key + * @param string $value + * @global \psm\Service\Database $db + */ function psm_update_conf($key, $value) { global $db; @@ -223,8 +223,8 @@ namespace { $db->save( PSM_DB_PREFIX . 'config', array( - 'key' => $key, - 'value' => $value, + 'key' => $key, + 'value' => $value, ) ); } else { @@ -243,16 +243,16 @@ namespace { # ############################################### -/** - * This function merely adds the message to the log table. It does not perform any checks, - * everything should have been handled when calling this function - * - * @param string $server_id - * @param string $type - * @param string $message - * - * @return int log_id - */ + /** + * This function merely adds the message to the log table. It does not perform any checks, + * everything should have been handled when calling this function + * + * @param string $server_id + * @param string $type + * @param string $message + * + * @return int log_id + */ function psm_add_log($server_id, $type, $message) { global $db; @@ -260,19 +260,19 @@ namespace { return $db->save( PSM_DB_PREFIX . 'log', array( - 'server_id' => $server_id, - 'type' => $type, - 'message' => $message, + 'server_id' => $server_id, + 'type' => $type, + 'message' => $message, ) ); } -/** - * This function just adds a user to the log_users table. - * - * @param $log_id - * @param $user_id - */ + /** + * This function just adds a user to the log_users table. + * + * @param $log_id + * @param $user_id + */ function psm_add_log_user($log_id, $user_id) { global $db; @@ -280,19 +280,19 @@ namespace { $db->save( PSM_DB_PREFIX . 'log_users', array( - 'log_id' => $log_id, - 'user_id' => $user_id, + 'log_id' => $log_id, + 'user_id' => $user_id, ) ); } -/** - * This function adds the result of a check to the uptime table for logging purposes. - * - * @param int $server_id - * @param int $status - * @param string $latency - */ + /** + * This function adds the result of a check to the uptime table for logging purposes. + * + * @param int $server_id + * @param int $status + * @param string $latency + */ function psm_log_uptime($server_id, $status, $latency) { global $db; @@ -300,62 +300,62 @@ namespace { $db->save( PSM_DB_PREFIX . 'servers_uptime', array( - 'server_id' => $server_id, - 'date' => date('Y-m-d H:i:s'), - 'status' => $status, - 'latency' => $latency, + 'server_id' => $server_id, + 'date' => date('Y-m-d H:i:s'), + 'status' => $status, + 'latency' => $latency, ) ); } -/** - * Converts an interval into a string - * - * @param DateInterval $interval - * @return string - */ + /** + * Converts an interval into a string + * + * @param DateInterval $interval + * @return string + */ function psm_format_interval(DateInterval $interval) { $result = ""; if ($interval->y) { $result .= $interval->format("%y ") . (($interval->y == 1) ? - psm_get_lang('system', 'year') : psm_get_lang('system', 'years')) . " "; + psm_get_lang('system', 'year') : psm_get_lang('system', 'years')) . " "; } if ($interval->m) { $result .= $interval->format("%m ") . (($interval->m == 1) ? - psm_get_lang('system', 'month') : psm_get_lang('system', 'months')) . " "; + psm_get_lang('system', 'month') : psm_get_lang('system', 'months')) . " "; } if ($interval->d) { $result .= $interval->format("%d ") . (($interval->d == 1) ? - psm_get_lang('system', 'day') : psm_get_lang('system', 'days')) . " "; + psm_get_lang('system', 'day') : psm_get_lang('system', 'days')) . " "; } if ($interval->h) { $result .= $interval->format("%h ") . (($interval->h == 1) ? - psm_get_lang('system', 'hour') : psm_get_lang('system', 'hours')) . " "; + psm_get_lang('system', 'hour') : psm_get_lang('system', 'hours')) . " "; } if ($interval->i) { $result .= $interval->format("%i ") . (($interval->i == 1) ? - psm_get_lang('system', 'minute') : psm_get_lang('system', 'minutes')) . " "; + psm_get_lang('system', 'minute') : psm_get_lang('system', 'minutes')) . " "; } if ($interval->s) { $result .= $interval->format("%s ") . (($interval->s == 1) ? - psm_get_lang('system', 'second') : psm_get_lang('system', 'seconds')) . " "; + psm_get_lang('system', 'second') : psm_get_lang('system', 'seconds')) . " "; } return $result; } -/** - * Parses a string from the language file with the correct variables replaced in the message - * - * @param boolean|null $status - * @param string $type is either 'sms', 'email', 'pushover_title', 'pushover_message' or 'telegram_message' - * @param array $vars server information about the server which may be placed in a message: - * %KEY% will be replaced by your value - * @param boolean $combi parse other message if notifications need to be send combined - * @return string parsed message - */ + /** + * Parses a string from the language file with the correct variables replaced in the message + * + * @param boolean|null $status + * @param string $type is either 'sms', 'email', 'pushover_title', 'pushover_message', 'webhook_title', 'webhook_message' or 'telegram_message' + * @param array $vars server information about the server which may be placed in a message: + * %KEY% will be replaced by your value + * @param boolean $combi parse other message if notifications need to be send combined + * @return string parsed message + */ function psm_parse_msg($status, $type, $vars, $combi = false) { if (is_bool($status)) { @@ -378,20 +378,20 @@ namespace { return $message; } -/** - * Shortcut to curl_init(), curl_exec and curl_close() - * - * @param string $href - * @param boolean $header return headers? - * @param boolean $body return body? - * @param int|null $timeout connection timeout in seconds. defaults to PSM_CURL_TIMEOUT (10 secs). - * @param boolean $add_agent add user agent? - * @param string|bool $website_username Username website - * @param string|bool $website_password Password website - * @param string|null $request_method Request method like GET, POST etc. - * @param string|null $post_field POST data - * @return array cURL result - */ + /** + * Shortcut to curl_init(), curl_exec and curl_close() + * + * @param string $href + * @param boolean $header return headers? + * @param boolean $body return body? + * @param int|null $timeout connection timeout in seconds. defaults to PSM_CURL_TIMEOUT (10 secs). + * @param boolean $add_agent add user agent? + * @param string|bool $website_username Username website + * @param string|bool $website_password Password website + * @param string|null $request_method Request method like GET, POST etc. + * @param string|null $post_field POST data + * @return array cURL result + */ function psm_curl_get( $href, $header = false, @@ -419,7 +419,7 @@ namespace { curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_CERTINFO, 1); - + if (!empty($request_method)) { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request_method); } @@ -452,33 +452,33 @@ namespace { } if ($add_agent) { - curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; phpservermon/' . - PSM_VERSION . '; +https://github.com/phpservermon/phpservermon)'); + curl_setopt($ch, CURLOPT_USERAGENT, psm_get_conf('user_agent', 'Mozilla/5.0 (compatible; phpservermon/' . + PSM_VERSION . '; +https://github.com/phpservermon/phpservermon)')); } $result['exec'] = curl_exec($ch); $result['info'] = curl_getinfo($ch); curl_close($ch); - + if (defined('PSM_DEBUG') && PSM_DEBUG === true && psm_is_cli()) { echo PHP_EOL . - '==============cURL Result for: ' . $href . '===========================================' . PHP_EOL; + '==============cURL Result for: ' . $href . '===========================================' . PHP_EOL; print_r($result); echo PHP_EOL . - '==============END cURL Resul for: ' . $href . '===========================================' . PHP_EOL; + '==============END cURL Resul for: ' . $href . '===========================================' . PHP_EOL; } return $result; } -/** - * Get a "nice" timespan message - * - * Source: http://www.interactivetools.com/forum/forum-posts.php?postNum=2208966 - * @param string $time - * @return string - */ + /** + * Get a "nice" timespan message + * + * Source: http://www.interactivetools.com/forum/forum-posts.php?postNum=2208966 + * @param string $time + * @return string + */ function psm_timespan($time) { if (empty($time) || $time == '0000-00-00 00:00:00') { @@ -489,7 +489,7 @@ namespace { } if ($time < strtotime(date('Y-m-d 00:00:00')) - 60 * 60 * 24 * 3) { $format = psm_get_lang('system', (date('Y') !== date('Y', $time)) ? - 'long_day_format' : 'short_day_format'); + 'long_day_format' : 'short_day_format'); // Check for Windows to find and replace the %e // modifier correctly if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { @@ -500,7 +500,7 @@ namespace { $d = time() - $time; if ($d >= 60 * 60 * 24) { $format = psm_get_lang('system', (date('l', time() - 60 * 60 * 24) == date('l', $time)) ? - 'yesterday_format' : 'other_day_format'); + 'yesterday_format' : 'other_day_format'); return strftime($format, $time); } if ($d >= 60 * 60 * 2) { @@ -522,11 +522,11 @@ namespace { return psm_get_lang('system', 'a_second_ago'); } -/** - * Get a localised date from MySQL date format - * @param string $time - * @return string - */ + /** + * Get a localised date from MySQL date format + * @param string $time + * @return string + */ function psm_date($time) { if (empty($time) || $time == '0000-00-00 00:00:00') { @@ -535,12 +535,12 @@ namespace { return strftime('%x %X', strtotime($time)); } -/** - * Check if an update is available for PHP Server Monitor. - * - * Will only check for new version if user turned updates on in config. - * @return boolean - */ + /** + * Check if an update is available for PHP Server Monitor. + * + * Will only check for new version if user turned updates on in config. + * @return boolean + */ function psm_update_available() { if (!psm_get_conf('show_update')) { @@ -582,14 +582,14 @@ namespace { return version_compare($latestVersion, $current, '>'); } -/** - * Prepare a new phpmailer instance. - * - * If the from name and email are left blank they will be prefilled from the config. - * @param string $from_name - * @param string $from_email - * @return \PHPMailer\PHPMailer\PHPMailer - */ + /** + * Prepare a new phpmailer instance. + * + * If the from name and email are left blank they will be prefilled from the config. + * @param string $from_name + * @param string $from_email + * @return \PHPMailer\PHPMailer\PHPMailer + */ function psm_build_mail($from_name = null, $from_email = null) { $phpmailer = new \PHPMailer\PHPMailer\PHPMailer(); @@ -600,7 +600,7 @@ namespace { if (psm_get_conf('email_smtp') == '1') { $phpmailer->IsSMTP(); $phpmailer->Host = psm_get_conf('email_smtp_host'); - $phpmailer->Port = (int) psm_get_conf('email_smtp_port'); + $phpmailer->Port = (int)psm_get_conf('email_smtp_port'); $phpmailer->SMTPSecure = psm_get_conf('email_smtp_security'); $smtp_user = psm_get_conf('email_smtp_username'); @@ -628,11 +628,11 @@ namespace { return $phpmailer; } -/** - * Prepare a new Pushover util. - * - * @return \Pushover - */ + /** + * Prepare a new Pushover util. + * + * @return \Pushover + */ function psm_build_pushover() { $pushover = new \Pushover(); @@ -641,10 +641,22 @@ namespace { return $pushover; } -/** - * - * @return \Telegram - */ + /** + * Prepare a new Webhook util. + * + * @return Webhook + */ + function psm_build_webhook() + { + $webhook = new Webhook(); + + return $webhook; + } + + /** + * + * @return \Telegram + */ function psm_build_telegram() { $telegram = new \Telegram(); @@ -656,12 +668,12 @@ namespace { /** * Send message via XMPP. * - * @param string $host - * @param string $username - * @param string $password - * @param array $receivers - * @param string $message - * @param int|null $port + * @param string $host + * @param string $username + * @param string $password + * @param array $receivers + * @param string $message + * @param int|null $port * @param string|null $domain */ function psm_jabber_send_message($host, $username, $password, $receivers, $message, $port = null, $domain = null) @@ -689,7 +701,7 @@ namespace { $client->add_cb('on_auth_success', function () use ($client, $receivers, $message) { JAXLLogger::info('got on_auth_success cb'); foreach ($receivers as $receiver) { - $client->send_chat_msg($receiver, $message); + $client->send_chat_msg($receiver, $message); } $client->send_end_stream(); }); @@ -707,11 +719,11 @@ namespace { } } -/** - * Prepare a new SMS util. - * - * @return \psm\Txtmsg\TxtmsgInterface - */ + /** + * Prepare a new SMS util. + * + * @return \psm\Txtmsg\TxtmsgInterface + */ function psm_build_sms() { $sms = null; @@ -758,6 +770,9 @@ namespace { case 'octopush': $sms = new \psm\Txtmsg\Octopush(); break; + case 'ovhsms': + $sms = new \psm\Txtmsg\OVHsms(); + break; case 'smsgw': $sms = new \psm\Txtmsg\Smsgw(); break; @@ -781,6 +796,8 @@ namespace { break; case 'ysmal': $sms = new \psm\Txtmsg\Ysmal(); + case 'smsapi': + $sms = new \psm\Txtmsg\SMSAPI(); break; } @@ -793,20 +810,20 @@ namespace { return $sms; } -/** - * Generate a new link to the current monitor - * @param array|string $params key value pairs or pre-formatted string - * @param boolean $urlencode urlencode all params? - * @param boolean $htmlentities use entities in url? - * @return string - */ + /** + * Generate a new link to the current monitor + * @param array|string $params key value pairs or pre-formatted string + * @param boolean $urlencode urlencode all params? + * @param boolean $htmlentities use entities in url? + * @return string + */ function psm_build_url($params = array(), $urlencode = true, $htmlentities = true) { if (defined('PSM_BASE_URL') && PSM_BASE_URL !== null) { $url = PSM_BASE_URL; } else { $url = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || - $_SERVER['SERVER_PORT'] == 443 ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; + $_SERVER['SERVER_PORT'] == 443 ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST']; // on Windows, dirname() adds both back- and forward slashes (http://php.net/dirname). // for urls, we only want the forward slashes. $url .= dirname($_SERVER['SCRIPT_NAME']); @@ -833,12 +850,12 @@ namespace { return $url; } -/** - * Try existence of a GET var, if not return the alternative - * @param string $key - * @param string $alt - * @return mixed - */ + /** + * Try existence of a GET var, if not return the alternative + * @param string $key + * @param string $alt + * @return mixed + */ function psm_GET($key, $alt = null) { if (isset($_GET[$key])) { @@ -848,12 +865,12 @@ namespace { } } -/** - * Try existence of a POST var, if not return the alternative - * @param string $key - * @param string|array|bool $alt - * @return mixed - */ + /** + * Try existence of a POST var, if not return the alternative + * @param string $key + * @param string|array|bool $alt + * @return mixed + */ function psm_POST($key, $alt = null) { if (isset($_POST[$key])) { @@ -863,12 +880,12 @@ namespace { } } -/** - * Check if we are in CLI mode - * - * Note, php_sapi cannot be used because cgi-fcgi returns both for web and cli. - * @return boolean - */ + /** + * Check if we are in CLI mode + * + * Note, php_sapi cannot be used because cgi-fcgi returns both for web and cli. + * @return boolean + */ function psm_is_cli() { return (!isset($_SERVER['SERVER_SOFTWARE']) || php_sapi_name() == 'cli'); @@ -880,11 +897,11 @@ namespace { # ############################################### -/** - * Only used for debugging and testing - * - * @param mixed $arr - */ + /** + * Only used for debugging and testing + * + * @param mixed $arr + */ function pre($arr = null) { echo "
";
@@ -895,9 +912,9 @@ namespace {
         echo "
"; } -/** - * Send headers to the browser to avoid caching - */ + /** + * Send headers to the browser to avoid caching + */ function psm_no_cache() { header("Expires: Mon, 20 Dec 1998 01:00:00 GMT"); @@ -906,14 +923,14 @@ namespace { header("Pragma: no-cache"); } -/** - * Encrypts the password for storage in the database - * - * @param string $key - * @param string $password - * @return string - * @author Pavel Laupe Dvorak - */ + /** + * Encrypts the password for storage in the database + * + * @param string $key + * @param string $password + * @return string + * @author Pavel Laupe Dvorak + */ function psm_password_encrypt($key, $password) { if (empty($password)) { @@ -942,14 +959,14 @@ namespace { return $encrypted; } -/** - * Decrypts password stored in the database for future use - * - * @param string $key - * @param string $encryptedString - * @return string - * @author Pavel Laupe Dvorak - */ + /** + * Decrypts password stored in the database for future use + * + * @param string $key + * @param string $encryptedString + * @return string + * @author Pavel Laupe Dvorak + */ function psm_password_decrypt($key, $encryptedString) { if (empty($encryptedString)) { @@ -957,7 +974,7 @@ namespace { } if (empty($key)) { - throw new \InvalidArgumentException('invalid_encryption_key'); + throw new \InvalidArgumentException('invalid_encryption_key'); } // using open ssl @@ -972,16 +989,16 @@ namespace { OPENSSL_RAW_DATA, $iv ); - + return $decrypted; } -/** -* Send notification to Telegram -* -* @return string -* @author Tim Zandbergen -*/ + /** + * Send notification to Telegram + * + * @return string + * @author Tim Zandbergen + */ class Telegram { private $token; @@ -991,12 +1008,14 @@ namespace { public function setToken($token) { - $this->token = (string) $token; + $this->token = (string)$token; } + public function setUser($user) { - $this->user = (string) $user; + $this->user = (string)$user; } + public function setMessage($message) { $message = str_replace("
    ", "", $message); @@ -1005,8 +1024,9 @@ namespace { $message = str_replace("", "\n", $message); $message = str_replace("
    ", "\n", $message); $message = str_replace("
    ", "\n", $message); - $this->message = (string) $message; + $this->message = (string)$message; } + public function sendurl() { $con = curl_init($this->url); @@ -1017,15 +1037,17 @@ namespace { $response = json_decode($response, true); return $response; } + public function send() { if (!empty($this->token) && !empty($this->user) && !empty($this->message)) { $this->url = 'https://api.telegram.org/bot' . urlencode($this->token) . - '/sendMessage?chat_id=' . urlencode($this->user) . '&text=' . - urlencode($this->message) . '&parse_mode=HTML&disable_web_page_preview=True'; + '/sendMessage?chat_id=' . urlencode($this->user) . '&text=' . + urlencode($this->message) . '&parse_mode=HTML&disable_web_page_preview=True'; } return $this->sendurl(); } + // Get the bots username public function getBotUsername() { @@ -1035,4 +1057,121 @@ namespace { return $this->sendurl(); } } + + /** + * Send notification via webhooks + * + * @return string + * @author Malte Grosse + */ + class Webhook + { + protected $url; + protected $json; + protected $message; + + /** + * Send Webhook + * + * @return bool|string + * @var string $message + * + */ + + public function sendWebhook($message) + { + $error = ""; + $success = 1; + + $this->setMessage($message); + $jsonMessage = strtr($this->json, array('#message' => $this->message)); + + $curl = curl_init($this->url); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonMessage); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($curl, CURLOPT_TIMEOUT, 60); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); + $result = curl_exec($curl); + + $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $err = curl_errno($curl); + + if ($err != 0 || $httpcode < 200 || $httpcode >= 300) { + $success = 0; + $error = "HTTP_code: " . $httpcode . ".\ncURL error (" . $err . "): " . $err . ". \nResult: " . $result; + } + + curl_close($curl); + + if ($success) { + return 1; + } + return $error; + } + + /** + * setUrl + * + * @var string $url + * + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * getUrl + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * setJson + * + * @var string $json + * + */ + public function setJson($json) + { + $this->json = $json; + } + + /** + * getJson + * + * @return string + */ + public function getJson() + { + return $this->json; + } + + /** + * Set message + * + * @return string + * @var string $message + * + */ + public function setMessage($message) + { + $message = str_replace("
      ", "", $message); + $message = str_replace("
    ", "\n", $message); + $message = str_replace("
  • ", "- ", $message); + $message = str_replace("
  • ", "\n", $message); + $message = str_replace("
    ", "\n", $message); + $message = str_replace("
    ", "\n", $message); + $message = str_replace("", "", $message); + $message = str_replace("", "", $message); + $message = strip_tags($message); + $this->message = (string)$message; + } + } } diff --git a/src/includes/psmconfig.inc.php b/src/includes/psmconfig.inc.php index 2491f172..25d33fb8 100644 --- a/src/includes/psmconfig.inc.php +++ b/src/includes/psmconfig.inc.php @@ -30,7 +30,7 @@ /** * Current PSM version */ -define('PSM_VERSION', '3.5.0'); +define('PSM_VERSION', '3.6.0.beta1'); /** * URL to check for updates. Will not be checked if turned off on config page. diff --git a/src/lang/bg_BG.lang.php b/src/lang/bg_BG.lang.php index c38d7fab..1e4f3846 100644 --- a/src/lang/bg_BG.lang.php +++ b/src/lang/bg_BG.lang.php @@ -66,6 +66,7 @@ $sm_lang = array( 'a_minute_ago' => 'преди минута', 'seconds_ago' => 'преди %d секунди', 'a_second_ago' => 'преди секунда', + 'seconds' => 'секунди', ), 'menu' => array( 'config' => 'Настройки', @@ -219,8 +220,6 @@ $sm_lang = array( 'email_smtp' => 'Активиране на SMTP', 'email_smtp_host' => 'SMTP сървър', 'email_smtp_port' => 'SMTP порт', - 'email_smtp_security' => 'SMTP security', - 'email_smtp_security_none' => 'None', 'email_smtp_username' => 'SMTP потребителско име', 'email_smtp_password' => 'SMTP парола', 'email_smtp_noauth' => 'Оставете празно за "без аутентикация"', @@ -267,7 +266,6 @@ $sm_lang = array( системата', 'log_sms' => 'Да се пази ли лог на изпратените SMS съобщения от системата', - 'log_pushover' => 'Log pushover messages sent by the script', 'updated' => 'Настройките са обновени успешно.', 'tab_email' => 'Имейл', 'tab_sms' => 'SMS', diff --git a/src/lang/ca_ES.lang.php b/src/lang/ca_ES.lang.php index 4be6eb74..94dcc8d6 100644 --- a/src/lang/ca_ES.lang.php +++ b/src/lang/ca_ES.lang.php @@ -347,7 +347,6 @@ $sm_lang = array( 'auto_refresh' => 'Auto-recàrrega', 'auto_refresh_description' => 'Recarregar automàticament la plana Servidors.
    Temps en segons, si poseu ZERO la plana no s\'auto-recarregarà.', - 'seconds' => 'segons', 'test' => 'Provar', 'test_email' => 'S\'enviarà un correu a l\'adreça que teniu al vostre perfil d\'usuari.', 'test_sms' => 'S\'enviarà un SMS al telèfon que teniu al vostre perfil d\'usuari.', diff --git a/src/lang/cs_CZ.lang.php b/src/lang/cs_CZ.lang.php index e4360f5a..fff3a945 100644 --- a/src/lang/cs_CZ.lang.php +++ b/src/lang/cs_CZ.lang.php @@ -68,6 +68,7 @@ $sm_lang = array( 'a_minute_ago' => 'cca před minutou', 'seconds_ago' => 'před %d vteřinami', 'a_second_ago' => 'před chvílí', + 'seconds' => 'sekunder', ), 'menu' => array( 'config' => 'Konfigurace', @@ -260,7 +261,6 @@ $sm_lang = array( 'auto_refresh' => 'Automaticky obnovit', 'auto_refresh_description' => 'Automaticky obnovit stránku Servery.
    Čas v sekundách, 0 pro vypnutí automatického obnovení.', - 'seconds' => 'sekund', 'test' => 'Test', 'test_email' => 'E-mail bude odeslán na adresu uvedenou v uživatelském profilu.', 'test_sms' => 'SMS bude odeslána na telefonní číslo uvedené v uživatelském profilu.', diff --git a/src/lang/da_DK.lang.php b/src/lang/da_DK.lang.php index 662d1620..4b805583 100644 --- a/src/lang/da_DK.lang.php +++ b/src/lang/da_DK.lang.php @@ -65,6 +65,7 @@ $sm_lang = array( 'a_minute_ago' => 'omkring et minut siden', 'seconds_ago' => '%d sekunder siden', 'a_second_ago' => 'et sekund siden', + 'seconds' => 'sekunder', ), 'menu' => array( 'config' => 'Indstillinger', @@ -245,7 +246,6 @@ $sm_lang = array( 'auto_refresh' => 'Genopfrisk automatisk', 'auto_refresh_description' => 'Genopfrisk automatisk serversider.
    Tid i sekunder. Hvis 0 vil siden ikke genopfriske automatisk', - 'seconds' => 'sekunder', 'test' => 'Test', 'test_email' => 'En e-mail vil blive sendt til den adresse, der er angivet i din brugerprofil.', 'test_sms' => 'En SMS vil blive sendt til det nummer, der er angivet i din brugerprofil.', diff --git a/src/lang/de_DE.lang.php b/src/lang/de_DE.lang.php index ea8b4074..9f0473de 100644 --- a/src/lang/de_DE.lang.php +++ b/src/lang/de_DE.lang.php @@ -66,6 +66,7 @@ $sm_lang = array( 'a_minute_ago' => 'vor über einer Minute', 'seconds_ago' => 'vor %d Sekunden', 'a_second_ago' => 'vor über einer Sekunde', + 'seconds' => 'Sekunden', ), 'menu' => array( 'config' => 'Einstellungen', @@ -249,7 +250,6 @@ $sm_lang = array( 'auto_refresh_description' => 'Automatische Aktualisierung der Server-Übersichtsseite
    Zeit in Sekunden - die Ziffer \'0\' deaktiviert die automatische Aktualisierung.', - 'seconds' => 'Sekunden', 'test' => 'Test', 'test_email' => 'Eine E-Mail wird an die E-Mail-Adresse gesendet, die in Ihrem Profil hinterlegt ist.', 'test_sms' => 'Eine SMS wird an die Telefonnummer gesendet, die in Ihrem Profil hinterlegt ist.', diff --git a/src/lang/en_US.lang.php b/src/lang/en_US.lang.php index 3c461c14..acec4425 100644 --- a/src/lang/en_US.lang.php +++ b/src/lang/en_US.lang.php @@ -83,6 +83,8 @@ $sm_lang = array( 'minutes' => 'minutes', 'second' => 'second', 'seconds' => 'seconds', + 'millisecond' => 'millisecond', + 'milliseconds' => 'milliseconds', 'current' => 'current', 'settings' => 'Settings', 'search' => 'Search', @@ -136,6 +138,13 @@ $sm_lang = array( 'jabber' => 'Jabber', 'jabber_label' => 'Jabber', 'jabber_description' => 'You Jabber account', + 'webhook' => 'Webhook', + 'webhook_description' => 'Send a json webhook to a certain endpoint.
    The json can be customized, e.g. { +"text":"servermon: #message"}', + 'webhook_url' => 'Webhook Url', + 'webhook_url_description' => 'Webhook public endpoint url, should start with https://.', + 'webhook_json' => 'Webhook JSON', + 'webhook_json_description' => 'Define a custom json, use #message as message variable.', 'delete_title' => 'Delete User', 'delete_message' => 'Are you sure you want to delete user \'%1\'?', 'deleted' => 'User deleted.', @@ -162,6 +171,7 @@ $sm_lang = array( 'email' => 'Email', 'sms' => 'SMS', 'pushover' => 'Pushover', + 'webhook' => 'Webhook', 'telegram' => 'Telegram', 'jabber' => 'Jabber', 'no_logs' => 'No logs', @@ -227,6 +237,8 @@ $sm_lang = array( 'send_email' => 'Send Email', 'sms' => 'SMS', 'send_sms' => 'Send SMS', + 'webhook' => 'Webhook', + 'send_webhook' => 'Send Webhook notification', 'pushover' => 'Pushover', 'send_pushover' => 'Send Pushover notification', 'telegram' => 'Telegram', @@ -266,6 +278,7 @@ $sm_lang = array( 'chart_short_time_format' => '%H:%M', 'warning_notifications_disabled_sms' => 'SMS notifications are disabled.', 'warning_notifications_disabled_email' => 'Email notifications are disabled.', + 'warning_notifications_disabled_webhook' => 'Webhook notifications are disabled.', 'warning_notifications_disabled_pushover' => 'Pushover notifications are disabled.', 'warning_notifications_disabled_telegram' => 'Telegram notifications are disabled.', 'warning_notifications_disabled_jabber' => 'Jabber notifications are disabled.', @@ -281,6 +294,7 @@ $sm_lang = array( ), 'config' => array( 'general' => 'General', + 'site_title' => 'Site title', 'language' => 'Language', 'show_update' => 'Check for updates?', 'password_encrypt_key' => 'The encryption key password', @@ -306,6 +320,12 @@ $sm_lang = array( 'sms_gateway_username' => 'Gateway username', 'sms_gateway_password' => 'Gateway password', 'sms_from' => 'Sender\'s phone number', + 'webhook_status' => 'Allow sending webhooks', + 'webhook_description' => 'Allow sending webhooks to services like slack. The message payload end endpoint are defined in the profile settings.', + 'webhook_url' => 'Webhook Url', + 'webhook_url_description' => 'Url to webhook endpoint', + 'webhook_json' => 'Webhook Json', + 'webhook_json_description' => 'Customized Json, use #message as message variable.', 'pushover_status' => 'Allow sending Pushover messages', 'pushover_description' => 'Pushover is a service that makes it easy to get real-time notifications. See their website for more info.', @@ -360,17 +380,20 @@ $sm_lang = array( 'log_email' => 'Log emails sent by the script', 'log_sms' => 'Log text messages sent by the script', 'log_pushover' => 'Log pushover messages sent by the script', + 'log_webhook' => 'Log webhook messages sent by the script', 'log_telegram' => 'Log Telegram messages sent by the script', 'log_jabber' => 'Log Jabber messages sent by the script', 'updated' => 'The configuration has been updated.', 'tab_email' => 'Email', 'tab_sms' => 'SMS', 'tab_pushover' => 'Pushover', + 'tab_webhook' => 'Webhook', 'tab_telegram' => 'Telegram', 'tab_jabber' => 'Jabber', 'settings_email' => 'Email settings', 'settings_sms' => 'Text message settings', 'settings_pushover' => 'Pushover settings', + 'settings_webhook' => 'Webhook settings', 'settings_telegram' => 'Telegram settings', 'settings_jabber' => 'Jabber settings', 'settings_notification' => 'Notification settings', @@ -379,12 +402,12 @@ $sm_lang = array( 'auto_refresh' => 'Auto-refresh', 'auto_refresh_description' => 'Auto-refresh servers page.
    Time in seconds, if 0 the page won\'t refresh.', - 'seconds' => 'seconds', 'test' => 'Test', 'test_email' => 'An email will be sent to the address specified in your user profile.', 'test_sms' => 'An SMS will be sent to the phone number specified in your user profile.', 'test_pushover' => 'A Pushover notification will be sent to the user key/device specified in your user profile.', + 'test_webhook' => 'A webhook notification will be sent to the given url endpoint.', 'test_telegram' => 'A Telegram notification will be sent to the chat id specified in your user profile.', 'test_jabber' => 'A Jabber notification will be sent to the jabber account specified in your user profile.', 'send' => 'Send', @@ -395,6 +418,10 @@ $sm_lang = array( 'sms_sent' => 'SMS sent', 'sms_error' => 'An error has occurred while sending the SMS: %s', 'sms_error_nomobile' => 'Unable to send test SMS: no valid phone number found in your profile.', + 'webhook_sent' => 'Webhook notification sent', + 'webhook_error' => 'An error has occurred while sending the webhook notification: %s', + 'webhook_error_nourl' => 'Unable to send test notification: no url found in user profile.', + 'webhook_error_nojson' => 'Unable to send test notification: no json found in user profile.', 'pushover_sent' => 'Pushover notification sent', 'pushover_error' => 'An error has occurred while sending the Pushover notification: %s', 'pushover_error_noapp' => 'Unable to send test notification: no Pushover App API token found in the global @@ -414,12 +441,17 @@ $sm_lang = array( 'log_retention_period_description' => 'Number of days to keep logs of notifications and archives of server uptime. Enter 0 to disable log cleanup.', 'log_retention_days' => 'days', + 'user_agent' => 'User Agent', + 'user_agent_key_note' => 'Custom user agent used by monitor within communication with external services.', ), 'notifications' => array( 'off_sms' => 'Server \'%LABEL%\' is DOWN: ip=%IP%, port=%PORT%. Error=%ERROR%', 'off_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is DOWN', 'off_email_body' => 'Failed to connect to the following server:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Error: %ERROR%
    Date: %DATE%', + 'off_webhook_title' => 'Server \'%LABEL%\' is DOWN', + 'off_webhook_message' => 'Failed to connect to the following server:

    Server: %LABEL%
    IP: + %IP%
    Port: %PORT%
    Error: %ERROR%
    Date: %DATE%', 'off_pushover_title' => 'Server \'%LABEL%\' is DOWN', 'off_pushover_message' => 'Failed to connect to the following server:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Error: %ERROR%
    Date: %DATE%', @@ -431,6 +463,10 @@ $sm_lang = array( 'on_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is RUNNING', 'on_email_body' => 'Server \'%LABEL%\' is running again, it was down for %LAST_OFFLINE_DURATION%:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Date: + %DATE%', + 'on_webhook_title' => 'Server \'%LABEL%\' is RUNNING', + 'on_webhook_message' => 'Server \'%LABEL%\' is running again, it was down for + %LAST_OFFLINE_DURATION%:

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Date: %DATE%', 'on_pushover_title' => 'Server \'%LABEL%\' is RUNNING', 'on_pushover_message' => 'Server \'%LABEL%\' is running again, it was down for @@ -443,6 +479,8 @@ $sm_lang = array( %LAST_OFFLINE_DURATION%

    Server: %LABEL%
    IP: %IP%
    Port: %PORT%
    Date: %DATE%', 'combi_off_email_message' => '
    • Server: %LABEL%
    • IP: %IP%
    • Port: %PORT%
    • Error: + %ERROR%
    • Date: %DATE%
    ', + 'combi_off_webhook_message' => '
    • Server: %LABEL%
    • IP: %IP%
    • Port: %PORT%
    • Error: %ERROR%
    • Date: %DATE%
    ', 'combi_off_pushover_message' => '
    • Server: %LABEL%
    • IP: %IP%
    • Port: %PORT%
    • Error: %ERROR%
    • Date: %DATE%
    ', @@ -452,6 +490,9 @@ $sm_lang = array( Date: %DATE%

    ', 'combi_on_email_message' => '
    • Server: %LABEL%
    • IP: %IP%
    • Port: %PORT%
    • Downtime: %LAST_OFFLINE_DURATION%
    • Date: %DATE%
    ', + 'combi_on_webhook_message' => '
    • Server: %LABEL%
    • IP: %IP%
    • Port: + %PORT%
    • Downtime: %LAST_OFFLINE_DURATION%
    • Date: + %DATE%
    ', 'combi_on_pushover_message' => '
    • Server: %LABEL%
    • IP: %IP%
    • Port: %PORT%
    • Downtime: %LAST_OFFLINE_DURATION%
    • Date: %DATE%
    ', @@ -460,8 +501,11 @@ $sm_lang = array( 'combi_on_jabber_message' => '- Server: %LABEL%
    - IP: %IP%
    - Port: %PORT%
    - Downtime: %LAST_OFFLINE_DURATION%
    - Date: %DATE%

    ', 'combi_email_subject' => 'IMPORTANT: \'%UP%\' servers UP again, \'%DOWN%\' servers DOWN', + 'combi_webhook_subject' => '\'%UP%\' servers UP again, \'%DOWN%\' servers DOWN', 'combi_pushover_subject' => '\'%UP%\' servers UP again, \'%DOWN%\' servers DOWN', 'combi_email_message' => 'The following servers went down:
    %DOWN_SERVERS%
    The following + servers are up again:
    %UP_SERVERS%', + 'combi_webhook_message' => 'The following servers went down:
    %DOWN_SERVERS%
    The following servers are up again:
    %UP_SERVERS%', 'combi_pushover_message' => 'The following servers went down:
    %DOWN_SERVERS%
    The following servers are up again:
    %UP_SERVERS%', diff --git a/src/lang/es_ES.lang.php b/src/lang/es_ES.lang.php index f869320a..88ed969d 100644 --- a/src/lang/es_ES.lang.php +++ b/src/lang/es_ES.lang.php @@ -354,7 +354,6 @@ $sm_lang = array( 'auto_refresh' => 'Auto-actualizar', 'auto_refresh_description' => 'Auto-actualizar la página de servidores.
    Tiempo en segundos, si se utiliza 0 la página no se actualizará.', - 'seconds' => 'segundos', 'test' => 'Prueba', 'test_email' => 'Un correo electrónico será enviado a la dirección especificada en su perfil de usuario.', 'test_sms' => 'Un SMS se enviará al número de teléfono especificado en su perfil de usuario.', diff --git a/src/lang/et_ET.lang.php b/src/lang/et_ET.lang.php index 31d5850f..3c13a6ac 100644 --- a/src/lang/et_ET.lang.php +++ b/src/lang/et_ET.lang.php @@ -66,6 +66,7 @@ $sm_lang = array( 'a_minute_ago' => 'umbes minut aega tagasi', 'seconds_ago' => '%d sekundit tagasi', 'a_second_ago' => 'üks sekund tagasi', + 'seconds' => 'sekundit', ), 'menu' => array( 'config' => 'Konfiguratsioon', @@ -244,7 +245,6 @@ $sm_lang = array( 'auto_refresh' => 'Automaatne värskendamine', 'auto_refresh_description' => 'Värskenda lehte automaatselt.
    Aeg sekundites, kui 0 siis lehte ei värskendata.', - 'seconds' => 'sekundit', 'test' => 'Test', 'test_email' => 'Email saadetakse profiilil märgitud aadressile.', 'test_sms' => 'SMS saadetakse profiilil märgitud numbrile.', diff --git a/src/lang/fa_IR.lang.php b/src/lang/fa_IR.lang.php index a3b0e904..c5ecea6a 100644 --- a/src/lang/fa_IR.lang.php +++ b/src/lang/fa_IR.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => 'حدود یک دقیقه پیش', 'seconds_ago' => '%d ثانیه پیش', 'a_second_ago' => 'یک ثانیه پیش', + 'seconds' => 'ثانیه', ), 'menu' => array( 'config' => 'تنظیم', @@ -263,7 +264,6 @@ $sm_lang = array( 'auto_refresh' => 'رفرش خودکار', 'auto_refresh_description' => 'رفرش خودکار صفحه سرورها.
    زمان به ثنیه, اگر 0 باشد صفحه رفرش نخواهد شد.', - 'seconds' => 'ثانیه', 'test' => 'تست', 'test_email' => 'یک ایمیل به آدرس تعیین شده در پروفایل شما ارسال خواهد شد.', diff --git a/src/lang/fi_FI.lang.php b/src/lang/fi_FI.lang.php index 8c3d8da2..17dec27d 100644 --- a/src/lang/fi_FI.lang.php +++ b/src/lang/fi_FI.lang.php @@ -244,7 +244,6 @@ $sm_lang = array( 'auto_refresh' => 'Automaattipäivitys', 'auto_refresh_description' => 'Päivittää automaattisesti palvelimet-sivun.
    Aika sekunteina, jos 0, sivu ei päivity automaattisesti.', - 'seconds' => 'sekuntia', 'test' => 'Testi', 'test_email' => 'Testisähköposti lähetetään profiilisi sähköpostiosoitteeseen.', 'test_sms' => 'Testitekstiviesti lähetetään profiilisi numeroon.', diff --git a/src/lang/fr_FR.lang.php b/src/lang/fr_FR.lang.php index 12ca83bc..e2bac203 100644 --- a/src/lang/fr_FR.lang.php +++ b/src/lang/fr_FR.lang.php @@ -361,7 +361,6 @@ $sm_lang = array( 'auto_refresh' => 'Auto-rachaîchissement', 'auto_refresh_description' => 'Auto-rachaîchissement de la page serveurs.
    Temps en secondes. Si 0, la page n\'est pas rafraîchie.', - 'seconds' => 'secondes', 'test' => 'Tester', 'test_email' => 'Un email va vous être envoyé à l\'adresse définie dans votre profil utilisateur.', 'test_sms' => 'Un SMS va vous être envoyé au numéro défini dans votre profil utilisateur.', diff --git a/src/lang/it_IT.lang.php b/src/lang/it_IT.lang.php index 80d15d76..29907826 100644 --- a/src/lang/it_IT.lang.php +++ b/src/lang/it_IT.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => 'circa un minuto fa', 'seconds_ago' => '%d secondi fa', 'a_second_ago' => 'un secondo fa', + 'seconds' => 'secondi', ), 'menu' => array( 'config' => 'Configurazione', @@ -247,7 +248,6 @@ $sm_lang = array( 'auto_refresh' => 'Auto-Aggiornamento', 'auto_refresh_description' => 'Auto-Aggiornamento pagina servers.
    Tempo in secondi, se impostato a 0 la pagina non si aggiornerà.', - 'seconds' => 'secondi', 'test' => 'Test', 'test_email' => 'Un Email verrà inviata all\'indirizzo specificato nel tuo profilo.', 'test_sms' => 'Un SMS verrà inviato al numero di telefono specificato nel tuo profilo.', diff --git a/src/lang/ja_JP.lang.php b/src/lang/ja_JP.lang.php index 3b6831f1..c15c60a4 100644 --- a/src/lang/ja_JP.lang.php +++ b/src/lang/ja_JP.lang.php @@ -345,7 +345,6 @@ $sm_lang = array( 'auto_refresh' => '自動更新', 'auto_refresh_description' => 'サーバーページを自動更新します。
    時間を秒で指定し、0に設定すると更新しません。', - 'seconds' => '秒', 'test' => 'テスト', 'test_email' => 'あなたのユーザープロフィールで指定されたアドレスに電子メールが送信されます。', 'test_sms' => 'あなたのユーザープロフィールで指定された電話番号にSMSが送信されます。', diff --git a/src/lang/nl_NL.lang.php b/src/lang/nl_NL.lang.php index 7ca106a4..a5a1c728 100644 --- a/src/lang/nl_NL.lang.php +++ b/src/lang/nl_NL.lang.php @@ -81,6 +81,8 @@ $sm_lang = array( 'minutes' => 'minuten', 'second' => 'seconde', 'seconds' => 'seconden', + 'millisecond' => 'milliseconde', + 'milliseconds' => 'milliseconden', 'current' => 'huidig', 'settings' => 'Instellingen', ), @@ -110,8 +112,8 @@ $sm_lang = array( 'email' => 'Email', 'pushover' => 'Pushover', 'pushover_description' => 'Pushover is een dienst die het gemakkelijk maakt om real-time notificaties te - ontvangen. Zie hun website voor meer - informatie.', + ontvangen. Zie hun website voor + meer informatie.', 'pushover_key' => 'Pushover Key', 'pushover_device' => 'Pushover Device', 'pushover_device_description' => 'Apparaat waar de berichten naar toe gaan. Laat leeg voor alle apparaten.', @@ -244,6 +246,7 @@ $sm_lang = array( ), 'config' => array( 'general' => 'Algemeen', + 'site_title' => 'Website titel', 'language' => 'Taal', 'show_update' => 'Controleer wekelijks voor updates?', 'email_status' => 'Sta email berichten toe?', @@ -264,8 +267,8 @@ $sm_lang = array( 'sms_from' => 'Telefoonnummer afzender', 'pushover_status' => 'Sta Pushover berichten toe?', 'pushover_description' => 'Pushover is een dienst die het gemakkelijk maakt om real-time notificaties te - ontvangen. Zie hun website voor meer - informatie.', + ontvangen. Zie hun website voor + meer informatie.', 'pushover_clone_app' => 'Klik hier om je Pushover app te maken', 'pushover_api_token' => 'Pushover App API Token', 'pushover_api_token_description' => 'Voordat je Pushover kunt gebruiken moet je een 'Herlaad automatisch', 'auto_refresh_description' => 'Auto-herladen servers pagina.
    Tijd in seconden, als de tijd 0 is wordt de pagina niet ververst.', - 'seconds' => 'seconden', 'test' => 'Test', 'test_email' => 'Er zal een email verstuurd worden naar het email adres in je profiel.', 'test_sms' => 'Er zal een SMS verstuurd worden naar het telefoonnummer in je profiel.', @@ -336,6 +338,9 @@ $sm_lang = array( 'log_retention_period_description' => 'Aantal dagen dat logs van notificaties en archieven van server uptime worden bewaard. Vul 0 in om log opruiming uit te zetten.', 'log_retention_days' => 'dagen', + 'user_agent' => 'User Agent', + 'user_agent_key_note' => 'Aangepaste user agent wordt door de monitor gebruikt bij de communicatie met externe + services.', ), 'notifications' => array( 'off_sms' => 'Server %LABEL% is DOWN: ip=%IP%, poort=%PORT%. Fout=%ERROR%', diff --git a/src/lang/no_NB.lang.php b/src/lang/no_NB.lang.php index 4e57b36f..e73ccc55 100644 --- a/src/lang/no_NB.lang.php +++ b/src/lang/no_NB.lang.php @@ -326,7 +326,6 @@ $sm_lang = array( 'auto_refresh' => 'Auto-refresh', 'auto_refresh_description' => 'Auto-refresh server side.
    Tid i sekunder, hvis 0 siden ikke blir oppdatert.', - 'seconds' => 'sekunder', 'test' => 'Test', 'test_email' => 'En e-post vil bli sendt til adressen spesifisert i brukerprofilen din.', 'test_sms' => 'En tekstmelding vil bli sendt til telefonnummeret som er angitt i brukerprofilen din.', diff --git a/src/lang/pl_PL.lang.php b/src/lang/pl_PL.lang.php index d5061f0d..1fa25092 100644 --- a/src/lang/pl_PL.lang.php +++ b/src/lang/pl_PL.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => 'minutę temu', 'seconds_ago' => '%d sekund temu', 'a_second_ago' => 'sekundę temu', + 'seconds' => 'sekund', ), 'menu' => array( 'config' => 'Konfiguracja', @@ -191,6 +192,7 @@ $sm_lang = array( ), 'config' => array( 'general' => 'Ogólne', + 'site_title' => 'Tytuł strony', 'language' => 'Język', 'show_update' => 'Sprawdzić aktualizacje?', 'email_status' => 'Pozwól na wysyłkę email', @@ -211,8 +213,8 @@ $sm_lang = array( 'sms_from' => 'Numer nadawcy', 'pushover_status' => 'Pozwól na wysyłkę notyfikacji Pushover', 'pushover_description' => 'Pushover jest usługą ułatwiającą otrzymywanie powiadomień w czasie - rzeczywistym. Sprawdź
    ich stronę aby uzyskać - więcej informacji.', + rzeczywistym. Sprawdź ich + stronę aby uzyskać więcej informacji.', 'pushover_clone_app' => 'Kliknij tutaj aby stworzyć aplikację korzystającą z Pushover', 'pushover_api_token' => 'Pushover App API Token', 'pushover_api_token_description' => 'Zanim zaczniesz używać Pushover, musisz 'Auto-odświeżanie', 'auto_refresh_description' => 'Auto-odświeżanie strony serwera.
    Czas w sekundach, dla czasu 0 strona nie będzie odświeżana.', - 'seconds' => 'sekund', 'test' => 'Test', 'test_email' => 'Email zostanie wysłany na adres podany w Twoim profilu.', 'test_sms' => 'SMS zostanie wysłany na numer podany w Twoim profilu.', @@ -272,6 +273,8 @@ $sm_lang = array( archiwizować uptime serwera. Wpisz 0 aby wyłączyć czyszczenie logów.', 'log_retention_days' => 'dni', + 'user_agent' => 'User Agent', + 'user_agent_key_note' => 'Nazwa używana przez monitoring do identyfikacji ze sprawdzaną usługą.', ), 'notifications' => array( 'off_sms' => 'Serwer \'%LABEL%\' przestał odpowiadać: ip=%IP%, port=%PORT%. Błąd=%ERROR%', diff --git a/src/lang/pt_BR.lang.php b/src/lang/pt_BR.lang.php index 1b48c183..91ead663 100644 --- a/src/lang/pt_BR.lang.php +++ b/src/lang/pt_BR.lang.php @@ -66,6 +66,7 @@ $sm_lang = array( 'a_minute_ago' => 'cerca de um minuto atrás', 'seconds_ago' => '%d segundos atrás', 'a_second_ago' => 'um segundo atrás', + 'seconds' => 'segundos', ), 'menu' => array( 'config' => 'Configuração', @@ -245,7 +246,6 @@ $sm_lang = array( 'auto_refresh' => 'Atualizar automaticamente', 'auto_refresh_description' => 'Atualizar automaticamente a página de servidores.
    Tempo em segundos, Se 0 a página não será atualizada.', - 'seconds' => 'segundos', 'test' => 'Teste', 'test_email' => 'Um e-mail será enviado para o endereço especificado em seu perfil de usuário.', 'test_sms' => 'Um SMS será enviado para o número de telefone especificado em seu perfil de usuário.', diff --git a/src/lang/ru_RU.lang.php b/src/lang/ru_RU.lang.php index fc5cfddd..40ad4041 100644 --- a/src/lang/ru_RU.lang.php +++ b/src/lang/ru_RU.lang.php @@ -121,12 +121,12 @@ $sm_lang = array( пустым, что бы отправлять уведомления на все устройства.', 'telegram' => 'Telegram', - 'telegram_description' => '
    Telegram удобный мессенджер - для получения уведомлений в реальном - времени. Посетите раздел документации - для получения доп. информации и инструкций по - установке.', + 'telegram_description' => 'Telegram удобный + мессенджер для получения уведомлений в + реальном времени. Посетите раздел + документации для получения доп. информации + и инструкций по установке.', 'telegram_chat_id' => 'Telegram chat id', 'telegram_chat_id_description' => 'Сообщения будут отправляться на указанный идентификатор чата.', @@ -313,12 +313,12 @@ $sm_lang = array( rel="noopener">"App" на их веб-сайте и ввести "App API Token" сюда.', 'telegram_status' => 'Разрешить отправку уведомлений в Telegram', - 'telegram_description' => 'Telegram удобный мессенджер - для получения уведомлений в реальном - времени. Посетите раздел документации - для получения доп. информации и инструкций по - установке.', + 'telegram_description' => 'Telegram удобный + мессенджер для получения уведомлений в + реальном времени. Посетите раздел + документации для получения доп. информации + и инструкций по установке.', 'telegram_api_token' => 'Telegram API Token', 'telegram_api_token_description' => 'Прежде чем вы сможете начать пользоваться Telegram, вам необходимо @@ -371,7 +371,6 @@ $sm_lang = array( серверов.
    Время в секундах. Если указано 0, то страница не будет обновляться.', - 'seconds' => 'секунд', 'test' => 'Проверка', 'test_email' => 'Сообщение будет отправлено на адрес указаный в профиле пользователя.', diff --git a/src/lang/sk_SK.lang.php b/src/lang/sk_SK.lang.php index f40379b0..f8faa782 100644 --- a/src/lang/sk_SK.lang.php +++ b/src/lang/sk_SK.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => 'cca pred minútou', 'seconds_ago' => 'pred %d sekundami', 'a_second_ago' => 'pred chvíľou', + 'seconds' => 'sekúnd', ), 'menu' => array( 'config' => 'Konfigurácia', @@ -259,7 +260,6 @@ $sm_lang = array( 'auto_refresh' => 'Automaticky obnoviť', 'auto_refresh_description' => 'Automaticky obnoviť stránku Servery.
    Čas v sekundách, 0 pre vypnutie automatického obnovenia.', - 'seconds' => 'sekúnd', 'test' => 'Test', 'test_email' => 'E-mail bude odoslaný na adresu uvedenú v užívateľskom profile.', 'test_sms' => 'SMS bude odoslaná na telefónne číslo uvedené v užívateľskom profile.', diff --git a/src/lang/sl_SI.lang.php b/src/lang/sl_SI.lang.php index 16695895..5dcf73df 100644 --- a/src/lang/sl_SI.lang.php +++ b/src/lang/sl_SI.lang.php @@ -65,6 +65,7 @@ $sm_lang = array( 'a_minute_ago' => 'pred približno minuto', 'seconds_ago' => 'pred %d sekundami', 'a_second_ago' => 'pred sekundo', + 'seconds' => 'sekund', ), 'menu' => array( 'config' => 'Nastavitve', @@ -92,8 +93,8 @@ $sm_lang = array( 'email' => 'E-pošta', 'pushover' => 'Pushover', 'pushover_description' => 'Pushover je storitev, ki omogoča enostavno prejemanje obvestil v realnem času. - Več informacij je na voljo na njihovi spletni - strani.', + Več informacij je na voljo na + njihovi spletni strani.', 'pushover_key' => 'Pushover ključ', 'pushover_device' => 'Pushover naprava', 'pushover_device_description' => 'Ime naprave na katero naj se pošlje obvestilo. Če želite obvestilo @@ -209,8 +210,8 @@ $sm_lang = array( 'sms_from' => 'Telefonska številka pošiljatelja', 'pushover_status' => 'Dovolim pošiljanje Pushover sporočil', 'pushover_description' => 'Pushover je storitev, ki omogoča enostavno prejemanje obvestil v realnem času. - Več informacij je na voljo na njihovi spletni - strani.', + Več informacij je na voljo na + njihovi spletni strani.', 'pushover_clone_app' => 'Kliknite za ustvarjanje vaše Pushover aplikacije', 'pushover_api_token' => 'Pushover API žeton', 'pushover_api_token_description' => 'Pred uporabo storitve Pushover, morate na njihovi spletni strani 'Samodejno posodabljanje pregleda statusa strežnikov.
    Čas v sekundah. Če je vrednost 0 se stran ne bo samodejno posodabljala.', - 'seconds' => 'sekund', 'test' => 'Test', 'test_email' => 'Na naslov, ki ste ga določili v vašem profilu, bo poslano e-sporočilo.', 'test_sms' => 'Na telefonsko številko, ki ste jo določili v vašem profilu, bo poslan SMS.', diff --git a/src/lang/sv_SE.lang.php b/src/lang/sv_SE.lang.php index 3c6ec8a6..ca8ae553 100644 --- a/src/lang/sv_SE.lang.php +++ b/src/lang/sv_SE.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => 'ungefär en minut sen', 'seconds_ago' => '%d sekunder sedan', 'a_second_ago' => 'en sekund sedan', + 'seconds' => 'sekunder', ), 'menu' => array( 'config' => 'Inställningar', @@ -94,7 +95,8 @@ $sm_lang = array( 'email' => 'Email', 'pushover' => 'Pushover', 'pushover_description' => 'Pushover är en tjänst som skickar meddelande i realtid. Se
    deras webbsida för mer information.', + href="https://pushover.net/" target="_blank">deras webbsida för mer + information.', 'pushover_key' => 'Pushover Key', 'pushover_device' => 'Pushover Device', 'pushover_device_description' => 'Enhetsnman att skicka meddelande till. Lämna tomt för att skicka till alla @@ -245,7 +247,6 @@ $sm_lang = array( 'auto_refresh' => 'Auto-uppdatera', 'auto_refresh_description' => 'Auto-uppdatera status-sidan.
    Tid i sekunder, om "0" så uppdateras sidan inte automatiskt.', - 'seconds' => 'sekunder', 'test' => 'Test', 'test_email' => 'Ett emial kommer skickas till adressen i din profil.', 'test_sms' => 'Ett SMS kommer skickas till mobilnumret i din profil.', diff --git a/src/lang/tr_TR.lang.php b/src/lang/tr_TR.lang.php index 78ac5131..efc6d6ce 100644 --- a/src/lang/tr_TR.lang.php +++ b/src/lang/tr_TR.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => 'yaklaşık bir dakika önce', 'seconds_ago' => '%d saniye önce', 'a_second_ago' => 'bir saniye önce', + 'seconds' => 'saniye', ), 'menu' => array( 'config' => 'Ayarlar', @@ -248,7 +249,6 @@ $sm_lang = array( 'auto_refresh' => 'Otomatik Yenileme', 'auto_refresh_description' => 'Otomatik yenileme sunucu sayfası
    Eğer sayfa yenilenmez ise.', - 'seconds' => 'saniye', 'test' => 'Test', 'test_email' => 'Profilinizde tanımladığınız e-posta adresinize bir e-posta gönderilecek.', 'test_sms' => 'Profilinizde tanımladığınız numaranıza bir SMS mesajı gönderilecek.', diff --git a/src/lang/uk_UA.lang.php b/src/lang/uk_UA.lang.php index 3bad1325..bdc02c74 100644 --- a/src/lang/uk_UA.lang.php +++ b/src/lang/uk_UA.lang.php @@ -124,11 +124,12 @@ $sm_lang = array( повідомлення. Залиште пустим, щоб надсилати на всі пристрої.', 'telegram' => 'Telegram', - 'telegram_description' => 'Telegram — чат-застосунок, що - дозволяє легко отримувати сповіщення у - реальному часі. За деталями й інструкцією зі - встановлення зверніться до документації.', + 'telegram_description' => 'Telegram — + чат-застосунок, що дозволяє легко отримувати + сповіщення у реальному часі. За деталями й + інструкцією зі встановлення зверніться до документації.', 'telegram_chat_id' => 'Ідентифікатор чату Telegram', 'telegram_chat_id_description' => 'Повідомлення буде надіслане у відповідний чат.', @@ -330,8 +331,8 @@ $sm_lang = array( 'pushover_status' => 'Дозволити надсилання Pushover-повідомлень', 'pushover_description' => 'Pushover — сервіс, що дозволяє легко отримувати сповіщення у реальному часі. За детальнішою - інформацію перейдіть на їхній вебсайт.', + інформацію перейдіть на їхній вебсайт.', 'pushover_clone_app' => 'Натисніть тут, щоб створити ваш Pushover-додаток', 'pushover_api_token' => 'Токен API Pushover-додатку', 'pushover_api_token_description' => 'Перед використанням Pushover ви маєте на їхньому вебсайті та ввести токен API Додатку тут.', 'telegram_status' => 'Дозволити надсилання Telegram-повідомлень', - 'telegram_description' => 'Telegram — чат-застосунок, що - дозволяє легко отримувати сповіщення у - реальному часі. Детальніша інформація та - інструкція зі встановлення доступні у документації.', + 'telegram_description' => 'Telegram — + чат-застосунок, що дозволяє легко отримувати + сповіщення у реальному часі. Детальніша + інформація та інструкція зі встановлення + доступні у документації.', 'telegram_api_token' => 'Токен Telegram API', 'telegram_api_token_description' => 'Перед використанням Telegram ви маєте отримати токен API. За довідкою - зверніться до документації.', + зверніться до документації.', 'alert_type' => 'Виберіть, коли б вам хотілося отримувати сповіщення.', 'alert_type_description' => 'Зміна статусу: Ви отримуватимете @@ -404,7 +406,6 @@ $sm_lang = array( 'auto_refresh_description' => 'Сторінка автооновлення серверів.
    Час у секундах; якщо 0, сторінка не оновлюватиметься.', - 'seconds' => 'секунд', 'test' => 'Тест', 'test_email' => 'Електронний лист буде надісланий на адресу, вказану у вашому профілі користувача.', diff --git a/src/lang/vi_VN.lang.php b/src/lang/vi_VN.lang.php index fe587d8a..9a5d7ef8 100644 --- a/src/lang/vi_VN.lang.php +++ b/src/lang/vi_VN.lang.php @@ -64,6 +64,7 @@ $sm_lang = array( 'a_minute_ago' => 'khoảng một phút trước', 'seconds_ago' => '%d giây trước', 'a_second_ago' => 'một giây trước', + 'seconds' => 'giây', ), 'menu' => array( 'config' => 'Cấu hình', @@ -91,8 +92,8 @@ $sm_lang = array( 'email' => 'Email', 'pushover' => 'Pushover', 'pushover_description' => 'Pushover là một dịch vụ dễ dàng nhận các thông báo theo thời gian - thực. Xem website của họ để biết - thêm thông tin.', + thực. Xem website của họ + để biết thêm thông tin.', 'pushover_key' => 'Pushover Key', 'pushover_device' => 'Pushover Device', 'pushover_device_description' => 'Tên thiết bị để gửi tin nhắn đến. Để trống để gửi @@ -208,8 +209,8 @@ $sm_lang = array( 'sms_from' => 'Số điện thoại của người gửi', 'pushover_status' => 'Cho phép gửi tin nhắn bằng Pushover', 'pushover_description' => 'Pushover là một dịch vụ dễ dàng nhận các thông báo theo thời gian - thực. Xem website của họ để biết - thêm thông tin.', + thực. Xem website của họ + để biết thêm thông tin.', 'pushover_clone_app' => 'Nhấn vào đây để tạo ứng dụng Pushover của bạn', 'pushover_api_token' => 'Pushover App API Token', 'pushover_api_token_description' => 'Trước khi bạn có thể sử dụng Pushover, bạn cần phải 'Tự động làm mới', 'auto_refresh_description' => 'Tự động làm mới servers page.
    Trong vài giây, nếu 0 trang sẽ không làm mới.', - 'seconds' => 'giây', 'test' => 'Thử', 'test_email' => 'Một email sẽ được gửi đến địa chỉ được xác định trong hồ sơ người dùng của bạn.', diff --git a/src/lang/zh_CN.lang.php b/src/lang/zh_CN.lang.php index c3350957..2d593b54 100644 --- a/src/lang/zh_CN.lang.php +++ b/src/lang/zh_CN.lang.php @@ -67,6 +67,7 @@ $sm_lang = array( 'a_minute_ago' => '1分钟前', 'seconds_ago' => '%d 秒前', 'a_second_ago' => '刚刚', + 'seconds' => '秒', ), 'menu' => array( 'config' => '设置', @@ -259,7 +260,6 @@ $sm_lang = array( 'auto_refresh' => '自动刷新', 'auto_refresh_description' => '自动刷新服务器页.
    单位为秒, 设置为 0 则不自动刷新.', - 'seconds' => '秒', 'test' => '测试', 'test_email' => '将发送一封邮件到您账户设置的邮件地址.', 'test_sms' => '将发送一封短信到您账户设置的手机号码.', diff --git a/src/lang/zh_TW.lang.php b/src/lang/zh_TW.lang.php index 29157d84..0069f8bd 100644 --- a/src/lang/zh_TW.lang.php +++ b/src/lang/zh_TW.lang.php @@ -258,7 +258,8 @@ $sm_lang = array( 'sms_from' => '發送人電話號碼', 'pushover_status' => '啟用Pushover通知', 'pushover_description' => 'Pushover是線上服務,讓您可以方便的收到即時通知,請參考
    網站 可以得到更詳細的資訊。 ', + href="https://pushover.net/" target="_blank"> 網站 + 可以得到更詳細的資訊。 ', 'pushover_clone_app' => '點選這裡可快速建立Pushover App', 'pushover_api_token_description' => '在您使用 Pushover 通知之前,需要先到這裡->註冊Pushover App帳號 @@ -269,7 +270,8 @@ $sm_lang = array( 文件庫 可以取得更多資訊與安裝說明。', 'telegram_api_token_description' => '使用 Telegram 通知之前,您必需先取得 API Token。請到 文件庫 取得說明。', + href="http://docs.phpservermonitor.org/" target="_blank">文件庫 + 取得說明。', 'alert_type' => '需要提醒的類別', 'alert_type_description' => '狀態改變: 伺服器 連線 -> 離線或連線 -> 連線的狀態變化將會收到提醒通知。

    離線: @@ -298,7 +300,6 @@ $sm_lang = array( 'settings_log' => '記錄設定', 'settings_proxy' => 'Proxy 設定', 'auto_refresh' => '自動更新', - 'seconds' => '秒', 'test' => '測試', 'test_email' => '電子郵件將發送到您在使用者設定內指定的電子郵件信箱。', 'test_sms' => '簡訊將發送到您在使用者設定內指定的行動電話號碼。', diff --git a/src/psm/Module/AbstractController.php b/src/psm/Module/AbstractController.php index 260543e6..284a0fef 100644 --- a/src/psm/Module/AbstractController.php +++ b/src/psm/Module/AbstractController.php @@ -217,7 +217,7 @@ abstract class AbstractController implements ControllerInterface if (!$this->xhr) { // in XHR mode, we will not add the main template $tpl_data = array( - 'title' => strtoupper(psm_get_lang('system', 'title')), + 'title' => psm_get_conf('site_title', strtoupper(psm_get_lang('system', 'title'))), 'label_back_to_top' => psm_get_lang('system', 'back_to_top'), 'add_footer' => $this->add_footer, 'version' => 'v' . PSM_VERSION, diff --git a/src/psm/Module/Config/Controller/ConfigController.php b/src/psm/Module/Config/Controller/ConfigController.php index 4c6a7e5d..8cf996e3 100644 --- a/src/psm/Module/Config/Controller/ConfigController.php +++ b/src/psm/Module/Config/Controller/ConfigController.php @@ -44,12 +44,14 @@ class ConfigController extends AbstractController 'email_smtp', 'sms_status', 'pushover_status', + 'webhook_status', 'telegram_status', 'jabber_status', 'log_status', 'log_email', 'log_sms', 'log_pushover', + 'log_webhook', 'log_telegram', 'log_jabber', 'show_update', @@ -72,12 +74,16 @@ class ConfigController extends AbstractController 'sms_gateway_username', 'sms_gateway_password', 'sms_from', + 'webhook_url', + 'webhook_json', 'pushover_api_token', 'telegram_api_token', - 'jabber_host', - 'jabber_port', - 'jabber_username', - 'jabber_domain' + 'jabber_host', + 'jabber_port', + 'jabber_username', + 'jabber_domain', + 'user_agent', + 'site_title' ); /** @@ -86,7 +92,7 @@ class ConfigController extends AbstractController */ protected $encryptedFields = [ 'email_smtp_password', - 'jabber_password' + 'jabber_password' ]; private $default_tab = 'general'; @@ -191,6 +197,14 @@ class ConfigController extends AbstractController foreach ($this->fields as $input_key) { $tpl_data[$input_key] = (isset($config[$input_key])) ? $config[$input_key] : ''; } + + $tpl_data['user_agent'] = empty($tpl_data['user_agent']) ? + 'Mozilla/5.0 (compatible; phpservermon/' . + PSM_VERSION . '; +https://github.com/phpservermon/phpservermon)' : $tpl_data['user_agent']; + + $tpl_data['site_title'] = empty($tpl_data['site_title']) ? + strtoupper(psm_get_lang('system', 'title')) : $tpl_data['site_title']; + // encrypted fields foreach ($this->encryptedFields as $encryptedField) { $tpl_data[$encryptedField] = ''; @@ -198,7 +212,7 @@ class ConfigController extends AbstractController $tpl_data[$this->default_tab . '_active'] = 'active'; - $testmodals = array('email', 'sms', 'pushover', 'telegram', 'jabber'); + $testmodals = array('email', 'sms', 'pushover','webhook', 'telegram', 'jabber'); foreach ($testmodals as $modal_id) { $modal = new \psm\Util\Module\Modal( $this->twig, @@ -221,9 +235,10 @@ class ConfigController extends AbstractController protected function executeSave() { if (!empty($_POST)) { - // save new config + // save new config $clean = array( 'language' => $_POST['language'], + 'site_title' => $_POST['site_title'], 'sms_gateway' => $_POST['sms_gateway'], 'alert_type' => $_POST['alert_type'], 'email_smtp_security' => @@ -234,7 +249,7 @@ class ConfigController extends AbstractController 'log_retention_period' => intval(psm_POST('log_retention_period', 365)), 'password_encrypt_key' => psm_POST('password_encrypt_key', sha1(microtime())) ); - foreach ($this->checkboxes as $input_key) { + foreach ($this->checkboxes as $input_key) { $clean[$input_key] = (isset($_POST[$input_key])) ? '1' : '0'; } foreach ($this->fields as $input_key) { @@ -261,10 +276,12 @@ class ConfigController extends AbstractController $this->testSMS(); } elseif (!empty($_POST['test_pushover'])) { $this->testPushover(); + }elseif (!empty($_POST['test_webhook'])) { + $this->testWebhook(); } elseif (!empty($_POST['test_telegram'])) { $this->testTelegram(); } elseif (!empty($_POST['test_jabber'])) { - $this->testJabber(); + $this->testJabber(); } if ($language_refresh) { @@ -280,10 +297,12 @@ class ConfigController extends AbstractController $this->default_tab = 'sms'; } elseif (isset($_POST['pushover_submit']) || !empty($_POST['test_pushover'])) { $this->default_tab = 'pushover'; + } elseif (isset($_POST['webhook_submit']) || !empty($_POST['test_webhook'])) { + $this->default_tab = 'webhook'; } elseif (isset($_POST['telegram_submit']) || !empty($_POST['test_telegram'])) { $this->default_tab = 'telegram'; } elseif (isset($_POST['jabber_submit']) || !empty($_POST['test_jabber'])) { - $this->default_tab = 'jabber'; + $this->default_tab = 'jabber'; } } return $this->runAction('index'); @@ -335,6 +354,34 @@ class ConfigController extends AbstractController } } + /** + * Execute webhook test + * + * @todo move test to separate class + */ + protected function testWebhook() + { + + $user = $this->getUser()->getUser(); + + + if (empty($user->webhook_url)) { + $this->addMessage(psm_get_lang('config', 'webhook_error_nourl'), 'error'); + } elseif (empty($user->webhook_json)) { + $this->addMessage(psm_get_lang('config', 'webhook_error_nojson'), 'error'); + } else { + $webhook = psm_build_webhook(); + $webhook->setUrl($user->webhook_url); + $webhook->setJson($user->webhook_json); + $message = (psm_get_lang('config', 'test_message')); + $result = $webhook->sendWebhook($message); + if ($result==1) { + $this->addMessage(psm_get_lang('config', 'webhook_sent'), 'success'); + } else { + $this->addMessage(sprintf(psm_get_lang('config', 'webhook_error'), $result), 'error'); + } + } + } /** * Execute pushover test * @@ -408,24 +455,24 @@ class ConfigController extends AbstractController } } - /** - * Test Jabber. - */ + /** + * Test Jabber. + */ protected function testJabber() { - $user = $this->getUser()->getUser(); - psm_jabber_send_message( - psm_get_conf('jabber_host'), - psm_get_conf('jabber_username'), - psm_password_decrypt(psm_get_conf('password_encrypt_key'), psm_get_conf('jabber_password')), - [$user->jabber], - psm_get_lang('config', 'test_message'), - (trim(psm_get_conf('jabber_port')) !== '' ? (int)psm_get_conf('jabber_port') : null), - (trim(psm_get_conf('jabber_domain')) !== '' ? psm_get_conf('jabber_domain') : null) - ); - // no message - async ... so just info - $this->addMessage(psm_get_lang('config', 'jabber_check'), 'info'); - // @todo possible to set message via ajax with callback ... + $user = $this->getUser()->getUser(); + psm_jabber_send_message( + psm_get_conf('jabber_host'), + psm_get_conf('jabber_username'), + psm_password_decrypt(psm_get_conf('password_encrypt_key'), psm_get_conf('jabber_password')), + [$user->jabber], + psm_get_lang('config', 'test_message'), + (trim(psm_get_conf('jabber_port')) !== '' ? (int)psm_get_conf('jabber_port') : null), + (trim(psm_get_conf('jabber_domain')) !== '' ? psm_get_conf('jabber_domain') : null) + ); + // no message - async ... so just info + $this->addMessage(psm_get_lang('config', 'jabber_check'), 'info'); + // @todo possible to set message via ajax with callback ... } protected function getLabels() @@ -434,13 +481,15 @@ class ConfigController extends AbstractController 'label_tab_email' => psm_get_lang('config', 'tab_email'), 'label_tab_sms' => psm_get_lang('config', 'tab_sms'), 'label_tab_pushover' => psm_get_lang('config', 'tab_pushover'), + 'label_tab_webhook' => psm_get_lang('config', 'tab_webhook'), 'label_tab_telegram' => psm_get_lang('config', 'tab_telegram'), - 'label_tab_jabber' => psm_get_lang('config', 'tab_jabber'), + 'label_tab_jabber' => psm_get_lang('config', 'tab_jabber'), 'label_settings_email' => psm_get_lang('config', 'settings_email'), 'label_settings_sms' => psm_get_lang('config', 'settings_sms'), + 'label_settings_webhook' => psm_get_lang('config', 'settings_webhook'), 'label_settings_pushover' => psm_get_lang('config', 'settings_pushover'), 'label_settings_telegram' => psm_get_lang('config', 'settings_telegram'), - 'label_settings_jabber' => psm_get_lang('config', 'settings_jabber'), + 'label_settings_jabber' => psm_get_lang('config', 'settings_jabber'), 'label_settings_notification' => psm_get_lang('config', 'settings_notification'), 'label_settings_log' => psm_get_lang('config', 'settings_log'), 'label_settings_proxy' => psm_get_lang('config', 'settings_proxy'), @@ -468,6 +517,12 @@ class ConfigController extends AbstractController 'label_sms_gateway_username' => psm_get_lang('config', 'sms_gateway_username'), 'label_sms_gateway_password' => psm_get_lang('config', 'sms_gateway_password'), 'label_sms_from' => psm_get_lang('config', 'sms_from'), + 'label_webhook_description' => psm_get_lang('config', 'webhook_description'), + 'label_webhook_status' => psm_get_lang('config', 'webhook_status'), + 'label_webhook_url' => psm_get_lang('config', 'webhook_url'), + 'label_webhook_url_description' => psm_get_lang('config', 'webhook_url_description'), + 'label_webhook_json' => psm_get_lang('config', 'webhook_json'), + 'label_webhook_json_description' => psm_get_lang('config', 'webhook_json_description'), 'label_pushover_description' => psm_get_lang('config', 'pushover_description'), 'label_pushover_status' => psm_get_lang('config', 'pushover_status'), 'label_pushover_clone_app' => psm_get_lang('config', 'pushover_clone_app'), @@ -482,17 +537,17 @@ class ConfigController extends AbstractController 'label_telegram_api_token' => psm_get_lang('config', 'telegram_api_token'), 'label_telegram_api_token_description' => psm_get_lang('config', 'telegram_api_token_description'), 'label_jabber_status' => psm_get_lang('config', 'jabber_status'), - 'label_jabber_description' => psm_get_lang('config', 'jabber_description'), + 'label_jabber_description' => psm_get_lang('config', 'jabber_description'), 'label_jabber_host' => psm_get_lang('config', 'jabber_host'), - 'label_jabber_host_description' => psm_get_lang('config', 'jabber_host_description'), - 'label_jabber_port' => psm_get_lang('config', 'jabber_port'), - 'label_jabber_port_description' => psm_get_lang('config', 'jabber_port_description'), - 'label_jabber_username' => psm_get_lang('config', 'jabber_username'), - 'label_jabber_username_description' => psm_get_lang('config', 'jabber_username_description'), - 'label_jabber_domain' => psm_get_lang('config', 'jabber_domain'), - 'label_jabber_domain_description' => psm_get_lang('config', 'jabber_domain_description'), - 'label_jabber_password' => psm_get_lang('config', 'jabber_password'), - 'label_jabber_password_description' => psm_get_lang('config', 'jabber_password_description'), + 'label_jabber_host_description' => psm_get_lang('config', 'jabber_host_description'), + 'label_jabber_port' => psm_get_lang('config', 'jabber_port'), + 'label_jabber_port_description' => psm_get_lang('config', 'jabber_port_description'), + 'label_jabber_username' => psm_get_lang('config', 'jabber_username'), + 'label_jabber_username_description' => psm_get_lang('config', 'jabber_username_description'), + 'label_jabber_domain' => psm_get_lang('config', 'jabber_domain'), + 'label_jabber_domain_description' => psm_get_lang('config', 'jabber_domain_description'), + 'label_jabber_password' => psm_get_lang('config', 'jabber_password'), + 'label_jabber_password_description' => psm_get_lang('config', 'jabber_password_description'), 'label_alert_type' => psm_get_lang('config', 'alert_type'), 'label_alert_type_description' => psm_get_lang('config', 'alert_type_description'), 'label_combine_notifications' => psm_get_lang('config', 'combine_notifications'), @@ -502,13 +557,14 @@ class ConfigController extends AbstractController 'label_log_email' => psm_get_lang('config', 'log_email'), 'label_log_sms' => psm_get_lang('config', 'log_sms'), 'label_log_pushover' => psm_get_lang('config', 'log_pushover'), + 'label_log_webhook' => psm_get_lang('config', 'log_webhook'), 'label_log_telegram' => psm_get_lang('config', 'log_telegram'), - 'label_log_jabber' => psm_get_lang('config', 'log_jabber'), + 'label_log_jabber' => psm_get_lang('config', 'log_jabber'), 'label_alert_proxy' => psm_get_lang('config', 'alert_proxy'), 'label_alert_proxy_url' => psm_get_lang('config', 'alert_proxy_url'), 'label_auto_refresh' => psm_get_lang('config', 'auto_refresh'), 'label_auto_refresh_description' => psm_get_lang('config', 'auto_refresh_description'), - 'label_seconds' => psm_get_lang('config', 'seconds'), + 'label_seconds' => psm_get_lang('system', 'seconds'), 'label_save' => psm_get_lang('system', 'save'), 'label_test' => psm_get_lang('config', 'test'), 'label_log_retention_period' => psm_get_lang('config', 'log_retention_period'), @@ -516,7 +572,9 @@ class ConfigController extends AbstractController 'label_log_retention_days' => psm_get_lang('config', 'log_retention_days'), 'label_days' => psm_get_lang('config', 'log_retention_days'), 'label_leave_blank' => psm_get_lang('users', 'password_leave_blank'), - + 'label_user_agent' => psm_get_lang('config', 'user_agent'), + 'label_user_agent_key_note' => psm_get_lang('config', 'user_agent_key_note'), + 'label_site_title' => psm_get_lang('config', 'site_title'), ); } } diff --git a/src/psm/Module/Install/Controller/InstallController.php b/src/psm/Module/Install/Controller/InstallController.php index cd1e38e0..b1b9bc0b 100644 --- a/src/psm/Module/Install/Controller/InstallController.php +++ b/src/psm/Module/Install/Controller/InstallController.php @@ -75,11 +75,11 @@ class InstallController extends AbstractController $phpv = phpversion(); if ( - version_compare($phpv, '5.6.0', '<') || + version_compare($phpv, '5.5.9', '<') || (version_compare($phpv, '7.0.8', '<') && version_compare($phpv, '7.0.0', '>=')) ) { $errors++; - $this->addMessage('PHP 5.6.0+ or 7.0.8+ is required to run PHP Server Monitor. You\'re using ' . + $this->addMessage('PHP 5.5.9+ or 7.0.8+ is required to run PHP Server Monitor. You\'re using ' . $phpv . '.', 'error'); } else { $this->addMessage('PHP version: ' . $phpv, 'success'); @@ -101,6 +101,53 @@ class InstallController extends AbstractController if (!in_array('mysql', \PDO::getAvailableDrivers())) { $errors++; $this->addMessage('The PDO MySQL driver needs to be installed.', 'error'); + } else { + $this->addMessage('PHP PDO MySQL driver found', 'success'); + } + if (!extension_loaded('filter')) { + $this->addMessage('PHP is installed without the filter module. Please install filter.', 'warning'); + } else { + $this->addMessage('PHP filter module found', 'success'); + } + if (!extension_loaded('ctype')) { + $this->addMessage('PHP is installed without the ctype module. Please install ctype.', 'warning'); + } else { + $this->addMessage('PHP ctype module found', 'success'); + } + if (!extension_loaded('hash')) { + $this->addMessage('PHP is installed without the hash module. Please install hash.', 'warning'); + } else { + $this->addMessage('PHP hash module found', 'success'); + } + if (!extension_loaded('json')) { + $this->addMessage('PHP is installed without the json module. Please install json.', 'warning'); + } else { + $this->addMessage('PHP json module found', 'success'); + } + if (!extension_loaded('libxml')) { + $this->addMessage('PHP is installed without the libxml module. Please install libxml.', 'warning'); + } else { + $this->addMessage('PHP libxml module found', 'success'); + } + if (!extension_loaded('openssl')) { + $this->addMessage('PHP is installed without the openssl module. Please install openssl.', 'warning'); + } else { + $this->addMessage('PHP openssl module found', 'success'); + } + if (!extension_loaded('pcre')) { + $this->addMessage('PHP is installed without the pcre module. Please install pcre.', 'warning'); + } else { + $this->addMessage('PHP pcre module found', 'success'); + } + if (!extension_loaded('sockets')) { + $this->addMessage('PHP is installed without the sockets module. Please install sockets.', 'warning'); + } else { + $this->addMessage('PHP sockets module found', 'success'); + } + if (!extension_loaded('xml')) { + $this->addMessage('PHP is installed without the xml module. Please install xml.', 'warning'); + } else { + $this->addMessage('PHP xml module found', 'success'); } if (!ini_get('date.timezone')) { $this->addMessage( @@ -256,8 +303,10 @@ class InstallController extends AbstractController 'level' => PSM_USER_ADMIN, 'pushover_key' => '', 'pushover_device' => '', + 'webhook_url' => '', + 'webhook_json' => '', 'telegram_id' => '', - 'jabber' => '' + 'jabber' => '' ); $validator = $this->container->get('util.user.validator'); diff --git a/src/psm/Module/Server/Controller/AbstractServerController.php b/src/psm/Module/Server/Controller/AbstractServerController.php index befad354..5d191fe3 100644 --- a/src/psm/Module/Server/Controller/AbstractServerController.php +++ b/src/psm/Module/Server/Controller/AbstractServerController.php @@ -81,6 +81,7 @@ abstract class AbstractServerController extends AbstractController `s`.`active`, `s`.`email`, `s`.`sms`, + `s`.`webhook`, `s`.`pushover`, `s`.`telegram`, `s`.`jabber`, @@ -114,7 +115,7 @@ abstract class AbstractServerController extends AbstractController */ protected function formatServer($server) { - $server['rtime'] = round((float) $server['rtime'], 4); + $server['rtime'] = $server['rtime']; $server['last_online'] = psm_timespan($server['last_online']); $server['last_offline'] = psm_timespan($server['last_offline']); if ($server['last_offline'] != psm_get_lang('system', 'never')) { diff --git a/src/psm/Module/Server/Controller/LogController.php b/src/psm/Module/Server/Controller/LogController.php index 72685baf..05f26c0d 100644 --- a/src/psm/Module/Server/Controller/LogController.php +++ b/src/psm/Module/Server/Controller/LogController.php @@ -56,8 +56,9 @@ class LogController extends AbstractServerController 'label_email' => psm_get_lang('log', 'email'), 'label_sms' => psm_get_lang('log', 'sms'), 'label_pushover' => psm_get_lang('log', 'pushover'), + 'label_webhook' => psm_get_lang('log', 'webhook'), 'label_telegram' => psm_get_lang('log', 'telegram'), - 'label_jabber' => psm_get_lang('log', 'jabber'), + 'label_jabber' => psm_get_lang('log', 'jabber'), 'label_title' => psm_get_lang('log', 'title'), 'label_server' => psm_get_lang('servers', 'server'), 'label_type' => psm_get_lang('log', 'type'), @@ -89,7 +90,7 @@ class LogController extends AbstractServerController ); } - $log_types = array('status', 'email', 'sms', 'pushover', 'telegram', 'jabber'); + $log_types = array('status', 'email', 'sms', 'pushover', 'webhook','telegram', 'jabber'); foreach ($log_types as $key) { $records = $this->getEntries($key); @@ -161,9 +162,9 @@ class LogController extends AbstractServerController if ($this->getUser()->getUserLevel() > PSM_USER_ADMIN) { // restrict by user_id $sql_join = "JOIN `" . PSM_DB_PREFIX . "users_servers` AS `us` ON ( - `us`.`user_id`={$this->getUser()->getUserId()} - AND `us`.`server_id`=`servers`.`server_id` - )"; + `us`.`user_id`={$this->getUser()->getUserId()} + AND `us`.`server_id`=`servers`.`server_id` + )"; } $entries = $this->db->query( 'SELECT ' . diff --git a/src/psm/Module/Server/Controller/ServerController.php b/src/psm/Module/Server/Controller/ServerController.php index 16f0860d..1a8ea05f 100644 --- a/src/psm/Module/Server/Controller/ServerController.php +++ b/src/psm/Module/Server/Controller/ServerController.php @@ -101,8 +101,9 @@ class ServerController extends AbstractServerController 'email' => 'icon-envelope', 'sms' => 'icon-mobile', 'pushover' => 'icon-pushover', + 'webhook' => 'icon-webhook', 'telegram' => 'icon-telegram', - 'jabber' => 'icon-jabber' + 'jabber' => 'icon-jabber' ); $servers = $this->getServers(); @@ -130,6 +131,7 @@ class ServerController extends AbstractServerController $tpl_data['config']['email'] = psm_get_conf('email_status'); $tpl_data['config']['sms'] = psm_get_conf('sms_status'); + $tpl_data['config']['webhook'] = psm_get_conf('webhook_status'); $tpl_data['config']['pushover'] = psm_get_conf('pushover_status'); $tpl_data['config']['telegram'] = psm_get_conf('telegram_status'); @@ -174,6 +176,13 @@ class ServerController extends AbstractServerController $tpl_data['users'] = $this->db->select(PSM_DB_PREFIX . 'users', null, array('user_id', 'name'), '', 'name'); + foreach ($tpl_data['users'] as &$user) { + $user['id'] = $user['user_id']; + unset($user['user_id']); + $user['label'] = $user['name']; + unset($user['name']); + } + switch ($this->server_id) { case 0: // insert mode @@ -194,10 +203,6 @@ class ServerController extends AbstractServerController $user_idc_selected = $this->getServerUsers($this->server_id); foreach ($tpl_data['users'] as &$user) { - $user['id'] = $user['user_id']; - unset($user['user_id']); - $user['label'] = $user['name']; - unset($user['name']); if (in_array($user['id'], $user_idc_selected)) { $user['edit_selected'] = 'selected="selected"'; } @@ -234,13 +239,14 @@ class ServerController extends AbstractServerController 'edit_active_selected' => $edit_server['active'], 'edit_email_selected' => $edit_server['email'], 'edit_sms_selected' => $edit_server['sms'], + 'edit_webhook_selected' => $edit_server['webhook'], 'edit_pushover_selected' => $edit_server['pushover'], 'edit_telegram_selected' => $edit_server['telegram'], - 'edit_jabber_selected' => $edit_server['jabber'], + 'edit_jabber_selected' => $edit_server['jabber'], )); } - $notifications = array('email', 'sms', 'pushover', 'telegram', 'jabber'); + $notifications = array('email', 'sms', 'pushover','webhook', 'telegram', 'jabber'); foreach ($notifications as $notification) { if (psm_get_conf($notification . '_status') == 0) { $tpl_data['warning_' . $notification] = true; @@ -309,8 +315,9 @@ class ServerController extends AbstractServerController 'email' => in_array($_POST['email'], array('yes', 'no')) ? $_POST['email'] : 'no', 'sms' => in_array($_POST['sms'], array('yes', 'no')) ? $_POST['sms'] : 'no', 'pushover' => in_array($_POST['pushover'], array('yes', 'no')) ? $_POST['pushover'] : 'no', + 'webhook' => in_array($_POST['webhook'], array('yes', 'no')) ? $_POST['webhook'] : 'no', 'telegram' => in_array($_POST['telegram'], array('yes', 'no')) ? $_POST['telegram'] : 'no', - 'jabber' => in_array($_POST['jabber'], array('yes', 'no')) ? $_POST['jabber'] : 'no', + 'jabber' => in_array($_POST['jabber'], array('yes', 'no')) ? $_POST['jabber'] : 'no', ); // make sure websites start with http:// if ( @@ -518,6 +525,15 @@ class ServerController extends AbstractServerController if (strlen($tpl_data['last_error_output']) > 255) { $tpl_data['last_error_output_truncated'] = substr($tpl_data['last_error_output'], 0, 255) . '...'; } + + // fetch server status logs + $log_entries = $this->getServerLogs($this->server_id); + for ($x = 0; $x < count($log_entries); $x++) { + $record = &$log_entries[$x]; + $record['datetime_format'] = psm_date($record['datetime']); + } + + $tpl_data['log_entries'] = $log_entries; return $this->twig->render('module/server/server/view.tpl.html', $tpl_data); } @@ -578,11 +594,13 @@ class ServerController extends AbstractServerController 'label_sms' => psm_get_lang('servers', 'sms'), 'label_send_sms' => psm_get_lang('servers', 'send_sms'), 'label_send_pushover' => psm_get_lang('servers', 'send_pushover'), + 'label_send_webhook' => psm_get_lang('servers', 'send_webhook'), 'label_telegram' => psm_get_lang('servers', 'telegram'), - 'label_jabber' => psm_get_lang('servers', 'jabber'), + 'label_jabber' => psm_get_lang('servers', 'jabber'), + 'label_webhook' => psm_get_lang('servers', 'webhook'), 'label_pushover' => psm_get_lang('servers', 'pushover'), 'label_send_telegram' => psm_get_lang('servers', 'send_telegram'), - 'label_send_jabber' => psm_get_lang('servers', 'send_jabber'), + 'label_send_jabber' => psm_get_lang('servers', 'send_jabber'), 'label_users' => psm_get_lang('servers', 'users'), 'label_warning_threshold' => psm_get_lang('servers', 'warning_threshold'), 'label_warning_threshold_description' => psm_get_lang('servers', 'warning_threshold_description'), @@ -597,7 +615,8 @@ class ServerController extends AbstractServerController 'label_yes' => psm_get_lang('system', 'yes'), 'label_no' => psm_get_lang('system', 'no'), 'label_add_new' => psm_get_lang('system', 'add_new'), - 'label_seconds' => psm_get_lang('config', 'seconds'), + 'label_seconds' => psm_get_lang('system', 'seconds'), + 'label_milliseconds' => psm_get_lang('system', 'milliseconds'), 'label_online' => psm_get_lang('servers', 'online'), 'label_offline' => psm_get_lang('servers', 'offline'), 'label_ok' => psm_get_lang('system', 'ok'), @@ -606,6 +625,10 @@ class ServerController extends AbstractServerController 'label_settings' => psm_get_lang('system', 'settings'), 'label_output' => psm_get_lang('servers', 'output'), 'label_search' => psm_get_lang('system', 'search'), + 'label_log_title' => psm_get_lang('log', 'title'), + 'label_log_no_logs' => psm_get_lang('log', 'no_logs'), + 'label_date' => psm_get_lang('system', 'date'), + 'label_message' => psm_get_lang('system', 'message'), ); } @@ -627,4 +650,42 @@ class ServerController extends AbstractServerController } return $result; } + + /** + * Get logs for a server + * @param int $server_id + * @param string $type status/email/sms + * @return \PDOStatement array + */ + protected function getServerLogs($server_id, $type = 'status') + { + $sql_join = ''; + if ($this->getUser()->getUserLevel() > PSM_USER_ADMIN) { + // restrict by user_id + $sql_join = "JOIN `" . PSM_DB_PREFIX . "users_servers` AS `us` ON ( + `us`.`user_id`={$this->getUser()->getUserId()} + AND `us`.`server_id`=`servers`.`server_id` + )"; + } + $entries = $this->db->query( + 'SELECT ' . + '`servers`.`label`, ' . + '`servers`.`ip`, ' . + '`servers`.`port`, ' . + '`servers`.`type` AS server_type, ' . + '`log`.`log_id`, ' . + '`log`.`type`, ' . + '`log`.`message`, ' . + '`log`.`datetime` ' . + 'FROM `' . PSM_DB_PREFIX . 'log` AS `log` ' . + 'JOIN `' . PSM_DB_PREFIX . 'servers` AS `servers` ON (`servers`.`server_id`=`log`.`server_id`) ' . + $sql_join . + 'WHERE `log`.`type`=\'' . $type . '\' ' . + 'AND `log`.`server_id`=' . $server_id . ' ' . + 'ORDER BY `datetime` DESC ' . + 'LIMIT 0,20' + ); + + return $entries; + } } diff --git a/src/psm/Module/User/Controller/ProfileController.php b/src/psm/Module/User/Controller/ProfileController.php index 09f88691..74aeec9d 100644 --- a/src/psm/Module/User/Controller/ProfileController.php +++ b/src/psm/Module/User/Controller/ProfileController.php @@ -39,7 +39,7 @@ class ProfileController extends AbstractController * @var array $profile_fields */ protected $profile_fields = - array('name', 'user_name', 'email', 'mobile', 'pushover_key', 'pushover_device', 'telegram_id', 'jabber'); + array('name', 'user_name', 'email', 'mobile', 'pushover_key', 'pushover_device','webhook_url', 'webhook_json', 'telegram_id', 'jabber'); public function __construct(Database $db, \Twig_Environment $twig) { @@ -78,6 +78,12 @@ class ProfileController extends AbstractController 'label_password_repeat' => psm_get_lang('users', 'password_repeat'), 'label_level' => psm_get_lang('users', 'level'), 'label_mobile' => psm_get_lang('users', 'mobile'), + 'label_webhook' => psm_get_lang('users', 'webhook'), + 'label_webhook_description' => psm_get_lang('users', 'webhook_description'), + 'label_webhook_url' => psm_get_lang('users', 'webhook_url'), + 'label_webhook_url_description' => psm_get_lang('users', 'webhook_url_description'), + 'label_webhook_json' => psm_get_lang('users', 'webhook_json'), + 'label_webhook_json_description' => psm_get_lang('users', 'webhook_json_description'), 'label_pushover' => psm_get_lang('users', 'pushover'), 'label_pushover_description' => psm_get_lang('users', 'pushover_description'), 'label_pushover_key' => psm_get_lang('users', 'pushover_key'), @@ -90,8 +96,8 @@ class ProfileController extends AbstractController 'label_activate_telegram' => psm_get_lang('users', 'activate_telegram'), 'label_telegram_get_chat_id' => psm_get_lang('users', 'telegram_get_chat_id'), 'telegram_get_chat_id_url' => PSM_TELEGRAM_GET_ID_URL, - 'label_jabber' => psm_get_lang('users', 'jabber'), - 'label_jabber_description' => psm_get_lang('users', 'jabber_description'), + 'label_jabber' => psm_get_lang('users', 'jabber'), + 'label_jabber_description' => psm_get_lang('users', 'jabber_description'), 'label_email' => psm_get_lang('users', 'email'), 'label_save' => psm_get_lang('system', 'save'), 'form_action' => psm_build_url(array( diff --git a/src/psm/Module/User/Controller/UserController.php b/src/psm/Module/User/Controller/UserController.php index 1104a4d0..a648ee68 100644 --- a/src/psm/Module/User/Controller/UserController.php +++ b/src/psm/Module/User/Controller/UserController.php @@ -158,6 +158,8 @@ class UserController extends AbstractController 'name', 'user_name', 'mobile', + 'webhook_url', + 'webhook_json', 'pushover_key', 'pushover_device', 'telegram_id', @@ -255,6 +257,8 @@ class UserController extends AbstractController 'password_repeat', 'level', 'mobile', + 'webhook_url', + 'webhook_json', 'pushover_key', 'pushover_device', 'telegram_id', @@ -392,6 +396,12 @@ class UserController extends AbstractController 'label_level' => psm_get_lang('users', 'level'), 'label_level_description' => psm_get_lang('users', 'level_description'), 'label_mobile' => psm_get_lang('users', 'mobile'), + 'label_webhook' => psm_get_lang('users', 'webhook'), + 'label_webhook_description' => psm_get_lang('users', 'webhook_description'), + 'label_webhook_url' => psm_get_lang('users', 'webhook_url'), + 'label_webhook_url_description' => psm_get_lang('users', 'webhook_url_description'), + 'label_webhook_json' => psm_get_lang('users', 'webhook_json'), + 'label_webhook_json_description' => psm_get_lang('users', 'webhook_json_description'), 'label_pushover' => psm_get_lang('users', 'pushover'), 'label_pushover_description' => psm_get_lang('users', 'pushover_description'), 'label_pushover_key' => psm_get_lang('users', 'pushover_key'), diff --git a/src/psm/Module/User/UserEvents.php b/src/psm/Module/User/UserEvents.php index 33767928..de5ca932 100644 --- a/src/psm/Module/User/UserEvents.php +++ b/src/psm/Module/User/UserEvents.php @@ -35,15 +35,15 @@ final class UserEvents /** * @var string */ - public const USER_ADD = 'user.add'; + const USER_ADD = 'user.add'; /** * @var string */ - public const USER_EDIT = 'user.edit'; + const USER_EDIT = 'user.edit'; /** * @var string */ - public const USER_DELETE = 'user.delete'; + const USER_DELETE = 'user.delete'; } diff --git a/src/psm/Txtmsg/CMBulkSMS.php b/src/psm/Txtmsg/CMBulkSMS.php index a009d252..9a20c126 100644 --- a/src/psm/Txtmsg/CMBulkSMS.php +++ b/src/psm/Txtmsg/CMBulkSMS.php @@ -67,10 +67,10 @@ class CMBulkSMS extends Core protected $messageBody; /** @var string JSON Gateway API URL */ - public const GATEWAY_URL_JSON = "https://gw.cmtelecom.com/v1.0/message"; + const GATEWAY_URL_JSON = "https://gw.cmtelecom.com/v1.0/message"; /** @var string XML Gateway API URL */ - public const GATEWAY_URL_XML = "https://sgw01.cm.nl/gateway.ashx"; + const GATEWAY_URL_XML = "https://sgw01.cm.nl/gateway.ashx"; /** * Build the message and send cURL request to the sms gateway diff --git a/src/psm/Txtmsg/OVHsms.php b/src/psm/Txtmsg/OVHsms.php new file mode 100644 index 00000000..709f9d19 --- /dev/null +++ b/src/psm/Txtmsg/OVHsms.php @@ -0,0 +1,101 @@ +. + * + * @package phpservermon + * @author Alexis Urien + * @Author Tim Zandbergen + * @author Ward Pieters + * @author Alexandre ZANELLI + * @copyright Copyright (c) 2016 Alexis Urien + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 3.5 + **/ + +namespace psm\Txtmsg; + +class OVHsms extends Core { + + /** + * Send sms using the OVH http2sms gateway + * Online documentation :https://docs.ovh.com/fr/sms/envoyer_des_sms_depuis_une_url_-_http2sms/ + * Ovh need Account and Login, then use format login@account in username field. + * + * @var string $message + * @var string $this->username + * @var string $this->password + * @var array $this->recipients + * @var array $this->originator + * + * @var resource $curl + * @var SimpleXMLElement $xmlResults + * @var string $err + * @var string $recipient + * @var mixed $result + * + * @var int $success + * @var string $error + * + * @return bool|string + */ + + + public function sendSMS($message) { + $error = ""; + $success = 1; + + $account_login = explode('@',$this->username); + + $recipients = join(',', $this->recipients); + + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, "https://www.ovh.com/cgi-bin/sms/http2sms.cgi?".http_build_query( + array( + "account" => $account_login[1], + "login" => $account_login[0], + "password" => $this->password, + "from" => str_replace('+', '00', $this->originator), + "to" => $recipients, + "message" => $message, + "contentType" => "text/xml", + "noStop" => 1, + ) + ) + ); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + + $result = curl_exec($curl); + $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $xmlResults = simplexml_load_string($result); + $err = curl_errno($curl); + + if ($err != 0 || $httpcode != 200 || $xmlResults === false ||($xmlResults->status != '100' && $xmlResults->status != '101')) { + $success = 0; + $error = "HTTP_code: ".$httpcode.".\ncURL error (".$err."): ".curl_strerror($err).". \nResult: ".$xmlResults->status." \n".$xmlResults->Message; + } + curl_close($curl); + + if ($success) { + return 1; + } + return $error; + } +} diff --git a/src/psm/Txtmsg/Octopush.php b/src/psm/Txtmsg/Octopush.php index 942f91b5..5d1731d0 100644 --- a/src/psm/Txtmsg/Octopush.php +++ b/src/psm/Txtmsg/Octopush.php @@ -63,7 +63,7 @@ class Octopush extends Core $recipients = join(',', $this->recipients); - $message = ($smsType == "FR") ? urlencode($message . " STOP au XXXX") : urlencode($message); + $message = ($smsType == "FR") ? rawurlencode($message . " STOP au XXXXX") : rawurlencode($message); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, "http://www.octopush-dm.com/api/sms/?" . http_build_query( diff --git a/src/psm/Txtmsg/SMSAPI.php b/src/psm/Txtmsg/SMSAPI.php new file mode 100755 index 00000000..83601e05 --- /dev/null +++ b/src/psm/Txtmsg/SMSAPI.php @@ -0,0 +1,149 @@ +. + * + * @package phpservermon + * @author Mateusz Małek + * @copyright Copyright (c) 2008-2017 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 3.5 + **/ + +namespace psm\Txtmsg; + +class SMSAPI extends Core +{ + const VARIANT_INTERNATIONAL = 1; + const VARIANT_POLISH = 2; + + /** + * SMSAPI comes with two variants - designed for polish or international customers. + * + * @var int + */ + private $variant = self::VARIANT_INTERNATIONAL; + + /** + * Name of the sender. As a default the sender name is set to "Test". + * Only verified names are being accepted. + * Sender name may be set after logging into Customer Portal on Sendernames. + * @see https://www.smsapi.com/docs/#2-single-sms + * + * @var string + */ + protected $originator; + + /** + * Token used to authenticate in SMSAPI system. + * @see https://www.smsapi.com/docs/#authentication + * + * @var string + */ + protected $password; + + /** + * Send sms using the SMSAPI + * + * @var string $message + * @var array $this->recipients + * @var array $this->originator + * @var string $this->password + * @var array $recipients_chunk + * @var string $host + * + * @var mixed $result + * @var array $headers + * + * @var int $success + * @var string $error + * + * @return bool|string + */ + + public function sendSMS($message) + { + $tld = ($this->variant === static::VARIANT_INTERNATIONAL) ? "com" : "pl"; + $host = "api.smsapi.{$tld}"; + $backupHost = "api2.smsapi.{$tld}"; + + // One user at a time. + $recipients_chunk = array_chunk($this->recipients, 1); + foreach ($recipients_chunk as $recipient) { + try { + $response = $this->processSendOperation($host, $recipient, $message); + } catch (\RuntimeException $e) { + try { + $response = $this->processSendOperation($backupHost, $recipient, $message); + } catch (\RuntimeException $e) { + return "({$recipient}) " . $e->getMessage(); + } + } + + if (isset($response->error)) { + return $response->message; + } + + return 1; + } + } + + /** + * Perform actual SMS sending operation + * + * @param $host + * @param $recipient + * @param $message + * @return object + * @throws RuntimeException + */ + private function processSendOperation($host, $recipient, $message) + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://{$host}/sms.do"); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + "access_token" => $this->password, + "from" => $this->originator, + "to" => $recipient, + "message" => $message, + "encoding" => "utf-8", + "normalize" => "1", + "format" => "json" + ))); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + + $result = curl_exec($ch); + + $error = false; + if (curl_errno($ch)) { + $error = curl_error($ch); + } + + curl_close($ch); + + if ($error !== false) { + throw new \RuntimeException($error); + } + + return json_decode($result); + } +} diff --git a/src/psm/Util/Install/Installer.php b/src/psm/Util/Install/Installer.php index 888af309..a1476467 100644 --- a/src/psm/Util/Install/Installer.php +++ b/src/psm/Util/Install/Installer.php @@ -134,11 +134,11 @@ class Installer $queries = array(); $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "servers` ( `ip`, `port`, `label`, `type`, `pattern`, `pattern_online`, `redirect_check`, - `status`, `rtime`, `active`, `email`, `sms`, `pushover`, `telegram`, `jabber`) + `status`, `rtime`, `active`, `email`, `sms`, `pushover`,`webhook`, `telegram`, `jabber`) VALUES ('http://sourceforge.net/index.php', 80, 'SourceForge', 'website', '', - 'yes', 'bad', 'on', '0.0000000', 'yes', 'yes', 'yes', 'yes', 'yes', 'yes'), + 'yes', 'bad', 'on', '0.0000000', 'yes', 'yes', 'yes', 'yes','yes', 'yes', 'yes'), ('smtp.gmail.com', 465, 'Gmail SMTP', 'service', '', - 'yes', 'bad','on', '0.0000000', 'yes', 'yes', 'yes', 'yes', 'yes', 'yes')"; + 'yes', 'bad','on', '0.0000000', 'yes', 'yes', 'yes', 'yes', 'yes', 'yes', 'yes')"; $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "users_servers` (`user_id`,`server_id`) VALUES (1, 1), (1, 2);"; $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "config` (`key`, `value`) VALUE ('language', 'en_US'), @@ -160,6 +160,7 @@ class Installer ('sms_gateway_username', 'username'), ('sms_gateway_password', 'password'), ('sms_from', '1234567890'), + ('webhook_status', '0'), ('pushover_status', '0'), ('pushover_api_token', ''), ('telegram_status', '0'), @@ -176,6 +177,7 @@ class Installer ('log_email', '1'), ('log_sms', '1'), ('log_pushover', '1'), + ('log_webhook', '1'), ('log_telegram', '1'), ('log_jabber', '1'), ('log_retention_period', '365'), @@ -214,7 +216,9 @@ class Installer `mobile` varchar(15) NOT NULL, `pushover_key` varchar(255) NOT NULL, `pushover_device` varchar(255) NOT NULL, - `telegram_id` varchar(255) NOT NULL, + `webhook_url` varchar(255) NOT NULL, + `webhook_json` varchar(255) NOT NULL DEFAULT '{\"text\":\"servermon: #message\"}', + `telegram_id` varchar(255) NOT NULL , `jabber` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`user_id`), @@ -235,7 +239,7 @@ class Installer PSM_DB_PREFIX . 'log' => "CREATE TABLE `" . PSM_DB_PREFIX . "log` ( `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `server_id` int(11) unsigned NOT NULL, - `type` enum('status','email','sms','pushover','telegram', 'jabber') NOT NULL, + `type` enum('status','email','sms','pushover','webhook','telegram', 'jabber') NOT NULL, `message` TEXT NOT NULL, `datetime` timestamp NOT NULL default CURRENT_TIMESTAMP, PRIMARY KEY (`log_id`) @@ -270,6 +274,7 @@ class Installer `email` enum('yes','no') NOT NULL default 'yes', `sms` enum('yes','no') NOT NULL default 'no', `pushover` enum('yes','no') NOT NULL default 'yes', + `webhook` enum('yes','no') NOT NULL default 'yes', `telegram` enum('yes','no') NOT NULL default 'yes', `jabber` enum('yes','no') NOT NULL default 'yes', `warning_threshold` mediumint(1) unsigned NOT NULL DEFAULT '1', @@ -357,6 +362,9 @@ class Installer if (version_compare($version_from, '3.5.0', '<')) { $this->upgrade350(); } + if (version_compare($version_from, '3.6.0', '<')) { + $this->upgrade360(); + } psm_update_conf('version', $version_to); } @@ -553,7 +561,7 @@ class Installer $this->execSQL($queries); - // Create log_users table + // Create log_users table $this->execSQL("CREATE TABLE `" . PSM_DB_PREFIX . "log_users` ( `log_id` int(11) UNSIGNED NOT NULL , `user_id` int(11) UNSIGNED NOT NULL , @@ -659,7 +667,7 @@ class Installer $this->execSQL($queries); $this->log('Combined notifications enabled. Check out the config page for more info.'); } - + /** * Patch for v3.4.2 release * Version_compare was forgotten in v3.4.1 and query failed. @@ -715,4 +723,27 @@ class Installer $this->execSQL($queries); } + + /** + * Upgrade for v3.6.0 release + */ + protected function upgrade360() + { + $queries = array(); + + $queries[] = 'ALTER TABLE `' . PSM_DB_PREFIX . 'users` + ADD `webhook_url` VARCHAR( 255 ) NOT NULL AFTER `telegram_id`;'; + $queries[] = 'ALTER TABLE `' . PSM_DB_PREFIX . 'users` + ADD `webhook_json` VARCHAR( 255 ) NOT NULL AFTER `telegram_id`;'; + $queries[] = "ALTER TABLE `' . PSM_DB_PREFIX . 'log` + CHANGE `type` `type` ENUM('status','email','sms','webhook','pushover','telegram','jabber') + CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;"; + $queries[] = "ALTER TABLE `' . PSM_DB_PREFIX . 'servers` + ADD `webhook` ENUM( 'yes','no' ) NOT NULL DEFAULT 'yes' AFTER `telegram`;"; + $queries[] = "INSERT INTO `' . PSM_DB_PREFIX . 'config` (`key`, `value`) VALUE + ('webhook_status', '0'), + ('log_webhook', '1')"; + + $this->execSQL($queries); + } } diff --git a/src/psm/Util/Module/Modal.php b/src/psm/Util/Module/Modal.php index 76d27ee6..21176096 100644 --- a/src/psm/Util/Module/Modal.php +++ b/src/psm/Util/Module/Modal.php @@ -32,9 +32,9 @@ namespace psm\Util\Module; class Modal implements ModalInterface { - public const MODAL_TYPE_OK = 0; - public const MODAL_TYPE_OKCANCEL = 1; - public const MODAL_TYPE_DANGER = 2; + const MODAL_TYPE_OK = 0; + const MODAL_TYPE_OKCANCEL = 1; + const MODAL_TYPE_DANGER = 2; /** * prefix used for modal dialog box elements @@ -145,7 +145,7 @@ class Modal implements ModalInterface $tpl = $this->twig->loadTemplate('util/module/modal.tpl.html'); $html = $tpl->render(array( 'modal_id' => $this->modal_id, - 'modal_title' => !empty($this->title) ? $this->title : psm_get_lang('system', 'title'), + 'modal_title' => !empty($this->title) ? $this->title : psm_get_conf('site_title', psm_get_lang('system', 'title')), 'modal_body' => $message, 'has_cancel' => $has_cancel, 'label_cancel' => psm_get_lang('system', 'cancel'), diff --git a/src/psm/Util/Server/HistoryGraph.php b/src/psm/Util/Server/HistoryGraph.php index 8b5b8e38..11425e8b 100644 --- a/src/psm/Util/Server/HistoryGraph.php +++ b/src/psm/Util/Server/HistoryGraph.php @@ -29,7 +29,10 @@ namespace psm\Util\Server; +use DateTime; use psm\Service\Database; +use Twig\Error\Error; +use Twig_Environment; /** * History util, create HTML for server graphs @@ -39,17 +42,17 @@ class HistoryGraph /** * Database service - * @var \psm\Service\Database $db; + * @var Database $db; */ protected $db; /** * Twig environment - * @var \Twig_Environment $twig + * @var Twig_Environment $twig */ protected $twig; - public function __construct(Database $db, \Twig_Environment $twig) + public function __construct(Database $db, Twig_Environment $twig) { $this->db = $db; $this->twig = $twig; @@ -57,7 +60,9 @@ class HistoryGraph /** * Prepare the HTML for the graph - * @return string + * @param string $server_id ID of server to fetch data for + * @return string Created HTML + * @throws Error On twig error */ public function createHTML($server_id) { @@ -65,16 +70,16 @@ class HistoryGraph $archive = new ArchiveManager($this->db); $archive->archive($server_id); - $now = new \DateTime(); - $last_week = new \DateTime('-1 week 0:0:0'); - $last_year = new \DateTime('-1 year -1 week 0:0:0'); + $now = new DateTime(); + $last_week = new DateTime('-1 week 0:0:0'); + $last_year = new DateTime('-1 year -1 week 0:0:0'); $graphs = array( 0 => $this->generateGraphUptime($server_id, $last_week, $now), 1 => $this->generateGraphHistory($server_id, $last_year, $last_week), ); $info_fields = array( - 'latency_avg' => '%01.4f', + 'latency_avg' => '%01.5f', 'uptime' => '%01.3f%%', ); @@ -101,8 +106,8 @@ class HistoryGraph /** * Generate data for uptime graph * @param int $server_id - * @param \DateTime $start_time Lowest DateTime of the graph - * @param \DateTime $end_time Highest DateTime of the graph + * @param DateTime $start_time Lowest DateTime of the graph + * @param DateTime $end_time Highest DateTime of the graph * @return array */ public function generateGraphUptime($server_id, $start_time, $end_time) @@ -112,9 +117,9 @@ class HistoryGraph 'latency' => array(), ); - $hour = new \DateTime('-1 hour'); - $day = new \DateTime('-1 day'); - $week = new \DateTime('-1 week'); + $hour = new DateTime('-1 hour'); + $day = new DateTime('-1 day'); + $week = new DateTime('-1 week'); $records = $this->getRecords('uptime', $server_id, $start_time, $end_time); @@ -148,8 +153,8 @@ class HistoryGraph /** * Generate data for history graph * @param int $server_id - * @param \DateTime $start_time Lowest DateTime of the graph - * @param \DateTime $end_time Highest DateTime of the graph + * @param DateTime $start_time Lowest DateTime of the graph + * @param DateTime $end_time Highest DateTime of the graph * @return array */ public function generateGraphHistory($server_id, $start_time, $end_time) @@ -160,9 +165,9 @@ class HistoryGraph 'latency_max' => array(), ); - $week = new \DateTime('-2 week 0:0:0'); - $month = new \DateTime('-1 month -1 week 0:0:0'); - $year = new \DateTime('-1 year -1 week 0:0:0'); + $week = new DateTime('-2 week 0:0:0'); + $month = new DateTime('-1 month -1 week 0:0:0'); + $year = new DateTime('-1 year -1 week 0:0:0'); $records = $this->getRecords('history', $server_id, $year, $end_time); @@ -197,8 +202,8 @@ class HistoryGraph * Get all uptime/history records for a server * @param string $type * @param int $server_id - * @param \DateTime $start_time Lowest DateTime of the graph - * @param \DateTime $end_time Highest DateTime of the graph + * @param DateTime $start_time Lowest DateTime of the graph + * @param DateTime $end_time Highest DateTime of the graph * @return array */ protected function getRecords($type, $server_id, $start_time, $end_time) @@ -207,17 +212,19 @@ class HistoryGraph return array(); } - $records = $this->db->execute( - "SELECT * + /** @noinspection SqlNoDataSourceInspection */ + /** @noinspection SqlResolve */ + /** @noinspection PhpUndefinedConstantInspection */ + return $this->db->execute( + "SELECT *, UNIX_TIMESTAMP(CONVERT_TZ(`date`, '+00:00', @@session.time_zone)) AS date_ts FROM `" . PSM_DB_PREFIX . "servers_$type` - WHERE `server_id` = :server_id AND `date` BETWEEN :start_time AND :end_time ORDER BY `date` ASC", + WHERE `server_id` = :server_id AND `date` BETWEEN :start_time AND :end_time ORDER BY `date`", array( 'server_id' => $server_id, 'start_time' => $start_time->format('Y-m-d H:i:s'), 'end_time' => $end_time->format('Y-m-d H:i:s'), ) ); - return $records; } /** @@ -225,12 +232,9 @@ class HistoryGraph * @param array $records All uptime records to parse, MUST BE SORTED BY DATE IN ASCENDING ORDER * @param array $lines Array with keys as line ids to prepare (key must be available in uptime records) * @param string $latency_avg_key which key from uptime records to use for calculating averages - * @param \DateTime $start_time Lowest DateTime of the graph - * @param \DateTime $end_time Highest DateTime of the graph + * @param DateTime $start_time Lowest DateTime of the graph + * @param DateTime $end_time Highest DateTime of the graph * @param boolean $add_uptime Add uptime calculation? - * @param array $prev Previous result - * @param int $downtime Total downtime - * @param int $prev_downtime Timestamp from last offline record. 0 when last record is uptime * @return array */ protected function generateGraphLines( @@ -241,64 +245,90 @@ class HistoryGraph $end_time, $add_uptime = false ) { - $now = new \DateTime(); + $now = new DateTime(); $data = array(); // PLEASE NOTE: all times are in microseconds! because of javascript. $latency_avg = 0; + /** @var array $prev Previous record */ $prev = reset($records); + // Timestamp from last offline record. 0 when last record is up. $prev_downtime = 0; + // Total downtime $downtime = 0; + // The keys of the lines iterated + $line_keys = array_keys($lines); + // Determine whether to process data for the short history graph + $is_short_graph = count($line_keys) === 1 && $line_keys[0] === 'latency'; + + // get highest latency record for offline height + $highest_latency = 0.0; + if ($is_short_graph) { + foreach ($records as $record) { + $latency = (float) $record['latency']; + if ($latency > $highest_latency) { + $highest_latency = $latency; + } + } + // to ms + $highest_latency = round($highest_latency * 1000); + } + // Create the list of points and server down zones foreach ($records as $record) { - $time = strtotime($record['date']); // use the first line to calculate average latency $latency_avg += (float) $record[$latency_avg_key]; - foreach ($lines as $key => $value) { - // add the value for each of the different lines - if (isset($record[$key])) { - if (isset($record['status'])) { - // down - if ($record['status'] == 0) { - $lines['online'][] = $prev['status'] - // Previous datapoint was online - ? '{ x: ' . ($time * 1000) . ', y: ' . $prev['latency'] . '}' - // Previous datapoint was offline - : '{ x: ' . ($time * 1000) . ', y: null}'; - // new outage start - $lines['offline'][] = '{ x: ' . ($time * 1000) . ', y:0.1}'; + if ($is_short_graph) { + $time = (int) $record['date_ts']; + // Timestamp in milliseconds + $time_ms = $time * 1000; + if (!$record['status']) { + // down + $lines['online'][] = $prev['status'] + // Previous datapoint was online + ? ['x' => $time_ms, 'y' => round($prev['latency'] * 1000, 3)] + // Previous datapoint was offline + : ['x' => $time_ms, 'y' => null]; + // new outage start + $lines['offline'][] = ['x' => $time_ms, 'y' => $highest_latency]; - $prev_downtime != 0 ?: $prev_downtime = $time; - } else { - // up - // outage ends - $lines['offline'][] = $prev['status'] - // Previous datapoint was online - ? '{ x: ' . ($time * 1000) . ', y:null}' - // Previous datapoint was offline - : '{ x: ' . ($time * 1000) . ', y:0.1}'; - $lines['online'][] = '{ x: ' . ($time * 1000) . ', y: ' . - round((float) $record[$key], 4) . '}'; - - $prev_downtime == 0 ?: $downtime += ($time - $prev_downtime); - $prev_downtime = 0; - } - } else { - $lines[$key][] = '{ x: \'' . $record['date'] . '\', y: ' . $record[$key] . '}'; + if ($prev_downtime === 0) { + $prev_downtime = $time; } - $prev = $record; + } else { + // up + // outage ends + $lines['offline'][] = $prev['status'] + // Previous datapoint was online + ? ['x' => $time_ms, 'y' => null] + // Previous datapoint was offline + : ['x' => $time_ms, 'y' => $highest_latency]; + $lines['online'][] = ['x' => $time_ms, 'y' => round($record['latency'] * 1000, 3)]; + + if ($prev_downtime !== 0) { + $downtime += ($time - $prev_downtime); + } + $prev_downtime = 0; + } + } else { + foreach ($line_keys as $key) { + // add the value for each of the different lines + $lines[$key][] = ['x' => $record['date'], 'y' => $record[$key] * 1000]; } } + $prev = $record; } // Was down before. // Record the first and last date as a string in the down array $prev_downtime == 0 ?: $downtime += ($now->getTimestamp() - $prev_downtime); if ($add_uptime) { - $prev['status'] ?: $lines['offline'][] = '{ x: ' . ($now->getTimestamp() * 1000) . ', y:0.1}'; + if (!$prev['status']) { + $lines['offline'][] = ['x' => $now->getTimestamp() * 1000, 'y' => $highest_latency]; + } $data['uptime'] = 100 - ($downtime / ($end_time->getTimestamp() - $start_time->getTimestamp())); } @@ -307,11 +337,12 @@ class HistoryGraph if (empty($line_value)) { continue; } - $lines_merged[$line_key]['value'] = implode(', ', $line_value); + $lines_merged[$line_key]['value'] = json_encode($line_value); $lines_merged[$line_key]['name'] = psm_get_lang('servers', $line_key); } - $data['latency_avg'] = count($records) > 0 ? ($latency_avg / count($records)) : 0; + $n_records = count($records); + $data['latency_avg'] = $n_records > 0 ? ($latency_avg / $n_records) : 0; $data['lines'] = sizeof($lines_merged) ? $lines_merged : ''; $data['end_timestamp'] = number_format($end_time->getTimestamp(), 0, '', '') * 1000; $data['start_timestamp'] = number_format($start_time->getTimestamp(), 0, '', '') * 1000; diff --git a/src/psm/Util/Server/UpdateManager.php b/src/psm/Util/Server/UpdateManager.php index 2e3ee42c..2ba6ff0a 100644 --- a/src/psm/Util/Server/UpdateManager.php +++ b/src/psm/Util/Server/UpdateManager.php @@ -53,9 +53,9 @@ class UpdateManager implements ContainerAwareInterface */ public function run($skip_perms = false, $status = null) { - if (false === in_array($status, ['on', 'off'], true)) { - $status = null; - } + if (false === in_array($status, ['on', 'off'], true)) { + $status = null; + } // check if we need to restrict the servers to a certain user $sql_join = ''; @@ -63,17 +63,17 @@ class UpdateManager implements ContainerAwareInterface if (!$skip_perms && $this->container->get('user')->getUserLevel() > PSM_USER_ADMIN) { // restrict by user_id $sql_join = "JOIN `" . PSM_DB_PREFIX . "users_servers` AS `us` ON ( - `us`.`user_id`={$this->container->get('user')->getUserId()} - AND `us`.`server_id`=`s`.`server_id` - )"; + `us`.`user_id`={$this->container->get('user')->getUserId()} + AND `us`.`server_id`=`s`.`server_id` + )"; } $sql = "SELECT `s`.`server_id`,`s`.`ip`,`s`.`port`,`s`.`label`,`s`.`type`,`s`.`pattern`,`s`.`header_name`, - `s`.`header_value`,`s`.`status`,`s`.`active`,`s`.`email`,`s`.`sms`,`s`.`pushover`,`s`.`telegram`, + `s`.`header_value`,`s`.`status`,`s`.`active`,`s`.`email`,`s`.`sms`,`s`.`pushover`,`s`.`webhook`,`s`.`telegram`, `s`.`jabber` - FROM `" . PSM_DB_PREFIX . "servers` AS `s` - {$sql_join} - WHERE `active`='yes' " . ($status !== null ? ' AND `status` = \'' . $status . '\'' : ''); + FROM `" . PSM_DB_PREFIX . "servers` AS `s` + {$sql_join} + WHERE `active`='yes' " . ($status !== null ? ' AND `status` = \'' . $status . '\'' : ''); $servers = $this->container->get('db')->query($sql); diff --git a/src/psm/Util/Server/Updater/StatusNotifier.php b/src/psm/Util/Server/Updater/StatusNotifier.php index 5b0672a6..263bfdf1 100644 --- a/src/psm/Util/Server/Updater/StatusNotifier.php +++ b/src/psm/Util/Server/Updater/StatusNotifier.php @@ -63,16 +63,22 @@ class StatusNotifier */ protected $send_pushover = false; + /** + * Send webhook notification? + * @var boolean $send_webhook + */ + protected $send_webhook = false; + /** * Send telegram? * @var boolean $send_telegram */ protected $send_telegram = false; - /** - * Send Jabber? - * @var bool - */ + /** + * Send Jabber? + * @var bool + */ protected $send_jabber = false; /** @@ -128,6 +134,7 @@ class StatusNotifier $this->send_emails = (bool)psm_get_conf('email_status'); $this->send_sms = (bool)psm_get_conf('sms_status'); + $this->send_webhook = (bool)psm_get_conf('webhook_status'); $this->send_pushover = (bool)psm_get_conf('pushover_status'); $this->send_telegram = (bool)psm_get_conf('telegram_status'); $this->send_jabber = (bool)psm_get_conf('jabber_status'); @@ -149,6 +156,7 @@ class StatusNotifier if ( !$this->send_emails && !$this->send_sms && + !$this->send_webhook && !$this->send_pushover && !$this->send_telegram && !$this->send_jabber && @@ -175,6 +183,7 @@ class StatusNotifier 'error', 'email', 'sms', + 'webhook', 'pushover', 'telegram', 'jabber', @@ -245,7 +254,11 @@ class StatusNotifier // yay lets wake those nerds up! $this->notifyByTxtMsg($users); } - + // check if webhook is enabled for this server + if ($this->send_webhook && $this->server['webhook'] == 'yes') { + // yay lets wake those nerds up! + $this->combine ? $this->setCombi('webhook') : $this->notifyByWebhook($users); + } // check if pushover is enabled for this server if ($this->send_pushover && $this->server['pushover'] == 'yes') { // yay lets wake those nerds up! @@ -258,7 +271,7 @@ class StatusNotifier } if ($this->send_jabber && $this->server['jaber'] == 'yes') { - $this->combine ? $this->setCombi('jabber') : $this->notifyByJabber($users); + $this->combine ? $this->setCombi('jabber') : $this->notifyByJabber($users); } return $notify; @@ -461,7 +474,7 @@ class StatusNotifier $pushover->setTitle($title); $pushover->setMessage(str_replace('
    ', "\n", $message)); $pushover->setUrl(psm_build_url()); - $pushover->setUrlTitle(psm_get_lang('system', 'title')); + $pushover->setUrlTitle(psm_get_conf('site_title', psm_get_lang('system', 'title'))); // Log if (psm_get_conf('log_pushover')) { @@ -482,7 +495,48 @@ class StatusNotifier $pushover->send(); } } + /** + * This functions performs the webhook notifications + * + * @param \PDOStatement $users + * @param array $combi contains message and subject (optional) + * @return void + */ + protected function notifyByWebhook($users, $combi = array()) + { + foreach ($users as $k => $user) { + if (trim($user['webhook_url']) == '') { + unset($users[$k]); + } + } + $webhook = psm_build_webhook(); + + $message = key_exists('message', $combi) ? + $combi['message'] : + psm_parse_msg($this->status_new, 'webhook_message', $this->server); + $message = str_replace('
    ', "\n", $message); + $message = str_replace('
    ', "\n", $message); + $title = key_exists('subject', $combi) ? + $combi['subject'] : + psm_parse_msg($this->status_new, 'webhook_title', $this->server); + + // Log + if (psm_get_conf('log_webhook')) { + $log_id = psm_add_log($this->server_id, 'webhook', $message); + } + + // send notifications to all users + foreach ($users as $user) { + // Log + if (!empty($log_id)) { + psm_add_log_user($log_id, $user['user_id']); + } + $webhook->setUrl($user['webhook_url']); + $webhook->setJson($user['webhook_json']); + $webhook->sendWebhook($message); + } + } /** * This functions performs the text message notifications * @@ -562,52 +616,52 @@ class StatusNotifier } } - /** - * @param array $users - * @param array $combi - */ + /** + * @param array $users + * @param array $combi + */ protected function notifyByJabber($users, $combi = []) { - // Remove users that have no jabber - foreach ($users as $k => $user) { - if (trim($user['jabber']) === '') { - unset($users[$k]); - } - } + // Remove users that have no jabber + foreach ($users as $k => $user) { + if (trim($user['jabber']) === '') { + unset($users[$k]); + } + } - // Validation - if (empty($users)) { - return; - } + // Validation + if (empty($users)) { + return; + } - // Message - $message = key_exists('message', $combi) ? - $combi['message'] : - psm_parse_msg($this->status_new, 'jabber_message', $this->server); + // Message + $message = key_exists('message', $combi) ? + $combi['message'] : + psm_parse_msg($this->status_new, 'jabber_message', $this->server); - // Log - if (psm_get_conf('log_jabber')) { - $log_id = psm_add_log($this->server_id, 'jabber', $message); - } + // Log + if (psm_get_conf('log_jabber')) { + $log_id = psm_add_log($this->server_id, 'jabber', $message); + } - $usersJabber = []; - foreach ($users as $user) { - // Log - if (!empty($log_id)) { - psm_add_log_user($log_id, $user['user_id']); - } - $usersJabber[] = $user['jabber']; - } - // Jabber - psm_jabber_send_message( - psm_get_conf('jabber_host'), - psm_get_conf('jabber_username'), - psm_password_decrypt(psm_get_conf('password_encrypt_key'), psm_get_conf('jabber_password')), - $usersJabber, - $message, - (trim(psm_get_conf('jabber_port')) !== '' ? (int)psm_get_conf('jabber_port') : null), - (trim(psm_get_conf('jabber_domain')) !== '' ? psm_get_conf('jabber_domain') : null) - ); + $usersJabber = []; + foreach ($users as $user) { + // Log + if (!empty($log_id)) { + psm_add_log_user($log_id, $user['user_id']); + } + $usersJabber[] = $user['jabber']; + } + // Jabber + psm_jabber_send_message( + psm_get_conf('jabber_host'), + psm_get_conf('jabber_username'), + psm_password_decrypt(psm_get_conf('password_encrypt_key'), psm_get_conf('jabber_password')), + $usersJabber, + $message, + (trim(psm_get_conf('jabber_port')) !== '' ? (int)psm_get_conf('jabber_port') : null), + (trim(psm_get_conf('jabber_domain')) !== '' ? psm_get_conf('jabber_domain') : null) + ); } /** @@ -619,15 +673,15 @@ class StatusNotifier { // find all the users with this server listed $users = $this->db->query(' - SELECT `u`.`user_id`, `u`.`name`,`u`.`email`, `u`.`mobile`, `u`.`pushover_key`, + SELECT `u`.`user_id`, `u`.`name`,`u`.`email`, `u`.`mobile`, `u`.`pushover_key`, `u`.`webhook_url`,`u`.`webhook_json`, `u`.`pushover_device`, `u`.`telegram_id`, `u`.`jabber` - FROM `' . PSM_DB_PREFIX . 'users` AS `u` - JOIN `' . PSM_DB_PREFIX . "users_servers` AS `us` ON ( - `us`.`user_id`=`u`.`user_id` - AND `us`.`server_id` = {$server_id} - ) - "); + FROM `' . PSM_DB_PREFIX . 'users` AS `u` + JOIN `' . PSM_DB_PREFIX . "users_servers` AS `us` ON ( + `us`.`user_id`=`u`.`user_id` + AND `us`.`server_id` = {$server_id} + ) + "); return $users; } } diff --git a/src/psm/Util/Server/Updater/StatusUpdater.php b/src/psm/Util/Server/Updater/StatusUpdater.php index 6d6ff34b..5cc42f1c 100644 --- a/src/psm/Util/Server/Updater/StatusUpdater.php +++ b/src/psm/Util/Server/Updater/StatusUpdater.php @@ -89,7 +89,7 @@ class StatusUpdater $this->error = ''; $this->header = ''; $this->curl_info = ''; - $this->rtime = ''; + $this->rtime = 0; // get server info from db $this->server = $this->db->selectRow(PSM_DB_PREFIX . 'servers', array( @@ -175,41 +175,14 @@ class StatusUpdater */ protected function updatePing($max_runs, $run = 1) { - if ($max_runs == null || $max_runs > 1) { - $max_runs = 1; - } - $result = null; - // Execute ping - $pingCommand = 'ping6'; - $serverIp = $this->server['ip']; - if (filter_var($serverIp,FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false){ - $pingCommand = 'ping'; - } - $txt = exec($pingCommand . " -c " . $max_runs . " " . $serverIp . " 2>&1", $output); - // Non-greedy match on filler - $re1 = '.*?'; - // Uninteresting: float - $re2 = '[+-]?\\d*\\.\\d+(?![-+0-9\\.])'; - // Non-greedy match on filler - $re3 = '.*?'; - // Float 1 - $re4 = '([+-]?\\d*\\.\\d+)(?![-+0-9\\.])'; - if (preg_match_all("/" . $re1 . $re2 . $re3 . $re4 . "/is", $txt, $matches)) { - $result = $matches[1][0]; - } - if (substr($output[0],0,4) == 'PING' && strpos($output[count($output)-2],'packets transmitted')){ - $result = 0; - } - if (!is_null($result)) { - $this->header = $output[0]; - $status = true; - } else { - $this->header = "-"; - $this->error = $output[0]; - $status = false; - } - //Divide by a thousand to convert to milliseconds - $this->rtime = $result / 1000; + // Settings + $max_runs = ($max_runs == null || $max_runs > 1) ? 1 : $max_runs; + $server_ip = escapeshellcmd($this->server['ip']); + $os_is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + + $status = $os_is_windows ? + $this->pingFromWindowsMachine($server_ip, $max_runs) : + $this->pingFromNonWindowsMachine($server_ip, $max_runs); // check if server is available and rerun if asked. if (!$status && $run < $max_runs) { @@ -233,7 +206,7 @@ class StatusUpdater $starttime = microtime(true); $serverIp = $this->server['ip']; - if (filter_var($serverIp,FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false){ + if (filter_var($serverIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { $serverIp = "[$serverIp]"; } $fp = @fsockopen($serverIp, $this->server['port'], $errno, $this->error, $timeout); @@ -425,7 +398,9 @@ class StatusUpdater !empty($this->curl_info['certinfo']) && $server['ssl_cert_expiry_days'] > 0 ) { - $cert_expiration_date = strtotime($this->curl_info['certinfo'][0]['Expire date']); + $certinfo = reset($this->curl_info['certinfo']); + $certinfo = openssl_x509_parse($certinfo['Cert']); + $cert_expiration_date = $certinfo['validTo_time_t']; $expiration_time = round((int)($cert_expiration_date - time()) / 86400); $latest_time = time() + (86400 * $server['ssl_cert_expiry_days']); @@ -449,4 +424,87 @@ class StatusUpdater $this->db->save(PSM_DB_PREFIX . 'servers', $save, array('server_id' => $this->server_id)); } } + + /** + * Ping from a Windows Machine + * @param string $server_id + * @param int $max_runs + * @return boolean + */ + private function pingFromWindowsMachine($server_ip, $max_runs) + { + // Windows / Linux variant: use socket on Windows, commandline on Linux + // socket ping - Code from http://stackoverflow.com/a/20467492 + // save response time + $starttime = microtime(true); + + // set ping payload + $package = "\x08\x00\x7d\x4b\x00\x00\x00\x00PingHost"; + + $socket = socket_create(AF_INET, SOCK_RAW, 1); + socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 10, 'usec' => 0)); + socket_connect($socket, $server_ip, null); + socket_send($socket, $package, strLen($package), 0); + // socket_read returns a string or false + $status = socket_read($socket, 255) !== false ? true : false; + + if ($status) { + $this->header = "Success."; + } else { + $this->error = "Couldn't create socket [" . $errorcode . "]: " . socket_strerror(socket_last_error()); + } + + $this->rtime = microtime(true) - $starttime; + socket_close($socket); + + return $status; + } + + /** + * Ping from a non Windows Machine + * @param string $server_id + * @param int $max_runs + * @param string $ping_command + * @return boolean + */ + private function pingFromNonWindowsMachine($server_ip, $max_runs) + { + + // Choose right ping version, ping6 for IPV6, ping for IPV4 + $ping_command = filter_var($server_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false ? 'ping6' : 'ping'; + + // execute PING + exec($ping_command . " -c " . $max_runs . " " . $server_ip . " 2>&1", $output); + + // Check if output is PING and if transmitted packets is equal to received packets. + preg_match( + '/^(\d{1,3}) packets transmitted, (\d{1,3}).*$/', + $output[count($output) - 2], + $output_package_loss + ); + + if ( + substr($output[0], 0, 4) == 'PING' && + !empty($output_package_loss) && + $output_package_loss[1] === $output_package_loss[2] + ) { + // Gets avg from 'round-trip min/avg/max/stddev = 7.109/7.109/7.109/0.000 ms' + preg_match_all("/(\d+\.\d+)/", $output[count($output) - 1], $result); + // Converted to milliseconds + $this->rtime = floatval($result[0][1]) / 1000; + + $this->header = ""; + foreach ($output as $key => $value) { + $this->header .= $value . "\n"; + } + return true; + } + + $this->header = "-"; + foreach ($output as $key => $value) { + $this->header .= $value . "\n"; + } + $this->error = $output[count($output) - 2]; + return false; + } } diff --git a/src/templates/default/main/body.tpl.html b/src/templates/default/main/body.tpl.html index 7a1c3523..d294556c 100644 --- a/src/templates/default/main/body.tpl.html +++ b/src/templates/default/main/body.tpl.html @@ -31,31 +31,33 @@ {{ html_modal|raw }} -
    +
    {% if not user_level and subtitle %}

    {{ subtitle }}

    {% endif %} -
    {{ header_accessories|raw }}
    +
    {{ header_accessories|raw }}
    {% for msg in messages %} {% endfor %} -
    +
    {{ html_sidebar|raw }}
    {{ html_content|raw }}
    @@ -74,7 +76,7 @@ {% endblock %} {% endif %} - + diff --git a/src/templates/default/module/config/config.tpl.html b/src/templates/default/module/config/config.tpl.html index 3f7064e5..a9c4f6c0 100644 --- a/src/templates/default/module/config/config.tpl.html +++ b/src/templates/default/module/config/config.tpl.html @@ -30,7 +30,12 @@ +
@@ -41,6 +46,8 @@ {{ label_general }} {{ macro.input_checkbox("show_update", "show_update[]", label_show_update, show_update_checked) }} + + {{ macro.input_field("text", "site_title", null, "site_title", label_site_title, site_title, label_site_title, "255", null, null, null, null, true) }} {{ macro.input_select("language", "language", label_language, languages, language_current) }} @@ -48,6 +55,8 @@ {{ macro.input_field("text", "password_encrypt_key", null, "password_encrypt_key", label_password_encrypt_key, password_encrypt_key, "cab03a766...", "40", "password_encrypt_key_help", label_password_encrypt_key_note) }} + + {{ macro.input_field("text", "user_agent", null, "user_agent", label_user_agent, user_agent, "Mozilla/5.0...", "255", "user_agent_key_help", label_user_agent_key_note) }}
@@ -179,6 +188,24 @@ {{ macro.button_save("jabber_submit", label_save) }}
+
+
+ {{ label_settings_webhook }} +

{{ label_webhook_description|raw }}

+ + {{ macro.input_checkbox("webhook_status", "webhook_status", label_webhook_status, webhook_status_checked) }} + + {{ macro.input_checkbox("log_webhook", "log_webhook", label_log_webhook, log_webhook_checked) }} + + + + + + {{ macro.button_test("testWebhook", label_test) }} + {{ macro.input_hidden("test_webhook", "0") }} + {{ macro.button_save("webhook_submit", label_save) }} +
+
{{ macro.input_csrf() }} diff --git a/src/templates/default/module/server/history.tpl.html b/src/templates/default/module/server/history.tpl.html index 7f76bc69..7c08a2b3 100644 --- a/src/templates/default/module/server/history.tpl.html +++ b/src/templates/default/module/server/history.tpl.html @@ -1,5 +1,7 @@ + + {% for graph in graphs %}
Your browser does not support the canvas element. @@ -20,7 +22,7 @@

{% endif %} - {{ graph.info.0.label }}: {{ graph.info.0.value }}s + {{ graph.info.0.label }}: {{ graph.info.0.value * 1000 }} ms {% if graph.id == 'history_short' %} @@ -30,7 +32,7 @@ data: { datasets: [ { - data: [{{ graph.lines.offline.value }}], + data: {{ graph.lines.offline.value|raw }}, label: '{{ graph.lines.offline.name }}', backgroundColor: '#dc3545', borderColor: '#dc3545', @@ -41,7 +43,7 @@ spanGaps: false, }, { - data: [{{ graph.lines.online.value }}], + data: {{ graph.lines.online.value|raw }}, label: '{{graph.lines.online.name }}', fill: false, spanGaps: false, @@ -72,6 +74,25 @@ source: 'auto', } }] + }, + plugins: { + zoom: { + pan: { + enabled: true, + mode: 'x', + rangeMax: { + x: new Date, + }, + }, + zoom: { + enabled: true, + mode: 'x', + rangeMax: { + x: new Date, + }, + speed: 0.05, + } + } } } }); @@ -99,7 +120,7 @@ datasets: [ {% for key,line in graph.lines %} { - data: [{{ line.value|raw }}], + data: {{ line.value|raw }}, label: '{{ line.name }}', backgroundColor: colors['{{key}}'], borderColor: colors['{{key}}'], @@ -133,6 +154,25 @@ source: 'auto', } }] + }, + plugins: { + zoom: { + pan: { + enabled: true, + mode: 'x', + rangeMax: { + x: new Date, + }, + }, + zoom: { + enabled: true, + mode: 'x', + rangeMax: { + x: new Date, + }, + speed: 0.05, + } + } } } }); diff --git a/src/templates/default/module/server/server/list.tpl.html b/src/templates/default/module/server/server/list.tpl.html index 110c1261..c32c86b1 100644 --- a/src/templates/default/module/server/server/list.tpl.html +++ b/src/templates/default/module/server/server/list.tpl.html @@ -9,7 +9,7 @@ {{ label_domain }} {{ label_port }} {{ label_type }} - {{ label_rtime }} + {{ label_rtime }} (ms) {{ label_last_online }} {{ label_last_offline }} {{ label_monitoring }} @@ -41,7 +41,7 @@
{{ server.ip|raw }}
{{ server.port }} {{ server.type }} - {{ server.rtime }} + {{ (server.rtime * 1000)|round(2) }}
{{ server.last_online }}
{{ server.last_offline }}
@@ -73,7 +73,10 @@ J - {% endif %} + {% endif %} + {% if server.webhook|lower == 'yes' and config.webhook|lower %} + + {% endif %} {% if user_level == 10 %} diff --git a/src/templates/default/module/server/server/update.tpl.html b/src/templates/default/module/server/server/update.tpl.html index 38292434..0cefdd98 100644 --- a/src/templates/default/module/server/server/update.tpl.html +++ b/src/templates/default/module/server/server/update.tpl.html @@ -143,7 +143,9 @@ {{ macro.input_select_monitoring("telegram", "telegram", label_send_telegram, edit_telegram_selected, label_yes, label_no, warning_telegram, label_warning_telegram) }} - {{ macro.input_select_monitoring("jabber", "jabber", label_send_jabber, edit_jabber_selected, label_yes, label_no, warning_jabber, label_warning_jabber) }} + {{ macro.input_select_monitoring("jabber", "jabber", label_send_jabber, edit_jabber_selected, label_yes, label_no, warning_jabber, label_warning_jabber) }} + + {{ macro.input_select_monitoring("webhook", "webhook", label_send_webhook, edit_webhook_selected, label_yes, label_no, warning_webhook, label_warning_webhook) }}
diff --git a/src/templates/default/module/server/server/view.tpl.html b/src/templates/default/module/server/server/view.tpl.html index 20218480..154ea26c 100644 --- a/src/templates/default/module/server/server/view.tpl.html +++ b/src/templates/default/module/server/server/view.tpl.html @@ -1,6 +1,6 @@ {% import 'main/macros.tpl.html' as macro %} {{ macro.input_csrf() }} -
+
@@ -51,7 +51,7 @@
  • {{ label_rtime }}:
    -
    {{ rtime }} {{ label_seconds }}
    +
    {{ (rtime * 1000)|round(2) }} {{ label_milliseconds }}
  • @@ -347,13 +347,55 @@ {% endif %} - + +
  • + {{ label_webhook }}: + {% if webhook|lower == 'yes' %} + + + {% elseif webhook|lower == 'no' %} + + + {% else %} + + + {% endif %} +
  • {{ html_history|raw }}
    + {% if log_entries %} +
    +
    +
    + {{ label_log_title }} +
    +
    +
    + + + + + + + + + {% for entry in log_entries %} + + + + + {% endfor %} + +
    {{ label_date }}{{ label_message }}
    {{ entry.message|raw }}
    +
    +
    +
    +
    + {% endif %}
    @@ -36,7 +36,7 @@ - {{ macro.input_field("text", "telegram_id", null, "telegram_id", label_telegram_chat_id, telegram_id, label_telegram_chat_id, "255", "telegram_id_help", telegram_id_description) }} + {{ macro.input_field("text", "telegram_id", null, "telegram_id", label_telegram_chat_id, telegram_id, label_telegram_chat_id, "255", "telegram_id_help", label_telegram_id_description) }} {{ macro.input_hidden("activate_telegram", "0") }}
    @@ -44,7 +44,16 @@
    {{ label_jabber }} - {{ macro.input_field("text", "jabber", null, "jabber", label_jabber, jabber, label_jabber, "255", "jabber_help", jabber_description) }} -
    + {{ macro.input_field("text", "jabber", null, "jabber", label_jabber, jabber, label_jabber, "255", "jabber_help", label_jabber_description) }} + + +
    + {{ label_webhook }} +

    {{ label_webhook_description|raw }}

    + + {{ macro.input_field("text", "webhook_url", null, "webhook_url", label_webhook_url, webhook_url, "https://test.com/api/abcde", "255", "webhook_url_help", label_webhook_url_description) }} + + {{ macro.input_field("text", "webhook_json", null, "webhook_json", label_webhook_json, webhook_json, "{\"text\":\"servermon: #message\"}", "255", "webhook_json_help", label_webhook_json_description) }} +
    {{ macro.button_save(null, label_save) }} \ No newline at end of file diff --git a/src/templates/default/module/user/user/update.tpl.html b/src/templates/default/module/user/user/update.tpl.html index 0e86d617..bde98390 100644 --- a/src/templates/default/module/user/user/update.tpl.html +++ b/src/templates/default/module/user/user/update.tpl.html @@ -17,6 +17,10 @@ {{ macro.input_field("email", "email", null, "email", label_email, edit_value_email, null, "255") }} {{ macro.input_field("tel", "mobile", null, "mobile", label_mobile, edit_value_mobile, null, "20") }} + + {{ macro.input_field("text", "webhook_url", null, "webhook_url", label_webhook_url, edit_value_webhook_url, null, "255") }} + + {{ macro.input_field("text", "webhook_json", null, "webhook_json", label_webhook_json, edit_value_webhook_json, null, "255") }} {{ macro.input_field("text", "pushover_key", null, "pushover_key", label_pushover_key, edit_value_pushover_key, null, "255") }} diff --git a/src/templates/default/static/css/style.min.css b/src/templates/default/static/css/style.min.css index 5bc79370..a0236f19 100644 --- a/src/templates/default/static/css/style.min.css +++ b/src/templates/default/static/css/style.min.css @@ -1,2 +1,2 @@ -html{position:relative;min-height:100%}html[dir='rtl'] #auto_refresh_description,html[dir='rtl'] #log_retention_period_help{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}html[dir='ltr'] #auto_refresh_description,html[dir='ltr'] #log_retention_period_help{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}body{padding-top:4.5rem;margin-bottom:80px}.footer{position:absolute;bottom:0;width:100%;height:60px;line-height:60px;background-color:#f5f5f5}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,0.64)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,0.85)}dl,dt,dd{margin-bottom:0}footer .text-muted{color:#4C5557 !important}a,button,.nav-link{min-height:44px !important;min-width:44px !important}a.icon{text-decoration:none;cursor:pointer;padding-left:10px}form.form-signin input[type="text"],form.form-reset input[type="text"]{border-bottom-left-radius:0;border-bottom-right-radius:0}form.form-signin input[type="password"]{border-top-left-radius:0;border-top-right-radius:0}form.form-reset input#input-password{border-radius:0}form.form-reset input#input-password-repeat{border-top-left-radius:0;border-top-right-radius:0}form.form-signin,form.form-forgot,form.form-reset{margin:auto}table{table-layout:fixed}th,td{max-width:1px}.content{word-wrap:break-word;overflow-wrap:break-word}table tr[visible='false'],.no-result{display:none}table tr[visible='true']{display:table-row}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:unset}#meter{border-radius:200px 200px 0 0;height:100px;margin:50px auto 0;overflow:hidden;position:relative;width:200px}#meter:before{background:#fbfbfb;border-radius:200px 200px 0 0;-webkit-box-shadow:3px 1px 8px rgba(0,0,0,0.15) inset;box-shadow:3px 1px 8px rgba(0,0,0,0.15) inset;content:"";height:100px;position:absolute;width:200px}#meter:after{background:#fff;border-radius:140px 140px 0 0;bottom:0;-webkit-box-shadow:3px 1px 8px rgba(0,0,0,0.15);box-shadow:3px 1px 8px rgba(0,0,0,0.15);content:"\a" attr(data-value) "%\a" attr(translation);font-size:1.5em;font-weight:100;height:80px;left:20px;line-height:25px;position:absolute;text-align:center;width:160px;z-index:3;white-space:pre}#needle{background:rgba(52,52,64,0.7);border-radius:4px;bottom:-4px;-webkit-box-shadow:3px -1px 4px rgba(0,0,0,0.4);box-shadow:3px -1px 4px rgba(0,0,0,0.4);display:block;height:8px;left:5px;position:absolute;width:95px;-webkit-transform-origin:100% 4px;transform-origin:100% 4px;-webkit-transition:all 1s;transition:all 1s;border-radius:4px;bottom:-4px;box-shadow:3px -1px 4px rgba(0,0,0,0.4);display:block;height:8px;left:5px;position:absolute;width:95px;transform-origin:100% 4px;transition:all 1s}.dropdown-menu.show{left:inherit;right:0px} +html{position:relative;min-height:100%}html[dir='rtl'] #auto_refresh_description,html[dir='rtl'] #log_retention_period_help{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}html[dir='ltr'] #auto_refresh_description,html[dir='ltr'] #log_retention_period_help{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}body{padding-top:4.5rem;margin-bottom:80px}.container-fluid{max-width:1920px}.footer{position:absolute;bottom:0;width:100%;height:60px;line-height:60px;background-color:#f5f5f5}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,0.64)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,0.85)}dl,dt,dd{margin-bottom:0}footer .text-muted{color:#4C5557 !important}a,button,.nav-link{min-height:44px !important;min-width:44px !important}a.icon{text-decoration:none;cursor:pointer;padding-left:10px}form.form-signin input[type="text"],form.form-reset input[type="text"]{border-bottom-left-radius:0;border-bottom-right-radius:0}form.form-signin input[type="password"]{border-top-left-radius:0;border-top-right-radius:0}form.form-reset input#input-password{border-radius:0}form.form-reset input#input-password-repeat{border-top-left-radius:0;border-top-right-radius:0}form.form-signin,form.form-forgot,form.form-reset{margin:auto}table{table-layout:fixed}th,td{max-width:1px}.content{word-wrap:break-word;overflow-wrap:break-word}table tr[visible='false'],.no-result{display:none}table tr[visible='true']{display:table-row}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:unset}#meter{border-radius:200px 200px 0 0;height:100px;margin:50px auto 0;overflow:hidden;position:relative;width:200px}#meter:before{background:#fbfbfb;border-radius:200px 200px 0 0;-webkit-box-shadow:3px 1px 8px rgba(0,0,0,0.15) inset;box-shadow:3px 1px 8px rgba(0,0,0,0.15) inset;content:"";height:100px;position:absolute;width:200px}#meter:after{background:#fff;border-radius:140px 140px 0 0;bottom:0;-webkit-box-shadow:3px 1px 8px rgba(0,0,0,0.15);box-shadow:3px 1px 8px rgba(0,0,0,0.15);content:"\a" attr(data-value) "%\a" attr(translation);font-size:1.5em;font-weight:100;height:80px;left:20px;line-height:25px;position:absolute;text-align:center;width:160px;z-index:3;white-space:pre}#needle{background:rgba(52,52,64,0.7);border-radius:4px;bottom:-4px;-webkit-box-shadow:3px -1px 4px rgba(0,0,0,0.4);box-shadow:3px -1px 4px rgba(0,0,0,0.4);display:block;height:8px;left:5px;position:absolute;width:95px;-webkit-transform-origin:100% 4px;transform-origin:100% 4px;-webkit-transition:all 1s;transition:all 1s;border-radius:4px;bottom:-4px;box-shadow:3px -1px 4px rgba(0,0,0,0.4);display:block;height:8px;left:5px;position:absolute;width:95px;transform-origin:100% 4px;transition:all 1s}.dropdown-menu.show{left:inherit;right:0px} /*# sourceMappingURL=style.min.css.map */ \ No newline at end of file diff --git a/src/templates/default/static/css/style.min.css.map b/src/templates/default/static/css/style.min.css.map index e9d01194..39e49a9c 100644 --- a/src/templates/default/static/css/style.min.css.map +++ b/src/templates/default/static/css/style.min.css.map @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "AAAA,AAAA,IAAI,AAAC,CACD,QAAQ,CAAE,QAAQ,CAClB,UAAU,CAAE,IAAI,CACnB,AAED,AACI,IADA,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EACD,yBAAyB,CAD7B,IAAI,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EAED,0BAA0B,AAAC,CACvB,sBAAsB,CAAE,MAAM,CAC9B,yBAAyB,CAAE,MAAM,CACpC,AAGL,AACI,IADA,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EACD,yBAAyB,CAD7B,IAAI,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EAED,0BAA0B,AAAC,CACvB,uBAAuB,CAAE,MAAM,CAC/B,0BAA0B,CAAE,MAAM,CACrC,AAGL,AAAA,IAAI,AAAC,CACD,WAAW,CAAE,MAAM,CACnB,aAAa,CAAE,IAAI,CACtB,AAED,AAAA,OAAO,AAAC,CACJ,QAAQ,CAAE,QAAQ,CAClB,MAAM,CAAE,CAAC,CACT,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,WAAW,CAAE,IAAI,CACjB,gBAAgB,CAAE,OAAO,CAC5B,AAED,AAAA,YAAY,CAAC,WAAW,CAAC,SAAS,AAAC,CAC/B,KAAK,CAAE,sBAAwB,CAMlC,AAPD,AAGI,YAHQ,CAAC,WAAW,CAAC,SAAS,AAG7B,MAAM,CAHX,YAAY,CAAC,WAAW,CAAC,SAAS,AAI7B,MAAM,AAAC,CACJ,KAAK,CAAE,sBAAwB,CAClC,AAGL,AAAA,EAAE,CACF,EAAE,CACF,EAAE,AAAC,CACC,aAAa,CAAE,CAAC,CACnB,AAED,AAAA,MAAM,CAAC,WAAW,AAAC,CACf,KAAK,CAAE,kBAAkB,CAC5B,AAED,AAAA,CAAC,CACD,MAAM,CACN,SAAS,AAAC,CACN,UAAU,CAAE,eAAe,CAC3B,SAAS,CAAE,eAAe,CAC7B,AAED,AAAA,CAAC,AAAA,KAAK,AAAC,CACH,eAAe,CAAE,IAAI,CACrB,MAAM,CAAE,OAAO,CACf,YAAY,CAAE,IAAI,CACrB,AAED,AAAA,IAAI,AAAA,YAAY,CAAC,KAAK,CAAA,AAAA,IAAC,CAAK,MAAM,AAAX,EACvB,IAAI,AAAA,WAAW,CAAC,KAAK,CAAA,AAAA,IAAC,CAAK,MAAM,AAAX,CAAa,CAC/B,yBAAyB,CAAE,CAAC,CAC5B,0BAA0B,CAAE,CAAC,CAChC,AAED,AAAA,IAAI,AAAA,YAAY,CAAC,KAAK,CAAA,AAAA,IAAC,CAAK,UAAU,AAAf,CAAiB,CACpC,sBAAsB,CAAE,CAAC,CACzB,uBAAuB,CAAE,CAAC,CAC7B,AAED,AACI,IADA,AAAA,WAAW,CACX,KAAK,AAAA,eAAe,AAAC,CACjB,aAAa,CAAE,CAAC,CACnB,AAHL,AAKI,IALA,AAAA,WAAW,CAKX,KAAK,AAAA,sBAAsB,AAAC,CACxB,sBAAsB,CAAE,CAAC,CACzB,uBAAuB,CAAE,CAAC,CAC7B,AAGL,AAAA,IAAI,AAAA,YAAY,CAChB,IAAI,AAAA,YAAY,CAChB,IAAI,AAAA,WAAW,AAAC,CACZ,MAAM,CAAE,IAAI,CACf,AAED,AAAA,KAAK,AAAC,CACF,YAAY,CAAE,KAAK,CACtB,AAED,AAAA,EAAE,CAAE,EAAE,AAAC,CACH,SAAS,CAAC,GAAG,CAChB,AACD,AAAA,QAAQ,AAAC,CACL,SAAS,CAAC,UAAU,CACpB,aAAa,CAAC,UAAU,CAC3B,AAED,AAAA,KAAK,CAAC,EAAE,CAAA,AAAA,OAAC,CAAQ,OAAO,AAAf,EACT,UAAU,AAAC,CACP,OAAO,CAAE,IAAI,CAChB,AAED,AAAA,KAAK,CAAC,EAAE,CAAA,AAAA,OAAC,CAAQ,MAAM,AAAd,CAAgB,CACrB,OAAO,CAAE,SAAS,CACrB,AAED,AAAA,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,CACjD,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,AAAA,OAAO,CACxD,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,AAAA,MAAM,CACvD,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,AAAA,MAAM,AAAC,CACpD,KAAK,CAAE,KAAK,CACf,AAED,AAAA,MAAM,AAAC,CACH,aAAa,CAAE,eAAe,CAC9B,MAAM,CAAE,KAAK,CACb,MAAM,CAAE,WAAW,CACnB,QAAQ,CAAE,MAAM,CAChB,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,KAAK,CA4Bf,AAlCD,AAOI,MAPE,AAOD,OAAO,AAAC,CACL,UAAU,CAAE,OAAO,CACnB,aAAa,CAAE,eAAe,CAC9B,UAAU,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAmB,CAAC,KAAK,CACjD,OAAO,CAAE,EAAE,CACX,MAAM,CAAE,KAAK,CACb,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,KAAK,CACf,AAfL,AAgBI,MAhBE,AAgBD,MAAM,AAAC,CACJ,UAAU,CAAE,IAAI,CAChB,aAAa,CAAE,eAAe,CAC9B,MAAM,CAAE,CAAC,CACT,kBAAkB,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAmB,CACnD,UAAU,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAmB,CAC3C,OAAO,CAAE,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAA,iBAAiB,CACrD,SAAS,CAAE,KAAK,CAChB,WAAW,CAAE,GAAG,CAChB,MAAM,CAAE,IAAI,CACZ,IAAI,CAAE,IAAI,CACV,WAAW,CAAE,IAAI,CACjB,QAAQ,CAAE,QAAQ,CAClB,UAAU,CAAE,MAAM,CAClB,KAAK,CAAE,KAAK,CACZ,OAAO,CAAE,CAAC,CACV,WAAW,CAAE,GAAG,CACnB,AAGL,AAAA,OAAO,AAAC,CACJ,UAAU,CAAE,kBAAqB,CACjC,aAAa,CAAE,GAAG,CAClB,MAAM,CAAE,IAAI,CACZ,UAAU,CAAE,GAAG,CAAE,IAAG,CAAC,GAAG,CAAC,eAAkB,CAC3C,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,GAAG,CACX,IAAI,CAAE,GAAG,CACT,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,QAAQ,CAC1B,UAAU,CAAE,MAAM,CAClB,aAAa,CAAE,GAAG,CAClB,MAAM,CAAE,IAAI,CACZ,UAAU,CAAE,GAAG,CAAE,IAAG,CAAC,GAAG,CAAC,eAAkB,CAC3C,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,GAAG,CACX,IAAI,CAAE,GAAG,CACT,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,QAAQ,CAC1B,UAAU,CAAE,MAAM,CACrB,AAED,AAAA,cAAc,AAAA,KAAK,AAAC,CAChB,IAAI,CAAE,OAAO,CACb,KAAK,CAAE,GAAG,CACb", + "mappings": "AAAA,AAAA,IAAI,AAAC,CACD,QAAQ,CAAE,QAAQ,CAClB,UAAU,CAAE,IAAI,CACnB,AAED,AACI,IADA,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EACD,yBAAyB,CAD7B,IAAI,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EAED,0BAA0B,AAAC,CACvB,sBAAsB,CAAE,MAAM,CAC9B,yBAAyB,CAAE,MAAM,CACpC,AAGL,AACI,IADA,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EACD,yBAAyB,CAD7B,IAAI,CAAA,AAAA,GAAC,CAAI,KAAK,AAAT,EAED,0BAA0B,AAAC,CACvB,uBAAuB,CAAE,MAAM,CAC/B,0BAA0B,CAAE,MAAM,CACrC,AAGL,AAAA,IAAI,AAAC,CACD,WAAW,CAAE,MAAM,CACnB,aAAa,CAAE,IAAI,CACtB,AAED,AAAA,gBAAgB,AAAC,CACb,SAAS,CAAE,MAAM,CACpB,AAED,AAAA,OAAO,AAAC,CACJ,QAAQ,CAAE,QAAQ,CAClB,MAAM,CAAE,CAAC,CACT,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,WAAW,CAAE,IAAI,CACjB,gBAAgB,CAAE,OAAO,CAC5B,AAED,AAAA,YAAY,CAAC,WAAW,CAAC,SAAS,AAAC,CAC/B,KAAK,CAAE,sBAAwB,CAMlC,AAPD,AAGI,YAHQ,CAAC,WAAW,CAAC,SAAS,AAG7B,MAAM,CAHX,YAAY,CAAC,WAAW,CAAC,SAAS,AAI7B,MAAM,AAAC,CACJ,KAAK,CAAE,sBAAwB,CAClC,AAGL,AAAA,EAAE,CACF,EAAE,CACF,EAAE,AAAC,CACC,aAAa,CAAE,CAAC,CACnB,AAED,AAAA,MAAM,CAAC,WAAW,AAAC,CACf,KAAK,CAAE,kBAAkB,CAC5B,AAED,AAAA,CAAC,CACD,MAAM,CACN,SAAS,AAAC,CACN,UAAU,CAAE,eAAe,CAC3B,SAAS,CAAE,eAAe,CAC7B,AAED,AAAA,CAAC,AAAA,KAAK,AAAC,CACH,eAAe,CAAE,IAAI,CACrB,MAAM,CAAE,OAAO,CACf,YAAY,CAAE,IAAI,CACrB,AAED,AAAA,IAAI,AAAA,YAAY,CAAC,KAAK,CAAA,AAAA,IAAC,CAAK,MAAM,AAAX,EACvB,IAAI,AAAA,WAAW,CAAC,KAAK,CAAA,AAAA,IAAC,CAAK,MAAM,AAAX,CAAa,CAC/B,yBAAyB,CAAE,CAAC,CAC5B,0BAA0B,CAAE,CAAC,CAChC,AAED,AAAA,IAAI,AAAA,YAAY,CAAC,KAAK,CAAA,AAAA,IAAC,CAAK,UAAU,AAAf,CAAiB,CACpC,sBAAsB,CAAE,CAAC,CACzB,uBAAuB,CAAE,CAAC,CAC7B,AAED,AACI,IADA,AAAA,WAAW,CACX,KAAK,AAAA,eAAe,AAAC,CACjB,aAAa,CAAE,CAAC,CACnB,AAHL,AAKI,IALA,AAAA,WAAW,CAKX,KAAK,AAAA,sBAAsB,AAAC,CACxB,sBAAsB,CAAE,CAAC,CACzB,uBAAuB,CAAE,CAAC,CAC7B,AAGL,AAAA,IAAI,AAAA,YAAY,CAChB,IAAI,AAAA,YAAY,CAChB,IAAI,AAAA,WAAW,AAAC,CACZ,MAAM,CAAE,IAAI,CACf,AAED,AAAA,KAAK,AAAC,CACF,YAAY,CAAE,KAAK,CACtB,AAED,AAAA,EAAE,CAAE,EAAE,AAAC,CACH,SAAS,CAAC,GAAG,CAChB,AACD,AAAA,QAAQ,AAAC,CACL,SAAS,CAAC,UAAU,CACpB,aAAa,CAAC,UAAU,CAC3B,AAED,AAAA,KAAK,CAAC,EAAE,CAAA,AAAA,OAAC,CAAQ,OAAO,AAAf,EACT,UAAU,AAAC,CACP,OAAO,CAAE,IAAI,CAChB,AAED,AAAA,KAAK,CAAC,EAAE,CAAA,AAAA,OAAC,CAAQ,MAAM,AAAd,CAAgB,CACrB,OAAO,CAAE,SAAS,CACrB,AAED,AAAA,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,CACjD,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,AAAA,OAAO,CACxD,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,AAAA,MAAM,CACvD,iBAAiB,CAAC,gBAAgB,AAAA,eAAe,AAAA,MAAM,AAAC,CACpD,KAAK,CAAE,KAAK,CACf,AAED,AAAA,MAAM,AAAC,CACH,aAAa,CAAE,eAAe,CAC9B,MAAM,CAAE,KAAK,CACb,MAAM,CAAE,WAAW,CACnB,QAAQ,CAAE,MAAM,CAChB,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,KAAK,CA4Bf,AAlCD,AAOI,MAPE,AAOD,OAAO,AAAC,CACL,UAAU,CAAE,OAAO,CACnB,aAAa,CAAE,eAAe,CAC9B,UAAU,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAmB,CAAC,KAAK,CACjD,OAAO,CAAE,EAAE,CACX,MAAM,CAAE,KAAK,CACb,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,KAAK,CACf,AAfL,AAgBI,MAhBE,AAgBD,MAAM,AAAC,CACJ,UAAU,CAAE,IAAI,CAChB,aAAa,CAAE,eAAe,CAC9B,MAAM,CAAE,CAAC,CACT,kBAAkB,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAmB,CACnD,UAAU,CAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAmB,CAC3C,OAAO,CAAE,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAA,iBAAiB,CACrD,SAAS,CAAE,KAAK,CAChB,WAAW,CAAE,GAAG,CAChB,MAAM,CAAE,IAAI,CACZ,IAAI,CAAE,IAAI,CACV,WAAW,CAAE,IAAI,CACjB,QAAQ,CAAE,QAAQ,CAClB,UAAU,CAAE,MAAM,CAClB,KAAK,CAAE,KAAK,CACZ,OAAO,CAAE,CAAC,CACV,WAAW,CAAE,GAAG,CACnB,AAGL,AAAA,OAAO,AAAC,CACJ,UAAU,CAAE,kBAAqB,CACjC,aAAa,CAAE,GAAG,CAClB,MAAM,CAAE,IAAI,CACZ,UAAU,CAAE,GAAG,CAAE,IAAG,CAAC,GAAG,CAAC,eAAkB,CAC3C,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,GAAG,CACX,IAAI,CAAE,GAAG,CACT,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,QAAQ,CAC1B,UAAU,CAAE,MAAM,CAClB,aAAa,CAAE,GAAG,CAClB,MAAM,CAAE,IAAI,CACZ,UAAU,CAAE,GAAG,CAAE,IAAG,CAAC,GAAG,CAAC,eAAkB,CAC3C,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,GAAG,CACX,IAAI,CAAE,GAAG,CACT,QAAQ,CAAE,QAAQ,CAClB,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,QAAQ,CAC1B,UAAU,CAAE,MAAM,CACrB,AAED,AAAA,cAAc,AAAA,KAAK,AAAC,CAChB,IAAI,CAAE,OAAO,CACb,KAAK,CAAE,GAAG,CACb", "sources": [ "../scss/style.scss" ], diff --git a/src/templates/default/static/plugin/chartjs/plugin-zoom.min.js b/src/templates/default/static/plugin/chartjs/plugin-zoom.min.js new file mode 100755 index 00000000..51b054d6 --- /dev/null +++ b/src/templates/default/static/plugin/chartjs/plugin-zoom.min.js @@ -0,0 +1,11 @@ +/*! + * @license + * chartjs-plugin-zoom + * http://chartjs.org/ + * Version: 0.7.7 + * + * Copyright 2020 Chart.js Contributors + * Released under the MIT license + * https://github.com/chartjs/chartjs-plugin-zoom/blob/master/LICENSE.md + */ +!function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o(require("chart.js"),require("hammerjs")):"function"==typeof define&&define.amd?define(["chart.js","hammerjs"],o):(e=e||self).ChartZoom=o(e.Chart,e.Hammer)}(this,(function(e,o){"use strict";e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e,o=o&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o;var t=e.helpers,n=e.Zoom=e.Zoom||{},a=n.zoomFunctions=n.zoomFunctions||{},i=n.panFunctions=n.panFunctions||{};function m(e,o){var n={};void 0!==e.options.pan&&(n.pan=e.options.pan),void 0!==e.options.zoom&&(n.zoom=e.options.zoom);var a=e.$zoom;o=a._options=t.merge({},[o,n]);var i=a._node,m=o.zoom&&o.zoom.enabled,r=o.zoom.drag;m&&!r?i.addEventListener("wheel",a._wheelHandler):i.removeEventListener("wheel",a._wheelHandler),m&&r?(i.addEventListener("mousedown",a._mouseDownHandler),i.ownerDocument.addEventListener("mouseup",a._mouseUpHandler)):(i.removeEventListener("mousedown",a._mouseDownHandler),i.removeEventListener("mousemove",a._mouseMoveHandler),i.ownerDocument.removeEventListener("mouseup",a._mouseUpHandler))}function r(e){var o=e.$zoom._originalOptions;t.each(e.scales,(function(e){o[e.id]||(o[e.id]=t.clone(e.options))})),t.each(o,(function(t,n){e.scales[n]||delete o[n]}))}function l(e,o,t){return void 0===e||("string"==typeof e?-1!==e.indexOf(o):"function"==typeof e&&-1!==e({chart:t}).indexOf(o))}function s(e,o){if(e.scaleAxes&&e.rangeMax&&!t.isNullOrUndef(e.rangeMax[e.scaleAxes])){var n=e.rangeMax[e.scaleAxes];o>n&&(o=n)}return o}function c(e,o){if(e.scaleAxes&&e.rangeMin&&!t.isNullOrUndef(e.rangeMin[e.scaleAxes])){var n=e.rangeMin[e.scaleAxes];o=c&&s<=u?(i.min=l,i.max=s):lu&&(a=u-r,i.max=u,i.min=m+a)}function v(e,o,t){var n=i[e.type];n&&n(e,o,t)}e.Zoom.defaults=e.defaults.global.plugins.zoom={pan:{enabled:!1,mode:"xy",speed:20,threshold:10},zoom:{enabled:!1,mode:"xy",sensitivity:3,speed:.1}},n.zoomFunctions.category=function(e,o,t,a){var i=e.chart.data.labels,m=e.minIndex,r=i.length-1,l=e.maxIndex,u=a.sensitivity,d=e.isHorizontal()?e.left+e.width/2:e.top+e.height/2,p=e.isHorizontal()?t.x:t.y;n.zoomCumulativeDelta=o>1?n.zoomCumulativeDelta+1:n.zoomCumulativeDelta-1,Math.abs(n.zoomCumulativeDelta)>u&&(n.zoomCumulativeDelta<0?(p>=d?m<=0?l=Math.min(r,l+1):m=Math.max(0,m-1):p=r?m=Math.max(0,m-1):l=Math.min(r,l+1)),n.zoomCumulativeDelta=0):n.zoomCumulativeDelta>0&&(p>=d?m=mm?l=Math.max(m,l-1):l),n.zoomCumulativeDelta=0),e.options.ticks.min=c(a,i[m]),e.options.ticks.max=s(a,i[l]))},n.zoomFunctions.time=function(e,o,t,n){u(e,o,t,n);var a=e.options;a.time&&(a.time.min&&(a.time.min=a.ticks.min),a.time.max&&(a.time.max=a.ticks.max))},n.zoomFunctions.linear=u,n.zoomFunctions.logarithmic=u,n.panFunctions.category=function(e,o,t){var a,i=e.chart.data.labels,m=i.length-1,r=Math.max(e.ticks.length,1),l=t.speed,u=e.minIndex,d=Math.round(e.width/(r*l));n.panCumulativeDelta+=o,u=n.panCumulativeDelta>d?Math.max(0,u-1):n.panCumulativeDelta<-d?Math.min(m-r+1,u+1):u,n.panCumulativeDelta=u!==e.minIndex?0:n.panCumulativeDelta,a=Math.min(m,u+r-1),e.options.ticks.min=c(t,i[u]),e.options.ticks.max=s(t,i[a])},n.panFunctions.time=function(e,o,t){f(e,o,t);var n=e.options;n.time&&(n.time.min&&(n.time.min=n.ticks.min),n.time.max&&(n.time.max=n.ticks.max))},n.panFunctions.linear=f,n.panFunctions.logarithmic=f,n.panCumulativeDelta=0,n.zoomCumulativeDelta=0;var h={id:"zoom",afterInit:function(e){e.resetZoom=function(){r(e);var o=e.$zoom._originalOptions;t.each(e.scales,(function(e){var t=e.options.time,n=e.options.ticks;o[e.id]?(t&&(t.min=o[e.id].time.min,t.max=o[e.id].time.max),n&&(n.min=o[e.id].ticks.min,n.max=o[e.id].ticks.max)):(t&&(delete t.min,delete t.max),n&&(delete n.min,delete n.max))})),e.update()}},beforeUpdate:function(e,o){m(e,o)},beforeInit:function(e,a){e.$zoom={_originalOptions:{}};var i=e.$zoom._node=e.ctx.canvas;m(e,a);var s=e.$zoom._options,c=s.pan&&s.pan.threshold;e.$zoom._mouseDownHandler=function(o){i.addEventListener("mousemove",e.$zoom._mouseMoveHandler),e.$zoom._dragZoomStart=o},e.$zoom._mouseMoveHandler=function(o){e.$zoom._dragZoomStart&&(e.$zoom._dragZoomEnd=o,e.update(0))},e.$zoom._mouseUpHandler=function(o){if(e.$zoom._dragZoomStart){i.removeEventListener("mousemove",e.$zoom._mouseMoveHandler);var t=e.$zoom._dragZoomStart,n=t.target.getBoundingClientRect().left,a=Math.min(t.clientX,o.clientX)-n,m=Math.max(t.clientX,o.clientX)-n,r=t.target.getBoundingClientRect().top,c=Math.min(t.clientY,o.clientY)-r,u=m-a,d=Math.max(t.clientY,o.clientY)-r-c;e.$zoom._dragZoomStart=null,e.$zoom._dragZoomEnd=null;var f=s.zoom&&s.zoom.threshold||0;if(!(u<=f&&d<=f)){var v=e.chartArea,h=e.$zoom._options.zoom,x=v.right-v.left,g=l(h.mode,"x",e)&&u?1+(x-u)/x:1,z=v.bottom-v.top,y=l(h.mode,"y",e);p(e,g,y&&d?1+(z-d)/z:1,{x:(a-v.left)/(1-u/x)+v.left,y:(c-v.top)/(1-d/z)+v.top},void 0,h.drag.animationDuration),"function"==typeof h.onZoomComplete&&h.onZoomComplete({chart:e})}}};var u=null;if(e.$zoom._wheelHandler=function(o){if(o.cancelable&&o.preventDefault(),void 0!==o.deltaY){var t=o.target.getBoundingClientRect(),n={x:o.clientX-t.left,y:o.clientY-t.top},a=e.$zoom._options.zoom,i=a.speed;o.deltaY>=0&&(i=-i),p(e,1+i,1+i,n),clearTimeout(u),u=setTimeout((function(){"function"==typeof a.onZoomComplete&&a.onZoomComplete({chart:e})}),250)}},o){var d,f=new o.Manager(i);f.add(new o.Pinch),f.add(new o.Pan({threshold:c}));var h=function(o){var t=1/d*o.scale,n=o.target.getBoundingClientRect(),a={x:o.center.x-n.left,y:o.center.y-n.top},i=Math.abs(o.pointers[0].clientX-o.pointers[1].clientX),m=Math.abs(o.pointers[0].clientY-o.pointers[1].clientY),r=i/m;p(e,t,t,a,r>.3&&r<1.7?"xy":i>m?"x":"y");var l=e.$zoom._options.zoom;"function"==typeof l.onZoomComplete&&l.onZoomComplete({chart:e}),d=o.scale};f.on("pinchstart",(function(){d=1})),f.on("pinch",h),f.on("pinchend",(function(e){h(e),d=null,n.zoomCumulativeDelta=0}));var x=null,g=null,z=!1,y=function(o){if(null!==x&&null!==g){z=!0;var n=o.deltaX-x,a=o.deltaY-g;x=o.deltaX,g=o.deltaY,function(e,o,n){r(e);var a=e.$zoom._options.pan;if(a.enabled){var i="function"==typeof a.mode?a.mode({chart:e}):a.mode;t.each(e.scales,(function(t){t.isHorizontal()&&l(i,"x",e)&&0!==o?(a.scaleAxes="x",v(t,o,a)):!t.isHorizontal()&&l(i,"y",e)&&0!==n&&(a.scaleAxes="y",v(t,n,a))})),e.update(0),"function"==typeof a.onPan&&a.onPan({chart:e})}}(e,n,a)}};f.on("panstart",(function(e){x=0,g=0,y(e)})),f.on("panmove",y),f.on("panend",(function(){x=null,g=null,n.panCumulativeDelta=0,setTimeout((function(){z=!1}),500);var o=e.$zoom._options.pan;"function"==typeof o.onPanComplete&&o.onPanComplete({chart:e})})),e.$zoom._ghostClickHandler=function(e){z&&e.cancelable&&(e.stopImmediatePropagation(),e.preventDefault())},i.addEventListener("click",e.$zoom._ghostClickHandler),e._mc=f}},beforeDatasetsDraw:function(e){var o=e.ctx;if(e.$zoom._dragZoomEnd){var t=function(e){for(var o=e.scales,t=Object.keys(o),n=0;n0&&(o.lineWidth=v.borderWidth,o.strokeStyle=v.borderColor||"rgba(225,225,225)",o.strokeRect(m,s,p,f)),o.restore()}},destroy:function(e){if(e.$zoom){var o=e.$zoom,t=o._node;t.removeEventListener("mousedown",o._mouseDownHandler),t.removeEventListener("mousemove",o._mouseMoveHandler),t.ownerDocument.removeEventListener("mouseup",o._mouseUpHandler),t.removeEventListener("wheel",o._wheelHandler),t.removeEventListener("click",o._ghostClickHandler),delete e.$zoom;var n=e._mc;n&&(n.remove("pinchstart"),n.remove("pinch"),n.remove("pinchend"),n.remove("panstart"),n.remove("pan"),n.remove("panend"),n.destroy())}}};return e.plugins.register(h),h})); \ No newline at end of file diff --git a/src/templates/default/static/plugin/hammer/hammer.min.js b/src/templates/default/static/plugin/hammer/hammer.min.js new file mode 100755 index 00000000..34a8c86f --- /dev/null +++ b/src/templates/default/static/plugin/hammer/hammer.min.js @@ -0,0 +1,7 @@ +/*! Hammer.JS - v2.0.7 - 2016-04-22 + * http://hammerjs.github.io/ + * + * Copyright (c) 2016 Jorik Tangelder; + * Licensed under the MIT license */ +!function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),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("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(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 fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){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[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.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},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.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 f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(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?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===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]||oe.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]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(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(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("