Rebuild application for UniFi OS, Network v7
32
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# When the workflow succeeds the package will be published to docker hub
|
||||||
|
|
||||||
|
name: Publish Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: auth/.
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6
|
||||||
|
push: true
|
||||||
|
tags: glenndehaan/unifi-voucher-site:latest
|
||||||
33
Dockerfile
@@ -1,19 +1,40 @@
|
|||||||
FROM alpine:3.13
|
#
|
||||||
|
# Define OS
|
||||||
|
#
|
||||||
|
FROM alpine:3.16
|
||||||
|
|
||||||
|
#
|
||||||
|
# Basic OS management
|
||||||
|
#
|
||||||
|
|
||||||
# Install packages
|
# Install packages
|
||||||
RUN apk add --no-cache nginx nodejs npm
|
RUN apk add --no-cache nodejs npm
|
||||||
|
|
||||||
|
#
|
||||||
|
# Require app
|
||||||
|
#
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /app
|
||||||
|
|
||||||
# Bundle app source
|
# Bundle app source
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
# Create production build
|
# Create production build
|
||||||
RUN npm ci --only=production && npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
#
|
||||||
|
# Setup app
|
||||||
|
#
|
||||||
|
|
||||||
# Expose app
|
# Expose app
|
||||||
EXPOSE 3001
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Set node env
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
# Run app
|
# Run app
|
||||||
CMD ["node", "/usr/src/app/app/server.js"]
|
CMD ["node", "/app/server.js"]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A small UniFi Voucher Site for simple voucher creation
|
A small UniFi Voucher Site for simple voucher creation
|
||||||
|
|
||||||
[](https://hub.docker.com/r/glenndehaan/unifi-voucher-site) [](https://hub.docker.com/r/glenndehaan/unifi-voucher-site)
|
[](https://hub.docker.com/r/glenndehaan/unifi-voucher-site)
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
- ES6 Javascript
|
- ES6 Javascript
|
||||||
@@ -11,14 +11,14 @@ A small UniFi Voucher Site for simple voucher creation
|
|||||||
- Webpack
|
- Webpack
|
||||||
|
|
||||||
## Development Usage
|
## Development Usage
|
||||||
- Install NodeJS 8.0 or higher.
|
- Install NodeJS 16.0 or higher.
|
||||||
- Run `npm ci` in the root folder
|
- Run `npm ci` in the root folder
|
||||||
- Run `npm start` in the root folder
|
- Run `npm start` in the root folder
|
||||||
|
|
||||||
Then open up your favorite browser and go to http://localhost:3001/
|
Then open up your favorite browser and go to http://localhost:3000/
|
||||||
|
|
||||||
## Build Usage
|
## Build Usage
|
||||||
- Install NodeJS 8.0 or higher.
|
- Install NodeJS 16.0 or higher.
|
||||||
- Run `npm ci` in the root folder
|
- Run `npm ci` in the root folder
|
||||||
- Run `npm run build` in the root folder
|
- Run `npm run build` in the root folder
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
const webpack = require('webpack');
|
|
||||||
const path = require('path');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
|
|
||||||
|
|
||||||
//Get path so every environment works
|
|
||||||
const projectPath = path.resolve(__dirname);
|
|
||||||
|
|
||||||
//Environment check depending on call from
|
|
||||||
const env = process.env.NODE_ENV || 'development';
|
|
||||||
|
|
||||||
//Define all the global config for both production & dev
|
|
||||||
module.exports = {
|
|
||||||
entry: {
|
|
||||||
main: [
|
|
||||||
projectPath + '/../public/js/main.js',
|
|
||||||
projectPath + '/../public/scss/style.scss'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: projectPath + '/../public/dist/',
|
|
||||||
filename: '[name].[fullhash:6].js',
|
|
||||||
publicPath: ''
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
presets: [
|
|
||||||
require.resolve('@babel/preset-env')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [
|
|
||||||
MiniCssExtractPlugin.loader,
|
|
||||||
'css-loader?url=false',
|
|
||||||
'sass-loader'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env': {
|
|
||||||
NODE_ENV: JSON.stringify(env)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: '[name].[fullhash:6].css'
|
|
||||||
}),
|
|
||||||
new WebpackManifestPlugin({
|
|
||||||
fileName: 'rev-manifest.json'
|
|
||||||
})
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js'],
|
|
||||||
modules: [path.join(__dirname, '../node_modules')]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* General config
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
application: {
|
|
||||||
name: "UniFi Voucher",
|
|
||||||
env: " (local)",
|
|
||||||
basePath: "/",
|
|
||||||
port: 3001,
|
|
||||||
bind: "0.0.0.0"
|
|
||||||
},
|
|
||||||
security: {
|
|
||||||
code: process.env.SECURITY_CODE || "0000" // <- Only 4 digits
|
|
||||||
},
|
|
||||||
unifi: {
|
|
||||||
ip: process.env.UNIFI_IP || '192.168.1.1',
|
|
||||||
port: process.env.UNIFI_PORT || 8443,
|
|
||||||
username: process.env.UNIFI_USERNAME || 'admin',
|
|
||||||
password: process.env.UNIFI_PASSWORD || 'password',
|
|
||||||
siteID: process.env.UNIFI_SITE_ID || 'default'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
const config = require("../config/config");
|
|
||||||
const assets = require("../modules/Assets");
|
|
||||||
|
|
||||||
class BaseController {
|
|
||||||
constructor() {
|
|
||||||
this.baseConfig = {
|
|
||||||
config: config,
|
|
||||||
protocol: '',
|
|
||||||
hostname: '',
|
|
||||||
baseUrl: '',
|
|
||||||
assets: {
|
|
||||||
js: false,
|
|
||||||
css: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the complete config base + page specific
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param pageSpecificConfig
|
|
||||||
*/
|
|
||||||
mergePageConfig(request, pageSpecificConfig) {
|
|
||||||
const manifest = assets();
|
|
||||||
|
|
||||||
this.baseConfig.hostname = request.get('host');
|
|
||||||
this.baseConfig.protocol = request.protocol;
|
|
||||||
this.baseConfig.baseUrl = `${request.protocol}://${request.get('host')}${config.application.basePath}`;
|
|
||||||
|
|
||||||
this.baseConfig.assets.js = manifest["main.js"];
|
|
||||||
this.baseConfig.assets.css = manifest["main.css"];
|
|
||||||
|
|
||||||
return Object.assign(this.baseConfig, pageSpecificConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = BaseController;
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
const baseController = require('./BaseController');
|
|
||||||
|
|
||||||
class IndexController extends baseController {
|
|
||||||
/**
|
|
||||||
* Renders the Home page
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
*/
|
|
||||||
indexAction(req, res) {
|
|
||||||
res.render('index', this.mergePageConfig(req, {
|
|
||||||
template: 'index/index',
|
|
||||||
pageTitle: 'Home'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the 404 page
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
*/
|
|
||||||
notFoundAction(req, res) {
|
|
||||||
res.render('index', this.mergePageConfig(req, {
|
|
||||||
template: 'general/notfound',
|
|
||||||
pageTitle: 'Not Found'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the old browser page (Fallback page)
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
*/
|
|
||||||
oldBrowserAction(req, res) {
|
|
||||||
res.render('index', this.mergePageConfig(req, {
|
|
||||||
template: 'general/oldbrowser',
|
|
||||||
pageTitle: 'Old Browser'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the sitemap.xml
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @param routes
|
|
||||||
*/
|
|
||||||
siteMapAction(req, res, routes) {
|
|
||||||
res.type("application/xml");
|
|
||||||
res.render('general/sitemap', this.mergePageConfig(req, {
|
|
||||||
template: false,
|
|
||||||
pageTitle: false,
|
|
||||||
routes: routes
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the robots.txt
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
*/
|
|
||||||
robotsAction(req, res) {
|
|
||||||
res.type("text/plain");
|
|
||||||
res.render('general/robots', this.mergePageConfig(req, {
|
|
||||||
template: false,
|
|
||||||
pageTitle: false
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new IndexController();
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Import vendor modules
|
|
||||||
*/
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define public path
|
|
||||||
*/
|
|
||||||
const path = `${__dirname}/../../public/dist`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the manifest
|
|
||||||
*
|
|
||||||
* @return {any}
|
|
||||||
*/
|
|
||||||
module.exports = () => {
|
|
||||||
return JSON.parse(fs.existsSync(path) ? fs.readFileSync(`${path}/rev-manifest.json`) : "{}");
|
|
||||||
};
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
/**
|
|
||||||
* Import vendor packages
|
|
||||||
*/
|
|
||||||
const { v4: uuidv4 } = require('uuid');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import own packages
|
|
||||||
*/
|
|
||||||
const config = require('../config/config');
|
|
||||||
const unifi = require('./UniFi');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket
|
|
||||||
*/
|
|
||||||
class Socket {
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @param server
|
|
||||||
*/
|
|
||||||
constructor(server) {
|
|
||||||
this.io = require('socket.io')(server);
|
|
||||||
this.authenticatedUsers = [];
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
|
|
||||||
// Cleanup after 30 minutes
|
|
||||||
setInterval(() => this.cleanup(), 30 * 60 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init the socket connection
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.io.on('connection', (socket) => {
|
|
||||||
/**
|
|
||||||
* Triggered when a socket disconnects
|
|
||||||
*/
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
console.log(`[SOCKET] Client disconnected! ID: ${socket.id}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client requests a uuid
|
|
||||||
*/
|
|
||||||
socket.on('uuid', () => {
|
|
||||||
const uuid = uuidv4();
|
|
||||||
this.authenticatedUsers[uuid] = false;
|
|
||||||
|
|
||||||
socket.emit('uuid', {
|
|
||||||
uuid: uuid
|
|
||||||
});
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client requested a UUID! UUID: ${uuid}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client auth check
|
|
||||||
*/
|
|
||||||
socket.on('auth', (data) => {
|
|
||||||
if(typeof this.authenticatedUsers[data.uuid] !== "undefined") {
|
|
||||||
if(config.security.code === data.code) {
|
|
||||||
this.authenticatedUsers[data.uuid] = true;
|
|
||||||
socket.emit('auth', {
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client auth: OK`);
|
|
||||||
} else {
|
|
||||||
socket.emit('auth', {
|
|
||||||
success: false
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client auth: FAILED. Invalid code!`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
socket.emit('auth', {
|
|
||||||
success: false
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client auth: FAILED. Invalid UUID!`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create voucher method
|
|
||||||
*/
|
|
||||||
socket.on('voucher', (data) => {
|
|
||||||
if(typeof this.authenticatedUsers[data.uuid] !== "undefined") {
|
|
||||||
if(this.authenticatedUsers[data.uuid]) {
|
|
||||||
unifi((voucher) => {
|
|
||||||
if(voucher !== false) {
|
|
||||||
socket.emit('voucher', {
|
|
||||||
success: true,
|
|
||||||
voucher: voucher
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client voucher: OK. Voucher: ${voucher}!`);
|
|
||||||
} else {
|
|
||||||
socket.emit('voucher', {
|
|
||||||
success: false,
|
|
||||||
voucher: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client voucher: FAILED. UniFi Error!`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
socket.emit('voucher', {
|
|
||||||
success: false,
|
|
||||||
voucher: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client voucher: FAILED. Not authenticated!`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
socket.emit('voucher', {
|
|
||||||
success: false,
|
|
||||||
voucher: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET][${socket.id}] Client voucher: FAILED. Invalid UUID!`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[SOCKET] New client connected! ID: ${socket.id}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start listening on the right port/host for the Socket.IO server
|
|
||||||
*/
|
|
||||||
console.log('[SYSTEM] Socket.IO started !');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the authenticated users after 30 minutes
|
|
||||||
*/
|
|
||||||
cleanup() {
|
|
||||||
this.authenticatedUsers = [];
|
|
||||||
console.log('[SYSTEM] Cleanup authenticatedUsers complete!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export the socket class
|
|
||||||
*
|
|
||||||
* @type {Socket}
|
|
||||||
*/
|
|
||||||
module.exports = Socket;
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/**
|
|
||||||
* Import vendor modules
|
|
||||||
*/
|
|
||||||
const unifi = require('node-unifi');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import own modules
|
|
||||||
*/
|
|
||||||
const config = require('../config/config');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new UniFi controller object
|
|
||||||
*
|
|
||||||
* @type {Controller}
|
|
||||||
*/
|
|
||||||
const controller = new unifi.Controller(config.unifi.ip, config.unifi.port);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exports the UniFi voucher function
|
|
||||||
*
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
module.exports = (callback) => {
|
|
||||||
controller.login(config.unifi.username, config.unifi.password, (err) => {
|
|
||||||
if(err) {
|
|
||||||
console.log(`[UNIFI] Error: ${err}`);
|
|
||||||
callback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CREATE VOUCHER
|
|
||||||
controller.createVouchers(config.unifi.siteID, 480, (err, voucher_data) => {
|
|
||||||
if(err) {
|
|
||||||
console.log(`[UNIFI] Error: ${err}`);
|
|
||||||
callback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET VOUCHER CODE
|
|
||||||
controller.getVouchers(config.unifi.siteID, (err, voucher_data_complete) => {
|
|
||||||
if(err) {
|
|
||||||
console.log(`[UNIFI] Error: ${err}`);
|
|
||||||
callback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const voucher = `${[voucher_data_complete[0][0].code.slice(0, 5), '-', voucher_data_complete[0][0].code.slice(5)].join('')}`;
|
|
||||||
callback(voucher);
|
|
||||||
|
|
||||||
controller.logout();
|
|
||||||
}, voucher_data[0][0].create_time);
|
|
||||||
}, 1, 1);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/**
|
|
||||||
* Import base packages
|
|
||||||
*/
|
|
||||||
const express = require('express');
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import own packages
|
|
||||||
*/
|
|
||||||
const config = require('./config/config');
|
|
||||||
const socket = require('./modules/Socket');
|
|
||||||
const IndexController = require('./controllers/IndexController');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set template engine
|
|
||||||
*/
|
|
||||||
app.set('view engine', 'ejs');
|
|
||||||
app.set('views', `${__dirname}/views`);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trust proxy
|
|
||||||
*/
|
|
||||||
app.enable('trust proxy');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serve static public dir
|
|
||||||
*/
|
|
||||||
app.use(express.static(`${__dirname}/../public`));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure routes
|
|
||||||
*/
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
IndexController.indexAction(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable powered by header for security reasons
|
|
||||||
*/
|
|
||||||
app.disable('x-powered-by');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start listening on port
|
|
||||||
*/
|
|
||||||
const server = app.listen(config.application.port, config.application.bind, () => {
|
|
||||||
console.log(`[NODE] App is running on: ${config.application.bind}:${config.application.port}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init socket connection
|
|
||||||
*/
|
|
||||||
global.socket = new socket(server);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle nodemon shutdown
|
|
||||||
*/
|
|
||||||
process.once('SIGUSR2', () => {
|
|
||||||
server.close(() => {
|
|
||||||
console.log(`[NODE] Express exited! Port ${config.application.port} is now free!`);
|
|
||||||
process.kill(process.pid, 'SIGUSR2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<%- include('partials/head') %>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%- include('partials/preloader') %>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<%- include(template) %>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<%- include('partials/footer') %>
|
|
||||||
<script>
|
|
||||||
var expressConfig = {protocol: '<%= protocol %>', hostname: '<%= hostname %>', baseUrl: '<%= baseUrl %>'};
|
|
||||||
</script>
|
|
||||||
<script src="/dist/<%= assets.js %>" type="application/javascript"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<div id="particles"></div>
|
|
||||||
<div id="container">
|
|
||||||
<div id="center">
|
|
||||||
<img src="/images/unifi-icon.png" alt="UniFi Icon" width="100px" height="100px" /><br/>
|
|
||||||
<h2><%= config.application.name %></h2>
|
|
||||||
|
|
||||||
<div id="sign-in">
|
|
||||||
<h3>Access Code</h3>
|
|
||||||
<input type="number" class="access-fields" title="number-1" maxlength="1">
|
|
||||||
<input type="number" class="access-fields" title="number-2" maxlength="1">
|
|
||||||
<input type="number" class="access-fields" title="number-3" maxlength="1">
|
|
||||||
<input type="number" class="access-fields" title="number-4" maxlength="1">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="voucher" class="hidden">
|
|
||||||
<h3>Voucher:</h3>
|
|
||||||
<h4>XXXXX-XXXXX</h4>
|
|
||||||
<p>
|
|
||||||
This voucher is valid for one day on one device.
|
|
||||||
</p>
|
|
||||||
<button>
|
|
||||||
Generate
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="error" class="hidden">
|
|
||||||
<h3>Woops</h3>
|
|
||||||
<p>
|
|
||||||
We are having troubles connecting to the server... please try again later.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<footer>
|
|
||||||
<p>UniFi Voucher | <a href="https://github.com/glenndehaan/unifi-voucher-site" rel="noreferrer noopener">GitHub</a></p>
|
|
||||||
</footer>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<title><%= pageTitle %> | <%= config.application.name %> <%= config.application.env %></title>
|
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
|
||||||
|
|
||||||
<meta name="description" content="UniFi Voucher">
|
|
||||||
<meta name="author" content="Glenn de Haan">
|
|
||||||
|
|
||||||
<meta property="og:title" content="<%= config.application.name %>"/>
|
|
||||||
<meta property="og:type" content="website"/>
|
|
||||||
<meta property="og:url" content="<%= baseUrl %>"/>
|
|
||||||
<meta property="og:description" content="UniFi Voucher"/>
|
|
||||||
|
|
||||||
<link rel="alternate" href="<%= baseUrl %>" hreflang="en"/>
|
|
||||||
<link rel="canonical" href="<%= baseUrl %>"/>
|
|
||||||
<script type='application/ld+json'>
|
|
||||||
{
|
|
||||||
"@context": "http://schema.org",
|
|
||||||
"@type": "WebSite",
|
|
||||||
"name": "<%= config.application.name %>",
|
|
||||||
"url": "<%= baseUrl %>"
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link rel="manifest" href="/manifest.json">
|
|
||||||
<link rel="shortcut icon" href="/images/favicon.ico">
|
|
||||||
<link rel="apple-touch-icon" href="/images/icon/logo_256x256.png">
|
|
||||||
|
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
||||||
<meta name="theme-color" content="#1875b6">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#preloader {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #1875b6;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 42%;
|
|
||||||
|
|
||||||
width: 75px;
|
|
||||||
height: 75px;
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
border-radius: 100%;
|
|
||||||
-webkit-animation: scaleout 1.0s infinite ease-in-out;
|
|
||||||
animation: scaleout 1.0s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes scaleout {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: scale(0)
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: scale(1.0);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scaleout {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: scale(0);
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: scale(1.0);
|
|
||||||
transform: scale(1.0);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<link href="/dist/<%= assets.css %>" rel="stylesheet" type="text/css"/>
|
|
||||||
<link rel="preload" href="/fonts/text-security-disc.woff" as="font">
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<div id="preloader">
|
|
||||||
<div class="loader"></div>
|
|
||||||
</div>
|
|
||||||
28
css/style.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
@apply mt-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To show the lines on right
|
||||||
|
and left sides of the text */
|
||||||
|
.divider::after,
|
||||||
|
.divider::before {
|
||||||
|
content: "";
|
||||||
|
flex: 1;
|
||||||
|
@apply border;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Space on left and right sides of text */
|
||||||
|
.divider:not(:empty)::before {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider:not(:empty)::after {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ services:
|
|||||||
app:
|
app:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "8081:3001"
|
- "8081:3000"
|
||||||
environment:
|
environment:
|
||||||
UNIFI_IP: '192.168.1.1'
|
UNIFI_IP: '192.168.1.1'
|
||||||
UNIFI_PORT: 8443
|
UNIFI_PORT: 443
|
||||||
UNIFI_USERNAME: 'admin'
|
UNIFI_USERNAME: 'admin'
|
||||||
UNIFI_PASSWORD: 'password'
|
UNIFI_PASSWORD: 'password'
|
||||||
UNIFI_SITE_ID: 'default'
|
UNIFI_SITE_ID: 'default'
|
||||||
|
|||||||
56
modules/unifi.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Import vendor modules
|
||||||
|
*/
|
||||||
|
const unifi = require('node-unifi');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
unifi: {
|
||||||
|
ip: process.env.UNIFI_IP || '192.168.1.1',
|
||||||
|
port: process.env.UNIFI_PORT || 443,
|
||||||
|
username: process.env.UNIFI_USERNAME || 'admin',
|
||||||
|
password: process.env.UNIFI_PASSWORD || 'password',
|
||||||
|
siteID: process.env.UNIFI_SITE_ID || 'default'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new UniFi controller object
|
||||||
|
*
|
||||||
|
* @type {Controller}
|
||||||
|
*/
|
||||||
|
const controller = new unifi.Controller({host: config.unifi.ip, port: config.unifi.port, site: config.unifi.siteID, sslverify: false});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the UniFi voucher function
|
||||||
|
*
|
||||||
|
* @returns {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
module.exports = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
controller.login(config.unifi.username, config.unifi.password).then(() => {
|
||||||
|
controller.getSitesStats().then(() => {
|
||||||
|
controller.createVouchers(480).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);
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
8442
package-lock.json
generated
75
package.json
@@ -4,76 +4,25 @@
|
|||||||
"description": "NPM packages for unifi-voucher-site",
|
"description": "NPM packages for unifi-voucher-site",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "concurrently --kill-others 'npm run frontend' 'npm run backend:dev'",
|
"start": "node server.js",
|
||||||
"backend:start": "node ./app/server.js",
|
"dev": "nodemon --watch . --ignore db.json --exec 'node server.js'",
|
||||||
"backend:dev": "nodemon -L --watch ./app ./app/server.js",
|
"tailwind": "tailwindcss -i ./css/style.css -o ./public/dist/style.css --watch",
|
||||||
"prebuild": "rimraf ./public/dist",
|
"build": "tailwindcss -i ./css/style.css -o ./public/dist/style.css --minify"
|
||||||
"build": "cross-env NODE_ENV=production webpack --mode production --config ./_scripts/webpack.config.js",
|
|
||||||
"prefrontend": "rimraf ./public/dist",
|
|
||||||
"frontend": "webpack --watch --mode development --config ./_scripts/webpack.config.js",
|
|
||||||
"lint": "eslint -c ./package.json ./app ./public/js"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=16.0.0"
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 6,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-console": 0,
|
|
||||||
"comma-dangle": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
4
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"site": false,
|
|
||||||
"expressConfig": false
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"author": "Glenn de Haan",
|
"author": "Glenn de Haan",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.12.10",
|
"ejs": "^3.1.6",
|
||||||
"@babel/preset-env": "^7.12.11",
|
"express": "^4.17.3",
|
||||||
"animejs": "^3.2.1",
|
"multer": "^1.4.4",
|
||||||
"babel-core": "^6.26.3",
|
"node-unifi": "^2.1.0",
|
||||||
"babel-loader": "^8.2.2",
|
"tailwindcss": "^3.0.23",
|
||||||
"cross-env": "^7.0.3",
|
"uuid": "^8.3.2"
|
||||||
"css-loader": "^5.0.1",
|
|
||||||
"ejs": "^3.1.5",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"gsap": "^3.6.0",
|
|
||||||
"mini-css-extract-plugin": "^1.3.4",
|
|
||||||
"mitt": "^2.1.0",
|
|
||||||
"node-sass": "^5.0.0",
|
|
||||||
"node-unifi": "^1.3.8",
|
|
||||||
"particles.js": "^2.0.0",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"sass-loader": "^10.1.1",
|
|
||||||
"socket.io": "^3.1.0",
|
|
||||||
"socket.io-client": "^3.1.0",
|
|
||||||
"uuid": "^8.3.2",
|
|
||||||
"webpack": "^5.14.0",
|
|
||||||
"webpack-cli": "^4.3.1",
|
|
||||||
"webpack-manifest-plugin": "^3.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^5.3.0",
|
"nodemon": "^2.0.15"
|
||||||
"eslint": "^7.17.0",
|
|
||||||
"nodemon": "^2.0.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 182 KiB |
BIN
public/images/bg-1.jpg
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
public/images/bg-2.jpg
Normal file
|
After Width: | Height: | Size: 548 KiB |
BIN
public/images/bg-3.jpg
Normal file
|
After Width: | Height: | Size: 734 KiB |
BIN
public/images/bg-4.jpg
Normal file
|
After Width: | Height: | Size: 626 KiB |
BIN
public/images/bg-5.jpg
Normal file
|
After Width: | Height: | Size: 584 KiB |
BIN
public/images/bg-6.jpg
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
public/images/bg-7.jpg
Normal file
|
After Width: | Height: | Size: 509 KiB |
BIN
public/images/bg-8.jpg
Normal file
|
After Width: | Height: | Size: 468 KiB |
BIN
public/images/bg-9.jpg
Normal file
|
After Width: | Height: | Size: 575 KiB |
@@ -1,48 +0,0 @@
|
|||||||
import mitt from 'mitt';
|
|
||||||
import settings from './settings';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create global
|
|
||||||
*/
|
|
||||||
window.site = {};
|
|
||||||
site.modules = [];
|
|
||||||
site.events = mitt();
|
|
||||||
site.settings = settings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the app
|
|
||||||
*/
|
|
||||||
function initialize() {
|
|
||||||
site.html = document.querySelector('html');
|
|
||||||
site.body = document.querySelector('body');
|
|
||||||
|
|
||||||
console.log('JS Start');
|
|
||||||
|
|
||||||
initializeModules();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the modules with all the components
|
|
||||||
*/
|
|
||||||
function initializeModules() {
|
|
||||||
for(let modulesItem = 0; modulesItem < settings.modules.length; modulesItem++) {
|
|
||||||
const module = settings.modules[modulesItem];
|
|
||||||
|
|
||||||
const moduleClass = require(`./modules/${module.group}/${module.module}`).default;
|
|
||||||
const domElements = document.querySelectorAll(module.el);
|
|
||||||
|
|
||||||
if (typeof moduleClass !== 'undefined' && domElements.length > 0) {
|
|
||||||
if(module.global) {
|
|
||||||
site.modules[module.module] = new moduleClass({
|
|
||||||
el: domElements
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
new moduleClass({
|
|
||||||
el: domElements
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", initialize);
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
import io from 'socket.io-client';
|
|
||||||
import animejs from 'animejs';
|
|
||||||
|
|
||||||
export default class Socket {
|
|
||||||
constructor({el}) {
|
|
||||||
this.el = el;
|
|
||||||
this.socket = false;
|
|
||||||
this.uuid = false;
|
|
||||||
this.userSignedIn = false;
|
|
||||||
|
|
||||||
this.mainContainer = document.querySelector("#container");
|
|
||||||
this.signInContainer = document.querySelector("#sign-in");
|
|
||||||
this.voucherContainer = document.querySelector("#voucher");
|
|
||||||
this.errorContainer = document.querySelector("#error");
|
|
||||||
this.preloader = document.querySelector("#preloader");
|
|
||||||
|
|
||||||
document.querySelector("#voucher button").addEventListener("click", () => this.requestVoucher());
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the socket connection
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.socket = io.connect(`//${expressConfig.hostname}`);
|
|
||||||
|
|
||||||
this.socket.on('connect', () => this.connect());
|
|
||||||
this.socket.on('disconnect', () => this.disconnect());
|
|
||||||
this.socket.on('error', () => this.error());
|
|
||||||
|
|
||||||
this.socket.on('uuid', (data) => this.setUuid(data));
|
|
||||||
this.socket.on('auth', (data) => this.auth(data));
|
|
||||||
this.socket.on('voucher', (data) => this.voucher(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the socket is connected
|
|
||||||
*/
|
|
||||||
connect() {
|
|
||||||
console.log('[SOCKET] Connected!');
|
|
||||||
|
|
||||||
if (!this.uuid) {
|
|
||||||
console.log('[SOCKET] Requesting UUID!');
|
|
||||||
this.socket.emit('uuid');
|
|
||||||
}
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [1, 0],
|
|
||||||
easing: 'linear',
|
|
||||||
complete: () => {
|
|
||||||
if (!this.userSignedIn) {
|
|
||||||
this.signInContainer.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
this.voucherContainer.classList.remove("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.errorContainer.classList.add("hidden");
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [0, 1],
|
|
||||||
easing: 'linear',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the socket disconnects
|
|
||||||
*/
|
|
||||||
disconnect() {
|
|
||||||
console.log('[SOCKET] Disconnected!');
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [1, 0],
|
|
||||||
easing: 'linear',
|
|
||||||
complete: () => {
|
|
||||||
this.signInContainer.classList.add("hidden");
|
|
||||||
this.voucherContainer.classList.add("hidden");
|
|
||||||
this.errorContainer.classList.remove("hidden");
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [0, 1],
|
|
||||||
easing: 'linear'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the socket error's
|
|
||||||
*/
|
|
||||||
error() {
|
|
||||||
console.log('[SOCKET] Error!');
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [1, 0],
|
|
||||||
easing: 'linear',
|
|
||||||
complete: () => {
|
|
||||||
this.signInContainer.classList.add("hidden");
|
|
||||||
this.voucherContainer.classList.add("hidden");
|
|
||||||
this.errorContainer.classList.remove("hidden");
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [0, 1],
|
|
||||||
easing: 'linear'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event for getting the UUID
|
|
||||||
*/
|
|
||||||
setUuid(data) {
|
|
||||||
this.uuid = data.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event for getting the auth result
|
|
||||||
*/
|
|
||||||
auth(data) {
|
|
||||||
if (data.success) {
|
|
||||||
this.userSignedIn = true;
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [1, 0],
|
|
||||||
easing: 'linear',
|
|
||||||
complete: () => {
|
|
||||||
site.modules.Signin.resetForm();
|
|
||||||
this.signInContainer.classList.add("hidden");
|
|
||||||
this.voucherContainer.classList.remove("hidden");
|
|
||||||
|
|
||||||
animejs({
|
|
||||||
targets: this.mainContainer,
|
|
||||||
duration: 250,
|
|
||||||
opacity: [0, 1],
|
|
||||||
easing: 'linear'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
site.modules.Signin.invalidCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a guest voucher
|
|
||||||
*/
|
|
||||||
requestVoucher() {
|
|
||||||
if (this.userSignedIn) {
|
|
||||||
this.preloader.classList.remove("completed");
|
|
||||||
|
|
||||||
this.socket.emit('voucher', {
|
|
||||||
uuid: this.uuid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the voucher
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
voucher(data) {
|
|
||||||
this.preloader.classList.add("completed");
|
|
||||||
if (data.success) {
|
|
||||||
this.voucherContainer.querySelector("h4").innerHTML = data.voucher;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export default class Index {
|
|
||||||
constructor({el}) {
|
|
||||||
this.el = el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import "particles.js";
|
|
||||||
|
|
||||||
export default class Particles {
|
|
||||||
constructor({el}) {
|
|
||||||
this.el = el;
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
this.preloader = document.querySelector("#preloader");
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
window.particlesJS.load('particles', "/json/particles.json", () => {
|
|
||||||
console.log('particles.js Loaded!');
|
|
||||||
this.preloader.classList.add("completed");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
export default class Signin {
|
|
||||||
constructor({el}) {
|
|
||||||
this.el = el;
|
|
||||||
|
|
||||||
this.fields = document.querySelectorAll(".access-fields");
|
|
||||||
for(let field = 0; field < this.fields.length; field++) {
|
|
||||||
this.fields[field].addEventListener("keyup", () => this.moveOnMax(this.fields[field], field));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fields[0].addEventListener("keydown", () => this.removeError());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move focus to next field
|
|
||||||
*
|
|
||||||
* @param field
|
|
||||||
* @param currentId
|
|
||||||
*/
|
|
||||||
moveOnMax(field, currentId) {
|
|
||||||
if (field.value.length === 1) {
|
|
||||||
if(currentId < (this.fields.length - 1)) {
|
|
||||||
const nextField = currentId + 1;
|
|
||||||
this.fields[nextField].focus();
|
|
||||||
} else {
|
|
||||||
this.validate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the code is correct
|
|
||||||
*/
|
|
||||||
validate() {
|
|
||||||
site.modules.Socket.socket.emit('auth', {
|
|
||||||
uuid: site.modules.Socket.uuid,
|
|
||||||
code: `${this.fields[0].value}${this.fields[1].value}${this.fields[2].value}${this.fields[3].value}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the error state from the inputs
|
|
||||||
*/
|
|
||||||
removeError() {
|
|
||||||
for(let field = 0; field < this.fields.length; field++) {
|
|
||||||
this.fields[field].classList.remove("error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the invalid state to the inputs
|
|
||||||
*/
|
|
||||||
invalidCode() {
|
|
||||||
this.fields[0].focus();
|
|
||||||
|
|
||||||
for(let field = 0; field < this.fields.length; field++) {
|
|
||||||
this.fields[field].classList.add("error");
|
|
||||||
this.fields[field].value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the inputs
|
|
||||||
*/
|
|
||||||
resetForm() {
|
|
||||||
for(let field = 0; field < this.fields.length; field++) {
|
|
||||||
this.fields[field].value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
export default {
|
|
||||||
modules: [
|
|
||||||
{
|
|
||||||
module: "Socket",
|
|
||||||
group: "default",
|
|
||||||
el: "main",
|
|
||||||
global: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
module: "Index",
|
|
||||||
group: "index",
|
|
||||||
el: "#home"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
module: "Particles",
|
|
||||||
group: "index",
|
|
||||||
el: "#particles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
module: "Signin",
|
|
||||||
group: "index",
|
|
||||||
el: "#sign-in",
|
|
||||||
global: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
{
|
|
||||||
"particles": {
|
|
||||||
"number": {
|
|
||||||
"value": 45,
|
|
||||||
"density": {
|
|
||||||
"enable": true,
|
|
||||||
"value_area": 800
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"value": "#ffffff"
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"type": "circle",
|
|
||||||
"stroke": {
|
|
||||||
"width": 0,
|
|
||||||
"color": "#000000"
|
|
||||||
},
|
|
||||||
"polygon": {
|
|
||||||
"nb_sides": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"opacity": {
|
|
||||||
"value": 0.5,
|
|
||||||
"random": true,
|
|
||||||
"anim": {
|
|
||||||
"enable": false,
|
|
||||||
"speed": 1,
|
|
||||||
"opacity_min": 0.1,
|
|
||||||
"sync": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"value": 3,
|
|
||||||
"random": true,
|
|
||||||
"anim": {
|
|
||||||
"enable": false,
|
|
||||||
"speed": 40,
|
|
||||||
"size_min": 0.1,
|
|
||||||
"sync": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"line_linked": {
|
|
||||||
"enable": true,
|
|
||||||
"distance": 150,
|
|
||||||
"color": "#ffffff",
|
|
||||||
"opacity": 0.4,
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"move": {
|
|
||||||
"enable": true,
|
|
||||||
"speed": 6,
|
|
||||||
"direction": "none",
|
|
||||||
"random": false,
|
|
||||||
"straight": false,
|
|
||||||
"out_mode": "out",
|
|
||||||
"bounce": false,
|
|
||||||
"attract": {
|
|
||||||
"enable": false,
|
|
||||||
"rotateX": 600,
|
|
||||||
"rotateY": 1200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"interactivity": {
|
|
||||||
"events": {
|
|
||||||
"onhover": {
|
|
||||||
"enable": false
|
|
||||||
},
|
|
||||||
"onclick": {
|
|
||||||
"enable": false
|
|
||||||
},
|
|
||||||
"resize": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"retina_detect": true
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#container {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#center {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
padding: 5px;
|
|
||||||
|
|
||||||
border-radius: 25px;
|
|
||||||
background-color: rgba($white, 0.3);
|
|
||||||
color: $white;
|
|
||||||
width: 90%;
|
|
||||||
height: auto;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100px;
|
|
||||||
border-radius: 25px;
|
|
||||||
background-color: rgba($white, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
footer {
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
background-color: rgba($white, 0.7);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
input {
|
|
||||||
padding: 10px;
|
|
||||||
border: 0;
|
|
||||||
width: 23px;
|
|
||||||
height: 23px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 30px;
|
|
||||||
color: $black;
|
|
||||||
font-family: "text-security-disc";
|
|
||||||
margin-bottom: 2px;
|
|
||||||
border: #FFF solid 2px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
border: #F00 solid 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-image: linear-gradient(to bottom, #3498db, #2980b9);
|
|
||||||
border-radius: 7px;
|
|
||||||
box-shadow: 0px 1px 3px #666666;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 6px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-image: linear-gradient(to bottom, #3cb0fd, #3498db);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#preloader {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 1.5s ease-in-out;
|
|
||||||
|
|
||||||
&.completed {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'text-security-disc';
|
|
||||||
src: url('/fonts/text-security-disc.eot');
|
|
||||||
src: url('/fonts/text-security-disc.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('/fonts/text-security-disc.woff') format('woff'),
|
|
||||||
url('/fonts/text-security-disc.ttf') format('truetype'),
|
|
||||||
url('/fonts/text-security-disc.svg#text-security') format('svg');
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//
|
|
||||||
// General
|
|
||||||
//
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: $blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Set default font family to sans-serif.
|
|
||||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
|
||||||
* user zoom.
|
|
||||||
*/
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: sans-serif; /* 1 */
|
|
||||||
-ms-text-size-adjust: 100%; /* 2 */
|
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove default margin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HTML5 display definitions
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
|
||||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
|
||||||
* and Firefox.
|
|
||||||
* Correct `block` display not defined for `main` in IE 11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
article,
|
|
||||||
aside,
|
|
||||||
details,
|
|
||||||
figcaption,
|
|
||||||
figure,
|
|
||||||
footer,
|
|
||||||
header,
|
|
||||||
hgroup,
|
|
||||||
main,
|
|
||||||
menu,
|
|
||||||
nav,
|
|
||||||
section,
|
|
||||||
summary {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
|
||||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
|
||||||
*/
|
|
||||||
|
|
||||||
audio,
|
|
||||||
canvas,
|
|
||||||
progress,
|
|
||||||
video {
|
|
||||||
display: inline-block; /* 1 */
|
|
||||||
vertical-align: baseline; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent modern browsers from displaying `audio` without controls.
|
|
||||||
* Remove excess height in iOS 5 devices.
|
|
||||||
*/
|
|
||||||
|
|
||||||
audio:not([controls]) {
|
|
||||||
display: none;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
|
||||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[hidden],
|
|
||||||
template {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Links
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the gray background color from active links in IE 10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Improve readability when focused and also mouse hovered in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a:active,
|
|
||||||
a:hover {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Embedded content
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove border when inside `a` element in IE 8/9/10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct overflow not hidden in IE 9/10/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
svg:not(:root) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
|
||||||
* styling of `select`, unless a `border` property is set.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct color not being inherited.
|
|
||||||
* Known issue: affects color of disabled elements.
|
|
||||||
* 2. Correct font properties not being inherited.
|
|
||||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
color: inherit; /* 1 */
|
|
||||||
font: inherit; /* 2 */
|
|
||||||
margin: 0; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
|
||||||
* All other form control elements do not inherit `text-transform` values.
|
|
||||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
|
||||||
* Correct `select` style inheritance in Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
|
||||||
* and `video` controls.
|
|
||||||
* 2. Correct inability to style clickable `input` types in iOS.
|
|
||||||
* 3. Improve usability and consistency of cursor style between image-type
|
|
||||||
* `input` and others.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
html input[type="button"], /* 1 */
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"] {
|
|
||||||
appearance: none; /* 2 */
|
|
||||||
border: none;
|
|
||||||
cursor: pointer; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-set default cursor for disabled elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button[disabled],
|
|
||||||
html input[disabled] {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove inner padding and border in Firefox 4+.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button::-moz-focus-inner,
|
|
||||||
input::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
|
||||||
* the UA stylesheet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
|
||||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
|
||||||
* (include `-moz` to future-proof).
|
|
||||||
*/
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
-webkit-appearance: textfield; /* 1 */
|
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
-webkit-box-sizing: content-box; /* 2 */
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
|
||||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
|
||||||
* padding (and `textfield` appearance).
|
|
||||||
*/
|
|
||||||
|
|
||||||
input[type="search"]::-webkit-search-cancel-button,
|
|
||||||
input[type="search"]::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tables
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove most spacing between table cells.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Visibility
|
|
||||||
.hidden {
|
|
||||||
opacity: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
|
|
||||||
//
|
|
||||||
// General
|
|
||||||
//
|
|
||||||
|
|
||||||
// Variables
|
|
||||||
@import "variables/colors";
|
|
||||||
|
|
||||||
// Global
|
|
||||||
@import "global/normalize";
|
|
||||||
@import "global/utils";
|
|
||||||
@import "global/fonts";
|
|
||||||
@import "global/general";
|
|
||||||
|
|
||||||
//
|
|
||||||
// Local App
|
|
||||||
//
|
|
||||||
|
|
||||||
// Components
|
|
||||||
@import "components/preloader";
|
|
||||||
@import "components/form";
|
|
||||||
@import "components/container";
|
|
||||||
@import "components/footer";
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// colors
|
|
||||||
$black: #000000;
|
|
||||||
$white: #ffffff;
|
|
||||||
$blue: #1875b6;
|
|
||||||
114
server.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Import base packages
|
||||||
|
*/
|
||||||
|
const express = require('express');
|
||||||
|
const multer = require('multer');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const unifi = require('./modules/unifi');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define global functions
|
||||||
|
*/
|
||||||
|
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trust proxy
|
||||||
|
*/
|
||||||
|
app.enable('trust proxy');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set template engine
|
||||||
|
*/
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.set('views', `${__dirname}/template`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable multer
|
||||||
|
*/
|
||||||
|
app.use(multer().none());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request logger
|
||||||
|
*/
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
console.log(`[Web][REQUEST]: ${req.originalUrl}`);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serve static public dir
|
||||||
|
*/
|
||||||
|
app.use(express.static(`${__dirname}/public`));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure routers
|
||||||
|
*/
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
const hour = new Date().getHours();
|
||||||
|
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||||
|
|
||||||
|
res.render('home', {
|
||||||
|
error: typeof req.query.error === 'string' && req.query.error !== '',
|
||||||
|
error_text: req.query.error || '',
|
||||||
|
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||||
|
app_header: timeHeader,
|
||||||
|
sid: uuidv4()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.post('/', async (req, res) => {
|
||||||
|
const check = req.body.password === (process.env.SECURITY_CODE || "0000");
|
||||||
|
|
||||||
|
if(!check) {
|
||||||
|
res.redirect(encodeURI(`/?error=Invalid password!`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect(encodeURI(`/voucher?code=${req.body.password}`));
|
||||||
|
});
|
||||||
|
app.get('/voucher', async (req, res) => {
|
||||||
|
if(req.query.code !== (process.env.SECURITY_CODE || "0000")) {
|
||||||
|
res.status(403).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hour = new Date().getHours();
|
||||||
|
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||||
|
const voucherCode = await unifi();
|
||||||
|
|
||||||
|
console.log(voucherCode);
|
||||||
|
|
||||||
|
res.render('voucher', {
|
||||||
|
error: typeof req.query.error === 'string' && req.query.error !== '',
|
||||||
|
error_text: req.query.error || '',
|
||||||
|
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||||
|
app_header: timeHeader,
|
||||||
|
code: req.query.code,
|
||||||
|
voucher_code: voucherCode,
|
||||||
|
sid: uuidv4()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup default 404 message
|
||||||
|
*/
|
||||||
|
app.use((req, res) => {
|
||||||
|
res.status(404);
|
||||||
|
res.send('Not Found!');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable powered by header for security reasons
|
||||||
|
*/
|
||||||
|
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`);
|
||||||
|
});
|
||||||
12
tailwind.config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module.exports = {
|
||||||
|
mode: 'jit',
|
||||||
|
content: ["./template/**/*.{html,js,ejs}"],
|
||||||
|
darkMode: 'media',
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
plugins: []
|
||||||
|
};
|
||||||
72
template/home.ejs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Login | UniFi Voucher</title>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||||
|
|
||||||
|
<meta name="description" content="UniFi Voucher">
|
||||||
|
<meta name="author" content="Glenn de Haan">
|
||||||
|
|
||||||
|
<meta property="og:title" content="Login | UniFi Voucher"/>
|
||||||
|
<meta property="og:type" content="website"/>
|
||||||
|
<meta property="og:description" content="UniFi Voucher"/>
|
||||||
|
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
<link rel="shortcut icon" href="/images/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="/images/icon/logo_256x256.png">
|
||||||
|
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="theme-color" content="#1875b6">
|
||||||
|
|
||||||
|
<link rel="preload" href="<%= banner_image %>" as="image">
|
||||||
|
<link rel="preload" href="/images/unifi-icon.png" as="image">
|
||||||
|
<link rel="preload" href="/dist/style.css" as="style">
|
||||||
|
<link href="/dist/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-white dark:bg-neutral-900 dark:text-gray-100 h-screen">
|
||||||
|
<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"/>
|
||||||
|
</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">
|
||||||
|
<p class="text-center text-3xl"><%= app_header %></p>
|
||||||
|
<% if(error) { %>
|
||||||
|
<div class="bg-red-500 text-white p-3 mt-8 rounded shadow-lg flex items-center">
|
||||||
|
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<div><%= error_text %></div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<form class="flex flex-col pt-3 md:pt-8" action="/" method="post" enctype="multipart/form-data">
|
||||||
|
<div class="flex flex-col pt-4">
|
||||||
|
<label for="password" class="text-lg">Password</label>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="text-center text-gray-400 text-sm italic pt-12 pb-12">
|
||||||
|
<p>
|
||||||
|
Powered by: <a href="https://glenndehaan.com" class="underline font-semibold">Glenn de Haan</a>.<br/>
|
||||||
|
Want your own portal? Checkout the project on: <a href="https://github.com/glenndehaan/unifi-voucher-site" class="underline font-semibold">GitHub</a>
|
||||||
|
</p>
|
||||||
|
<p class="text-[10px] not-italic">
|
||||||
|
SID: <%= sid %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-1/2 shadow-2xl">
|
||||||
|
<img class="object-cover w-full h-screen hidden md:block" src="<%= banner_image %>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
68
template/voucher.ejs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Voucher | UniFi Voucher</title>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||||
|
|
||||||
|
<meta name="description" content="UniFi Voucher">
|
||||||
|
<meta name="author" content="Glenn de Haan">
|
||||||
|
|
||||||
|
<meta property="og:title" content="Voucher | UniFi Voucher"/>
|
||||||
|
<meta property="og:type" content="website"/>
|
||||||
|
<meta property="og:description" content="UniFi Voucher"/>
|
||||||
|
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
<link rel="shortcut icon" href="/images/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="/images/icon/logo_256x256.png">
|
||||||
|
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="theme-color" content="#1875b6">
|
||||||
|
|
||||||
|
<link rel="preload" href="<%= banner_image %>" as="image">
|
||||||
|
<link rel="preload" href="/images/unifi-icon.png" as="image">
|
||||||
|
<link rel="preload" href="/dist/style.css" as="style">
|
||||||
|
<link href="/dist/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-white dark:bg-neutral-900 dark:text-gray-100 h-screen">
|
||||||
|
<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"/>
|
||||||
|
</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">
|
||||||
|
<p class="text-center text-3xl"><%= app_header %></p>
|
||||||
|
<p class="mt-4 text-center">
|
||||||
|
Voucher generated successfully!
|
||||||
|
</p>
|
||||||
|
<form class="flex flex-col pt-3 md:pt-8" action="/" method="post" enctype="multipart/form-data">
|
||||||
|
<div class="flex flex-col pt-4">
|
||||||
|
<label for="voucher" class="text-lg">Voucher</label>
|
||||||
|
<input type="text" id="voucher" name="voucher" disabled value="<%= voucher_code %>" 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">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" id="password" name="password" value="<%= code %>"/>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="text-center text-gray-400 text-sm italic pt-12 pb-12">
|
||||||
|
<p>
|
||||||
|
Powered by: <a href="https://glenndehaan.com" class="underline font-semibold">Glenn de Haan</a>.<br/>
|
||||||
|
Want your own portal? Checkout the project on: <a href="https://github.com/glenndehaan/unifi-voucher-site" class="underline font-semibold">GitHub</a>
|
||||||
|
</p>
|
||||||
|
<p class="text-[10px] not-italic">
|
||||||
|
SID: <%= sid %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-1/2 shadow-2xl">
|
||||||
|
<img class="object-cover w-full h-screen hidden md:block" src="<%= banner_image %>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||