├── .dockerignore ├── .gitignore ├── static ├── img │ ├── nano-mark-light.png │ ├── favicon │ │ ├── Nano │ │ │ ├── favicon.ico │ │ │ ├── favicon-16.png │ │ │ ├── favicon-32.png │ │ │ ├── favicon-57.png │ │ │ ├── favicon-60.png │ │ │ ├── favicon-64.png │ │ │ ├── favicon-70.png │ │ │ ├── favicon-72.png │ │ │ ├── favicon-76.png │ │ │ ├── favicon-96.png │ │ │ ├── favicon-114.png │ │ │ ├── favicon-120.png │ │ │ ├── favicon-144.png │ │ │ ├── favicon-150.png │ │ │ ├── favicon-152.png │ │ │ ├── favicon-160.png │ │ │ ├── favicon-180.png │ │ │ ├── favicon-192.png │ │ │ ├── favicon-310.png │ │ │ └── browserconfig.xml │ │ ├── PAW │ │ │ ├── favicon.ico │ │ │ ├── favicon-150.png │ │ │ ├── favicon-16.png │ │ │ ├── favicon-180.png │ │ │ ├── favicon-192.png │ │ │ ├── favicon-256.png │ │ │ ├── favicon-32.png │ │ │ └── browserconfig.xml │ │ └── Banano │ │ │ ├── favicon.ico │ │ │ ├── favicon-114.png │ │ │ ├── favicon-120.png │ │ │ ├── favicon-144.png │ │ │ ├── favicon-150.png │ │ │ ├── favicon-152.png │ │ │ ├── favicon-16.png │ │ │ ├── favicon-160.png │ │ │ ├── favicon-180.png │ │ │ ├── favicon-192.png │ │ │ ├── favicon-310.png │ │ │ ├── favicon-32.png │ │ │ ├── favicon-57.png │ │ │ ├── favicon-60.png │ │ │ ├── favicon-64.png │ │ │ ├── favicon-70.png │ │ │ ├── favicon-72.png │ │ │ ├── favicon-76.png │ │ │ ├── favicon-96.png │ │ │ └── browserconfig.xml │ ├── nano-mark-light.svg │ ├── nano-full-dark.svg │ ├── nano-full-paw.svg │ ├── nano-mark-paw.svg │ ├── nano-full-light.svg │ └── nano-mark-dark.svg ├── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.ttf │ ├── fa-solid-900.eot │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ └── fa-solid-900.woff2 ├── themes │ ├── light │ │ ├── img │ │ │ └── bg-logo-dark.png │ │ └── css │ │ │ └── theme.css │ ├── banano │ │ └── css │ │ │ └── theme.css │ ├── dark │ │ └── css │ │ │ └── theme.css │ ├── paw │ │ └── css │ │ │ └── theme.css │ └── banano-dark │ │ └── css │ │ └── theme.css ├── js │ ├── main.js │ ├── index.js │ ├── clipboard.min.js │ ├── axios.min.js │ ├── popper.min.js │ └── bootstrap-native-v4.min.js └── css │ └── main.css ├── .github ├── theme-preview │ ├── nano-dark.png │ ├── nano-light.png │ ├── banano-dark.png │ └── banano-light.png ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── docker-publish.yml ├── docker-compose.dev.yml ├── modules ├── cache │ ├── NullCache.php │ ├── ApcCache.php │ ├── ApcuCache.php │ ├── RedisCache.php │ └── FileCache.php ├── widget.php ├── navbar.php ├── includes.php ├── Cache.php ├── constants.php ├── footer.php ├── defaults.php ├── config.sample.php ├── functions_rpc.php ├── header.php └── functions.php ├── Dockerfile ├── entry.sh ├── index.php ├── README.md ├── templates └── index.hbs ├── api.php └── LICENSE /.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | modules/config.php 3 | .idea/ 4 | nanote.txt 5 | nanoNodeMonitor -------------------------------------------------------------------------------- /static/img/nano-mark-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/nano-mark-light.png -------------------------------------------------------------------------------- /static/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /static/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /static/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /static/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /static/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /.github/theme-preview/nano-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/.github/theme-preview/nano-dark.png -------------------------------------------------------------------------------- /.github/theme-preview/nano-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/.github/theme-preview/nano-light.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon.ico -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon.ico -------------------------------------------------------------------------------- /static/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /static/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /static/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /static/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /static/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /static/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /static/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /.github/theme-preview/banano-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/.github/theme-preview/banano-dark.png -------------------------------------------------------------------------------- /.github/theme-preview/banano-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/.github/theme-preview/banano-light.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon.ico -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-16.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-32.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-57.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-60.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-64.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-70.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-72.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-76.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-96.png -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon-150.png -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon-16.png -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon-180.png -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon-192.png -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon-256.png -------------------------------------------------------------------------------- /static/img/favicon/PAW/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/PAW/favicon-32.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-114.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-120.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-144.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-150.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-152.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-16.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-160.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-180.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-192.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-310.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-32.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-57.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-60.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-64.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-70.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-72.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-76.png -------------------------------------------------------------------------------- /static/img/favicon/Banano/favicon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Banano/favicon-96.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-114.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-120.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-144.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-150.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-152.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-160.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-180.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-192.png -------------------------------------------------------------------------------- /static/img/favicon/Nano/favicon-310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/img/favicon/Nano/favicon-310.png -------------------------------------------------------------------------------- /static/themes/light/img/bg-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanoTools/nanoNodeMonitor/HEAD/static/themes/light/img/bg-logo-dark.png -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "3000:80" 7 | volumes: 8 | - .:/var/www/html 9 | - .:/opt -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # GitHub Sponsor 4 | github: BitDesert 5 | 6 | # donate NANO 7 | custom: https://mynano.ninja/account/my-nano-ninja/send 8 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | for (i = 0, length = init.length; i < length; i++) { 2 | try { 3 | init[i](); 4 | } catch (e) { 5 | console.error('Error in init %d', i); 6 | console.error(e); 7 | } 8 | } -------------------------------------------------------------------------------- /modules/cache/NullCache.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # apache with php base image 2 | FROM php:8.0-apache 3 | 4 | # copy all contents to public html 5 | COPY . /var/www/html 6 | 7 | # cleanup as we don't have a seperate public folder 8 | RUN rm /var/www/html/Dockerfile /var/www/html/entry.sh 9 | 10 | # entry shell 11 | COPY entry.sh /entry.sh 12 | 13 | # make it executable 14 | RUN chmod +x /entry.sh 15 | 16 | # go for it! 17 | CMD ["/bin/bash", "/entry.sh"] -------------------------------------------------------------------------------- /static/img/favicon/Nano/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #FFFFFF 9 | 10 | 11 | -------------------------------------------------------------------------------- /static/img/favicon/Banano/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #FFFFFF 9 | 10 | 11 | -------------------------------------------------------------------------------- /modules/cache/ApcCache.php: -------------------------------------------------------------------------------- 1 | ttl = array_key_exists('ttl', $options) ? $options['ttl'] : 30; 8 | } 9 | 10 | public function read($key) { 11 | $data = apc_fetch($key, $success); 12 | if ($success) return $data; 13 | return NULL; 14 | } 15 | 16 | public function write($key, $data) { 17 | return apc_store($key, $data, $this->ttl); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/cache/ApcuCache.php: -------------------------------------------------------------------------------- 1 | ttl = array_key_exists('ttl', $options) ? $options['ttl'] : 30; 8 | } 9 | 10 | public function read($key) { 11 | $data = apcu_fetch($key, $success); 12 | if ($success) return $data; 13 | return NULL; 14 | } 15 | 16 | public function write($key, $data) { 17 | return apcu_store($key, $data, $this->ttl); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set the config directory 4 | monitordir="/opt/nanoNodeMonitor" 5 | 6 | # create config dir 7 | mkdir -p "${monitordir}" 8 | 9 | # check for config file 10 | if [ ! -f "${monitordir}/config.php" ]; then 11 | echo "Config File not found, adding default." 12 | cp "/var/www/html/modules/config.sample.php" "${monitordir}/config.php" 13 | fi 14 | 15 | # create config symlink 16 | ln -s $monitordir/config.php /var/www/html/modules/config.php 17 | 18 | # change folder rights so www-data can read 19 | chmod 755 /opt 20 | 21 | # start apache 22 | apache2-foreground 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: BitDesert 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: BitDesert 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 1.6.0] 30 | - Node Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /modules/cache/RedisCache.php: -------------------------------------------------------------------------------- 1 | ttl = array_key_exists('ttl', $options) ? $options['ttl'] : 30; 8 | 9 | if (array_key_exists('redis', $options)) { 10 | $this->redis = $options['redis']; 11 | } else { 12 | $this->redis = new Redis(); 13 | $this->redis->connect( 14 | array_key_exists('host', $options) ? $options['host'] : '127.0.0.1', 15 | array_key_exists('port', $options) ? $options['port'] : 6379 16 | ); 17 | } 18 | } 19 | 20 | public function read($key) { 21 | $data = $this->redis->get($key); 22 | if ($data) return json_decode($data); 23 | return NULL; 24 | } 25 | 26 | public function write($key, $data) { 27 | return $this->redis->setEx($key, $this->ttl, json_encode($data)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/widget.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /modules/navbar.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/includes.php: -------------------------------------------------------------------------------- 1 | modules folder, ' . 7 | ' execute cp config.sample.php config.php and modify config.php ' . 8 | ' according to your needs.'); 9 | } 10 | 11 | // load the config 12 | require_once(__DIR__ . '/constants.php'); 13 | 14 | // load the defaults 15 | require_once(__DIR__ . '/defaults.php'); 16 | 17 | // load the config 18 | require_once(__DIR__ . '/config.php'); 19 | 20 | // load all RPC function 21 | require_once(__DIR__ . '/functions_rpc.php'); 22 | 23 | // load all other required functions 24 | require_once(__DIR__ . '/functions.php'); 25 | 26 | // load file caching lib 27 | require_once(__DIR__ . '/Cache.php'); 28 | 29 | // check for curl package (needs functions) 30 | if (!phpCurlAvailable()) { 31 | myError('Curl not available. Please install the php-curl package!'); 32 | } 33 | -------------------------------------------------------------------------------- /modules/Cache.php: -------------------------------------------------------------------------------- 1 | read($key); 26 | if (is_null($data)) { 27 | $data = $callback(); 28 | $this->write($key, $data); 29 | } 30 | 31 | return $data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | .truncate { 2 | white-space: nowrap; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | } 6 | 7 | .float-left-truncate 8 | { 9 | float: left; 10 | margin-right: auto; 11 | text-align: left; 12 | overflow: hidden; 13 | white-space: nowrap; 14 | text-overflow: ellipsis; 15 | width: 50%; 16 | } 17 | 18 | 19 | .float-right-truncate 20 | { 21 | float: right; 22 | margin-left: auto; 23 | text-align: right; 24 | overflow: hidden; 25 | white-space: nowrap; 26 | text-overflow: ellipsis; 27 | width: 50%; 28 | } 29 | 30 | .myError 31 | { 32 | padding: 20px; 33 | background-color: #f44336; /* Red */ 34 | color: white; 35 | margin-bottom: 30px; 36 | } 37 | 38 | .coinmarketcap-currency-widget div > span 39 | { 40 | color: white; 41 | } 42 | 43 | .coinmarketcap-currency-widget div > span:last-child 44 | { 45 | color: #7c7c7e !important; 46 | } 47 | 48 | .coinmarketcap-currency-widget > div > div:nth-child(2) > div 49 | { 50 | color: #dddddd; 51 | } 52 | 53 | #footer ul 54 | { 55 | text-align: right; 56 | letter-spacing: 0.02em; 57 | list-style: none; 58 | padding-right: 10px; 59 | } -------------------------------------------------------------------------------- /static/img/nano-mark-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 13 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /modules/constants.php: -------------------------------------------------------------------------------- 1 | logo -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker Publish 2 | 3 | on: 4 | schedule: 5 | - cron: "0 10 * * *" 6 | push: 7 | branches: 8 | - "**" 9 | tags: 10 | - "v*.*.*" 11 | pull_request: 12 | branches: 13 | - "main" 14 | 15 | jobs: 16 | docker: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Docker meta 22 | id: meta 23 | uses: docker/metadata-action@v3 24 | with: 25 | # list of Docker images to use as base name for tags 26 | images: | 27 | nanotools/nanonodemonitor 28 | ghcr.io/nanotools/nanonodemonitor 29 | # generate Docker tags based on the following events/attributes 30 | tags: | 31 | type=schedule 32 | type=ref,event=branch 33 | type=ref,event=pr 34 | type=semver,pattern={{version}} 35 | type=semver,pattern={{major}}.{{minor}} 36 | type=semver,pattern={{major}} 37 | type=sha 38 | - name: Set up QEMU 39 | uses: docker/setup-qemu-action@v1 40 | - name: Set up Docker Buildx 41 | uses: docker/setup-buildx-action@v1 42 | - name: Login to DockerHub 43 | if: github.event_name != 'pull_request' 44 | uses: docker/login-action@v1 45 | with: 46 | username: ${{ secrets.DOCKERHUB_USERNAME }} 47 | password: ${{ secrets.DOCKERHUB_TOKEN }} 48 | - name: Login to GHCR 49 | if: github.event_name != 'pull_request' 50 | uses: docker/login-action@v1 51 | with: 52 | registry: ghcr.io 53 | username: ${{ github.repository_owner }} 54 | password: ${{ secrets.GITHUB_TOKEN }} 55 | - name: Build and push 56 | uses: docker/build-push-action@v2 57 | with: 58 | context: . 59 | push: ${{ github.event_name != 'pull_request' }} 60 | tags: ${{ steps.meta.outputs.tags }} 61 | labels: ${{ steps.meta.outputs.labels }} 62 | -------------------------------------------------------------------------------- /static/img/nano-full-paw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /static/img/nano-mark-paw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /modules/footer.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | 7 | 20 | 21 |
    22 |
  • 23 | 10*60]); // cache for 10 minutes 26 | 27 | // set an API name so multiple monitors don't mix 28 | $apiName = "footer-$nanoNodeAccount"; 29 | 30 | // get cached response 31 | $versionData = $versionCache->fetch($apiName, function () { 32 | $versionData = new stdClass(); 33 | $versionData->latestVersion = getLatestReleaseVersion(); 34 | return $versionData; 35 | }); 36 | echo getVersionInformation($versionData->latestVersion); 37 | ?> 38 |
  • 39 |
  • Powered by Nano Node Monitor
  • 40 |
  • GitHub: Source
  • 41 |
  • 42 | Donate: 43 |
  • 44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 67 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /modules/defaults.php: -------------------------------------------------------------------------------- 1 | cache time in seconds 51 | // - "apcu" (APCu cache; requires extension; fast) 52 | // - Options: 'ttl' => cache time in seconds 53 | 54 | $cache = [ 55 | "engine" => "files", 56 | "options" => ["ttl" => $cacheTimeToLive] 57 | ]; 58 | 59 | // ----------- Nano Node Variables ----------- 60 | 61 | // IP address for RPC (default: 127.0.0.1) 62 | $nanoNodeRPCIP = '[::1]'; 63 | 64 | // IP address for RPC (default: 7076) 65 | // Nano nodes typically use port 7076. 66 | // Banano nodes typically use port 7072. 67 | $nanoNodeRPCPort = '7076'; 68 | 69 | // Account of this node 70 | $nanoNodeAccount = NULL; 71 | 72 | // Donation account for maintaining this node 73 | $nanoDonationAccount = $nanoNodeAccount; 74 | 75 | // Number of decimal places to display Nano balances, i.e. 76 | $nanoNumDecimalPlaces = 0; 77 | 78 | // ----------- Monitoring ----------- 79 | 80 | // Uptimerobot.com API key for external monitoring 81 | $uptimerobotApiKey = ''; 82 | 83 | // Google Analytics Tracking ID. 84 | $googleAnalyticsId = ''; 85 | 86 | // ----------- Social ----------- 87 | $socials = array(); 88 | -------------------------------------------------------------------------------- /static/img/nano-full-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 23 | 24 | 27 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /static/img/nano-mark-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | logo 26 | 27 | 28 | 29 | 31 | 55 | 57 | logo 59 | 66 | 72 | 73 | -------------------------------------------------------------------------------- /static/js/index.js: -------------------------------------------------------------------------------- 1 | var template; 2 | 3 | init.push(function(){ 4 | Handlebars.registerHelper('formatNumber', function (number, digits) { 5 | if(typeof number === 'undefined'){ 6 | return 0; 7 | } 8 | 9 | if (Number.isInteger(digits)) { 10 | return number.toLocaleString('en-US', {minimumFractionDigits: digits, maximumFractionDigits: digits}); 11 | } 12 | return number.toLocaleString('en-US'); 13 | }); 14 | 15 | Handlebars.registerHelper('formatNano', function (number) { 16 | if(typeof number === 'undefined'){ 17 | return 0; 18 | } 19 | 20 | return number.toLocaleString('en-US', {minimumFractionDigits: GLOBAL_DIGITS, maximumFractionDigits: GLOBAL_DIGITS}); 21 | }); 22 | 23 | Handlebars.registerHelper('formatSeconds', function (number) { 24 | if(typeof number === 'undefined'){ 25 | return 0; 26 | } 27 | 28 | var hours = Math.floor(number / 3600); 29 | var minutes = Math.floor((number - (hours * 3600)) / 60); 30 | var seconds = number - (hours * 3600) - (minutes * 60); 31 | 32 | if (hours < 10) {hours = "0"+hours;} 33 | if (minutes < 10) {minutes = "0"+minutes;} 34 | if (seconds < 10) {seconds = "0"+seconds;} 35 | return hours+'h '+minutes+'m '+seconds+'s'; 36 | }); 37 | 38 | Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) { 39 | 40 | switch (operator) { 41 | case '==': 42 | return (v1 == v2) ? options.fn(this) : options.inverse(this); 43 | case '===': 44 | return (v1 === v2) ? options.fn(this) : options.inverse(this); 45 | case '!=': 46 | return (v1 != v2) ? options.fn(this) : options.inverse(this); 47 | case '!==': 48 | return (v1 !== v2) ? options.fn(this) : options.inverse(this); 49 | case '<': 50 | return (v1 < v2) ? options.fn(this) : options.inverse(this); 51 | case '<=': 52 | return (v1 <= v2) ? options.fn(this) : options.inverse(this); 53 | case '>': 54 | return (v1 > v2) ? options.fn(this) : options.inverse(this); 55 | case '>=': 56 | return (v1 >= v2) ? options.fn(this) : options.inverse(this); 57 | case '&&': 58 | return (v1 && v2) ? options.fn(this) : options.inverse(this); 59 | case '||': 60 | return (v1 || v2) ? options.fn(this) : options.inverse(this); 61 | default: 62 | return options.inverse(this); 63 | } 64 | }); 65 | 66 | axios.get('templates/index.hbs') 67 | .then(function (response) { 68 | template=Handlebars.compile(response.data); 69 | 70 | updateStats(); 71 | }); 72 | }); 73 | 74 | function updateStats(){ 75 | axios.get('api.php') 76 | .then(function (response) { 77 | document.getElementById("content").innerHTML = template(response.data); 78 | new ClipboardJS('#copyAccount'); 79 | }) 80 | .catch(function (error) { 81 | console.log('FAIL', error); 82 | if(error.response){ 83 | document.getElementById("content").innerHTML = error.response.data; 84 | } 85 | }) 86 | .finally(function () { 87 | setTimeout(updateStats, GLOBAL_REFRESH * 1000); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /static/themes/light/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Load Font */ 2 | 3 | @import url('https://fonts.googleapis.com/css?family=Nunito:300,400'); 4 | 5 | /** 6 | 7 | -- Body CSS 8 | 9 | */ 10 | 11 | body { 12 | font-family: 'Nunito', sans-serif; 13 | background: #0f1121 url(../img/bg-logo-dark.png) center right no-repeat fixed; 14 | color: #fff; 15 | -webkit-background-size: cover; 16 | -moz-background-size: cover; 17 | -o-background-size: cover; 18 | background-size: 800px auto; 19 | padding-top: 145px; 20 | } 21 | 22 | /* 23 | 24 | -- Navigation Menu CSS 25 | 26 | */ 27 | 28 | .navbar { 29 | font-size: 1.1rem; 30 | line-height: 1.6rem; 31 | letter-spacing: 0.02rem; 32 | font-weight: 400; 33 | background-color: #ffffff; 34 | padding: 1.5% 4%; 35 | } 36 | 37 | 38 | .navbar-toggler-icon { 39 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(74,144,226, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); 40 | } 41 | 42 | 43 | .n-logo-theme { 44 | color: #0f1121; 45 | padding-left: 12px; 46 | } 47 | 48 | .nav-link { 49 | color: #0f1121; 50 | } 51 | 52 | 53 | .nav-link:hover { 54 | color: #4a90e2; 55 | width: 100%; 56 | height: 100%; 57 | content: ''; 58 | background: #ffffff; 59 | opacity: 1; 60 | transition: all 0s; 61 | } 62 | 63 | /* 64 | 65 | -- Body Content CSS 66 | 67 | */ 68 | 69 | .list-group-item { 70 | color: #0f1121 !important; 71 | font-size: 0.9rem; 72 | } 73 | 74 | h2 { 75 | font-size: 2.5rem; 76 | line-height: 2.9rem; 77 | font-weight: 400; 78 | max-width: 930px; 79 | } 80 | 81 | h3 { 82 | font-size: 1.4rem; 83 | font-weight: 400; 84 | max-width: 930px; 85 | } 86 | 87 | p { 88 | font-size: 0.9rem; 89 | line-height: 1.3rem; 90 | letter-spacing: 0.02em; 91 | } 92 | 93 | 94 | .n-address-theme { 95 | background-color: #ffffff !important; 96 | display: block; 97 | width: 100%; 98 | padding: .375rem .75rem; 99 | font-size: 1rem; 100 | line-height: 1.5; 101 | background-clip: padding-box; 102 | border: 1px solid #000034; 103 | border-radius: 0rem; 104 | transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; 105 | } 106 | 107 | .card { 108 | background-color: #fff; 109 | font-size: 0.9rem; 110 | } 111 | 112 | .list-group-item a { 113 | color: #0f1121; 114 | } 115 | 116 | a { 117 | color: #4a90e2 118 | } 119 | 120 | a:hover { 121 | color: #4a90e2 122 | } 123 | 124 | hr.light { 125 | display: block; 126 | height: 1px; 127 | border: 0; 128 | border-top: 1px solid #4a90e2; 129 | margin: 1em 0; 130 | padding: 0; 131 | opacity: 0.2; 132 | } 133 | 134 | .btn-clipboard { 135 | color: #0f1121; 136 | } 137 | 138 | 139 | .btn-clipboard:hover{ 140 | color: #4a90e2; 141 | } 142 | 143 | .btn-clipboard-light { 144 | color: #ffffff; 145 | } 146 | 147 | .btn-clipboard-light:hover { 148 | color: #4a90e2; 149 | } -------------------------------------------------------------------------------- /static/themes/banano/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Load Font */ 2 | 3 | @import url('https://fonts.googleapis.com/css?family=Nunito:300,400'); 4 | 5 | /** 6 | 7 | -- Body CSS 8 | 9 | */ 10 | 11 | body { 12 | font-family: 'Nunito', sans-serif; 13 | background: #f8fbff ; 14 | color: #222629; 15 | -webkit-background-size: cover; 16 | -moz-background-size: cover; 17 | -o-background-size: cover; 18 | background-size: 800px auto; 19 | padding-top: 145px; 20 | } 21 | 22 | /* 23 | 24 | -- Navigation Menu CSS 25 | 26 | */ 27 | 28 | .navbar { 29 | font-size: 1.1rem; 30 | line-height: 1.6rem; 31 | letter-spacing: 0.02rem; 32 | font-weight: 400; 33 | background-color: #56be4f; 34 | padding: 1.5% 4%; 35 | } 36 | 37 | 38 | .navbar-toggler-icon { 39 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(251,221,17, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); 40 | } 41 | 42 | 43 | .n-logo-theme { 44 | color: #ffffff; 45 | padding-left: 12px; 46 | } 47 | 48 | .nav-link { 49 | color: #ffffff; 50 | } 51 | 52 | 53 | .nav-link:hover { 54 | color: #fada40; 55 | width: 100%; 56 | height: 100%; 57 | content: ''; 58 | background: #56be4f; 59 | opacity: 1; 60 | transition: all 0s; 61 | } 62 | 63 | /* 64 | 65 | -- Body Content CSS 66 | 67 | */ 68 | 69 | 70 | 71 | h2 { 72 | font-size: 2.5rem; 73 | line-height: 2.9rem; 74 | font-weight: 400; 75 | max-width: 930px; 76 | } 77 | 78 | h3 { 79 | font-size: 1.4rem; 80 | font-weight: 400; 81 | max-width: 930px; 82 | color: #56be4f; 83 | } 84 | 85 | p { 86 | font-size: 0.9rem; 87 | line-height: 1.3rem; 88 | letter-spacing: 0.02em; 89 | } 90 | 91 | 92 | .n-address-theme { 93 | background-color: #ffffff !important; 94 | display: block; 95 | width: 100%; 96 | padding: .375rem .75rem; 97 | font-size: 1rem; 98 | line-height: 1.5; 99 | background-clip: padding-box; 100 | border: 1px solid #000034; 101 | border-radius: 0rem; 102 | transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; 103 | } 104 | 105 | .card { 106 | background-color: #fff; 107 | font-size: 0.9rem; 108 | } 109 | 110 | 111 | .list-group-item { 112 | background-color: #f9f9f9; 113 | color: #222629 !important; 114 | font-size: 0.9rem; 115 | } 116 | 117 | .list-group-item a { 118 | color: #222629; 119 | } 120 | 121 | a { 122 | color: #56be4f 123 | } 124 | 125 | a:hover { 126 | color: #fada40 127 | } 128 | 129 | hr.light { 130 | display: block; 131 | height: 1px; 132 | border: 0; 133 | border-top: 1px solid #56be4f; 134 | margin: 1em 0; 135 | padding: 0; 136 | opacity: 0.2; 137 | } 138 | 139 | 140 | .btn-clipboard { 141 | color: #56be4f; 142 | } 143 | 144 | .btn-clipboard:hover{ 145 | color: #fada40; 146 | } 147 | 148 | .btn-clipboard-light { 149 | color: #56be4f; 150 | } 151 | 152 | .btn-clipboard-light:hover { 153 | color: #fada40; 154 | } 155 | 156 | -------------------------------------------------------------------------------- /static/themes/dark/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Load Font */ 2 | @import url('https://fonts.googleapis.com/css?family=Nunito:300,400'); 3 | 4 | /** 5 | 6 | -- Body CSS 7 | 8 | */ 9 | 10 | body { 11 | font-family: 'Nunito', sans-serif; 12 | background: #272B30; 13 | color: #cccccc; 14 | -webkit-background-size: cover; 15 | -moz-background-size: cover; 16 | -o-background-size: cover; 17 | background-size: 800px auto; 18 | padding-top: 145px; 19 | } 20 | 21 | /* 22 | 23 | -- Navigation Menu CSS 24 | 25 | */ 26 | 27 | 28 | .navbar { 29 | font-size: 1.1rem; 30 | line-height: 1.6rem; 31 | letter-spacing: 0.02rem; 32 | font-weight: 400; 33 | background-color: #3A3F44; 34 | padding:1.5% 4%; 35 | border-bottom: 1px solid #242a38; 36 | } 37 | 38 | .navbar-toggler-icon { 39 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(243,136,46, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); 40 | } 41 | 42 | 43 | 44 | .n-logo-theme { 45 | color: #ffffff; 46 | padding-left:12px; 47 | } 48 | 49 | .nav-link:hover { 50 | color: #c8d6e5; 51 | width: 100%; 52 | height: 100%; 53 | content: ''; 54 | background: #272B30; 55 | opacity: 1; 56 | transition: all 0s; 57 | } 58 | 59 | /* 60 | 61 | -- Body Content CSS 62 | 63 | */ 64 | 65 | .list-group-item { 66 | color: #ffffff!important; 67 | } 68 | 69 | h2 { 70 | font-size: 2.5rem; 71 | line-height: 2.9rem; 72 | font-weight: 400; 73 | max-width: 930px; 74 | color: #ea882e; 75 | } 76 | 77 | h3 { 78 | font-size: 1.4rem; 79 | font-weight: 400; 80 | max-width: 930px; 81 | color: #ea882e; 82 | } 83 | 84 | p { 85 | font-size: 0.9rem; 86 | line-height: 1.3rem; 87 | letter-spacing: 0.02em; 88 | } 89 | 90 | .n-address-theme { 91 | background-color: #3A3F44!important; 92 | display: block; 93 | width: 100%; 94 | padding: .375rem .75rem; 95 | font-size: 1rem; 96 | line-height: 1.5; 97 | background-clip: padding-box; 98 | border: 1px solid rgba(0,0,0,0.4); 99 | border-radius: 0rem; 100 | transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; 101 | } 102 | 103 | .card { 104 | background-color: #3A3F44!important; 105 | font-size: 0.9rem; 106 | } 107 | 108 | .list-group-item { 109 | background-color: #3A3F44!important; 110 | border: 1px solid rgba(0,0,0,0.4); 111 | font-size: 0.9rem; 112 | } 113 | 114 | a { 115 | color: #ffffff; 116 | } 117 | 118 | a:hover { 119 | color: #ea882e; 120 | } 121 | 122 | hr.light { 123 | display: block; 124 | height: 1px; 125 | border: 0; 126 | border-top: 1px solid #ea882e; 127 | margin: 1em 0; 128 | padding: 0; 129 | opacity: 0.2; 130 | } 131 | 132 | .btn-clipboard { 133 | color: #ffffff; 134 | } 135 | 136 | .btn-clipboard:hover{ 137 | color: #ea882e; 138 | } 139 | 140 | .btn-clipboard-light { 141 | color: #ffffff; 142 | } 143 | 144 | .btn-clipboard-light:hover { 145 | color: #ea882e; 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /static/themes/paw/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Load Font */ 2 | 3 | @import url('https://fonts.googleapis.com/css?family=Nunito:300,400'); 4 | 5 | /** 6 | 7 | -- Body CSS 8 | 9 | */ 10 | 11 | body { 12 | font-family: 'Nunito', sans-serif; 13 | background: #181b1a; 14 | color: #00A99D; 15 | -webkit-background-size: cover; 16 | -moz-background-size: cover; 17 | -o-background-size: cover; 18 | background-size: 800px auto; 19 | padding-top: 145px; 20 | } 21 | 22 | /* 23 | 24 | -- Navigation Menu CSS 25 | 26 | */ 27 | 28 | .navbar { 29 | font-size: 1.1rem; 30 | line-height: 1.6rem; 31 | letter-spacing: 0.02rem; 32 | font-weight: 400; 33 | background-color: #222a2a; 34 | padding: 1.5% 4%; 35 | } 36 | 37 | footer { 38 | color: #ababc4; 39 | } 40 | 41 | .navbar-toggler-icon { 42 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#00A99D' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); 43 | } 44 | 45 | 46 | .n-logo-theme { 47 | color: #00a99d; 48 | padding-left: 12px; 49 | } 50 | 51 | .nav-link { 52 | color: #00a99d; 53 | } 54 | 55 | 56 | .nav-link:hover { 57 | color: #33ccc2; 58 | } 59 | 60 | /* 61 | 62 | -- Body Content CSS 63 | 64 | */ 65 | 66 | .card { 67 | border: none; 68 | background-color: transparent; 69 | } 70 | 71 | .list-group-item { 72 | color: #ababc4 !important; 73 | background-color: #222a2a !important; 74 | font-size: 0.9rem; 75 | } 76 | 77 | .float-right-truncate { 78 | color: #d5af4e; 79 | } 80 | 81 | h2 { 82 | font-size: 2.5rem; 83 | line-height: 2.9rem; 84 | font-weight: 400; 85 | max-width: 930px; 86 | } 87 | 88 | h3 { 89 | font-size: 1.4rem; 90 | font-weight: 400; 91 | max-width: 930px; 92 | } 93 | 94 | p { 95 | font-size: 0.9rem; 96 | line-height: 1.3rem; 97 | letter-spacing: 0.02em; 98 | } 99 | 100 | .btn-secondary { 101 | color: #ababc4; 102 | background-color: #222a2a; 103 | border-color: #222a2a; 104 | } 105 | 106 | .btn-secondary:hover { 107 | color: #33ccc2; 108 | background-color: #222a2a; 109 | border-color: #222a2a; 110 | } 111 | 112 | .n-address-theme { 113 | display: block; 114 | width: 100%; 115 | padding: .375rem .75rem; 116 | font-size: 1rem; 117 | line-height: 1.5; 118 | background-clip: padding-box; 119 | border: 1px solid #000034; 120 | border-radius: 0rem; 121 | transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; 122 | } 123 | 124 | .list-group-item a { 125 | color: #d5af4e; 126 | } 127 | 128 | .list-group-item a:hover { 129 | color: #d5af4e; 130 | } 131 | 132 | a { 133 | color: #00A99D; 134 | } 135 | 136 | a:hover { 137 | color: #33ccc2; 138 | } 139 | 140 | hr.light { 141 | display: block; 142 | height: 1px; 143 | border: 0; 144 | border-top: 1px solid #0A6EBD; 145 | margin: 1em 0; 146 | padding: 0; 147 | opacity: 0.2; 148 | } 149 | 150 | .btn-clipboard { 151 | color: #d5af4e; 152 | } 153 | 154 | 155 | .btn-clipboard:hover{ 156 | color: #d5af4e; 157 | } 158 | 159 | .btn-clipboard-light { 160 | color: #d5af4e; 161 | } 162 | 163 | .btn-clipboard-light:hover { 164 | color: #d5af4e; 165 | } -------------------------------------------------------------------------------- /static/themes/banano-dark/css/theme.css: -------------------------------------------------------------------------------- 1 | /* Load Font */ 2 | 3 | @import url('https://fonts.googleapis.com/css?family=Nunito:300,400'); 4 | 5 | /** 6 | 7 | -- Body CSS 8 | 9 | */ 10 | 11 | body { 12 | font-family: 'Nunito', sans-serif; 13 | background: #222222 ; 14 | color: #fafafa; 15 | -webkit-background-size: cover; 16 | -moz-background-size: cover; 17 | -o-background-size: cover; 18 | background-size: 800px auto; 19 | padding-top: 145px; 20 | } 21 | 22 | /* 23 | 24 | -- Navigation Menu CSS 25 | 26 | */ 27 | 28 | .navbar { 29 | font-size: 1.1rem; 30 | line-height: 1.6rem; 31 | letter-spacing: 0.02rem; 32 | font-weight: 400; 33 | background-color: #222222; 34 | padding: 1.5% 4%; 35 | } 36 | 37 | 38 | .navbar-toggler-icon { 39 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(251,221,17, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); 40 | } 41 | 42 | 43 | .n-logo-theme { 44 | color: #ffffff; 45 | padding-left: 12px; 46 | } 47 | 48 | .n-logo-theme:hover { 49 | color: #fada40; 50 | } 51 | 52 | .nav-link { 53 | color: #fafafa; 54 | } 55 | 56 | 57 | .nav-link:hover { 58 | color: #fada40; 59 | width: 100%; 60 | height: 100%; 61 | content: ''; 62 | background: #222222; 63 | opacity: 1; 64 | transition: all 0s; 65 | } 66 | 67 | /* 68 | 69 | -- Body Content CSS 70 | 71 | */ 72 | 73 | 74 | 75 | h2 { 76 | font-size: 2.5rem; 77 | line-height: 2.9rem; 78 | font-weight: 400; 79 | max-width: 930px; 80 | color: #fada40; 81 | } 82 | 83 | h3 { 84 | font-size: 1.4rem; 85 | font-weight: 400; 86 | max-width: 930px; 87 | color: #fada40; 88 | } 89 | 90 | p { 91 | font-size: 0.9rem; 92 | line-height: 1.3rem; 93 | letter-spacing: 0.02em; 94 | } 95 | 96 | 97 | .n-address-theme { 98 | background-color: #111111 !important; 99 | display: block; 100 | width: 100%; 101 | padding: .375rem .75rem; 102 | font-size: 1rem; 103 | line-height: 1.5; 104 | background-clip: padding-box; 105 | border: 1px solid #333333; 106 | border-radius: 0rem; 107 | transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; 108 | } 109 | 110 | .card { 111 | background-color: #111111; 112 | font-size: 0.9rem; 113 | } 114 | 115 | 116 | .list-group-item { 117 | background-color: #333333 !important; 118 | color: #fafafa !important; 119 | font-size: 0.9rem; 120 | } 121 | 122 | .list-group-item a { 123 | color: #fafafa; 124 | } 125 | 126 | a { 127 | color: #fafafa; 128 | } 129 | 130 | a:hover { 131 | color: #fada40; 132 | } 133 | 134 | hr.light { 135 | display: block; 136 | height: 1px; 137 | border: 0; 138 | border-top: 1px solid #fada40; 139 | margin: 1em 0; 140 | padding: 0; 141 | opacity: 0.2; 142 | } 143 | 144 | .card { 145 | border: none !important; 146 | } 147 | 148 | .btn-clipboard { 149 | color: #fafafa; 150 | } 151 | 152 | .btn-clipboard:hover{ 153 | color: #fada40; 154 | } 155 | 156 | .btn-clipboard-light { 157 | color: #fafafa; 158 | } 159 | 160 | .btn-clipboard-light:hover { 161 | color: #fada40; 162 | } 163 | 164 | .btn-secondary { 165 | background: #333333; 166 | color: #fafafa; 167 | border: none; 168 | } 169 | 170 | .btn-secondary:hover { 171 | background: #333333; 172 | color: #fada40; 173 | } -------------------------------------------------------------------------------- /modules/config.sample.php: -------------------------------------------------------------------------------- 1 | cache time in seconds 60 | // - "apcu" (APCu cache; requires extension; fast) 61 | // - Options: 'ttl' => cache time in seconds 62 | // - "redis" (Redis cache; requires extension; fast) 63 | // - Options: 64 | // - 'host' => optional; address of the Redis host (defaults to localhost) 65 | // - 'port' => optional; port for the Redis host (defaults to 6379) 66 | // - 'redis' => optional; an instantiated Redis instance (replaces use of host/port) 67 | // - 'ttl' => optional; cache time in seconds 68 | 69 | // $cache = [ 70 | // "engine" => "files", 71 | // "options" => ["ttl" => $cacheTimeToLive] 72 | // ]; 73 | 74 | // ----------- Node Variables ----------- 75 | 76 | // IP address for RPC (default: [::1]) 77 | // $nanoNodeRPCIP = '[::1]'; 78 | 79 | // Port for RPC (default: 7076) 80 | // Nano nodes typically use port 7076. 81 | // Nano Beta nodes typically use port 55000. 82 | // Banano nodes typically use port 7072. 83 | // $nanoNodeRPCPort = '7076'; 84 | 85 | // Account of this node 86 | // $nanoNodeAccount = 'nano_1f56swb9qtpy3yoxiscq9799nerek153w43yjc9atoaeg3e91cc9zfr89ehj'; 87 | 88 | // Donation account for maintaining this node 89 | // $nanoDonationAccount = 'nano_1f56swb9qtpy3yoxiscq9799nerek153w43yjc9atoaeg3e91cc9zfr89ehj'; 90 | 91 | // Number of decimal places to display Nano balances, i.e. 92 | // $nanoNumDecimalPlaces = 2; 93 | 94 | // ----------- Analytics ----------- 95 | 96 | // Google Analytics Tracking ID, leave blank to disable Google Analytics. 97 | // $googleAnalyticsId = ''; 98 | 99 | // ----------- Social ----------- 100 | 101 | // Add your social accounts 102 | // Tutorial: https://github.com/NanoTools/nanoNodeMonitor/wiki/Social-Accounts 103 | // $socials['reddit'] = 'https://www.reddit.com/user/NANOFAN1337/'; 104 | -------------------------------------------------------------------------------- /modules/functions_rpc.php: -------------------------------------------------------------------------------- 1 | "version"); 33 | 34 | // post curl 35 | return postCurl($ch, $data); 36 | } 37 | 38 | // get block count from nano_node 39 | function getBlockCount($ch) 40 | { 41 | // get block count 42 | $data = array("action" => "block_count", "include_cemented" => "true"); 43 | 44 | // post curl 45 | return postCurl($ch, $data); 46 | } 47 | 48 | // get number of peers 49 | function getPeers($ch) 50 | { 51 | // get peers 52 | $data = array("action" => "peers"); 53 | 54 | // post curl 55 | return postCurl($ch, $data); 56 | } 57 | 58 | // get account balance for nano_node account 59 | function getAccountBalance($ch, $account) 60 | { 61 | // get account balance 62 | $data = array("action" => "account_balance", "account" => $account); 63 | 64 | // post curl 65 | return postCurl($ch, $data); 66 | } 67 | 68 | // get representative info for nano_node account 69 | function getRepresentativeInfo($ch, $account) 70 | { 71 | // get account info 72 | $data = array("action" => "account_info", 73 | "account" => $account, 74 | "representative" => "true", 75 | "weight" => "true"); 76 | 77 | // post curl 78 | return postCurl($ch, $data); 79 | } 80 | 81 | // get account weight nano_node account 82 | function getAccountWeight($ch, $account) 83 | { 84 | // get account weight 85 | $data = array( 86 | "action" => "account_weight", 87 | "account" => $account 88 | ); 89 | 90 | // post curl 91 | return postCurl($ch, $data); 92 | } 93 | 94 | // get number of peers 95 | function getStats($ch, $type = "counters") 96 | { 97 | // get peers 98 | $data = array( 99 | "action" => "stats", 100 | "type" => $type); 101 | 102 | // post curl 103 | return postCurl($ch, $data); 104 | } 105 | 106 | // get confirmation history from nano_node 107 | function getConfirmationHistory($ch) 108 | { 109 | // get confirmation history of latest 2048 elections 110 | $data = array("action" => "confirmation_history"); 111 | 112 | // post curl 113 | return postCurl($ch, $data); 114 | } 115 | 116 | // get node uptime 117 | function getUptime($ch) 118 | { 119 | // get uptime 120 | $data = array("action" => "uptime"); 121 | 122 | // post curl 123 | return postCurl($ch, $data); 124 | } 125 | 126 | // get active difficulty 127 | function getActiveDifficulty($ch) 128 | { 129 | // get uptime 130 | $data = array("action" => "active_difficulty"); 131 | 132 | // post curl 133 | return postCurl($ch, $data); 134 | } 135 | 136 | // get telemetry data from other nodes 137 | function getTelemetry($ch) 138 | { 139 | // get uptime 140 | $data = array("action" => "telemetry"); 141 | 142 | // post curl 143 | return postCurl($ch, $data); 144 | } 145 | 146 | // get telemetry data from other nodes 147 | function getDelegatorsCount($ch, $account) 148 | { 149 | // get uptime 150 | $data = array("action" => "delegators_count", "account" => $account); 151 | 152 | // post curl 153 | return postCurl($ch, $data); 154 | } 155 | -------------------------------------------------------------------------------- /modules/cache/FileCache.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | class FileCache extends Cache 17 | { 18 | 19 | /** 20 | * The root cache directory. 21 | * @var string 22 | */ 23 | private $cache_dir = '/tmp/cache'; 24 | 25 | /** 26 | * The cache time in seconds. 27 | */ 28 | private $ttl = 30; 29 | 30 | /** 31 | * Creates a FileCache object 32 | * 33 | * @param array $options 34 | */ 35 | public function __construct(array $options = array()) 36 | { 37 | $available_options = array('cache_dir', 'ttl'); 38 | foreach ($available_options as $name) { 39 | if (isset($options[$name])) { 40 | $this->$name = $options[$name]; 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Fetches an entry from the cache. 47 | * 48 | * @param string $id 49 | */ 50 | public function read($id) 51 | { 52 | $file_name = $this->getFileName($id); 53 | 54 | if (!is_file($file_name) || !is_readable($file_name)) { 55 | return NULL; 56 | } 57 | 58 | $lines = file($file_name); 59 | $lifetime = array_shift($lines); 60 | $lifetime = (int) trim($lifetime); 61 | 62 | if ($lifetime !== 0 && $lifetime < time()) { 63 | @unlink($file_name); 64 | return NULL; 65 | } 66 | $serialized = join('', $lines); 67 | $data = unserialize($serialized); 68 | return $data; 69 | } 70 | 71 | /** 72 | * Deletes a cache entry. 73 | * 74 | * @param string $id 75 | * 76 | * @return bool 77 | */ 78 | public function delete($id) 79 | { 80 | $file_name = $this->getFileName($id); 81 | return unlink($file_name); 82 | } 83 | 84 | /** 85 | * Puts data into the cache. 86 | * 87 | * @param string $id 88 | * @param mixed $data 89 | * 90 | * @return bool 91 | */ 92 | public function write($id, $data) 93 | { 94 | $dir = $this->getDirectory($id); 95 | if (!is_dir($dir)) { 96 | if (!mkdir($dir, 0755, true)) { 97 | return false; 98 | } 99 | } 100 | $file_name = $this->getFileName($id); 101 | $lifetime = time() + $this->ttl; 102 | $serialized = serialize($data); 103 | $result = file_put_contents($file_name, $lifetime . PHP_EOL . $serialized); 104 | if ($result === false) { 105 | return false; 106 | } 107 | return true; 108 | } 109 | 110 | //------------------------------------------------ 111 | // PRIVATE METHODS 112 | //------------------------------------------------ 113 | 114 | /** 115 | * Fetches a directory to store the cache data 116 | * 117 | * @param string $id 118 | * 119 | * @return string 120 | */ 121 | protected function getDirectory($id) 122 | { 123 | $hash = sha1($id, false); 124 | $dirs = array( 125 | $this->getCacheDirectory(), 126 | substr($hash, 0, 2), 127 | substr($hash, 2, 2) 128 | ); 129 | return join(DIRECTORY_SEPARATOR, $dirs); 130 | } 131 | 132 | /** 133 | * Fetches a base directory to store the cache data 134 | * 135 | * @return string 136 | */ 137 | protected function getCacheDirectory() 138 | { 139 | return $this->cache_dir; 140 | } 141 | 142 | /** 143 | * Fetches a file path of the cache data 144 | * 145 | * @param string $id 146 | * 147 | * @return string 148 | */ 149 | protected function getFileName($id) 150 | { 151 | $directory = $this->getDirectory($id); 152 | $hash = sha1($id, false); 153 | $file = $directory . DIRECTORY_SEPARATOR . $hash . '.cache'; 154 | return $file; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /modules/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <?php echo currencyName($currency); ?> Node Monitor - <?php echo $nanoNodeName; ?> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano Node Monitor 2 | 3 | ![GitHub release](https://img.shields.io/github/release/NanoTools/nanoNodeMonitor.svg?style=flat-square) [![StyleCI](https://styleci.io/repos/118352667/shield?branch=master)](https://styleci.io/repos/118352667) [![Docker Pulls](https://img.shields.io/docker/pulls/nanotools/nanonodemonitor.svg?style=flat-square)](https://hub.docker.com/r/nanotools/nanonodemonitor/) 4 | 5 | Nano Node Monitor is a server-side PHP-based monitor for Nano and Banano nodes. It connects to a running node via RPC and displays it's status on a simple webpage. Being server-side, it does not expose the RPC interface of the Nano node to the public. 6 | 7 | |Nano Light|Nano Dark|Banano Light|Banano Dark| 8 | |-|-|-|-| 9 | |![Light](.github/theme-preview/nano-light.png)|![Dark](.github/theme-preview/nano-dark.png)|![Banano](.github/theme-preview/banano-light.png)|![Banano Dark](.github/theme-preview/banano-dark.png)| 10 | 11 | ## Docker Installation 12 | 13 | ### Pulling Docker image 14 | 15 | sudo docker pull nanotools/nanonodemonitor 16 | 17 | ### Running 18 | 19 | #### Standalone 20 | 21 | sudo docker run -d -p 80:80 -v ~:/opt --restart=unless-stopped nanotools/nanonodemonitor 22 | 23 | This will create a directory called _nanoNodeMonitor_ inside your home directory with the _config.php_ inside it. 24 | Edit it according to your needs and you're good to go! 25 | 26 | #### Docker Compose 27 | 28 | 1. Create a directory called _nano_ and go inside it: `mkdir nano && cd nano` 29 | 30 | 2. Create a new file called _docker-compose.yml_ with the following contents (but replace the TAG with a proper version): 31 | 32 | ``` 33 | version: '3' 34 | services: 35 | monitor: 36 | image: "nanotools/nanonodemonitor:TAG" 37 | restart: "unless-stopped" 38 | ports: 39 | - "80:80" 40 | volumes: 41 | - "~:/opt" 42 | node: 43 | image: "nanocurrency/nano:TAG" 44 | restart: "unless-stopped" 45 | ports: 46 | - "7075:7075" 47 | - "127.0.0.1:7076:7076" 48 | volumes: 49 | - "~:/root" 50 | ``` 51 | 3. Nice! Now execute `sudo docker-compose up -d` to start everything. 52 | 53 | 4. Inside your home directory you will find a new directory called _nanoNodeMonitor_, edit the _config.php_: `cd ~/nanoNodeMonitor` 54 | 55 | 5. You will have to change the node IP to the name of the nodes Docker container e.g. `nano_node_1`. Edit the other things as well if you want to. 56 | 57 | 6. Done! 58 | 59 | ## Manual Installation 60 | 61 | ### Prerequisites 62 | 63 | - Running Nano Node with RPC enabled ([Tutorial](https://docs.nano.org/running-a-node/node-setup/)) 64 | - Webserver with PHP 8.0 65 | - PHP-Curl Module 66 | 67 | `sudo apt-get install php-curl` 68 | 69 | ### Installation 70 | 71 | In your empty webserver directory, e.g. `/var/www/html`, execute: 72 | 73 | git clone https://github.com/NanoTools/nanoNodeMonitor . 74 | 75 | If you want it to run a subdirectory remove the `.` at the end. 76 | 77 | In the `modules` folder, create your own config file by executing: 78 | 79 | cp config.sample.php config.php 80 | 81 | ## Usage 82 | 83 | You will have to add your node's account to the config file `config.php` by modifying the following lines. Make sure to remove the `//` in front of `$nanoNodeAccount`: 84 | 85 | ``` 86 | // account of this node 87 | $nanoNodeAccount = 'nano_1youraccountname24cq9799nerek153w43yjc9atoaeg3e91cc9zfr89ehj'; 88 | ``` 89 | 90 | Official documentation for creating an account on the node via RPC can be found at the following URL: 91 | 92 | https://docs.nano.org/running-a-node/voting-as-a-representative/#step-2-setup-representative-account 93 | 94 | If you are running a standalone node you might need to modify the IP-address and the port for the RPC in the file `config.php`. It should match the corresponding entries in `~/Nano/config.json`, e.g. 95 | 96 | ``` 97 | // ip address for RPC (default: [::1]) 98 | $nanoNodeRPCIP = '127.0.0.1'; 99 | 100 | // ip address for RPC (default: 7076) 101 | $nanoNodeRPCPort = '7076'; 102 | ``` 103 | 104 | ## Creating a Theme 105 | 106 | If you're interested in creating your own theme in addition to the official Light, Dark, and Banano themes, we've made it very simple for you to do so. Check out the [Wiki](https://github.com/NanoTools/nanoNodeMonitor/wiki/Create-a-theme) for more info. 107 | 108 | ## Support 109 | 110 | Donations to the development of Nano Node Monitor are very welcome to: 111 | 112 | nano_1ninja7rh37ehfp9utkor5ixmxyg8kme8fnzc4zty145ibch8kf5jwpnzr3r 113 | 114 | Or [sponsor the development on GitHub](https://github.com/sponsors/BitDesert)! Thanks! 115 | -------------------------------------------------------------------------------- /templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if newNodeVersionAvailable}} 2 |
