Added guestName translation to kiosk.json. Implemented time.json to time util translations. Updated info.js to reflect kiosk name requirement. Updated mail.js and print.js for localized time translations. Implemented optional guest name (voucher note) within kiosk.ejs. Implemented translations within time.js. Updated docker-compose.yml. Updated dependencies. Updated README.md

This commit is contained in:
Glenn de Haan
2025-07-07 21:29:45 +02:00
parent 87fd80b0e8
commit b3f1988982
14 changed files with 135 additions and 113 deletions

View File

@@ -127,6 +127,8 @@ services:
# 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 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)
# To skip a parameter just but nothing in between the comma's # To skip a parameter just but nothing in between the comma's
KIOSK_VOUCHER_TYPE: '480,1,,,' KIOSK_VOUCHER_TYPE: '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) # Sets the application Log Level (Valid Options: error|warn|info|debug|trace)
LOG_LEVEL: 'info' LOG_LEVEL: 'info'
# Sets the default translation for dropdowns # Sets the default translation for dropdowns
@@ -577,6 +579,10 @@ KIOSK_VOUCHER_TYPE: '480,1,,,'
- **Download Speed Limit (optional)**: Maximum download speed in Kbps. Leave empty to disable. - **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. - **Data Transfer Limit (optional)**: Total data limit in MB. Leave empty to disable.
- **`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.
### Usage ### Usage
Once enabled, the kiosk page is available at: Once enabled, the kiosk page is available at:

View File

@@ -33,5 +33,6 @@ services:
SMTP_PASSWORD: '' SMTP_PASSWORD: ''
KIOSK_ENABLED: 'false' KIOSK_ENABLED: 'false'
KIOSK_VOUCHER_TYPE: '480,1,,,' KIOSK_VOUCHER_TYPE: '480,1,,,'
KIOSK_NAME_REQUIRED: 'false'
LOG_LEVEL: 'info' LOG_LEVEL: 'info'
TRANSLATION_DEBUG: 'false' TRANSLATION_DEBUG: 'false'

View File

@@ -1,5 +1,6 @@
{ {
"title": "WiFi Voucher", "title": "WiFi Voucher",
"guestName": "Guest Name",
"generate": "Generate WiFi Voucher", "generate": "Generate WiFi Voucher",
"generating": "Generating Voucher", "generating": "Generating Voucher",
"use": "Use this code when connecting", "use": "Use this code when connecting",

8
locales/en/time.json Normal file
View File

@@ -0,0 +1,8 @@
{
"minute": "minute",
"minutes": "minutes",
"hour": "hour",
"hours": "hours",
"day": "day",
"days": "days"
}

View File

@@ -111,7 +111,7 @@ module.exports = () => {
*/ */
if(variables.kioskEnabled) { if(variables.kioskEnabled) {
const kioskType = types(variables.kioskVoucherType, true); const kioskType = types(variables.kioskVoucherType, true);
log.info('[Kiosk] Enabled!'); 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` : ''}`}`); 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` : ''}`}`);
} }

View File

@@ -59,6 +59,7 @@ module.exports = {
subject: t('title'), subject: t('title'),
text: `${t('greeting')},\n\n${t('intro')}:\n\n${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`, text: `${t('greeting')},\n\n${t('intro')}:\n\n${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
html: ejs.render(fs.readFileSync(`${__dirname}/../template/email/voucher.ejs`, 'utf-8'), { html: ejs.render(fs.readFileSync(`${__dirname}/../template/email/voucher.ejs`, 'utf-8'), {
language,
t, t,
voucher, voucher,
unifiSsid: variables.unifiSsid, unifiSsid: variables.unifiSsid,

View File

@@ -180,7 +180,7 @@ module.exports = {
}); });
doc.font('Roboto-Regular') doc.font('Roboto-Regular')
.fontSize(10) .fontSize(10)
.text(time(vouchers[item].duration)); .text(time(vouchers[item].duration, language));
if (vouchers[item].qos_usage_quota) { if (vouchers[item].qos_usage_quota) {
doc.font('Roboto-Bold') doc.font('Roboto-Bold')
@@ -310,7 +310,7 @@ module.exports = {
printer.invert(true); printer.invert(true);
printer.print(`${t('duration')}:`); printer.print(`${t('duration')}:`);
printer.invert(false); printer.invert(false);
printer.print(` ${time(voucher.duration)}`); printer.print(` ${time(voucher.duration, language)}`);
printer.newLine(); printer.newLine();
if(voucher.qos_usage_quota) { if(voucher.qos_usage_quota) {

View File

@@ -41,6 +41,7 @@ module.exports = {
smtpPassword: config('smtp_password') || process.env.SMTP_PASSWORD || '', smtpPassword: config('smtp_password') || process.env.SMTP_PASSWORD || '',
kioskEnabled: config('kiosk_enabled') || (process.env.KIOSK_ENABLED === 'true') || false, kioskEnabled: config('kiosk_enabled') || (process.env.KIOSK_ENABLED === 'true') || false,
kioskVoucherType: config('kiosk_voucher_type') || process.env.KIOSK_VOUCHER_TYPE || '480,1,,,', kioskVoucherType: config('kiosk_voucher_type') || process.env.KIOSK_VOUCHER_TYPE || '480,1,,,',
kioskNameRequired: config('kiosk_name_required') || (process.env.KIOSK_NAME_REQUIRED === 'true') || false,
logLevel: config('log_level') || process.env.LOG_LEVEL || 'info', logLevel: config('log_level') || process.env.LOG_LEVEL || 'info',
translationDefault: config('translation_default') || process.env.TRANSLATION_DEFAULT || 'en', translationDefault: config('translation_default') || process.env.TRANSLATION_DEFAULT || 'en',
translationDebug: config('translation_debug') || (process.env.TRANSLATION_DEBUG === 'true') || false, translationDebug: config('translation_debug') || (process.env.TRANSLATION_DEBUG === 'true') || false,

182
package-lock.json generated
View File

@@ -19,14 +19,14 @@
"multer": "^2.0.1", "multer": "^2.0.1",
"node-thermal-printer": "^4.5.0", "node-thermal-printer": "^4.5.0",
"node-unifi": "^2.5.1", "node-unifi": "^2.5.1",
"nodemailer": "^7.0.3", "nodemailer": "^7.0.4",
"pdfkit": "^0.17.1", "pdfkit": "^0.17.1",
"qrcode": "^1.5.4" "qrcode": "^1.5.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/cli": "^4.1.10", "@tailwindcss/cli": "^4.1.11",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"tailwindcss": "^4.1.10" "tailwindcss": "^4.1.11"
}, },
"engines": { "engines": {
"node": ">=22.0.0" "node": ">=22.0.0"
@@ -75,18 +75,14 @@
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
@@ -99,27 +95,17 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25", "version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -500,19 +486,19 @@
} }
}, },
"node_modules/@tailwindcss/cli": { "node_modules/@tailwindcss/cli": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz",
"integrity": "sha512-TuO7IOUpTG1JeqtMQbQXjR4RIhfZ43mor/vpCp3S5X9h0WxUom5NYgxfNO0PiFoLMJ6/eYCelC7KGvUOmqqK6A==", "integrity": "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@parcel/watcher": "^2.5.1", "@parcel/watcher": "^2.5.1",
"@tailwindcss/node": "4.1.10", "@tailwindcss/node": "4.1.11",
"@tailwindcss/oxide": "4.1.10", "@tailwindcss/oxide": "4.1.11",
"enhanced-resolve": "^5.18.1", "enhanced-resolve": "^5.18.1",
"mri": "^1.2.0", "mri": "^1.2.0",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"tailwindcss": "4.1.10" "tailwindcss": "4.1.11"
}, },
"bin": { "bin": {
"tailwindcss": "dist/index.mjs" "tailwindcss": "dist/index.mjs"
@@ -532,9 +518,9 @@
} }
}, },
"node_modules/@tailwindcss/node": { "node_modules/@tailwindcss/node": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
"integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==", "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -544,13 +530,13 @@
"lightningcss": "1.30.1", "lightningcss": "1.30.1",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"source-map-js": "^1.2.1", "source-map-js": "^1.2.1",
"tailwindcss": "4.1.10" "tailwindcss": "4.1.11"
} }
}, },
"node_modules/@tailwindcss/oxide": { "node_modules/@tailwindcss/oxide": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
"integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==", "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -562,24 +548,24 @@
"node": ">= 10" "node": ">= 10"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.10", "@tailwindcss/oxide-android-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-arm64": "4.1.10", "@tailwindcss/oxide-darwin-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-x64": "4.1.10", "@tailwindcss/oxide-darwin-x64": "4.1.11",
"@tailwindcss/oxide-freebsd-x64": "4.1.10", "@tailwindcss/oxide-freebsd-x64": "4.1.11",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.10", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.10", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.10", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-x64-musl": "4.1.10", "@tailwindcss/oxide-linux-x64-musl": "4.1.11",
"@tailwindcss/oxide-wasm32-wasi": "4.1.10", "@tailwindcss/oxide-wasm32-wasi": "4.1.11",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.10", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.10" "@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
} }
}, },
"node_modules/@tailwindcss/oxide-android-arm64": { "node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
"integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==", "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -594,9 +580,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-darwin-arm64": { "node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
"integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==", "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -611,9 +597,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-darwin-x64": { "node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
"integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==", "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -628,9 +614,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-freebsd-x64": { "node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
"integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==", "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -645,9 +631,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
"integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==", "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -662,9 +648,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": { "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
"integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==", "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -679,9 +665,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-arm64-musl": { "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
"integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==", "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -696,9 +682,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-x64-gnu": { "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
"integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==", "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -713,9 +699,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-x64-musl": { "node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
"integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==", "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -730,9 +716,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi": { "node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
"integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==", "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
"bundleDependencies": [ "bundleDependencies": [
"@napi-rs/wasm-runtime", "@napi-rs/wasm-runtime",
"@emnapi/core", "@emnapi/core",
@@ -751,7 +737,7 @@
"@emnapi/core": "^1.4.3", "@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3", "@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2", "@emnapi/wasi-threads": "^1.0.2",
"@napi-rs/wasm-runtime": "^0.2.10", "@napi-rs/wasm-runtime": "^0.2.11",
"@tybys/wasm-util": "^0.9.0", "@tybys/wasm-util": "^0.9.0",
"tslib": "^2.8.0" "tslib": "^2.8.0"
}, },
@@ -760,9 +746,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
"integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==", "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -777,9 +763,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-win32-x64-msvc": { "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
"integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==", "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1563,9 +1549,9 @@
} }
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2907,9 +2893,9 @@
} }
}, },
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "7.0.3", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz",
"integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", "integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==",
"license": "MIT-0", "license": "MIT-0",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -3540,9 +3526,9 @@
} }
}, },
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "4.1.10", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },

