├── .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 | Bitcoin Node |
130 |
131 |
132 |
133 | Running |
134 |
135 | {% if bitcoin.running %}
136 |
137 | {% else %}
138 |
139 | {% endif %}
140 | |
141 |
142 |
143 |
144 | Online |
145 |
146 | {% if bitcoin.online %}
147 |
148 | {% else %}
149 |
150 | {% endif %}
151 | |
152 |
153 |
154 |
155 | Peers |
156 | {{ bitcoin.peer_count }} |
157 |
158 |
159 |
160 | Block Height |
161 | {{ bitcoin.block_height }} |
162 |
163 |
164 |
165 | Version |
166 | {{ bitcoin.version }} |
167 |
168 |
169 |
170 | |
171 | {{ bitcoin.message }} |
172 |
173 |
174 |
175 |
176 |
177 |
178 | Lightning Node |
179 |
180 |
181 |
182 | Running |
183 |
184 | {% if lightning.running %}
185 |
186 | {% else %}
187 |
188 | {% endif %}
189 | |
190 |
191 |
192 |
193 | Peers |
194 | {{ lightning.peer_count }} |
195 |
196 |
197 |
198 | Channels |
199 | {{ lightning.channel_count }} |
200 |
201 |
202 |
203 | Block Height |
204 | {{ lightning.block_height }} |
205 |
206 |
207 |
208 | Version |
209 | {{ lightning.version }} |
210 |
211 |
212 |
213 | |
214 | {{ lightning.message }} |
215 |
216 |
217 |
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 |
--------------------------------------------------------------------------------