3 | 4 | Heads up! A new Nano Node version 5 | {{newNodeVersionAvailable}} is available. 6 | Download it here! 7 |
8 | {{/if}} 9 | 10 | {{#if nanoNodeAccount}} 11 |
12 |
13 |

Node Account

14 | 23 |
24 |
25 | {{/if}} 26 | 27 |
28 |
29 |

Node

30 |
31 |
    32 |
  • 33 | Version 34 | {{version}} 35 | {{#if store_vendor}} 36 |
  • 37 | Database 38 | {{store_vendor}} 39 |
  • 40 | {{/if}} 41 | 42 |
  • 43 | Node Uptime 44 | {{formatSeconds nodeUptimeStartup}} 45 |
  • 46 |
  • 47 | Peers 48 | {{formatNumber numPeers}} 49 |
  • 50 |
51 |
52 |
53 | 54 |
55 |

Blocks

56 |
57 |
    58 |
  • 59 | Current Block 60 | {{formatNumber currentBlock}} 61 |
  • 62 |
  • 63 | Cemented Blocks 64 | {{formatNumber cementedBlocks}} 65 |
  • 66 |
  • 67 | Unchecked Blocks 68 | {{formatNumber uncheckedBlocks}} 69 |
  • 70 |
  • 71 | Sync Status 72 | 73 | {{#if blockSync}} 74 | {{formatNumber blockSync}} % 75 | {{else}} 76 | N/A 77 | {{/if}} 78 | 79 |
  • 80 |
81 |
82 |
83 | 84 |
85 |

Node Account

86 |
    87 |
  • 88 | Balance 89 | {{formatNano accBalanceMnano}} {{currencySymbol}} 90 |
  • 91 |
  • 92 | Pending 93 | {{formatNano accPendingMnano 6}} {{currencySymbol}} 94 |
  • 95 |
  • 96 | Representative 97 | 98 | {{#if repAccount}} 99 | {{repAccount}} 100 | {{else}} Account not opened {{/if}} 101 | 102 |
  • 103 |
  • 104 | Voting Weight 105 | {{formatNano votingWeight 6}} {{currencySymbol}} 106 |
  • 107 |
108 |
109 | 110 |
111 |

System

112 |
    113 |
  • 114 | Host 115 | {{nanoNodeName}} 116 |
  • 117 |
  • 118 | Location 119 | 120 | {{#if nodeLocation}} 121 | {{nodeLocation}} 122 | {{else}} 123 | N/A 124 | {{/if}} 125 | 126 |
  • 127 |
  • 128 | Load 129 | {{formatNumber systemLoad}} 130 |
  • 131 |
  • 132 | Memory Used 133 | {{formatNumber usedMem}} / {{formatNumber totalMem}} MB 134 |
  • 135 |
136 |
137 |
138 | -------------------------------------------------------------------------------- /api.php: -------------------------------------------------------------------------------- 1 | fetch($apiName, function () use ( 13 | &$nanoNodeRPCIP, 14 | &$nanoNodeRPCPort, 15 | &$nanoNodeAccount, 16 | &$blockExplorer, 17 | &$nanoNodeName, 18 | &$nanoNumDecimalPlaces, 19 | &$uptimerobotApiKey, 20 | &$currency, 21 | &$nodeLocation 22 | ) { 23 | // get curl handle 24 | $ch = curl_init(); 25 | 26 | if (!$ch) { 27 | myError('Could not initialize curl!'); 28 | } 29 | 30 | // we have a valid curl handle here 31 | // set some curl options 32 | curl_setopt($ch, CURLOPT_URL, 'http://'.$nanoNodeRPCIP.':'.$nanoNodeRPCPort); 33 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); 34 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 35 | 36 | $data = new stdClass(); 37 | $data->nanoNodeAccount = $nanoNodeAccount; 38 | $data->nanoNodeAccountShort = truncateAddress($data->nanoNodeAccount); 39 | $data->nanoNodeAccountUrl = getAccountUrl($data->nanoNodeAccount, $blockExplorer); 40 | 41 | // -- Get Version String from nano node and node monitor 42 | $version = getVersion($ch); 43 | $data->version = $version->{'node_vendor'}; 44 | $data->store_version = (int) $version->{'store_version'} ?: 0; 45 | $data->protocol_version = (int) $version->{'protocol_version'} ?: 0; 46 | $data->store_vendor = (string) $version->{'store_vendor'} ?: ''; 47 | 48 | // Cache the github query for latest node version 49 | global $nodeVersionCache; 50 | $nodeVersionCache = new FileCache(['ttl' => 10 * 60]); // cache for 10 minutes 51 | 52 | // set a cache name so multiple monitors don't mix 53 | $cacheName = "nodeVersionCache-$nanoNodeAccount"; 54 | 55 | // get cached response 56 | $nodeVersionData = $nodeVersionCache->fetch($cacheName, function () { 57 | $nodeVersionData = new stdClass(); 58 | $nodeVersionData->latestNodeReleaseVersion = getLatestNodeReleaseVersion(); 59 | 60 | return $nodeVersionData; 61 | }); 62 | $latestVersion = $nodeVersionData->latestNodeReleaseVersion; 63 | $data->newNodeVersionAvailable = isNewNodeVersionAvailable(formatVersion($data->version), $latestVersion, $currency); 64 | $data->nodeMonitorVersion = PROJECT_VERSION; 65 | 66 | // -- Get get current block from nano_node 67 | $rpcBlockCount = getBlockCount($ch); 68 | $data->currentBlock = (int) $rpcBlockCount->{'count'}; 69 | $data->uncheckedBlocks = (int) $rpcBlockCount->{'unchecked'}; 70 | $data->cementedBlocks = (int) $rpcBlockCount->{'cemented'} ?: 0; 71 | 72 | // -- Get number of peers from nano_node 73 | $rpcPeers = getPeers($ch); 74 | $peers = (array) $rpcPeers->{'peers'}; 75 | $data->numPeers = count($peers); 76 | 77 | // -- Get confirmation info from nano_node. Average time, blocks used, time span and percentiles 78 | // -- over last X min (set by CONFIRMATION_TIME_LIMIT) or max 2048 blocks which is a node limitation 79 | //$timeStampBefore = microtime(true); // measure execution time 80 | $rpcConfHistory = getConfirmationHistory($ch); 81 | $confirmations = $rpcConfHistory->{'confirmations'}; // a list of last X confirmations {hash,duration,time,tally} 82 | //$confAverage = $rpcConfHistory->{'confirmation_stats'}->{'average'}; // average time [ms] of all confirmations 83 | //$confCount = $rpcConfHistory->{'confirmation_stats'}->{'count'}; // number of confirmations retrieved from the node 84 | 85 | // check if we have confirmations, otherwise skip 86 | if (!empty($confirmations)) { 87 | // remove data older than $timeLimit 88 | usort($confirmations, 'cmpByTime'); // sort array by time value [ms unix time] 89 | $confCompact = []; // new filtered array 90 | $durationTotal = 0; // for average calc 91 | $confAverage = 0; // average confirmation duration 92 | $timeSpan = 0; // full time span of the data [ms] 93 | foreach ($confirmations as $confirmation) { 94 | // only keep data which is later than X ms from latest (highest) value 95 | if ($confirmation->{'time'} >= $confirmations[0]->{'time'} - CONFIRMATION_TIME_LIMIT) { 96 | array_push($confCompact, $confirmation); // add new data 97 | $durationTotal += $confirmation->{'duration'}; 98 | } else { 99 | break; // stop iterating once we pass that limit to save time 100 | } 101 | } 102 | $confCount = count($confCompact); 103 | 104 | // calculate duration average and time span, avoid dividing by zero 105 | if ($confCount > 0) { 106 | $confAverage = round($durationTotal / $confCount); 107 | $timeSpan = $confCompact[0]->{'time'} - $confCompact[$confCount - 1]->{'time'}; // first minus last 108 | } 109 | 110 | // get percentiles directly from the filtered array 111 | usort($confCompact, 'cmpByDuration'); // sort array by duration value 112 | $percentile50 = getConfirmationsDurationPercentile(50, $confCompact); // 50 percentile also called median 113 | $percentile75 = getConfirmationsDurationPercentile(75, $confCompact); 114 | $percentile90 = getConfirmationsDurationPercentile(90, $confCompact); 115 | $percentile95 = getConfirmationsDurationPercentile(95, $confCompact); 116 | $percentile99 = getConfirmationsDurationPercentile(99, $confCompact); 117 | 118 | // combine an array with all confirmation info 119 | $confSummary = ['count' => $confCount, 'timeSpan' => $timeSpan, 'average' => $confAverage, 'percentile50' => $percentile50, 120 | 'percentile75' => $percentile75, 'percentile90' => $percentile90, 'percentile95' => $percentile95, 'percentile99' => $percentile99, ]; 121 | $data->confirmationInfo = $confSummary; 122 | //$data->apiProcTimeConf = round((microtime(true) - $timeStampBefore) * 1000); 123 | } 124 | 125 | // -- Get node account balance from nano_node 126 | $rpcNodeAccountBalance = getAccountBalance($ch, $nanoNodeAccount); 127 | $data->accBalanceMnano = rawToCurrency($rpcNodeAccountBalance->{'balance'} ?? 0, $currency); 128 | $data->accBalanceRaw = (int) ($rpcNodeAccountBalance->{'balance'} ?? 0); 129 | $data->accPendingMnano = rawToCurrency($rpcNodeAccountBalance->{'pending'} ?? 0, $currency); 130 | $data->accPendingRaw = (int) ($rpcNodeAccountBalance->{'pending'} ?? 0); 131 | 132 | // -- Get representative info for current node from nano_node 133 | $rpcNodeRepInfo = getRepresentativeInfo($ch, $nanoNodeAccount); 134 | $data->repAccount = $rpcNodeRepInfo->{'representative'} ?? ''; 135 | $data->repAccountShort = truncateAddress($data->repAccount); 136 | $data->repAccountUrl = getAccountUrl($data->repAccount, $blockExplorer); 137 | // $data->repDelegatorsCount = getDelegatorsCount($ch, $nanoNodeAccount)->count; // disabled (poor performance) 138 | 139 | // get the account weight 140 | $rpcNodeAccountWeight = getAccountWeight($ch, $nanoNodeAccount); 141 | $data->votingWeight = rawToCurrency($rpcNodeAccountWeight->{'weight'} ?? 0, $currency); 142 | 143 | // -- System uptime & memory info -- 144 | $data->systemLoad = getSystemLoadAvg(); 145 | $systemUptime = getSystemUptime(); 146 | $systemUptimeStr = $systemUptime['days'].' days, '.$systemUptime['hours'].' hrs, '.$systemUptime['mins'].' mins'; 147 | $data->systemUptime = $systemUptimeStr; 148 | $data->usedMem = getSystemUsedMem(); 149 | $data->totalMem = getSystemTotalMem(); 150 | //$data->uname = getUname(); 151 | $data->nanoNodeName = $nanoNodeName; 152 | $data->nodeUptimeStartup = (int) getUptime($ch)->{'seconds'} ?: 0; 153 | 154 | // get the node uptime (if we have a api key) 155 | if ($uptimerobotApiKey) { 156 | $data->nodeUptime = getNodeUptime($uptimerobotApiKey); 157 | } 158 | 159 | // get node location 160 | $data->nodeLocation = $nodeLocation; 161 | 162 | // currency and currency symbol 163 | $data->currency = $currency; 164 | $data->currencySymbol = currencySymbol($currency); 165 | 166 | // active_difficulty 167 | $data->active_difficulty = getActiveDifficulty($ch); 168 | 169 | // node statistics 170 | // maybe we get more stats later 171 | // so this is seperate object 172 | $data->stats = new stdClass(); 173 | 174 | // get the counters 175 | $data->stats->counters = getStats($ch, 'counters'); 176 | 177 | // get telemetry data from other nodes 178 | $data->telemetry = getTelemetry($ch); 179 | 180 | // sync status in % 181 | if ($data->telemetry->block_count) { 182 | $data->blockSync = getSyncStatus($data->currentBlock, $data->telemetry->block_count); 183 | } else { 184 | $data->blockSync = null; 185 | } 186 | 187 | // close curl handle 188 | curl_close($ch); 189 | 190 | // calculate total script execution time 191 | $data->apiProcTime = round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000); 192 | 193 | return $data; 194 | }); 195 | 196 | returnJson($data); 197 | -------------------------------------------------------------------------------- /static/js/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.6 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o' . $errorMsg . '
'); 8 | } 9 | 10 | // check whether php-curl is installed 11 | function phpCurlAvailable() 12 | { 13 | return function_exists('curl_version'); 14 | } 15 | 16 | // raw to Mnano 17 | function rawToMnano($raw) 18 | { 19 | return (float) ($raw / 1000000000000000000000000000000.0); 20 | } 21 | 22 | // raw to banano 23 | function rawToBanano($raw) 24 | { 25 | return rawToMnano($raw) * 10.; 26 | } 27 | 28 | // raw to banano 29 | function rawToPaw($raw) 30 | { 31 | return rawToMnano($raw) * 1000.; 32 | } 33 | 34 | // raw to currency 35 | function rawToCurrency($raw, $currency) 36 | { 37 | switch ($currency) 38 | { 39 | case 'banano': 40 | return rawToBanano($raw); 41 | case 'paw': 42 | return rawToPaw($raw); 43 | default: 44 | return rawToMnano($raw); 45 | } 46 | } 47 | 48 | // get system load average 49 | function getSystemLoadAvg() 50 | { 51 | return sys_getloadavg()[2]; 52 | } 53 | 54 | // get system memory info 55 | function getSystemMemInfo() 56 | { 57 | if (!file_exists("/proc/meminfo")) return NULL; 58 | $data = explode("\n", file_get_contents("/proc/meminfo")); 59 | $meminfo = array(); 60 | foreach ($data as $line) { 61 | list($key, $val) = explode(':', $line.':'); 62 | $meminfo[$key] = trim($val); 63 | } 64 | return $meminfo; 65 | } 66 | 67 | // get system total memory in MB 68 | function getSystemTotalMem() 69 | { 70 | return intval((int)getSystemMemInfo()["MemTotal"] / 1024); 71 | } 72 | 73 | // get system used memory in MB 74 | function getSystemUsedMem() 75 | { 76 | $meminfo = getSystemMemInfo(); 77 | return intval(((int)$meminfo["MemTotal"] - (int)$meminfo["MemAvailable"]) / 1024); 78 | } 79 | 80 | // get system uptime array with secs, mins, hours and days 81 | function getSystemUptime() 82 | { 83 | if (!file_exists('/proc/uptime')) return NULL; 84 | $str = file_get_contents('/proc/uptime'); 85 | $num = intval($str); 86 | $array = array(); 87 | $array["secs"] = $num % 60; 88 | $num = (int)($num / 60); 89 | $array["mins"] = $mins = $num % 60; 90 | $num = (int)($num / 60); 91 | $array["hours"] = $num % 24; 92 | $num = (int)($num / 24); 93 | $array["days"] = $num; 94 | return $array; 95 | } 96 | 97 | // returns JSON data to the client 98 | function returnJson($data) 99 | { 100 | header('Content-Type: application/json; charset=utf-8'); 101 | header('Access-Control-Allow-Origin: *'); 102 | echo json_encode($data); 103 | } 104 | 105 | // converts boolean to a string 106 | function bool2string($boolean) 107 | { 108 | return ($boolean) ? 'true' : 'false'; 109 | } 110 | 111 | // get version of latest release from github 112 | function getLatestReleaseVersion() 113 | { 114 | // get release tag of "latest" from github 115 | $curl = curl_init(); 116 | 117 | curl_setopt_array($curl, array( 118 | CURLOPT_URL => GITHUB_LATEST_API_URL, 119 | CURLOPT_RETURNTRANSFER => true, 120 | CURLOPT_ENCODING => "", 121 | CURLOPT_MAXREDIRS => 10, 122 | CURLOPT_TIMEOUT => 30, 123 | CURLOPT_CONNECTTIMEOUT => 2, 124 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 125 | CURLOPT_CUSTOMREQUEST => "GET", 126 | CURLOPT_HTTPHEADER => array( 127 | "cache-control: no-cache", 128 | "User-Agent: NanoNodeMonitor" 129 | ), 130 | )); 131 | 132 | $response = curl_exec($curl); 133 | $err = curl_error($curl); 134 | 135 | curl_close($curl); 136 | 137 | if ($err) { 138 | return "API error"; 139 | } 140 | 141 | // decode JSON response 142 | $response = json_decode($response); 143 | 144 | // tag string 145 | if (property_exists($response, "tag_name")) 146 | { 147 | $tagString = $response->tag_name; 148 | 149 | // search for version name x.x.x 150 | if (0 != preg_match('/(\d+\.?)+$/', $tagString, $versionString)) 151 | { 152 | return $versionString[0]; 153 | } 154 | } 155 | 156 | return PROJECT_VERSION; 157 | } 158 | 159 | // get a string with information about the 160 | // current version and possible updates 161 | function getVersionInformation($latestVersion) 162 | { 163 | $currentVersion = PROJECT_VERSION; 164 | $versionInfo = "Version: " . $currentVersion; 165 | 166 | if ( version_compare($currentVersion, $latestVersion) < 0 ) 167 | { 168 | $versionInfo .= "
A new version " . $latestVersion; 169 | $versionInfo .= " is available on "; 170 | $versionInfo .= "GitHub."; 171 | } 172 | 173 | return $versionInfo; 174 | 175 | } 176 | 177 | // get version of latest release from github 178 | function getLatestNodeReleaseVersion() 179 | { 180 | 181 | // get release tag of "latest" from github 182 | $curl = curl_init(); 183 | 184 | curl_setopt_array($curl, array( 185 | CURLOPT_URL => 'https://api.github.com/repos/nanocurrency/nano-node/releases/latest', 186 | CURLOPT_RETURNTRANSFER => true, 187 | CURLOPT_ENCODING => "", 188 | CURLOPT_MAXREDIRS => 10, 189 | CURLOPT_TIMEOUT => EXTERNAL_TIMEOUT, 190 | CURLOPT_CONNECTTIMEOUT => EXTERNAL_CONECTTIMEOUT, 191 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 192 | CURLOPT_CUSTOMREQUEST => "GET", 193 | CURLOPT_HTTPHEADER => array( 194 | "cache-control: no-cache", 195 | "User-Agent: NanoNodeMonitor" 196 | ), 197 | )); 198 | 199 | $response = curl_exec($curl); 200 | $err = curl_error($curl); 201 | 202 | curl_close($curl); 203 | 204 | if ($err) { 205 | return "API error"; 206 | } 207 | 208 | // decode JSON response 209 | $response = json_decode($response); 210 | 211 | // tag string 212 | if (property_exists($response, "tag_name")) 213 | { 214 | return substr($response->tag_name, 1); //delete the V at the beginning 215 | } 216 | 217 | return ''; 218 | } 219 | 220 | // gets the number from the version string 221 | function formatVersion($rawversion){ 222 | $formattedVersionArray = explode(' ', $rawversion); 223 | $formattedVersion = ltrim($formattedVersionArray[1], 'V'); 224 | 225 | return $formattedVersion; 226 | } 227 | 228 | // get a string with information about the 229 | // current version and possible updates 230 | function isNewNodeVersionAvailable($currentVersion, $latestVersion, $currency) 231 | { 232 | 233 | // for now, we can only check nano reliably 234 | if ($currency != "nano") { 235 | return false; 236 | } 237 | 238 | $currentVersion = $currentVersion; 239 | 240 | if ( version_compare($currentVersion, $latestVersion) < 0 ){ 241 | return $latestVersion; 242 | } else { 243 | return false; 244 | } 245 | } 246 | 247 | // info about operating system 248 | function getUname() 249 | { 250 | return php_uname(); 251 | } 252 | 253 | 254 | // get Node Uptime 255 | function getNodeUptime($apiKey, $uptimeRatio = 30) 256 | { 257 | $curl = curl_init(); 258 | 259 | curl_setopt_array($curl, array( 260 | CURLOPT_URL => "https://api.uptimerobot.com/v2/getMonitors", 261 | CURLOPT_RETURNTRANSFER => true, 262 | CURLOPT_ENCODING => "", 263 | CURLOPT_MAXREDIRS => 10, 264 | CURLOPT_TIMEOUT => EXTERNAL_TIMEOUT, 265 | CURLOPT_CONNECTTIMEOUT => EXTERNAL_CONECTTIMEOUT, 266 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 267 | CURLOPT_CUSTOMREQUEST => "POST", 268 | CURLOPT_POSTFIELDS => "api_key=$apiKey&format=json&custom_uptime_ratios=$uptimeRatio", 269 | CURLOPT_HTTPHEADER => array( 270 | "cache-control: no-cache", 271 | "content-type: application/x-www-form-urlencoded" 272 | ), 273 | )); 274 | 275 | $response = curl_exec($curl); 276 | $err = curl_error($curl); 277 | $errCode = -1; 278 | 279 | curl_close($curl); 280 | 281 | if ($err) { 282 | return $errCode; 283 | } 284 | 285 | // decode JSON response 286 | $response = json_decode($response); 287 | 288 | if (json_last_error() != JSON_ERROR_NONE) { 289 | return $errCode; 290 | } 291 | 292 | if (! array_key_exists('monitors', $response)) { 293 | return $errCode; 294 | } 295 | 296 | return (float)$response->monitors[0]->custom_uptime_ratio; 297 | } 298 | 299 | // truncate long Nano addresses to display the first and 300 | // last characters with ellipsis in the center 301 | function truncateAddress($addr) 302 | { 303 | $totalNumChar = NANO_ADDR_NUM_CHAR; 304 | $numEllipsis = 3; // ... 305 | $numPrefix = 4; // xrb_ 306 | 307 | // handle nano_ prefix of addresses 308 | 309 | if (substr($addr, 0, 5) === "nano_") 310 | { 311 | $numPrefix = 5; 312 | } 313 | 314 | $numAddrParts = floor(($totalNumChar-$numEllipsis-$numPrefix) / 2.0); 315 | 316 | return strlen($addr) > $totalNumChar ? substr($addr,0,$numPrefix+$numAddrParts)."...".substr($addr,-$numAddrParts) : $addr; 317 | } 318 | 319 | // get a block explorer URL from an account 320 | function getAccountUrl($account, $blockExplorer) 321 | { 322 | if(is_null($account)) return NULL; 323 | 324 | switch ($blockExplorer) 325 | { 326 | case 'ninja': 327 | return "https://mynano.ninja/account/" . $account; 328 | case 'nanocrawler-beta': 329 | return "https://beta.nanocrawler.cc/explorer/account/" . $account; 330 | case 'bananocreeper': 331 | return "https://creeper.banano.cc/explorer/account/" . $account; 332 | case 'tracker': 333 | return "https://tracker.paw.digital/account/" . $account; 334 | case 'bananolooker': 335 | return "https://bananolooker.com/account/" . $account; 336 | case 'yellowspyglass': 337 | return "https://yellowspyglass.com/account/" .$account; 338 | default: 339 | return "https://nanocrawler.cc/explorer/account/" . $account; 340 | } 341 | } 342 | 343 | // get sync status 344 | function getSyncStatus($node_blockcount, $telemetry_blockcount){ 345 | $sync = round(($node_blockcount / $telemetry_blockcount) * 100, 1); 346 | 347 | if($sync > 100){ 348 | return 100; 349 | } 350 | return $sync; 351 | } 352 | 353 | // get node location 354 | // 1) If location is set by user, we use it. 355 | // 2) If location not set by user, we try to get if from ninja. 356 | function getNodeLocation($nodeLocationByUser, $nodeNinja) { 357 | 358 | $locationDefault = "N/A"; 359 | $location = $locationDefault; 360 | 361 | if ($nodeLocationByUser) { 362 | // location set by user 363 | $location = $nodeLocationByUser; 364 | } 365 | elseif ($nodeNinja && array_key_exists('location', $nodeNinja)) { 366 | 367 | // location taken from ninja's location object 368 | $locationObj = $nodeNinja->{'location'}; 369 | 370 | $locCity = ''; 371 | $locCountry = ''; 372 | 373 | // get city from ninja's location object 374 | if ($locationObj && isset($locationObj->city)) { 375 | $locCity = $locationObj->city; 376 | } 377 | 378 | // get country from ninja's location object 379 | if ($locationObj && isset($locationObj->country)) { 380 | $locCountry = $locationObj->country; 381 | } 382 | 383 | // city and country available 384 | if (! empty($locCity) && ! empty($locCountry)) { 385 | $location = $locCity . ', ' . $locCountry; 386 | } 387 | elseif (! empty($locCity)) { 388 | // only city 389 | $location = $locCity; 390 | } 391 | elseif (! empty($locCountry)) { 392 | // only country 393 | $location = $locCountry; 394 | } 395 | else 396 | { 397 | // nothing given 398 | $location = $locationDefault; 399 | } 400 | } 401 | 402 | // run some final checks on location 403 | if (is_null($location) || empty($location)) { 404 | $location = $locationDefault; 405 | } 406 | 407 | return $location; 408 | } 409 | 410 | 411 | // get currency name from currency 412 | function currencyName($currency) 413 | { 414 | switch ($currency) { 415 | case 'banano': 416 | return "Banano"; 417 | 418 | case 'nano-beta': 419 | return "Nano BETA"; 420 | 421 | case 'paw': 422 | return "PAW"; 423 | 424 | default: 425 | return "Nano"; 426 | } 427 | 428 | } 429 | 430 | 431 | // get currency symbol from currency 432 | function currencySymbol($currency) 433 | { 434 | switch ($currency) { 435 | case 'banano': 436 | return "BANANO"; 437 | 438 | case 'nano-beta': 439 | return "\u{3B2}NANO"; 440 | 441 | case 'paw': 442 | return "PAW"; 443 | 444 | default: 445 | return "NANO"; 446 | } 447 | 448 | } 449 | 450 | // sort array by 'duration' sub value 451 | function cmpByDuration($a, $b) { 452 | return $a->{'duration'} - $b->{'duration'}; 453 | } 454 | 455 | // sort array by 'time' sub value (largest first) 456 | function cmpByTime($a, $b) { 457 | return $b->{'time'} - $a->{'time'}; 458 | } 459 | 460 | // get a percentile from a sorted json structure which contains sub element values with the name 'duration' 461 | // ex: get_percentile(75, $arr) to get the 75th percentile 462 | function getConfirmationsDurationPercentile($percentile, $array) { 463 | if (empty($array)) { 464 | return 0; 465 | } 466 | $index = ($percentile/100) * count($array); 467 | if (floor($index) == $index) { 468 | $result = ($array[$index-1]->{'duration'} + $array[$index]->{'duration'})/2; 469 | } 470 | else { 471 | $result = $array[floor($index)]->{'duration'}; 472 | } 473 | return (int) $result; 474 | } 475 | -------------------------------------------------------------------------------- /static/js/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.21.1 | (c) 2020 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(22),u=n(10),c=r(u);c.Axios=i,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),c.isAxiosError=n(26),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function s(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function i(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},s.forEach(["delete","get","head"],function(e){u.headers[e]={}}),s.forEach(["post","put","patch"],function(e){u.headers[e]=s.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),s=n(16),i=n(5),a=n(17),u=n(20),c=n(21),f=n(14);e.exports=function(e){return new Promise(function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";d.Authorization="Basic "+btoa(h+":"+m)}var y=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),i(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?u(l.getAllResponseHeaders()):null,s=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:s,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,i),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=(e.withCredentials||c(y))&&e.xsrfCookieName?s.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),n(e),l=null)}),p||(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,s){var i=new Error(e);return r(i,t,n,o,s)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,s,i){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(s)&&a.push("domain="+s),i===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(18),o=n(19);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,s,i={};return e?(r.forEach(e.split("\n"),function(e){if(s=e.indexOf(":"),t=r.trim(e.substr(0,s)).toLowerCase(),n=r.trim(e.substr(s+1)),t){if(i[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?i[t]=(i[t]?i[t]:[]).concat([n]):i[t]=i[t]?i[t]+", "+n:n}}),i):i}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){function n(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function o(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(s[o]=n(void 0,e[o])):s[o]=n(e[o],t[o])}t=t||{};var s={},i=["url","method","data"],a=["headers","auth","proxy","params"],u=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],c=["validateStatus"];r.forEach(i,function(e){r.isUndefined(t[e])||(s[e]=n(void 0,t[e]))}),r.forEach(a,o),r.forEach(u,function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(s[o]=n(void 0,e[o])):s[o]=n(void 0,t[o])}),r.forEach(c,function(r){r in t?s[r]=n(e[r],t[r]):r in e&&(s[r]=n(void 0,e[r]))});var f=i.concat(a).concat(u).concat(c),p=Object.keys(e).concat(Object.keys(t)).filter(function(e){return f.indexOf(e)===-1});return r.forEach(p,o),s}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}},function(e,t){"use strict";e.exports=function(e){return"object"==typeof e&&e.isAxiosError===!0}}])}); 3 | //# sourceMappingURL=axios.min.map -------------------------------------------------------------------------------- /static/js/popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2018 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){if(!e)return document.documentElement;for(var o=ie(10)?document.body:null,n=e.offsetParent;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?r(n):n:e?e.ownerDocument.documentElement:document.documentElement}function p(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||r(e.firstElementChild)===e)}function s(e){return null===e.parentNode?e:s(e.parentNode)}function d(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,a=document.createRange();a.setStart(n,0),a.setEnd(i,0);var l=a.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return p(l)?l:r(l);var f=s(e);return f.host?d(f.host,t):d(e,s(t).host)}function a(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0n[e]&&!t.escapeWithReference&&(i=X(p[o],n[e]-('right'===e?p.width:p.height))),se({},o,i)}};return i.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=de({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=J,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!q(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',g=a?'bottom':'right',u=L(i)[l];d[g]-us[g]&&(e.offsets.popper[m]+=d[m]+u-s[g]),e.offsets.popper=c(e.offsets.popper);var b=d[m]+d[l]/2-u/2,y=t(e.instance.popper),w=parseFloat(y['margin'+f],10),E=parseFloat(y['border'+f+'Width'],10),v=b-e.offsets.popper[m]-w-E;return v=Q(X(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},se(n,m,Math.round(v)),se(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(P(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=E(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=S(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case fe.FLIP:p=[n,i];break;case fe.CLOCKWISE:p=V(n);break;case fe.COUNTERCLOCKWISE:p=V(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=S(n);var a=e.offsets.popper,l=e.offsets.reference,f=J,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,y=-1!==['top','bottom'].indexOf(n),w=!!t.flipVariations&&(y&&'start'===r&&h||y&&'end'===r&&c||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),w&&(r=K(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=de({},e.offsets.popper,T(e.instance.popper,e.offsets.reference,e.placement)),e=N(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=S(t),e.offsets.popper=c(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!q(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right=p,N=g[k]+f.h/2+b.h/2>=m,I=g[k]-f.h<0,D=g[y]-f.w<0,B=g[k]+f.h+b.h>=m,S=g[y]+f.w+b.w>=p;a=(a===y||"right"===a)&&D&&S?k:a,a=a===k&&I?"bottom":a,a="bottom"===a&&B?k:a,a=a===y&&D?"right":a,a="right"===a&&S?y:a,-1===o.className.indexOf(a)&&(o.className=o.className.replace(A,a)),u=C.offsetWidth,h=C.offsetHeight,a===y||"right"===a?(s=a===y?g[y]+v.x-f.w-(T?u:0):g[y]+v.x+b.w,x?(r=g[k]+v.y,c=b.h/2-u):N?(r=g[k]+v.y-f.h+b.h,c=f.h-b.h/2-u):(r=g[k]+v.y-f.h/2+b.h/2,c=f.h/2-(T?.9*h:h/2))):a!==k&&"bottom"!==a||(r=a===k?g[k]+v.y-f.h-(T?h:0):g[k]+v.y+b.h,E?(s=0,d=g[y]+b.w/2-u):L?(s=p-1.01*f.w,d=f.w-(p-g[y])+b.w/2-u/2):(s=g[y]+v.x-f.w/2+b.w/2,d=f.w/2-u/2)),o[w][k]=r+"px",o[w][y]=s+"px",c&&(C[w][k]=c+"px"),d&&(C[w][y]=d+"px")};o.version="2.0.27";var Y=function(t){t=M(t);var e=this,i=S(t,".alert"),n=function(){H(i,"fade")?X(i,a):a()},o=function(n){i=S(n[s],".alert"),(t=M('[data-dismiss="alert"]',i))&&i&&(t===n[s]||t.contains(n[s]))&&e.close()},a=function(){U.call(i,"closed","alert"),R(t,"click",o),i[p].removeChild(i)};this.close=function(){i&&t&&H(i,"show")&&(U.call(i,"close","alert"),D(i,"show"),i&&n())},"Alert"in t||W(t,"click",o),t.Alert=e};a[b](["Alert",Y,'[data-dismiss="alert"]']);var G=function(t){t=M(t);var i=!1,n="checked",o=function(t){32===(t.which||t.keyCode)&&t[s]===e.activeElement&&l(t)},a=function(t){32===(t.which||t.keyCode)&&t.preventDefault()},l=function(e){var o="LABEL"===e[s].tagName?e[s]:"LABEL"===e[s][p].tagName?e[s][p]:null;if(o){var a=e[s],l=B(a[p],"btn"),r=o.getElementsByTagName("INPUT")[0];if(r){if("checkbox"===r.type&&(r[n]?(D(o,T),r[h](n),r.removeAttribute(n),r[n]=!1):(I(o,T),r[h](n),r[f](n,n),r[n]=!0),i||(i=!0,U.call(r,"change","button"),U.call(t,"change","button"))),"radio"===r.type&&!i&&!r[n]){I(o,T),r[f](n,n),r[n]=!0,U.call(r,"change","button"),U.call(t,"change","button"),i=!0;for(var c=0,d=l[m];c1?(t.preventDefault(),!1):void 0):void t.preventDefault()},$=function(t){if(b&&!w&&(E=C||parseInt(t.touches[0].pageX),b)){if((!n.contains(t[s])||!n.contains(t.relatedTarget))&&Math.abs(A-E)<75)return!1;CA&&g--,b=!1,p.slideTo(g),Q(R)}},_=function(){var e=n.getBoundingClientRect(),o=t.innerHeight||i.clientHeight;return e[k]<=o&&e.bottom>=0},tt=function(t){for(var e=0,i=q[m];et||o===N-1&&0===t)&&(S=p.direction="right"),t<0?t=N-1:t>=N&&(t=0),g=t,i=S===y?"next":"prev",U.call(n,"slide","carousel",L[t]),w=!0,clearInterval(v),v=null,tt(t),x&&H(n,"slide")?(I(L[t],"carousel-item-"+i),L[t].offsetWidth,I(L[t],"carousel-item-"+S),I(L[o],"carousel-item-"+S),X(L[t],function(a){var l=a&&a[s]!==L[t]?1e3*a.elapsedTime+100:20;w&&setTimeout(function(){w=!1,I(L[t],T),D(L[o],T),D(L[t],"carousel-item-"+i),D(L[t],"carousel-item-"+S),D(L[o],"carousel-item-"+S),U.call(n,"slid","carousel",L[t]),e.hidden||!p.interval||H(n,"paused")||p.cycle()},l)})):(I(L[t],T),L[t].offsetWidth,D(L[o],T),setTimeout(function(){w=!1,p.interval&&!H(n,"paused")&&p.cycle(),U.call(n,"slid","carousel",L[t])},100)))}},this.getActiveIndex=function(){return L.indexOf(B(n,"carousel-item active")[0])||0},"Carousel"in n||(p.pause&&p.interval&&(W(n,d[0],F),W(n,d[1],Y),W(n,u.start,F,z),W(n,u.end,Y,z)),L[m]>1&&W(n,u.start,V,z),O&&W(O,"click",J),P&&W(P,"click",J),j&&W(j,"click",G),p.keyboard&&W(t,"keydown",K)),p.getActiveIndex()<0&&(L[m]&&I(L[0],T),q[m]&&tt(0)),p.interval&&p.cycle(),n.Carousel=p}};a[b](["Carousel",J,'[data-ride="carousel"]']);var K=function(t,e){t=M(t),e=e||{};var i,n,o=null,a=null,l=this,r=t[h]("data-parent"),s=function(t,e){U.call(t,"show","collapse"),t.isAnimating=!0,I(t,"collapsing"),D(t,"collapse"),t[w].height=t.scrollHeight+"px",X(t,function(){t.isAnimating=!1,t[f]("aria-expanded","true"),e[f]("aria-expanded","true"),D(t,"collapsing"),I(t,"collapse"),I(t,"show"),t[w].height="",U.call(t,"shown","collapse")})},c=function(t,e){U.call(t,"hide","collapse"),t.isAnimating=!0,t[w].height=t.scrollHeight+"px",D(t,"collapse"),D(t,"show"),I(t,"collapsing"),t.offsetWidth,t[w].height="0px",X(t,function(){t.isAnimating=!1,t[f]("aria-expanded","false"),e[f]("aria-expanded","false"),D(t,"collapsing"),I(t,"collapse"),t[w].height="",U.call(t,"hidden","collapse")})};this.toggle=function(t){t.preventDefault(),H(a,"show")?l.hide():l.show()},this.hide=function(){a.isAnimating||(c(a,t),I(t,"collapsed"))},this.show=function(){o&&(i=M(".collapse.show",o),n=i&&(M('[data-target="#'+i.id+'"]',o)||M('[href="#'+i.id+'"]',o))),(!a.isAnimating||i&&!i.isAnimating)&&(n&&i!==a&&(c(i,n),I(n,"collapsed")),s(a,t),D(t,"collapsed"))},"Collapse"in t||W(t,"click",l.toggle),a=function(){var e=t.href&&t[h]("href"),i=t[h]("data-target"),n=e||i&&"#"===i.charAt(0)&&i;return n&&M(n)}(),a.isAnimating=!1,o=M(e.parent)||r&&S(t,r),t.Collapse=l};a[b](["Collapse",K,'[data-toggle="collapse"]']);var Q=function(t,i){t=M(t),this.persist=!0===i||"true"===t[h]("data-persist")||!1;var n=this,o=t[p],a=null,r=M(".dropdown-menu",o),c=function(){for(var t=r.children,e=[],i=0;i1?s-1:0:40===o&&s×',b=M(o.container),T=M(g),y=S(i,".modal"),A=S(i,".fixed-top"),C=S(i,".fixed-bottom");this.template=o.template?o.template:null,this.trigger=o.trigger?o.trigger:a||"hover",this[c]=o[c]&&"fade"!==o[c]?o[c]:l||"fade",this.placement=o.placement?o.placement:u||k,this[r]=parseInt(o[r]||m)||200,this.dismissible=!(!o.dismissible&&"true"!==p),this.container=b||(T||(A||(C||(y||e[n]))));var x=this,E=o.title||i[h]("data-title")||null,L=o.content||i[h]("data-content")||null;if(L||this.template){var N=null,B=0,P=this.placement,O=function(t){null!==N&&t[s]===M(".close",N)&&x.hide()},j=function(){x.container.removeChild(N),B=null,N=null},q=function(){E=o.title||i[h]("data-title"),L=o.content||i[h]("data-content"),L=L.trim(),N=e.createElement("div");var t=e.createElement("div");if(t[f]("class","arrow"),N.appendChild(t),null!==L&&null===x.template){if(N[f]("role","tooltip"),null!==E){var n=e.createElement("h3");n[f]("class","popover-header"),n.innerHTML=x.dismissible?E+v:E,N.appendChild(n)}var a=e.createElement("div");a[f]("class","popover-body"),a.innerHTML=x.dismissible&&null===E?L+v:L,N.appendChild(a)}else{var l=e.createElement("div");x.template=x.template.trim(),l.innerHTML=x.template,N.innerHTML=l.firstChild.innerHTML}x.container.appendChild(N),N[w].display="block",N[f]("class","popover bs-popover-"+P+" "+x[c])},Y=function(){!H(N,"show")&&I(N,"show")},G=function(){F(i,N,P,x.container)},J=function(n){"click"!=x.trigger&&"focus"!=x.trigger||!x.dismissible&&n(i,"blur",x.hide),x.dismissible&&n(e,"click",O),n(t,"resize",x.hide,z)},K=function(){J(W),U.call(i,"shown","popover")},Q=function(){J(R),j(),U.call(i,"hidden","popover")};this.toggle=function(){null===N?x.show():x.hide()},this.show=function(){clearTimeout(B),B=setTimeout(function(){null===N&&(P=x.placement,q(),G(),Y(),U.call(i,"show","popover"),x[c]?X(N,K):K())},20)},this.hide=function(){clearTimeout(B),B=setTimeout(function(){N&&null!==N&&H(N,"show")&&(U.call(i,"hide","popover"),D(N,"show"),x[c]?X(N,Q):Q())},x[r])},"Popover"in i||("hover"===x.trigger?(W(i,d[0],x.show),x.dismissible||W(i,d[1],x.hide)):"click"!=x.trigger&&"focus"!=x.trigger||W(i,x.trigger,x.toggle)),i.Popover=x}};a[b](["Popover",Z,'[data-toggle="popover"]']);var $=function(e,i){e=M(e);var n=M(e[h]("data-target")),o=e[h]("data-offset");if(i=i||{},i[s]||n){for(var a,l=this,r=i[s]&&M(i[s])||n,c=r&&r.getElementsByTagName("A"),d=parseInt(i.offset||o)||10,u=[],f=[],g=e.offsetHeight=c&&h>a;if(!s&&m)H(i,T)||(I(i,T),l&&!H(l,T)&&I(l,T),U.call(e,"activate","scrollspy",u[t]));else if(m){if(!m&&!s||s&&m)return}else H(i,T)&&(D(i,T),l&&H(l,T)&&!B(i[p],T).length&&D(l,T))},E=function(){a=v?q().y:e.scrollTop;for(var t=0,i=u[m];t1&&(t=e[e[m]-1]):t=e[0],t},E=function(){return M(C()[h]("href"))},L=function(t){t.preventDefault(),n=t.currentTarget,!u.isAnimating&&!H(n,T)&&d.show()};this.show=function(){n=n||t,l=M(n[h]("href")),o=C(),a=E(),u.isAnimating=!0,D(o,T),o[f]("aria-selected","false"),I(n,T),n[f]("aria-selected","true"),v&&(H(t[p],"dropdown-menu")?H(v,T)||I(v,T):H(v,T)&&D(v,T)),U.call(o,"hide","tab",n),H(a,"fade")?(D(a,"show"),X(a,A)):A()},"Tab"in t||W(t,"click",L),d.height&&(g=E()[p]),t.Tab=d}};a[b](["Tab",_,'[data-toggle="tab"]']);var tt=function(t,e){t=M(t),e=e||{};var i=t[h]("data-animation"),n=t[h]("data-autohide"),o=t[h]("data-delay");this.animation=!1===e.animation||"false"===i?0:1,this.autohide=!1===e.autohide||"false"===n?0:1,this[r]=parseInt(e[r]||o)||500;var a=this,l=0,s=S(t,".toast"),c=function(){D(s,"showing"),I(s,"show"),U.call(s,"shown","toast"),a.autohide&&a.hide()},d=function(){I(s,"hide"),U.call(s,"hidden","toast")},u=function(){D(s,"show"),a.animation?X(s,d):d()},f=function(){clearTimeout(l),l=null,I(s,"hide"),R(t,"click",a.hide),t.Toast=null,t=null,s=null};this.show=function(){s&&(U.call(s,"show","toast"),a.animation&&I(s,"fade"),D(s,"hide"),I(s,"showing"),a.animation?X(s,c):c())},this.hide=function(t){s&&H(s,"show")&&(U.call(s,"hide","toast"),t?u():l=setTimeout(u,a[r]))},this.dispose=function(){s&&H(s,"show")&&(D(s,"show"),a.animation?X(s,f):f())},"Toast"in t||W(t,"click",a.hide),t.Toast=a};a[b](["Toast",tt,'[data-dismiss="toast"]']);var et=function(i,o){i=M(i),o=o||{};var a=i[h]("data-animation"),l=i[h]("data-placement"),s=i[h]("data-delay"),u=i[h]("data-container"),p=M(o.container),m=M(u),g=S(i,".modal"),v=S(i,".fixed-top"),w=S(i,".fixed-bottom");this[c]=o[c]&&"fade"!==o[c]?o[c]:a||"fade",this.placement=o.placement?o.placement:l||k,this[r]=parseInt(o[r]||s)||200,this.container=p||(m||(v||(w||(g||e[n]))));var b=this,T=0,y=this.placement,A=null,C=i[h]("title")||i[h]("data-title")||i[h]("data-original-title");if(C&&""!=C){var x=function(){b.container.removeChild(A),A=null,T=null},E=function(){if(!(C=i[h]("title")||i[h]("data-title")||i[h]("data-original-title"))||""==C)return!1;A=e.createElement("div"),A[f]("role","tooltip");var t=e.createElement("div");t[f]("class","arrow"),A.appendChild(t);var n=e.createElement("div");n[f]("class","tooltip-inner"),A.appendChild(n),n.innerHTML=C,b.container.appendChild(A),A[f]("class","tooltip bs-tooltip-"+y+" "+b[c])},L=function(){F(i,A,y,b.container)},N=function(){!H(A,"show")&&I(A,"show")},B=function(){W(t,"resize",b.hide,z),U.call(i,"shown","tooltip")},P=function(){R(t,"resize",b.hide,z),x(),U.call(i,"hidden","tooltip")};this.show=function(){clearTimeout(T),T=setTimeout(function(){if(null===A){if(y=b.placement,0==E())return;L(),N(),U.call(i,"show","tooltip"),b[c]?X(A,B):B()}},20)},this.hide=function(){clearTimeout(T),T=setTimeout(function(){A&&H(A,"show")&&(U.call(i,"hide","tooltip"),D(A,"show"),b[c]?X(A,P):P())},b[r])},this.toggle=function(){A?b.hide():b.show()},"Tooltip"in i||(i[f]("data-original-title",C),i.removeAttribute("title"),W(i,d[0],b.show),W(i,d[1],b.hide)),i.Tooltip=b}};a[b](["Tooltip",et,'[data-toggle="tooltip"]']);var it=function(t,e){for(var i=0,n=e[m];i 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------