Updated screenshots. Fixed inconsistent 'unlimited' messaging. Implemented direct usage without overrides within unifi.js. Implemented voucher duration type dropdown during custom voucher creation. Implemented Multi-use (Limited/Quota) option during custom voucher creation. Updated README.md to reflect multi-use quota function within voucher type presets.

This commit is contained in:
Glenn de Haan
2025-01-21 17:34:24 +01:00
parent 5537b61ad5
commit ff455c57bf
11 changed files with 34 additions and 9 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -98,7 +98,7 @@ services:
AUTH_OIDC_CLIENT_SECRET: ''
# Disables the login/authentication for the portal and API
AUTH_DISABLE: 'false'
# Voucher Types, format: expiration in minutes (required),single-use or multi-use vouchers value - '0' is for multi-use - '1' is for single-use (optional),upload speed limit in kbps (optional),download speed limit in kbps (optional),data transfer limit in MB (optional)
# 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
# After a voucher type add a semicolon, after the semicolon you can start a new voucher type
VOUCHER_TYPES: '480,1,,,;'

View File

@@ -69,7 +69,7 @@ module.exports = () => {
*/
log.info('[Voucher] Loaded the following types:');
types(variables.voucherTypes).forEach((type, key) => {
log.info(`[Voucher][Type][${key}] ${time(type.expiration)}, ${type.usage === '1' ? 'single-use' : 'multi-use'}${typeof type.upload === "undefined" && typeof type.download === "undefined" && typeof type.megabytes === "undefined" ? ', no limits' : `${typeof type.upload !== "undefined" ? `, upload bandwidth limit: ${type.upload} kb/s` : ''}${typeof type.download !== "undefined" ? `, download bandwidth limit: ${type.download} kb/s` : ''}${typeof type.megabytes !== "undefined" ? `, quota limit: ${type.megabytes} mb` : ''}`}`);
log.info(`[Voucher][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` : ''}`}`);
});
log.info(`[Voucher][Custom] ${variables.voucherCustom ? 'Enabled!' : 'Disabled!'}`);

View File