View File

@@ -31,13 +31,13 @@
"multer": "^2.0.1", "multer": "^2.0.1",
"node-thermal-printer": "^4.5.0", "node-thermal-printer": "^4.5.0",
"node-unifi": "^2.5.1", "node-unifi": "^2.5.1",
"nodemailer": "^7.0.3", "nodemailer": "^7.0.4",
"pdfkit": "^0.17.1", "pdfkit": "^0.17.1",
"qrcode": "^1.5.4" "qrcode": "^1.5.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/cli": "^4.1.10", "@tailwindcss/cli": "^4.1.11",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"tailwindcss": "^4.1.10" "tailwindcss": "^4.1.11"
} }
} }

View File

@@ -154,7 +154,8 @@ if(variables.serviceWeb) {
language: req.locale.language, language: req.locale.language,
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '', baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
error: req.flashMessage.type === 'error', error: req.flashMessage.type === 'error',
error_text: req.flashMessage.message || '' error_text: req.flashMessage.message || '',
kiosk_name_required: variables.kioskNameRequired
}); });
}); });
app.post('/kiosk', async (req, res) => { app.post('/kiosk', async (req, res) => {
@@ -207,7 +208,7 @@ if(variables.serviceWeb) {
} }
} else { } else {
// Create voucher code // Create voucher code
const voucherCode = await unifi.create(types(variables.kioskVoucherType, true)).catch((e) => { const voucherCode = await unifi.create(types(variables.kioskVoucherType, 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`); 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`);
}); });

View File

@@ -141,7 +141,7 @@
<p style="font-family: sans-serif; font-size: 20px; font-weight: bold; margin: 0; margin-bottom: 15px;"><%= t('details') %></p> <p style="font-family: sans-serif; font-size: 20px; font-weight: bold; margin: 0; margin-bottom: 15px;"><%= t('details') %></p>
<hr/> <hr/>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('type') %>:</span> <%= voucher.quota === 1 ? t('singleUse') : voucher.quota === 0 ? t('multiUse') : t('multiUse') %></p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('type') %>:</span> <%= voucher.quota === 1 ? t('singleUse') : voucher.quota === 0 ? t('multiUse') : t('multiUse') %></p>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('duration') %>:</span> <%= timeConvert(voucher.duration) %></p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('duration') %>:</span> <%= timeConvert(voucher.duration, language) %></p>
<% if(voucher.qos_usage_quota) { %> <% if(voucher.qos_usage_quota) { %>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('dataLimit') %>:</span> <%= bytesConvert(voucher.qos_usage_quota, 2) %></p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('dataLimit') %>:</span> <%= bytesConvert(voucher.qos_usage_quota, 2) %></p>
<% } %> <% } %>
@@ -164,7 +164,7 @@
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr> <tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center"> <td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center">
<%= t('poweredBy') %> <a href="https://github.com/glenndehaan/unifi-voucher-site" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">UniFi Voucher Site</a>. <%= t('poweredBy') %> <a href="https://github.com/glenndehaan/unifi-voucher-site" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">UniFi Voucher Site</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -75,7 +75,15 @@
<div class="p-4"> <div class="p-4">
<% if(typeof voucherCode === 'undefined') { %> <% if(typeof voucherCode === 'undefined') { %>
<div class="block"> <div class="block">
<form id="voucher-form" action="<%= baseUrl %>/kiosk?locale=<%= language %>-<%= language %>" method="post"> <form id="voucher-form" class="space-y-6" action="<%= baseUrl %>/kiosk?locale=<%= language %>-<%= language %>" method="post" enctype="multipart/form-data">
<% if(kiosk_name_required) { %>
<div>
<label for="voucher-note" class="block text-sm font-medium leading-6 text-gray-900 dark:text-white"><%= t('guestName') %></label>
<div class="mt-2">
<input type="text" id="voucher-note" name="voucher-note" autocomplete="off" required class="mt-2 block w-full rounded-md border-0 py-1.5 text-gray-900 dark:text-white dark:bg-white/5 ring-1 ring-inset ring-gray-300 dark:ring-white/10 focus:ring-2 focus:ring-sky-600 sm:text-sm sm:leading-6">
</div>
</div>
<% } %>
<button id="generate-button" class="w-full h-16 text-lg bg-sky-700 text-white rounded-md flex items-center justify-center font-medium hover:bg-sky-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700"> <button id="generate-button" class="w-full h-16 text-lg bg-sky-700 text-white rounded-md flex items-center justify-center font-medium hover:bg-sky-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12.55a11 11 0 0 1 14.08 0"></path> <path d="M5 12.55a11 11 0 0 1 14.08 0"></path>

View File

@@ -1,20 +1,29 @@
/**
* Import own modules
*/
const translation = require('../modules/translation');
/** /**
* Convert time minutes * Convert time minutes
* *
* @param minutes * @param minutes
* @param language
* @returns {string} * @returns {string}
*/ */
module.exports = (minutes) => { module.exports = (minutes, language = 'en') => {
// Create new translator
const t = translation('time', language);
if (minutes < 60) { if (minutes < 60) {
return `${minutes} minute(s)`; return `${minutes} ${minutes > 1 ? t('minutes') : t('minute')}`;
} }
const hours = minutes / 60; const hours = minutes / 60;
if (hours < 24) { if (hours < 24) {
return `${hours % 1 === 0 ? hours : hours.toFixed(2)} ${(hours > 1) ? 'hours' : 'hour'}`; return `${hours % 1 === 0 ? hours : hours.toFixed(2)} ${(hours > 1) ? t('hours') : t('hour')}`;
} }
const days = hours / 24; const days = hours / 24;
return `${days} ${(days > 1) ? 'days' : 'day'}`; return `${days} ${(days > 1) ? t('days') : t('day')}`;
} }