├── .gitignore ├── README.md ├── dashboard ├── __init__.py ├── admin.py ├── apps.py ├── middleware │ ├── __init__.py │ └── filter_host_middleware.py ├── models.py ├── static │ └── dashboard │ │ ├── bitcoin.png │ │ └── lightning.jpg ├── templates │ └── dashboard │ │ └── index.html ├── tests.py ├── urls.py └── views.py ├── db.sqlite3 ├── manage.py └── nodemonitor ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | /**/*.pyc 2 | /node_monitor_workspace 3 | .directory 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeMonitor is a simple Python Django website that makes calls to Bitcoin (bitcoind) and Lightning Network (c-lightning lightningd) nodes and displays the results on a simple 'dashboard' style status page. 2 | 3 | If you prefer to work with **Flask** I might have time to work on that version soon... or you can of course. It will be [here](https://github.com/wintercooled/NodeMonitor-Python-Flask) if I get round to it. 4 | 5 | ## The code makes calls to Bitcoin and Lightning daemons but you can easily add other daemons, such as Elements or Liquid, by editing just two files. 6 | 7 | ## The site can be accessed from any machine on your local network. 8 | 9 | ### Status 10 | 11 | Tested on Raspberry Pi running Raspbian and also on Ubuntu 18.04.01. 12 | 13 |
14 |

15 | 16 |

17 | 18 | ### How to install and run 19 | 20 | Assumes you already have Bitcoin Core daemon (bitcoind) and c-lightning daemon (lightningd) installed and running. If you don't have either running that's fine at this stage. Assumes you have python and python pip already installed. 21 | 22 | 23 | Install virtualenv so we can set up an isolated build environment. 24 | 25 | ```sudo apt install virtualenv``` 26 | 27 | Clone this repository into your chosen directory. 28 | 29 | ```git clone https://github.com/wintercooled/NodeMonitor-Python-Django.git``` 30 | 31 | Move into the new directory. 32 | 33 | ```cd NodeMonitor-Python-Django``` 34 | 35 | Add a virtualenv workspace for our project. 36 | 37 | ```virtualenv node_monitor_workspace``` 38 | 39 | Activate the workspace. 40 | 41 | ```source node_monitor_workspace/bin/activate``` 42 | 43 | Install the required dependancies to the workspace. These are: 44 | 45 | django - https://www.djangoproject.com/ 46 | 47 | pylightning - https://github.com/ElementsProject/lightning/tree/master/contrib/pylightning 48 | 49 | python-bitcoinrpc - https://github.com/jgarzik/python-bitcoinrpc 50 | 51 | ``` 52 | python -m pip install "django<2" 53 | pip install pylightning 54 | pip install python-bitcoinrpc 55 | ``` 56 | Check the set up worked by running the server. 57 | 58 | ```python manage.py runserver``` 59 | 60 | Browse to http://127.0.0.1:8000 to view the site. 61 | 62 | To stop the server press Ctrl+c. 63 | 64 | Don't forget to deactivate the virtualenv workspace when you are done: 65 | 66 | ```deactivate``` 67 | 68 | ...and remember to activate using ```source node_monitor_workspace/bin/activate``` from within the NodeMonitor-Python-Django directory whenever you want to run it again. 69 | 70 | Both nodes (Bitcoin and Lightning) will likely show as not running. This is because we have not set up the authentication details yet. 71 | 72 | Edit nodemonitor/dashboard/views.py and change the following lines to map to your own Bitcoin node's authentication settings: 73 | 74 | ``` 75 | rpc_port="8332" 76 | rpc_user="user82ue99fwo3049f7c8a8d93dkall2l1l11" 77 | rpc_password="passwordb084b7v85f7hd06s06d06fgd01shaj" 78 | ``` 79 | 80 | Also within nodemonitor/dashboard/views.py you need to change this to make sure it points to your nodes "lightning-rpc" socket file: 81 | 82 | ``` 83 | ln = LightningRpc("/home/pi/.lightning/lightning-rpc") 84 | ``` 85 | 86 | ### To access the website from another machine on your local network: 87 | 88 | Find your machine's IP 89 | 90 | ``` 91 | ifconfig 92 | ``` 93 | 94 | Let's say it is 192.188.1.150 for the sake of example. 95 | 96 | Run the server using like this: 97 | 98 | ``` 99 | python manage.py runserver 192.168.1.150:8000 100 | ``` 101 | 102 | Browse to http://192.168.1.150:8000 from any local machine on the 192.168.\*.\* IP address range, including mobile devices: 103 | 104 |
105 |

106 | 107 |

108 | 109 | If you want to make the website available publically you need to follow instructions like [this](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Deployment). 110 | 111 | ### To add extra node types or remove node types 112 | 113 | To remove a node type (e.g. if you do not run a Lightning node) or to add extra node types (e.g. if want to monitor an Elements or Liquid node) - edit the following files: 114 | 115 | ``` 116 | NodeMonitor/dashboard/templates/dashboard/index.html 117 | NodeMonitor/dashboard/views.py 118 | ``` 119 | views.py contains the code that connects to the nodes. 120 | 121 | index.html displays the data returned from views.py. 122 | 123 | ### By the way... 124 | The ". . ." at the bottom of each node status panel doesn't do anything (yet), sorry if you wasted time clicking on it! 125 | -------------------------------------------------------------------------------- /dashboard/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintercooled/NodeMonitor-Python-Django/8efb5b416f20eab602df54fa290774bc63ef56d5/dashboard/__init__.py -------------------------------------------------------------------------------- /dashboard/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | -------------------------------------------------------------------------------- /dashboard/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class DashboardConfig(AppConfig): 8 | name = 'dashboard' 9 | -------------------------------------------------------------------------------- /dashboard/middleware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintercooled/NodeMonitor-Python-Django/8efb5b416f20eab602df54fa290774bc63ef56d5/dashboard/middleware/__init__.py -------------------------------------------------------------------------------- /dashboard/middleware/filter_host_middleware.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseForbidden 2 | 3 | class FilterHostMiddleware(object): 4 | 5 | def process_request(self, request): 6 | 7 | allowed_hosts = ['127.0.0.1', 'localhost'] # specify complete host names here 8 | host = request.META.get('HTTP_HOST') 9 | 10 | if host[:7] == '192.168': # if the host starts with 192.168 then add to the allowed hosts 11 | allowed_hosts.append(host) 12 | 13 | if host not in allowed_hosts: 14 | raise HttpResponseForbidden 15 | 16 | return None -------------------------------------------------------------------------------- /dashboard/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | -------------------------------------------------------------------------------- /dashboard/static/dashboard/bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintercooled/NodeMonitor-Python-Django/8efb5b416f20eab602df54fa290774bc63ef56d5/dashboard/static/dashboard/bitcoin.png -------------------------------------------------------------------------------- /dashboard/static/dashboard/lightning.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintercooled/NodeMonitor-Python-Django/8efb5b416f20eab602df54fa290774bc63ef56d5/dashboard/static/dashboard/lightning.jpg -------------------------------------------------------------------------------- /dashboard/templates/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 119 | 120 | 121 | 122 | 123 | {% load static %} 124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 141 | 142 | 143 | 144 | 145 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 |
Bitcoin Node
Running 135 | {% if bitcoin.running %} 136 |
137 | {% else %} 138 |
139 | {% endif %} 140 |
Online 146 | {% if bitcoin.online %} 147 |
148 | {% else %} 149 |
150 | {% endif %} 151 |
Peers{{ bitcoin.peer_count }}
Block Height{{ bitcoin.block_height }}
Version{{ bitcoin.version }}
{{ bitcoin.message }}
175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 |
Lightning Node
Running 184 | {% if lightning.running %} 185 |
186 | {% else %} 187 |
188 | {% endif %} 189 |
Peers{{ lightning.peer_count }}
Channels{{ lightning.channel_count }}
Block Height{{ lightning.block_height }}
Version{{ lightning.version }}
{{ lightning.message }}
218 | 219 |
220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /dashboard/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /dashboard/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from dashboard import views 4 | 5 | urlpatterns = [ 6 | url(r'^$', views.index, name='index'), 7 | ] -------------------------------------------------------------------------------- /dashboard/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Lightning Network 4 | from lightning import LightningRpc 5 | 6 | # Bitcoin 7 | from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException 8 | 9 | def index(request): 10 | # Bitcoin RPC Credentials (you need to change these) 11 | rpc_port="8332" 12 | rpc_user="user82ue99fwo3049f7c8a8d93dkall2l1l11" 13 | rpc_password="passwordb084b7v85f7hd06s06d06fgd01shaj" 14 | 15 | # LIGHTNING NETWORK 16 | 17 | # Lightning Network Socket file (you might need to change this) 18 | ln = LightningRpc("/home/pi/.lightning/lightning-rpc") 19 | 20 | try: 21 | l_info = ln.getinfo() 22 | l = LightningViewData(True) 23 | l.block_height = l_info["blockheight"] 24 | l.version = l_info["version"] 25 | l.version = l.version.replace("v", "") 26 | 27 | l_peers = ln.listpeers() 28 | l.peer_count = len(l_peers["peers"]) 29 | 30 | l_funds = ln.listfunds() 31 | l.channel_count = len(l_funds["channels"]) 32 | except: 33 | l = LightningViewData(False) 34 | 35 | 36 | # BITCOIN 37 | 38 | b = BitcoinViewData(True) 39 | 40 | try: 41 | rpc_connection = AuthServiceProxy("http://%s:%s@127.0.0.1:%s"%(rpc_user, rpc_password, rpc_port)) 42 | b_conn_count = rpc_connection.getconnectioncount() 43 | if b_conn_count > 0: 44 | b.online = True 45 | except Exception as e: 46 | b.running = False 47 | 48 | if b.running == True: 49 | try: 50 | b.block_height = rpc_connection.getblockcount() 51 | b_network_info = rpc_connection.getnetworkinfo() 52 | b.peer_count = b_network_info["connections"] 53 | b.version = b_network_info["subversion"] 54 | b.version = b.version.replace("/", "") 55 | b.version = b.version.replace("Satoshi:", "") 56 | except Exception as e: 57 | b.message = str(e) 58 | 59 | 60 | # RETURN VIEW DATA 61 | 62 | return render(request, 'dashboard/index.html', {'lightning': l, 'bitcoin': b}) 63 | 64 | 65 | class BitcoinViewData: 66 | def __init__(self, running): 67 | self.running = running 68 | self.online = False 69 | self.peer_count = 0 70 | self.block_height = 0 71 | self.version = "" 72 | self.message = "" 73 | 74 | class LightningViewData: 75 | def __init__(self, running): 76 | self.running = running 77 | self.peer_count = 0 78 | self.channel_count = 0 79 | self.block_height = 0 80 | self.version = "" 81 | self.message = "" 82 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintercooled/NodeMonitor-Python-Django/8efb5b416f20eab602df54fa290774bc63ef56d5/db.sqlite3 -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "nodemonitor.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /nodemonitor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wintercooled/NodeMonitor-Python-Django/8efb5b416f20eab602df54fa290774bc63ef56d5/nodemonitor/__init__.py -------------------------------------------------------------------------------- /nodemonitor/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for nodemonitor project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.16. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = 'youXshouldXchangeXthisXtoXsomethingXelse' 23 | 24 | # With debug turned off Django won't handle static files for you any more - your production web server will have to 25 | DEBUG = True 26 | 27 | # Please note - although * is used in actual fact only addresses starting 192.168 are allowed as we have custom middleware to filter requests. 28 | # See settings.py MIDDLEWARE section 29 | ALLOWED_HOSTS = ['*'] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'dashboard', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'nodemonitor.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'nodemonitor.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-gb' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /nodemonitor/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.contrib import admin 3 | 4 | urlpatterns = [ 5 | url(r'^dashboard/', include('dashboard.urls')), 6 | url(r'^', include('dashboard.urls')), 7 | ] -------------------------------------------------------------------------------- /nodemonitor/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for rpidash project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rpidash.settings") 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------