@@ -83,7 +83,7 @@ const unifiModule = {
create: (type, amount = 1, note = null, retry = true) => {
return new Promise((resolve, reject) => {
startSession().then(() => {
controller.createVouchers(type.expiration, amount, parseInt(type.usage) === 1 ? 1 : 0, note, typeof type.upload !== "undefined" ? type.upload : null, typeof type.download !== "undefined" ? type.download : null, typeof type.megabytes !== "undefined" ? type.megabytes : null).then((voucher_data) => {
controller.createVouchers(type.expiration, amount, parseInt(type.usage), note, typeof type.upload !== "undefined" ? type.upload : null, typeof type.download !== "undefined" ? type.download : null, typeof type.megabytes !== "undefined" ? type.megabytes : null).then((voucher_data) => {
if(amount > 1) {
log.info(`[UniFi] Created ${amount} vouchers`);
resolve(true);

View File

@@ -203,7 +203,7 @@ if(variables.serviceWeb) {
}
// Create voucher code
const voucherCode = await unifi.create(types(req.body['voucher-type'] === 'custom' ? `${req.body['voucher-duration']},${req.body['voucher-usage']},${req.body['voucher-upload-limit']},${req.body['voucher-download-limit']},${req.body['voucher-data-limit']};` : req.body['voucher-type'], true), parseInt(req.body['voucher-amount']), req.body['voucher-note'] !== '' ? req.body['voucher-note'] : null).catch((e) => {
const voucherCode = await unifi.create(types(req.body['voucher-type'] === 'custom' ? `${req.body['voucher-duration-type'] === 'day' ? (parseInt(req.body['voucher-duration']) * 24 * 60) : req.body['voucher-duration-type'] === 'hour' ? (parseInt(req.body['voucher-duration']) * 60) : parseInt(req.body['voucher-duration'])},${req.body['voucher-usage'] === '-1' ? req.body['voucher-quota'] : req.body['voucher-usage']},${req.body['voucher-upload-limit']},${req.body['voucher-download-limit']},${req.body['voucher-data-limit']};` : req.body['voucher-type'], true), parseInt(req.body['voucher-amount']), req.body['voucher-note'] !== '' ? 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'] : ''}/vouchers`);
});

View File

@@ -267,7 +267,7 @@
<div class="mt-2">
<select id="voucher-type" name="voucher-type" class="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 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 [&_*]:text-black">
<% voucher_types.forEach((type) => { %>
<option value="<%= type.raw %>"><%= timeConvert(type.expiration) %>, <%= type.usage === '1' ? 'single-use' : 'multi-use' %><%= typeof type.upload === "undefined" && typeof type.download === "undefined" && typeof type.megabytes === "undefined" ? ', no limits' : '' %><%= typeof type.upload !== "undefined" ? `, upload bandwidth limit: ${type.upload} kb/s` : '' %><%= typeof type.download !== "undefined" ? `, download bandwidth limit: ${type.download} kb/s` : '' %><%= typeof type.megabytes !== "undefined" ? `, quota limit: ${type.megabytes} mb` : '' %></option>
<option value="<%= type.raw %>"><%= timeConvert(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` : '' %></option>
<% }); %>
<% if(voucher_custom) { %>
<option value="custom">Custom</option>
@@ -288,20 +288,32 @@
</div>
</div>
<div class="custom-voucher-field hidden border-t border-black/5 dark:border-white/25">
<label for="voucher-duration" class="mt-4 block text-sm font-medium leading-6 text-gray-900 dark:text-white">Duration (in Minutes)</label>
<div class="mt-2">
<input type="number" min="1" step="1" value="60" id="voucher-duration" name="voucher-duration" 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">
<label for="voucher-duration" class="mt-4 block text-sm font-medium leading-6 text-gray-900 dark:text-white">Duration</label>
<div class="mt-2 grid grid-cols-2 gap-2">
<input type="number" min="1" step="1" value="8" id="voucher-duration" name="voucher-duration" required class="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">
<select id="voucher-duration-type" name="voucher-duration-type" class="block w-full rounded-md border-0 py-1.5 pl-3 pr-10 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 [&_*]:text-black">
<option value="minute">Minute(s)</option>
<option value="hour" selected>Hour(s)</option>
<option value="day">Day(s)</option>
</select>
</div>
</div>
<div class="custom-voucher-field hidden">
<label for="voucher-usage" class="block text-sm font-medium leading-6 text-gray-900 dark:text-white">Voucher Usage</label>
<div class="mt-2">
<select id="voucher-usage" name="voucher-usage" class="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 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 [&_*]:text-black">
<option value="0">Multi-use</option>
<option value="0">Multi-use (Unlimited)</option>
<option value="-1">Multi-use (Limited/Quota)</option>
<option value="1">Single-use</option>
</select>
</div>
</div>
<div class="custom-voucher-field-quota hidden">
<label for="voucher-quota" class="block text-sm font-medium leading-6 text-gray-900 dark:text-white">Voucher Limit/Quota</label>
<div class="mt-2">
<input type="number" min="2" step="1" value="2" id="voucher-quota" name="voucher-quota" 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>
<div class="custom-voucher-field hidden">
<label for="voucher-data-limit" class="block text-sm font-medium leading-6 text-gray-900 dark:text-white">Data Limit (in MB)</label>
<div class="mt-2">
@@ -423,7 +435,9 @@
const bulkPrintDialog = document.querySelector('#bulk-print-dialog');
const createForm = document.querySelector('#voucher-form');
const voucherTypeField = document.querySelector('#voucher-type');
const voucherUsageField = document.querySelector('#voucher-usage');
const customVoucherFields = document.querySelectorAll('.custom-voucher-field');
const customVoucherFieldQuota = document.querySelectorAll('.custom-voucher-field-quota');
const createButtonHeader = document.querySelector('#create-button-header');
const createButtonInfo = document.querySelector('#create-button-info');
const cancelButton = document.querySelector('#cancel');
@@ -523,6 +537,17 @@
});
}
});
voucherUsageField.addEventListener('change', () => {
if(voucherUsageField.value === '-1') {
customVoucherFieldQuota.forEach((el) => {
el.classList.remove('hidden');
});
} else {
customVoucherFieldQuota.forEach((el) => {
el.classList.add('hidden');
});
}
});
vouchers.forEach((el) => {
el.addEventListener('click', async () => {
const htmlRes = await fetch(`<%= baseUrl %>/voucher/${el.dataset.id}`);