Skip to content

Commit 0206b2a

Browse files
authored
Merge pull request #80 from Madriix/master
Optional Web Admin Panel
2 parents 73f18fb + 1605e0f commit 0206b2a

File tree

7 files changed

+433
-1
lines changed

7 files changed

+433
-1
lines changed

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,35 @@ node bouncer.js somefile.conf &
6969
```
7070

7171
#### Keep it running forever (no downtime)
72+
### immortal
7273
To keep things running 24/7/365, there's a great app called [immortal](https://immortal.run/).
7374

7475
The immortaldir files are located in this repo (jbnc.yml).
7576

7677
Note: To use immortal on ubuntu, after following the steps on the page, please be sure to `systemctl enable immortaldir` as well as start.
7778

79+
### pm2
80+
Alternatively, there's pm2. To install:
81+
```
82+
npm install pm2 -g
83+
```
84+
To prevent logs from becoming too large, simply install:
85+
```
86+
pm2 install pm2-logrotate
87+
```
88+
No configuration is needed. It works as soon as any application is launched.
89+
90+
To start jbnc:
91+
```
92+
cd /home/folder/jbnc
93+
pm2 start bouncer.js
94+
or sudo pm2 start bouncer.js (if using SSL certificates from Let's Encrypt directories in /etc)
95+
```
96+
To stop:
97+
```
98+
Replace "start" with "stop"
99+
```
100+
78101
### IRC Client
79102
You just need to set your password in your jbnc config and then setup your IRC client:
80103
Just put this in your password:
@@ -142,6 +165,68 @@ SomePassword/buffername
142165
An example buffername could be 'desktop' and on the mobile phone could be 'mobile.'
143166

144167

168+
### Web Admin Panel
169+
170+
A web panel for an administrator is now integrated with jbnc. It is optional.
171+
To use it, simply install `npm install express express-session` and add the following to the jbnc.conf file:
172+
```
173+
"WebAdminPanel": true,
174+
"WebAdminPanelPort": 8889,
175+
"WebAdminPanelSecret":"<a_randomly_invented_key>",
176+
"WebAdminPanelPassword":"<password>",
177+
```
178+
179+
Then launch the web page in the browser at `http://127.0.0.1:8889`
180+
181+
The web page is designed to be used with a proxy from nginx or httpd, by creating a subdomain like https://j-bnc.domain.com and configuring the proxy:
182+
183+
For nginx, something like:
184+
185+
```
186+
server {
187+
listen 443 ssl;
188+
server_name j-bnc.domain.com;
189+
190+
ssl_certificate /path/to/your/certificate.crt;
191+
ssl_certificate_key /path/to/your/private.key;
192+
193+
location / {
194+
proxy_pass http://127.0.0.1:8889;
195+
...
196+
}
197+
...
198+
}
199+
```
200+
And for httpd:
201+
202+
```
203+
<VirtualHost *:443>
204+
ServerName j-bnc.domain.com
205+
206+
SSLEngine on
207+
SSLCertificateFile /path/to/your/certificate.crt
208+
SSLCertificateKeyFile /path/to/your/private.key
209+
210+
ProxyPass / http://127.0.0.1:8889/
211+
ProxyPassReverse / http://127.0.0.1:8889/
212+
...
213+
</VirtualHost>
214+
```
215+
216+
It is recommended to run the web page on an HTTPS-enabled site since both CDNs are HTTPS-enabled.
217+
218+
The web page is very basic, it displays:
219+
- The last launch of JBNC
220+
- The last bug of JBNC
221+
- The number of connections (clients)
222+
- The connected clients
223+
- Ability to kill a user
224+
- Ability to view the number of channels for each user
225+
226+
227+
###########
228+
229+
145230
### Copyright
146231

147232
(c) 2020 Andrew Lee <andrew@imperialfamily.com>

bouncer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// jbnc v0.9.0
1+
// jbnc v0.9.1
22
// Copyright (C) 2020 Andrew Lee <andrew@imperialfamily.com>
33
// All Rights Reserved.
44
const fs = require('fs');
@@ -39,10 +39,17 @@ global.DEBUG = config.debug ? config.debug : false;
3939
global.WEBIRCSPECIAL = config.webircSpecial ? config.webircSpecial : false;
4040
global.IRC_STANDARDS = config.ircStandards ? config.ircStandards : true;
4141
global.UNCAUGHTEXCEPTION = config.uncaughtException ? config.uncaughtException : true;
42+
global.WEBADMINPANEL = config.WebAdminPanel ? config.WebAdminPanel : false;
43+
global.WEBADMINPANEL_PORT = config.WebAdminPanelPort ? config.WebAdminPanelPort : 8889;
44+
global.WEBADMINPANEL_PASSWORD = config.WebAdminPanelPassword ? config.WebAdminPanelPassword : false;
45+
global.WEBADMINPANEL_SECRET = config.WebAdminPanelSecret ? config.WebAdminPanelSecret : 'keyboard cat';
4246

4347
global.ircCommandList = new Set(["JOIN", "PART", "QUIT", "MODE", "PING", "NICK", "KICK"]);
4448
global.ircCommandRedistributeMessagesOnConnect = new Set(["AWAY", "NICK", "ACCOUNT", "PART", "QUIT", "MODE", "KICK", "TOPIC"]);
4549

50+
global.LAST_LAUNCH = new Date().toLocaleString();
51+
global.LAST_BUG = 'none';
52+
4653

4754
// Reload passwords on sighup
4855
process.on('SIGHUP', function () {
@@ -59,6 +66,7 @@ if (global.UNCAUGHTEXCEPTION) {
5966
// Prevent BNC from crashing for all other users when an error is caused by a user (with log error and time)
6067
process.on('uncaughtException', (err, origin) => {
6168
console.error(`${parseInt(Number(new Date()) / 1000)} # Serious problem (${origin}) - this should not happen but the JBNC is still running. ${err.stack}`);
69+
global.LAST_BUG = new Date().toLocaleString();
6270
});
6371
}
6472

lib/Connections.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ class Connections {
1919
}
2020
return channels;
2121
}
22+
23+
userKill(hash) {
24+
let disconnected = false;
25+
if (this.connections[hash]) {
26+
this.connections[hash].end();
27+
disconnected=true;
28+
}
29+
return disconnected;
30+
}
2231
}
2332

2433
module.exports = Connections;

lib/Server.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ const ClientConnect = require('../lib/ClientConnect');
88
const ClientReconnect = require('../lib/ClientReconnect');
99
const Connections = require('../lib/Connections');
1010

11+
var WebAdminPanel = null;
12+
if (global.WEBADMINPANEL)
13+
WebAdminPanel = require('../lib/WebAdminPanel');
14+
1115
class Server {
1216
constructor() {
1317
this.config = global.config;
@@ -747,6 +751,11 @@ class Server {
747751
if (global.DEBUG)
748752
console.log("The Bouncer Server is started. listen()");
749753

754+
// Web admin panel
755+
if (global.WEBADMINPANEL) {
756+
const webPanel = new WebAdminPanel(global.WEBADMINPANEL_PORT, this.connections, this.instanceConnections);
757+
webPanel.start();
758+
}
750759
}
751760

752761
}

lib/WebAdminPanel.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const http = require('http');
2+
const express = require('express');
3+
const session = require('express-session');
4+
5+
class WebAdminPanel {
6+
constructor(port, connections, instanceConnections) {
7+
this.port = port;
8+
this.connections = connections;
9+
this.instanceConnections = instanceConnections;
10+
this.app = express();
11+
this.app.use(express.urlencoded({ extended: true }));
12+
this.app.use(express.json());
13+
this.setupMiddleware();
14+
this.setupRoutes();
15+
this.setup404Error();
16+
}
17+
18+
start() {
19+
this.server = http.createServer(this.app);
20+
this.server.listen(this.port, () => {
21+
console.log(`Server WebPanel started and listening on port http://localhost:${this.port}`);
22+
});
23+
}
24+
25+
setupMiddleware() {
26+
this.app.use(session({
27+
secret: global.WEBADMINPANEL_SECRET,
28+
resave: false,
29+
saveUninitialized: true
30+
}));
31+
}
32+
33+
setupRoutes() {
34+
this.app.get('/', (req, res) => {
35+
res.sendFile(__dirname + '/webadminpanel/login.html');
36+
});
37+
38+
this.app.post('/login', (req, res) => {
39+
const { username, password } = req.body;
40+
if (!!global.WEBADMINPANEL_SECRET && !!global.BOUNCER_ADMIN && !!global.WEBADMINPANEL_PASSWORD && username === global.BOUNCER_ADMIN && password === global.WEBADMINPANEL_PASSWORD) {
41+
req.session.authenticated = true;
42+
res.redirect('/dashboard');
43+
} else {
44+
res.status(401).send('Incorrect username or password');
45+
}
46+
});
47+
48+
this.app.get('/dashboard', (req, res) => {
49+
if (req.session.authenticated) {
50+
res.sendFile(__dirname + '/webadminpanel/dashboard.html');
51+
} else {
52+
res.redirect('/');
53+
}
54+
});
55+
56+
this.app.get('/connections.json', (req, res) => {
57+
if (req.session.authenticated) {
58+
let connectionsData = [];
59+
for (const key in this.connections) {
60+
if (Object.hasOwnProperty.call(this.connections, key)) {
61+
let connection = {
62+
key: key,
63+
nick: this.connections[key].nick,
64+
channelCount: this.instanceConnections.userChannelCount(key)
65+
};
66+
connectionsData.push(connection);
67+
}
68+
}
69+
let responseData = {
70+
connections: connectionsData,
71+
count: connectionsData.length,
72+
last_launch: global.LAST_LAUNCH,
73+
last_bug: global.LAST_BUG
74+
};
75+
76+
res.json(responseData);
77+
} else {
78+
res.redirect('/');
79+
}
80+
81+
});
82+
83+
this.app.get('/send', (req, res) => {
84+
const command = req.query.command;
85+
if (req.session.authenticated) {
86+
if (command === 'kill') {
87+
const key = req.query.key;
88+
let disconnected = this.instanceConnections.userKill(key);
89+
if(disconnected)
90+
res.send("disconnected");
91+
else
92+
res.send("none disconnected");
93+
}
94+
else {
95+
res.send("none");
96+
}
97+
} else {
98+
res.redirect('/');
99+
}
100+
101+
});
102+
}
103+
104+
setup404Error() {
105+
this.app.use((req, res, next) => {
106+
const errorMessage = "Sorry, the page you are looking for doesn't exist.";
107+
res.status(404).send(`
108+
<!DOCTYPE html>
109+
<html lang="en">
110+
<head>
111+
<meta charset="UTF-8">
112+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
113+
<title>Error 404</title>
114+
</head>
115+
<body>
116+
<h1>Error 404</h1>
117+
<p>${errorMessage}</p>
118+
</body>
119+
</html>
120+
`);
121+
});
122+
}
123+
}
124+
125+
126+
127+
module.exports = WebAdminPanel;

0 commit comments

Comments
 (0)