mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-03-31 06:24:02 -04:00
Implemented voucher types. Updated the README.md
This commit is contained in:
32
README.md
32
README.md
@@ -6,14 +6,14 @@ A small UniFi Voucher Site for simple voucher creation
|
||||
|
||||
## Structure
|
||||
- ES6 Javascript
|
||||
- SCSS
|
||||
- ExpressJS
|
||||
- Node UniFi
|
||||
- Webpack
|
||||
- Tailwind
|
||||
|
||||
## Development Usage
|
||||
- Install NodeJS 16.0 or higher.
|
||||
- Run `npm ci` in the root folder
|
||||
- Run `npm start` in the root folder
|
||||
- Run `npm start` & `npm run tailwind` in the root folder
|
||||
|
||||
Then open up your favorite browser and go to http://localhost:3000/
|
||||
|
||||
@@ -25,6 +25,32 @@ Then open up your favorite browser and go to http://localhost:3000/
|
||||
## Docker
|
||||
- Code from master is build by Docker Hub
|
||||
- Builds can be pulled by using this command: `docker pull glenndehaan/unifi-voucher-site`
|
||||
- An example docker compose file can be found below:
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: glenndehaan/unifi-voucher-site
|
||||
ports:
|
||||
- "8081:3000"
|
||||
environment:
|
||||
# The IP address to your UniFi OS Console
|
||||
UNIFI_IP: '192.168.1.1'
|
||||
# The port of your UniFi OS Console, this could be 443 or 8443
|
||||
UNIFI_PORT: 443
|
||||
# The username of a local UniFi OS account
|
||||
UNIFI_USERNAME: 'admin'
|
||||
# The password of a local UniFi OS account
|
||||
UNIFI_PASSWORD: 'password'
|
||||
# The UniFi Site ID
|
||||
UNIFI_SITE_ID: 'default'
|
||||
# The 'password' used to log in to this voucher portal
|
||||
SECURITY_CODE: '0000'
|
||||
# Voucher Types, format: expiration in minutes (required),single-use or multi-use vouchers value - '0' is for multi-use - '1' is for single-use (optional),upload speed limit in kbps (optional),download speed limit in kbps (optional),data transfer limit in MB (optional)
|
||||
# To skip a parameter just but nothing in between the comma's
|
||||
# After a voucher type add a semicolon, after the semicolon you can start a new voucher type
|
||||
VOUCHER_TYPES: '480,0,,,;'
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -11,3 +11,4 @@ services:
|
||||
UNIFI_PASSWORD: 'password'
|
||||
UNIFI_SITE_ID: 'default'
|
||||
SECURITY_CODE: '0000'
|
||||
VOUCHER_TYPES: '480,0,,,;'
|
||||
|
||||
15
modules/logo.js
Normal file
15
modules/logo.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Output the ascii logo
|
||||
*/
|
||||
module.exports = () => {
|
||||
console.log(` __ __ _ _______ _ __ __ `);
|
||||
console.log(` / / / /___ (_) ____(_) | | / /___ __ _______/ /_ ___ _____`);
|
||||
console.log(` / / / / __ \\/ / /_ / / | | / / __ \\/ / / / ___/ __ \\/ _ \\/ ___/`);
|
||||
console.log(`/ /_/ / / / / / __/ / / | |/ / /_/ / /_/ / /__/ / / / __/ / `);
|
||||
console.log(`\\____/_/ /_/_/_/ /_/ |___/\\____/\\__,_/\\___/_/ /_/\\___/_/ `);
|
||||
console.log('');
|
||||
console.log(' UniFi Voucher ');
|
||||
console.log(' By: Glenn de Haan ');
|
||||
console.log(' https://github.com/glenndehaan/unifi-voucher-site ');
|
||||
console.log('');
|
||||
}
|
||||
20
modules/types.js
Normal file
20
modules/types.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Returns an array or object of voucher type(s)
|
||||
*
|
||||
* @param string
|
||||
* @param single
|
||||
* @returns {*}
|
||||
*/
|
||||
module.exports = (string, single = false) => {
|
||||
if(single) {
|
||||
const match = string.match(/^(?<expiration>\d+)?,(?<usage>\d+)?,(?<upload>\d+)?,(?<download>\d+)?,(?<megabytes>\d+)?/);
|
||||
return match.groups.expiration ? {...match.groups, raw: string} : undefined;
|
||||
}
|
||||
|
||||
const types = string.split(';');
|
||||
|
||||
return types.filter(n => n).map((type) => {
|
||||
const match = type.match(/^(?<expiration>\d+)?,(?<usage>\d+)?,(?<upload>\d+)?,(?<download>\d+)?,(?<megabytes>\d+)?/);
|
||||
return match.groups.expiration ? {...match.groups, raw: type} : undefined;
|
||||
}).filter(n => n);
|
||||
}
|
||||
@@ -26,13 +26,14 @@ const controller = new unifi.Controller({host: config.unifi.ip, port: config.uni
|
||||
/**
|
||||
* Exports the UniFi voucher function
|
||||
*
|
||||
* @param type
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
module.exports = () => {
|
||||
module.exports = (type) => {
|
||||
return new Promise((resolve) => {
|
||||
controller.login(config.unifi.username, config.unifi.password).then(() => {
|
||||
controller.getSitesStats().then(() => {
|
||||
controller.createVouchers(480).then((voucher_data) => {
|
||||
controller.createVouchers(type.expiration, 1, type.usage === 1 ? 1 : 0, null, typeof type.upload !== "undefined" ? type.upload : null, typeof type.download !== "undefined" ? type.download : null, typeof type.megabytes !== "undefined" ? type.megabytes : null).then((voucher_data) => {
|
||||
controller.getVouchers(voucher_data[0].create_time).then((voucher_data_complete) => {
|
||||
const voucher = `${[voucher_data_complete[0].code.slice(0, 5), '-', voucher_data_complete[0].code.slice(5)].join('')}`;
|
||||
resolve(voucher);
|
||||
|
||||
37
server.js
37
server.js
@@ -9,12 +9,33 @@ const app = express();
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const logo = require('./modules/logo');
|
||||
const types = require('./modules/types');
|
||||
const unifi = require('./modules/unifi');
|
||||
|
||||
/**
|
||||
* Define global functions
|
||||
* Define global functions and variables
|
||||
*/
|
||||
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;
|
||||
const voucherTypes = types(process.env.VOUCHER_TYPES || '480,0,,,;');
|
||||
|
||||
/**
|
||||
* Output logo
|
||||
*/
|
||||
logo();
|
||||
|
||||
/**
|
||||
* Log voucher types
|
||||
*/
|
||||
console.log('[VoucherType] Loaded the following types:');
|
||||
voucherTypes.forEach((type, key) => {
|
||||
console.log(`[VoucherType][${key}] ${type.expiration} minutes, ${type.usage === '1' ? 'single-use' : 'multi-use'}${typeof type.upload === "undefined" && typeof type.download === "undefined" && typeof type.megabytes === "undefined" ? ', no limits' : `${typeof type.upload !== "undefined" ? `, upload bandwidth limit: ${type.upload} kb/s` : ''}${typeof type.download !== "undefined" ? `, download bandwidth limit: ${type.download} kb/s` : ''}${typeof type.megabytes !== "undefined" ? `, quota limit: ${type.megabytes} mb` : ''}`}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Log controller
|
||||
*/
|
||||
console.log(`[UniFi] Using Controller on: ${process.env.UNIFI_IP || '192.168.1.1'}:${process.env.UNIFI_PORT || 443} (Site ID: ${process.env.UNIFI_SITE_ID || 'default'})`);
|
||||
|
||||
/**
|
||||
* Trust proxy
|
||||
@@ -36,7 +57,7 @@ app.use(multer().none());
|
||||
* Request logger
|
||||
*/
|
||||
app.use((req, res, next) => {
|
||||
console.log(`[Web][REQUEST]: ${req.originalUrl}`);
|
||||
console.log(`[Web]: ${req.originalUrl}`);
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -57,7 +78,8 @@ app.get('/', (req, res) => {
|
||||
error_text: req.query.error || '',
|
||||
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||
app_header: timeHeader,
|
||||
sid: uuidv4()
|
||||
sid: uuidv4(),
|
||||
voucher_types: voucherTypes
|
||||
});
|
||||
});
|
||||
app.post('/', async (req, res) => {
|
||||
@@ -68,7 +90,7 @@ app.post('/', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
res.redirect(encodeURI(`/voucher?code=${req.body.password}`));
|
||||
res.redirect(encodeURI(`/voucher?code=${req.body.password}&type=${req.body['voucher-type']}`));
|
||||
});
|
||||
app.get('/voucher', async (req, res) => {
|
||||
if(req.query.code !== (process.env.SECURITY_CODE || "0000")) {
|
||||
@@ -78,9 +100,7 @@ app.get('/voucher', async (req, res) => {
|
||||
|
||||
const hour = new Date().getHours();
|
||||
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||
const voucherCode = await unifi();
|
||||
|
||||
console.log(voucherCode);
|
||||
const voucherCode = await unifi(types(req.query.type, true));
|
||||
|
||||
res.render('voucher', {
|
||||
error: typeof req.query.error === 'string' && req.query.error !== '',
|
||||
@@ -88,6 +108,7 @@ app.get('/voucher', async (req, res) => {
|
||||
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||
app_header: timeHeader,
|
||||
code: req.query.code,
|
||||
type: req.query.type,
|
||||
voucher_code: voucherCode,
|
||||
sid: uuidv4()
|
||||
});
|
||||
@@ -110,5 +131,5 @@ app.disable('x-powered-by');
|
||||
* Start listening on port
|
||||
*/
|
||||
app.listen(3000, '0.0.0.0', () => {
|
||||
console.log(`App is running on: 0.0.0.0:3000`);
|
||||
console.log(`[App] Running on: 0.0.0.0:3000`);
|
||||
});
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
<div class="w-full flex flex-wrap">
|
||||
<div class="w-full md:w-1/2 flex flex-col">
|
||||
<div class="flex justify-center md:justify-start pt-12 md:pl-12 md:-mb-24">
|
||||
<img class="h-20 w-20" src="/images/unifi-icon.png"/>
|
||||
<a href="/" title="Homepage">
|
||||
<img class="h-20 w-20" alt="UniFi Logo" src="/images/unifi-icon.png"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center md:justify-start my-auto pt-8 md:pt-0 px-8 md:px-24 lg:px-32">
|
||||
@@ -49,6 +51,22 @@
|
||||
<input type="password" id="password" name="password" placeholder="Password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100" required>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col pt-4">
|
||||
<label for="voucher-type" class="text-lg">Voucher Type</label>
|
||||
<div class="relative inline-block w-full">
|
||||
<select id="voucher-type" name="voucher-type" class="shadow appearance-none border rounded w-full py-2 px-3 pr-8 text-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100" required>
|
||||
<% voucher_types.forEach((type) => { %>
|
||||
<option value="<%= type.raw %>"><%= type.expiration %> minutes, <%= type.usage === '1' ? 'single-use' : 'multi-use' %><%= typeof type.upload === "undefined" && typeof type.download === "undefined" && typeof type.megabytes === "undefined" ? ', no limits' : '' %><%= typeof type.upload !== "undefined" ? `, upload bandwidth limit: ${type.upload} kb/s` : '' %><%= typeof type.download !== "undefined" ? `, download bandwidth limit: ${type.download} kb/s` : '' %><%= typeof type.megabytes !== "undefined" ? `, quota limit: ${type.megabytes} mb` : '' %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 mt-1 pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 fill-current" viewBox="0 0 20 20">
|
||||
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" fillRule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Create Voucher" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 mt-8 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
<div class="w-full flex flex-wrap">
|
||||
<div class="w-full md:w-1/2 flex flex-col">
|
||||
<div class="flex justify-center md:justify-start pt-12 md:pl-12 md:-mb-24">
|
||||
<img class="h-20 w-20" src="/images/unifi-icon.png"/>
|
||||
<a href="/" title="Homepage">
|
||||
<img class="h-20 w-20" alt="UniFi Logo" src="/images/unifi-icon.png"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center md:justify-start my-auto pt-8 md:pt-0 px-8 md:px-24 lg:px-32">
|
||||
@@ -45,6 +47,7 @@
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="password" name="password" value="<%= code %>"/>
|
||||
<input type="hidden" id="voucher-type" name="voucher-type" value="<%= type %>"/>
|
||||
<input type="submit" value="Create Another Voucher" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 mt-8 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user