diff --git a/README.md b/README.md index b1cb4a0..1726792 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net  -> Upgrading from 5.x to 6.x? Please take a look at the [migration guide](#migration-from-5x-to-6x) +> Upgrading from 6.x to 7.x? Please take a look at the [migration guide](#migration-from-6x-to-7x) + +--- ## Features @@ -21,6 +23,8 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net - **Localized Email/Print Templates** Fully localized templates, with support for multiple languages. - **Scan to Connect QR Codes** Quickly connect users via a phone's camera. (Available within Email and Print Layouts) +--- + ## Structure - Node.js @@ -33,6 +37,8 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net - Node Thermal Printer - QRCode +--- + ## Prerequisites - UniFi Network Controller (Cloud Key, Dream Machine, or Controller software) @@ -47,6 +53,8 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net > Note: When creating a Local UniFi account ensure you give 'Full Management' access rights to the Network controller. The 'Hotspot Role' won't give access to the API and therefore the application will throw errors. +--- + ## Installation ### Docker @@ -59,9 +67,8 @@ You can easily run UniFi Voucher Site using Docker. We provide two release track **Below is an example docker-compose.yml file that can help you get started:** ```yaml -version: '3' services: - app: + unifi-voucher-site: image: glenndehaan/unifi-voucher-site:latest ports: - "3000:3000" @@ -124,9 +131,10 @@ services: SMTP_PASSWORD: '' # Enable/disable the kiosk page on /kiosk KIOSK_ENABLED: 'false' - # Kiosk Voucher Type, format: expiration in minutes (required),single-use or multi-use vouchers value - '0' is for multi-use (unlimited) - '1' is for single-use - 'N' is for multi-use (Nx) (optional),upload speed limit in kbps (optional),download speed limit in kbps (optional),data transfer limit in MB (optional) + # Kiosk Voucher Types, format: expiration in minutes (required),single-use or multi-use vouchers value - '0' is for multi-use (unlimited) - '1' is for single-use - 'N' is for multi-use (Nx) (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 - KIOSK_VOUCHER_TYPE: '480,1,,,' + # After a voucher type add a semicolon, after the semicolon you can start a new voucher type + KIOSK_VOUCHER_TYPES: '480,1,,,;' # Enable/disable the requirement for a guest to enter their name before generating a voucher KIOSK_NAME_REQUIRED: 'false' # Sets the application Log Level (Valid Options: error|warn|info|debug|trace) @@ -135,6 +143,9 @@ services: TRANSLATION_DEFAULT: 'en' # Enables/disables translation debugging, when enabled only translation keys are shown TRANSLATION_DEBUG: 'false' + # Optional volume mapping to override assets + volumes: + - ./branding:/kiosk ``` ### Home Assistant Add-on @@ -153,6 +164,8 @@ To install the UniFi Voucher Site add-on for Home Assistant, follow these steps: 4. Once the repository is added, you will find the "UniFi Voucher Site" add-on in the add-on store. Click on it. 5. Click "Install" and wait for the installation to complete. +--- + ## Development - Install Node.js 22.0 or higher. @@ -161,6 +174,8 @@ To install the UniFi Voucher Site add-on for Home Assistant, follow these steps: Then open up your favorite browser and go to http://localhost:3000/ +--- + ## Services The project consists of two main services: Web and API. @@ -362,6 +377,8 @@ the different endpoints available in the API: request authorization header. The token must match the value of the `AUTH_INTERNAL_BEARER_TOKEN` environment variable. Without this token, access to the endpoint will be denied. +--- + ## Authentication The UniFi Voucher Site provides three options for authenticating access to the web service. @@ -453,6 +470,8 @@ AUTH_DISABLE: 'true' > Note: This disables the token based authentication on the API +--- + ## Print Functionality The UniFi Voucher Site application includes built-in support for printing vouchers using 80mm receipt printers, offering a convenient way to distribute vouchers in physical format. @@ -511,6 +530,8 @@ Just like with PDF printing, navigate to the voucher and click on the "Print" bu  +--- + ## Email Functionality The UniFi Voucher Site includes a convenient email feature that allows you to send vouchers directly to users from the web interface. By configuring the SMTP settings, you can enable email sending and make it easy to distribute vouchers digitally. @@ -547,6 +568,8 @@ Once the SMTP environment variables are configured, the email feature will be av  +--- + ## Kiosk Functionality The UniFi Voucher Site includes a **Kiosk Mode**, allowing users to generate their own vouchers via a self-service interface. This is ideal for public areas, such as cafés, hotels, and co-working spaces, where users can obtain internet access without staff intervention. @@ -555,7 +578,7 @@ To enable the kiosk functionality, set the following environment variables: ```env KIOSK_ENABLED: 'true' -KIOSK_VOUCHER_TYPE: '480,1,,,' +KIOSK_VOUCHER_TYPES: '480,1,,,;' ``` ### Configuration @@ -564,7 +587,7 @@ KIOSK_VOUCHER_TYPE: '480,1,,,' - Set to `'true'` to enable the kiosk page, making it accessible at `/kiosk`. - Set to `'false'` to disable the kiosk functionality. -- **`KIOSK_VOUCHER_TYPE`**: Defines the voucher properties for kiosk-generated vouchers. The format consists of the following parameters: +- **`KIOSK_VOUCHER_TYPES`**: Defines the voucher properties for kiosk-generated vouchers. The format consists of the following parameters: ```text expiration_in_minutes,single_use_or_multi_use,upload_speed_limit_kbps,download_speed_limit_kbps,data_transfer_limit_MB @@ -579,10 +602,56 @@ KIOSK_VOUCHER_TYPE: '480,1,,,' - **Download Speed Limit (optional)**: Maximum download speed in Kbps. Leave empty to disable. - **Data Transfer Limit (optional)**: Total data limit in MB. Leave empty to disable. + > **Multiple Voucher Types:** + > You can define multiple voucher types by separating each configuration with a semicolon (`;`). + > Example: + > + > ```text + > 480,1,,,;1440,0,512,2048,1000 + > ``` + > + > This defines two voucher types: + > + > 1. A single-use voucher valid for 480 minutes with no speed or data limits. + > 2. A multi-use (unlimited) voucher valid for 1440 minutes, limited to 512 Kbps upload, 2048 Kbps download, and 1000 MB data. + - **`KIOSK_NAME_REQUIRED`**: - Set to `'true'` to enable the requirement for a guest to enter their name before generating a voucher. - Set to `'false'` to disable to allow generation of vouchers without a name. +### Custom Branding (Logo and Background) + +You can customize the appearance of the kiosk page by providing your own `logo.png` and `bg.jpg` images. + +To do this, use Docker volume mappings to mount your custom assets to the `/kiosk` directory inside the container. The application will use these files (if present) instead of the default ones. + +#### Example + +Suppose you have your custom images in a local directory called `branding/`: + +``` +branding/ +├── logo.png +└── bg.jpg +``` + +You can configure this using Docker Compose: + +```yaml +services: + unifi-voucher-site: + image: glenndehaan/unifi-voucher-site:latest + ports: + - "3000:3000" + environment: + KIOSK_ENABLED: 'true' + KIOSK_VOUCHER_TYPES: '480,1,,,;' + volumes: + - ./branding:/kiosk +``` + +> **Note:** Ensure `logo.png` and `bg.jpg` are valid image files. Both are optional — only override the ones you want to customize. + ### Usage Once enabled, the kiosk page is available at: @@ -597,6 +666,8 @@ Users can visit this URL and generate a voucher without administrative intervent  +--- + ## Translations The UniFi Voucher Site supports multiple languages, and we're actively working to expand the list of available translations. To facilitate this, we use **Crowdin**, a platform that allows people from around the world to help translate and improve the localization of the project. @@ -620,6 +691,8 @@ Once you're there, you can choose your language and start contributing immediate Your contributions will be automatically included in the next release after review. +--- + ## Screenshots ### Login (Desktop) @@ -646,12 +719,42 @@ Your contributions will be automatically included in the next release after revi ### Voucher Details (Mobile)  +--- + ## Release Notes Detailed information on the changes in each release can be found on the [GitHub Releases](https://github.com/glenndehaan/unifi-voucher-site/releases) page. It is highly recommended to review the release notes before updating or deploying a new version, especially if you are upgrading from a previous version. +--- + ## Migration Guide +### Migration from 6.x to 7.x + +When upgrading from 6.x to 7.x, the following changes need to be made: + +1. **`KIOSK_VOUCHER_TYPE` Renamed to `KIOSK_VOUCHER_TYPES`** + + * The configuration variable **`KIOSK_VOUCHER_TYPE`** has been **renamed** to **`KIOSK_VOUCHER_TYPES`** in 7.x. + * This change supports multiple voucher types and enhances configurability. + + **Before (6.x):** + + ```env + KIOSK_VOUCHER_TYPE='480,1,,,' + ``` + + **After (7.x):** + + ```env + KIOSK_VOUCHER_TYPES='480,1,,,;' + ``` + + * Update your environment configuration to use the new `KIOSK_VOUCHER_TYPES` variable. + * Ensure the values provided are valid and supported — refer to the [Kiosk Configuration](#configuration-3) for the complete list of accepted types and formatting rules. + +> Make sure to remove the deprecated `KIOSK_VOUCHER_TYPE` and follow the structure outlined in the documentation to avoid misconfiguration issues during deployment. + ### Migration from 5.x to 6.x When upgrading from 5.x to 6.x, the following changes need to be made: @@ -789,6 +892,8 @@ No migration steps are required. Versions before v1 do not have a direct migration path. If you are using a version earlier than v1, a fresh installation is required. Be sure to back up any important data before proceeding with a re-install. +--- + ## License MIT diff --git a/docker-compose.yml b/docker-compose.yml index e804cd9..2cd63e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ services: - app: + unifi-voucher-site: build: . ports: - "3000:3000" @@ -32,7 +32,7 @@ services: SMTP_USERNAME: '' SMTP_PASSWORD: '' KIOSK_ENABLED: 'false' - KIOSK_VOUCHER_TYPE: '480,1,,,' + KIOSK_VOUCHER_TYPES: '480,1,,,;' KIOSK_NAME_REQUIRED: 'false' LOG_LEVEL: 'info' TRANSLATION_DEBUG: 'false' diff --git a/locales/en/kiosk.json b/locales/en/kiosk.json index 69bedf8..78c85a2 100644 --- a/locales/en/kiosk.json +++ b/locales/en/kiosk.json @@ -1,5 +1,14 @@ { "title": "WiFi Voucher", + "language": "Language", + "type": "Type", + "multiUse": "Multi-use", + "singleUse": "Single-use", + "unlimitedUse": "Unlimited", + "duration": "Duration", + "dataLimit": "Data Limit", + "downloadLimit": "Download Limit", + "uploadLimit": "Upload Limit", "guestName": "Guest Name", "generate": "Generate WiFi Voucher", "generating": "Generating Voucher", diff --git a/modules/info.js b/modules/info.js index 0cb2c54..8d85cbe 100644 --- a/modules/info.js +++ b/modules/info.js @@ -110,9 +110,10 @@ module.exports = () => { * Log kiosk status */ if(variables.kioskEnabled) { - const kioskType = types(variables.kioskVoucherType, true); log.info(`[Kiosk] Enabled! ${variables.kioskNameRequired ? '(Guest Name Required!)' : ''}`); - log.info(`[Kiosk][Type] ${time(kioskType.expiration)}, ${kioskType.usage === '1' ? 'single-use' : kioskType.usage === '0' ? 'multi-use (unlimited)' : `multi-use (${kioskType.usage}x)`}${typeof kioskType.upload === "undefined" && typeof kioskType.download === "undefined" && typeof kioskType.megabytes === "undefined" ? ', no limits' : `${typeof kioskType.upload !== "undefined" ? `, upload bandwidth limit: ${kioskType.upload} kb/s` : ''}${typeof kioskType.download !== "undefined" ? `, download bandwidth limit: ${kioskType.download} kb/s` : ''}${typeof kioskType.megabytes !== "undefined" ? `, quota limit: ${kioskType.megabytes} mb` : ''}`}`); + types(variables.kioskVoucherTypes).forEach((type, key) => { + log.info(`[Kiosk][Type][${key}] ${time(type.expiration)}, ${type.usage === '1' ? 'single-use' : type.usage === '0' ? 'multi-use (unlimited)' : `multi-use (${type.usage}x)`}${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` : ''}`}`); + }); } /** diff --git a/modules/variables.js b/modules/variables.js index e36891c..2b194db 100644 --- a/modules/variables.js +++ b/modules/variables.js @@ -40,7 +40,7 @@ module.exports = { smtpUsername: config('smtp_username') || process.env.SMTP_USERNAME || '', smtpPassword: config('smtp_password') || process.env.SMTP_PASSWORD || '', kioskEnabled: config('kiosk_enabled') || (process.env.KIOSK_ENABLED === 'true') || false, - kioskVoucherType: config('kiosk_voucher_type') || process.env.KIOSK_VOUCHER_TYPE || '480,1,,,', + kioskVoucherTypes: config('kiosk_voucher_types') || process.env.KIOSK_VOUCHER_TYPES || '480,1,,,;', kioskNameRequired: config('kiosk_name_required') || (process.env.KIOSK_NAME_REQUIRED === 'true') || false, logLevel: config('log_level') || process.env.LOG_LEVEL || 'info', translationDefault: config('translation_default') || process.env.TRANSLATION_DEFAULT || 'en', diff --git a/public/images/kiosk_bg.jpg b/public/images/kiosk/bg.jpg similarity index 100% rename from public/images/kiosk_bg.jpg rename to public/images/kiosk/bg.jpg diff --git a/public/images/kiosk/logo.png b/public/images/kiosk/logo.png new file mode 100644 index 0000000..14a05e7 Binary files /dev/null and b/public/images/kiosk/logo.png differ diff --git a/server.js b/server.js index 1821b76..a9f3e3b 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,7 @@ /** * Import base packages */ +const fs = require('fs'); const os = require('os'); const crypto = require('crypto'); const express = require('express'); @@ -88,6 +89,13 @@ app.use((req, res, next) => { next(); }); +/** + * Override kiosk images dir if available + */ +if(fs.existsSync('/kiosk')) { + app.use('/images/kiosk', express.static('/kiosk')); +} + /** * Serve static public dir */ @@ -155,6 +163,9 @@ if(variables.serviceWeb) { baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '', error: req.flashMessage.type === 'error', error_text: req.flashMessage.message || '', + timeConvert: time, + bytesConvert: bytes, + voucher_types: types(variables.kioskVoucherTypes), kiosk_name_required: variables.kioskNameRequired }); }); @@ -207,8 +218,15 @@ if(variables.serviceWeb) { }); } } else { + const typeCheck = (variables.kioskVoucherTypes).split(';').includes(req.body['voucher-type']); + + if (!typeCheck) { + res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`); + return; + } + // Create voucher code - const voucherCode = await unifi.create(types(variables.kioskVoucherType, true), 1, variables.kioskNameRequired ? req.body['voucher-note'] : null).catch((e) => { + const voucherCode = await unifi.create(types(req.body['voucher-type'], true), 1, variables.kioskNameRequired ? req.body['voucher-note'] : null).catch((e) => { res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`); }); diff --git a/template/kiosk.ejs b/template/kiosk.ejs index 638a4de..a8f2872 100644 --- a/template/kiosk.ejs +++ b/template/kiosk.ejs @@ -35,25 +35,13 @@ <% } %>
+