mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-03-31 06:24:00 -04:00
Refactored print.js, bulk-print.ejs, details.ejs, email.ejs, print.ejs, voucher.ejs and size.js for object compatibility with the UniFi Integration API. Updated the unifi.js module to implement the UniFi Integration API. Added the UNIFI_TOKEN to variables.js. Added fetch.js util. Check for undefined state in notes.js. Added undici to the dependencies. server.js refactored for compatibility with the UniFi integration API. Fixed incorrect quote filter within server.js. Temporary fixed guest mapping to voucher_code instead of ids
This commit is contained in:
@@ -171,7 +171,7 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
doc.font('Roboto-Regular')
|
doc.font('Roboto-Regular')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(vouchers[item].quota === 1 ? t('singleUse') : vouchers[item].quota === 0 ? t('multiUse') : t('multiUse'));
|
.text(!vouchers[item].authorizedGuestLimit ? t('multiUse') : vouchers[item].authorizedGuestLimit === 1 ? t('singleUse') : t('multiUse'));
|
||||||
|
|
||||||
doc.font('Roboto-Bold')
|
doc.font('Roboto-Bold')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
@@ -180,9 +180,9 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
doc.font('Roboto-Regular')
|
doc.font('Roboto-Regular')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(time(vouchers[item].duration, language));
|
.text(time(vouchers[item].timeLimitMinutes, language));
|
||||||
|
|
||||||
if (vouchers[item].qos_usage_quota) {
|
if (vouchers[item].dataUsageLimitMBytes) {
|
||||||
doc.font('Roboto-Bold')
|
doc.font('Roboto-Bold')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(`${t('dataLimit')}: `, {
|
.text(`${t('dataLimit')}: `, {
|
||||||
@@ -190,10 +190,10 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
doc.font('Roboto-Regular')
|
doc.font('Roboto-Regular')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(`${bytes(vouchers[item].qos_usage_quota, 2)}`);
|
.text(`${bytes(vouchers[item].dataUsageLimitMBytes, 2)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vouchers[item].qos_rate_max_down) {
|
if (vouchers[item].rxRateLimitKbps) {
|
||||||
doc.font('Roboto-Bold')
|
doc.font('Roboto-Bold')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(`${t('downloadLimit')}: `, {
|
.text(`${t('downloadLimit')}: `, {
|
||||||
@@ -201,10 +201,10 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
doc.font('Roboto-Regular')
|
doc.font('Roboto-Regular')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(`${bytes(vouchers[item].qos_rate_max_down, 1, true)}`);
|
.text(`${bytes(vouchers[item].rxRateLimitKbps, 1, true)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vouchers[item].qos_rate_max_up) {
|
if (vouchers[item].txRateLimitKbps) {
|
||||||
doc.font('Roboto-Bold')
|
doc.font('Roboto-Bold')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(`${t('uploadLimit')}: `, {
|
.text(`${t('uploadLimit')}: `, {
|
||||||
@@ -212,7 +212,7 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
doc.font('Roboto-Regular')
|
doc.font('Roboto-Regular')
|
||||||
.fontSize(10)
|
.fontSize(10)
|
||||||
.text(`${bytes(vouchers[item].qos_rate_max_up, 1, true)}`);
|
.text(`${bytes(vouchers[item].txRateLimitKbps, 1, true)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,40 +303,40 @@ module.exports = {
|
|||||||
printer.invert(true);
|
printer.invert(true);
|
||||||
printer.print(`${t('type')}:`);
|
printer.print(`${t('type')}:`);
|
||||||
printer.invert(false);
|
printer.invert(false);
|
||||||
printer.print(voucher.quota === 1 ? ` ${t('singleUse')}` : voucher.quota === 0 ? ` ${t('multiUse')}` : ` ${t('multiUse')}`);
|
printer.print(!voucher.authorizedGuestLimit ? ` ${t('multiUse')}` : voucher.authorizedGuestLimit === 1 ? ` ${t('singleUse')}` : ` ${t('multiUse')}`);
|
||||||
printer.newLine();
|
printer.newLine();
|
||||||
|
|
||||||
printer.setTextDoubleHeight();
|
printer.setTextDoubleHeight();
|
||||||
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, language)}`);
|
printer.print(` ${time(voucher.timeLimitMinutes, language)}`);
|
||||||
printer.newLine();
|
printer.newLine();
|
||||||
|
|
||||||
if(voucher.qos_usage_quota) {
|
if(voucher.dataUsageLimitMBytes) {
|
||||||
printer.setTextDoubleHeight();
|
printer.setTextDoubleHeight();
|
||||||
printer.invert(true);
|
printer.invert(true);
|
||||||
printer.print(`${t('dataLimit')}:`);
|
printer.print(`${t('dataLimit')}:`);
|
||||||
printer.invert(false);
|
printer.invert(false);
|
||||||
printer.print(` ${bytes(voucher.qos_usage_quota, 2)}`);
|
printer.print(` ${bytes(voucher.dataUsageLimitMBytes, 2)}`);
|
||||||
printer.newLine();
|
printer.newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(voucher.qos_rate_max_down) {
|
if(voucher.rxRateLimitKbps) {
|
||||||
printer.setTextDoubleHeight();
|
printer.setTextDoubleHeight();
|
||||||
printer.invert(true);
|
printer.invert(true);
|
||||||
printer.print(`${t('downloadLimit')}:`);
|
printer.print(`${t('downloadLimit')}:`);
|
||||||
printer.invert(false);
|
printer.invert(false);
|
||||||
printer.print(` ${bytes(voucher.qos_rate_max_down, 1, true)}`);
|
printer.print(` ${bytes(voucher.rxRateLimitKbps, 1, true)}`);
|
||||||
printer.newLine();
|
printer.newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(voucher.qos_rate_max_up) {
|
if(voucher.txRateLimitKbps) {
|
||||||
printer.setTextDoubleHeight();
|
printer.setTextDoubleHeight();
|
||||||
printer.invert(true);
|
printer.invert(true);
|
||||||
printer.print(`${t('uploadLimit')}:`);
|
printer.print(`${t('uploadLimit')}:`);
|
||||||
printer.invert(false);
|
printer.invert(false);
|
||||||
printer.print(` ${bytes(voucher.qos_rate_max_up, 1, true)}`);
|
printer.print(` ${bytes(voucher.txRateLimitKbps, 1, true)}`);
|
||||||
printer.newLine();
|
printer.newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
185
modules/unifi.js
185
modules/unifi.js
@@ -8,6 +8,7 @@ const unifi = require('node-unifi');
|
|||||||
*/
|
*/
|
||||||
const variables = require('./variables');
|
const variables = require('./variables');
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
|
const fetch = require('../utils/fetch');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UniFi Settings
|
* UniFi Settings
|
||||||
@@ -68,7 +69,7 @@ const startSession = () => {
|
|||||||
/**
|
/**
|
||||||
* UniFi module functions
|
* UniFi module functions
|
||||||
*
|
*
|
||||||
* @type {{create: (function(*, number=, null=, boolean=): Promise<*>), remove: (function(*, boolean=): Promise<*>), list: (function(boolean=): Promise<*>), guests: (function(boolean=): Promise<*>)}}
|
* @type {{create: (function(*, number=, string=): Promise<*>), remove: (function(*): Promise<*>), list: (function(): Promise<*>), guests: (function(boolean=): Promise<*>)}}
|
||||||
*/
|
*/
|
||||||
const unifiModule = {
|
const unifiModule = {
|
||||||
/**
|
/**
|
||||||
@@ -77,57 +78,50 @@ const unifiModule = {
|
|||||||
* @param type
|
* @param type
|
||||||
* @param amount
|
* @param amount
|
||||||
* @param note
|
* @param note
|
||||||
* @param retry
|
|
||||||
* @return {Promise<unknown>}
|
* @return {Promise<unknown>}
|
||||||
*/
|
*/
|
||||||
create: (type, amount = 1, note = null, retry = true) => {
|
create: (type, amount = 1, note = '') => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
startSession().then(() => {
|
// Set base 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) => {
|
const data = {
|
||||||
if(amount > 1) {
|
count: amount,
|
||||||
log.info(`[UniFi] Created ${amount} vouchers`);
|
name: note,
|
||||||
resolve(true);
|
timeLimitMinutes: type.expiration
|
||||||
} else {
|
};
|
||||||
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('')}`;
|
|
||||||
log.info(`[UniFi] Created voucher with code: ${voucher}`);
|
|
||||||
resolve(voucher);
|
|
||||||
}).catch((e) => {
|
|
||||||
log.error('[UniFi] Error while getting voucher!');
|
|
||||||
log.debug(e);
|
|
||||||
reject('[UniFi] Error while getting voucher!');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).catch((e) => {
|
|
||||||
log.error('[UniFi] Error while creating voucher!');
|
|
||||||
log.debug(e);
|
|
||||||
|
|
||||||
// Check if token expired, if true attempt login then try again
|
// Set voucher limit usage if limited
|
||||||
if (e.response) {
|
if(parseInt(type.usage) !== 0) {
|
||||||
if(e.response.status === 401 && retry) {
|
data.authorizedGuestLimit = parseInt(type.usage);
|
||||||
log.info('[UniFi] Attempting re-authentication & retry...');
|
}
|
||||||
|
|
||||||
controller = null;
|
// Set data usage limit if limited
|
||||||
unifiModule.create(type, amount, note, false).then((e) => {
|
if(typeof type.megabytes !== "undefined") {
|
||||||
resolve(e);
|
data.dataUsageLimitMBytes = type.megabytes;
|
||||||
}).catch((e) => {
|
}
|
||||||
reject(e);
|
|
||||||
});
|
// Set download speed limit if limited
|
||||||
} else {
|
if(typeof type.download !== "undefined") {
|
||||||
// Something else went wrong lets clear the current controller so a user can retry
|
data.rxRateLimitKbps = type.download;
|
||||||
log.error(`[UniFi] Unexpected ${JSON.stringify({status: e.response.status, retry})} cleanup controller...`);
|
}
|
||||||
controller = null;
|
|
||||||
reject('[UniFi] Error while creating voucher!');
|
// Set upload speed limit if limited
|
||||||
}
|
if(typeof type.upload !== "undefined") {
|
||||||
} else {
|
data.txRateLimitKbps = type.upload;
|
||||||
// Something else went wrong lets clear the current controller so a user can retry
|
}
|
||||||
log.error('[UniFi] Unexpected cleanup controller...');
|
|
||||||
controller = null;
|
fetch(`/hotspot/vouchers`, 'POST', {}, data).then((response) => {
|
||||||
reject('[UniFi] Error while creating voucher!');
|
if(amount > 1) {
|
||||||
}
|
log.info(`[UniFi] Created ${amount} vouchers`);
|
||||||
});
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
const voucherCode = `${[response.vouchers[0].code.slice(0, 5), '-', response.vouchers[0].code.slice(5)].join('')}`;
|
||||||
|
log.info(`[UniFi] Created voucher with code: ${voucherCode}`);
|
||||||
|
resolve(voucherCode);
|
||||||
|
}
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
reject(e);
|
log.error('[UniFi] Error while creating voucher!');
|
||||||
|
log.debug(e);
|
||||||
|
reject('[UniFi] Error while creating voucher!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -136,44 +130,17 @@ const unifiModule = {
|
|||||||
* Removes a UniFi Voucher
|
* Removes a UniFi Voucher
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
* @param retry
|
|
||||||
* @return {Promise<unknown>}
|
* @return {Promise<unknown>}
|
||||||
*/
|
*/
|
||||||
remove: (id, retry = true) => {
|
remove: (id) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
startSession().then(() => {
|
fetch(`/hotspot/vouchers/${id}`, 'DELETE').then(() => {
|
||||||
controller.revokeVoucher(id).then(() => {
|
log.info(`[UniFi] Deleted voucher: ${id}`);
|
||||||
resolve(true);
|
resolve(true);
|
||||||
}).catch((e) => {
|
|
||||||
log.error('[UniFi] Error while removing voucher!');
|
|
||||||
log.debug(e);
|
|
||||||
|
|
||||||
// Check if token expired, if true attempt login then try again
|
|
||||||
if (e.response) {
|
|
||||||
if(e.response.status === 401 && retry) {
|
|
||||||
log.info('[UniFi] Attempting re-authentication & retry...');
|
|
||||||
|
|
||||||
controller = null;
|
|
||||||
unifiModule.remove(id, false).then((e) => {
|
|
||||||
resolve(e);
|
|
||||||
}).catch((e) => {
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Something else went wrong lets clear the current controller so a user can retry
|
|
||||||
log.error(`[UniFi] Unexpected ${JSON.stringify({status: e.response.status, retry})} cleanup controller...`);
|
|
||||||
controller = null;
|
|
||||||
reject('[UniFi] Error while removing voucher!');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Something else went wrong lets clear the current controller so a user can retry
|
|
||||||
log.error('[UniFi] Unexpected cleanup controller...');
|
|
||||||
controller = null;
|
|
||||||
reject('[UniFi] Error while removing voucher!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
reject(e);
|
log.error('[UniFi] Error while removing voucher!');
|
||||||
|
log.debug(e);
|
||||||
|
reject('[UniFi] Error while removing voucher!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -181,45 +148,22 @@ const unifiModule = {
|
|||||||
/**
|
/**
|
||||||
* Returns a list with all UniFi Vouchers
|
* Returns a list with all UniFi Vouchers
|
||||||
*
|
*
|
||||||
* @param retry
|
|
||||||
* @return {Promise<unknown>}
|
* @return {Promise<unknown>}
|
||||||
*/
|
*/
|
||||||
list: (retry = true) => {
|
list: () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
startSession().then(() => {
|
fetch('/hotspot/vouchers', 'GET', {
|
||||||
controller.getVouchers().then((vouchers) => {
|
limit: 10000
|
||||||
log.info(`[UniFi] Found ${vouchers.length} voucher(s)`);
|
}).then((vouchers) => {
|
||||||
resolve(vouchers);
|
log.info(`[UniFi] Found ${vouchers.length} voucher(s)`);
|
||||||
}).catch((e) => {
|
resolve(vouchers.sort((a, b) => {
|
||||||
log.error('[UniFi] Error while getting vouchers!');
|
if (a.createdAt > b.createdAt) return -1;
|
||||||
log.debug(e);
|
if (a.createdAt < b.createdAt) return 1;
|
||||||
|
}));
|
||||||
// Check if token expired, if true attempt login then try again
|
|
||||||
if (e.response) {
|
|
||||||
if(e.response.status === 401 && retry) {
|
|
||||||
log.info('[UniFi] Attempting re-authentication & retry...');
|
|
||||||
|
|
||||||
controller = null;
|
|
||||||
unifiModule.list(false).then((e) => {
|
|
||||||
resolve(e);
|
|
||||||
}).catch((e) => {
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Something else went wrong lets clear the current controller so a user can retry
|
|
||||||
log.error(`[UniFi] Unexpected ${JSON.stringify({status: e.response.status, retry})} cleanup controller...`);
|
|
||||||
controller = null;
|
|
||||||
reject('[UniFi] Error while getting vouchers!');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Something else went wrong lets clear the current controller so a user can retry
|
|
||||||
log.error('[UniFi] Unexpected cleanup controller...');
|
|
||||||
controller = null;
|
|
||||||
reject('[UniFi] Error while getting vouchers!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
reject(e);
|
log.error('[UniFi] Error while getting vouchers!');
|
||||||
|
log.debug(e);
|
||||||
|
reject('[UniFi] Error while getting vouchers!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -232,6 +176,17 @@ const unifiModule = {
|
|||||||
*/
|
*/
|
||||||
guests: (retry = true) => {
|
guests: (retry = true) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// fetch('/clients', 'GET', {
|
||||||
|
// filter: 'access.type.eq(\'GUEST\')',
|
||||||
|
// limit: 10000
|
||||||
|
// }).then((clients) => {
|
||||||
|
// console.log(clients)
|
||||||
|
// }).catch((e) => {
|
||||||
|
// log.error('[UniFi] Error while getting vouchers!');
|
||||||
|
// log.debug(e);
|
||||||
|
// reject('[UniFi] Error while getting vouchers!');
|
||||||
|
// });
|
||||||
|
|
||||||
startSession().then(() => {
|
startSession().then(() => {
|
||||||
controller.getGuests().then((guests) => {
|
controller.getGuests().then((guests) => {
|
||||||
log.info(`[UniFi] Found ${guests.length} guest(s)`);
|
log.info(`[UniFi] Found ${guests.length} guest(s)`);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ module.exports = {
|
|||||||
unifiPort: config('unifi_port') || process.env.UNIFI_PORT || 443,
|
unifiPort: config('unifi_port') || process.env.UNIFI_PORT || 443,
|
||||||
unifiUsername: config('unifi_username') || process.env.UNIFI_USERNAME || 'admin',
|
unifiUsername: config('unifi_username') || process.env.UNIFI_USERNAME || 'admin',
|
||||||
unifiPassword: config('unifi_password') || process.env.UNIFI_PASSWORD || 'password',
|
unifiPassword: config('unifi_password') || process.env.UNIFI_PASSWORD || 'password',
|
||||||
|
unifiToken: config('unifi_token') || process.env.UNIFI_TOKEN || '',
|
||||||
unifiSiteId: config('unifi_site_id') || process.env.UNIFI_SITE_ID || 'default',
|
unifiSiteId: config('unifi_site_id') || process.env.UNIFI_SITE_ID || 'default',
|
||||||
unifiSsid: config('unifi_ssid') || process.env.UNIFI_SSID || '',
|
unifiSsid: config('unifi_ssid') || process.env.UNIFI_SSID || '',
|
||||||
unifiSsidPassword: config('unifi_ssid_password') || process.env.UNIFI_SSID_PASSWORD || '',
|
unifiSsidPassword: config('unifi_ssid_password') || process.env.UNIFI_SSID_PASSWORD || '',
|
||||||
|
|||||||
24
package-lock.json
generated
24
package-lock.json
generated
@@ -21,7 +21,8 @@
|
|||||||
"node-unifi": "^2.5.1",
|
"node-unifi": "^2.5.1",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
"pdfkit": "^0.17.1",
|
"pdfkit": "^0.17.1",
|
||||||
"qrcode": "^1.5.4"
|
"qrcode": "^1.5.4",
|
||||||
|
"undici": "^5.29.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.11",
|
"@tailwindcss/cli": "^4.1.11",
|
||||||
@@ -46,6 +47,15 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hapi/hoek": {
|
"node_modules/@hapi/hoek": {
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||||
@@ -3699,6 +3709,18 @@
|
|||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "5.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||||
|
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
"node-unifi": "^2.5.1",
|
"node-unifi": "^2.5.1",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
"pdfkit": "^0.17.1",
|
"pdfkit": "^0.17.1",
|
||||||
"qrcode": "^1.5.4"
|
"qrcode": "^1.5.4",
|
||||||
|
"undici": "^5.29.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.11",
|
"@tailwindcss/cli": "^4.1.11",
|
||||||
|
|||||||
58
server.js
58
server.js
@@ -187,7 +187,7 @@ if(variables.serviceWeb) {
|
|||||||
|
|
||||||
// Get voucher from cache
|
// Get voucher from cache
|
||||||
const voucher = cache.vouchers.find((e) => {
|
const voucher = cache.vouchers.find((e) => {
|
||||||
return e._id === req.body.id;
|
return e.id === req.body.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(voucher) {
|
if(voucher) {
|
||||||
@@ -275,7 +275,7 @@ if(variables.serviceWeb) {
|
|||||||
unifiSsid: variables.unifiSsid,
|
unifiSsid: variables.unifiSsid,
|
||||||
unifiSsidPassword: variables.unifiSsidPassword,
|
unifiSsidPassword: variables.unifiSsidPassword,
|
||||||
qr: await qr(),
|
qr: await qr(),
|
||||||
voucherId: voucherData._id,
|
voucherId: voucherData.id,
|
||||||
voucherCode
|
voucherCode
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -410,7 +410,7 @@ if(variables.serviceWeb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
const voucher = cache.vouchers.find((e) => {
|
||||||
return e._id === req.params.id;
|
return e.id === req.params.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(voucher) {
|
if(voucher) {
|
||||||
@@ -441,7 +441,7 @@ if(variables.serviceWeb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
const voucher = cache.vouchers.find((e) => {
|
||||||
return e._id === req.params.id;
|
return e.id === req.params.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(voucher) {
|
if(voucher) {
|
||||||
@@ -476,7 +476,7 @@ if(variables.serviceWeb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
const voucher = cache.vouchers.find((e) => {
|
||||||
return e._id === req.params.id;
|
return e.id === req.params.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(voucher) {
|
if(voucher) {
|
||||||
@@ -506,7 +506,7 @@ if(variables.serviceWeb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
const voucher = cache.vouchers.find((e) => {
|
||||||
return e._id === req.params.id;
|
return e.id === req.params.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(voucher) {
|
if(voucher) {
|
||||||
@@ -580,31 +580,31 @@ if(variables.serviceWeb) {
|
|||||||
voucher_custom: variables.voucherCustom,
|
voucher_custom: variables.voucherCustom,
|
||||||
vouchers: cache.vouchers.filter((item) => {
|
vouchers: cache.vouchers.filter((item) => {
|
||||||
if(variables.authOidcRestrictVisibility && req.oidc) {
|
if(variables.authOidcRestrictVisibility && req.oidc) {
|
||||||
return notes(item.note).auth_oidc_domain === user.email.split('@')[1].toLowerCase();
|
return item.name && notes(item.name).auth_oidc_domain === user.email.split('@')[1].toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}).filter((item) => {
|
}).filter((item) => {
|
||||||
if(req.query.status === 'available') {
|
if(req.query.status === 'available') {
|
||||||
return item.used === 0 && item.status !== 'EXPIRED';
|
return item.authorizedGuestCount === 0 && !item.expired;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.query.status === 'in-use') {
|
if(req.query.status === 'in-use') {
|
||||||
return item.used > 0 && item.status !== 'EXPIRED';
|
return item.authorizedGuestCount > 0 && !item.expired;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.query.status === 'expired') {
|
if(req.query.status === 'expired') {
|
||||||
return item.status === 'EXPIRED';
|
return item.expired;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}).filter((item) => {
|
}).filter((item) => {
|
||||||
if(req.query.quota === 'multi-use') {
|
if(req.query.quota === 'multi-use') {
|
||||||
return item.quota === 0;
|
return (item.authorizedGuestLimit && item.authorizedGuestLimit > 1) || !item.authorizedGuestLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.query.quota === 'single-use') {
|
if(req.query.quota === 'single-use') {
|
||||||
return item.quota !== 0;
|
return item.authorizedGuestLimit && item.authorizedGuestLimit === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -615,18 +615,18 @@ if(variables.serviceWeb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(req.query.sort === 'note') {
|
if(req.query.sort === 'note') {
|
||||||
if ((notes(a.note).note || '') > (notes(b.note).note || '')) return -1;
|
if ((notes(a.name).note || '') > (notes(b.name).note || '')) return -1;
|
||||||
if ((notes(a.note).note || '') < (notes(b.note).note || '')) return 1;
|
if ((notes(a.name).note || '') < (notes(b.name).note || '')) return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.query.sort === 'duration') {
|
if(req.query.sort === 'duration') {
|
||||||
if (a.duration > b.duration) return -1;
|
if (a.timeLimitMinutes > b.timeLimitMinutes) return -1;
|
||||||
if (a.duration < b.duration) return 1;
|
if (a.timeLimitMinutes < b.timeLimitMinutes) return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.query.sort === 'status') {
|
if(req.query.sort === 'status') {
|
||||||
if (a.used > b.used) return -1;
|
if (a.authorizedGuestCount > b.authorizedGuestCount) return -1;
|
||||||
if (a.used < b.used) return 1;
|
if (a.authorizedGuestCount < b.authorizedGuestCount) return 1;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
updated: cache.updated,
|
updated: cache.updated,
|
||||||
@@ -639,10 +639,10 @@ if(variables.serviceWeb) {
|
|||||||
});
|
});
|
||||||
app.get('/voucher/:id', [authorization.web], async (req, res) => {
|
app.get('/voucher/:id', [authorization.web], async (req, res) => {
|
||||||
const voucher = cache.vouchers.find((e) => {
|
const voucher = cache.vouchers.find((e) => {
|
||||||
return e._id === req.params.id;
|
return e.id === req.params.id;
|
||||||
});
|
});
|
||||||
const guests = cache.guests.filter((e) => {
|
const guests = cache.guests.filter((e) => {
|
||||||
return e.voucher_id === req.params.id;
|
return e.voucher_code === voucher.code;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(voucher) {
|
if(voucher) {
|
||||||
@@ -717,7 +717,7 @@ if(variables.serviceWeb) {
|
|||||||
|
|
||||||
const vouchers = req.body.vouchers.map((voucher) => {
|
const vouchers = req.body.vouchers.map((voucher) => {
|
||||||
return cache.vouchers.find((e) => {
|
return cache.vouchers.find((e) => {
|
||||||
return e._id === voucher;
|
return e.id === voucher;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -818,13 +818,13 @@ if(variables.serviceApi) {
|
|||||||
message: 'OK',
|
message: 'OK',
|
||||||
vouchers: cache.vouchers.map((voucher) => {
|
vouchers: cache.vouchers.map((voucher) => {
|
||||||
return {
|
return {
|
||||||
id: voucher._id,
|
id: voucher.id,
|
||||||
code: `${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
|
code: `${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
|
||||||
type: voucher.quota === 1 ? 'single' : voucher.quota === 0 ? 'multi' : 'multi',
|
type: !voucher.authorizedGuestLimit ? 'multi' : voucher.authorizedGuestLimit === 1 ? 'single' : 'multi',
|
||||||
duration: voucher.duration,
|
duration: voucher.timeLimitMinutes,
|
||||||
data_limit: voucher.qos_usage_quota ? voucher.qos_usage_quota : null,
|
data_limit: voucher.dataUsageLimitMBytes ? voucher.dataUsageLimitMBytes : null,
|
||||||
download_limit: voucher.qos_rate_max_down ? voucher.qos_rate_max_down : null,
|
download_limit: voucher.rxRateLimitKbps ? voucher.rxRateLimitKbps : null,
|
||||||
upload_limit: voucher.qos_rate_max_up ? voucher.qos_rate_max_up : null
|
upload_limit: voucher.txRateLimitKbps ? voucher.txRateLimitKbps : null
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
updated: cache.updated
|
updated: cache.updated
|
||||||
@@ -920,7 +920,7 @@ if(variables.serviceApi) {
|
|||||||
data: {
|
data: {
|
||||||
message: 'OK',
|
message: 'OK',
|
||||||
voucher: {
|
voucher: {
|
||||||
id: voucherData._id,
|
id: voucherData.id,
|
||||||
code: voucherCode
|
code: voucherCode
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
@@ -936,7 +936,7 @@ if(variables.serviceApi) {
|
|||||||
data: {
|
data: {
|
||||||
message: 'OK',
|
message: 'OK',
|
||||||
voucher: {
|
voucher: {
|
||||||
id: voucherData._id,
|
id: voucherData.id,
|
||||||
code: voucherCode
|
code: voucherCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,18 +43,18 @@
|
|||||||
<ul role="list" class="max-h-96 h-96 overflow-y-auto mt-2 rounded-md border-0 divide-y divide-black/5 dark:divide-white/5 dark:bg-white/5 ring-1 ring-inset ring-gray-300 dark:ring-white/10">
|
<ul role="list" class="max-h-96 h-96 overflow-y-auto mt-2 rounded-md border-0 divide-y divide-black/5 dark:divide-white/5 dark:bg-white/5 ring-1 ring-inset ring-gray-300 dark:ring-white/10">
|
||||||
<% vouchers.forEach((voucher) => { %>
|
<% vouchers.forEach((voucher) => { %>
|
||||||
<li class="relative flex items-center space-x-4 px-4 py-4">
|
<li class="relative flex items-center space-x-4 px-4 py-4">
|
||||||
<input id="voucher-<%= voucher._id %>" aria-describedby="voucher-<%= voucher._id %>" name="vouchers" type="checkbox" value="<%= voucher._id %>" class="col-start-1 row-start-1 appearance-none rounded-sm border-0 dark:bg-white/5 ring-1 ring-inset ring-gray-300 dark:ring-white/10 focus:ring-2 focus:ring-sky-600 focus:ring-offset-0">
|
<input id="voucher-<%= voucher.id %>" aria-describedby="voucher-<%= voucher.id %>" name="vouchers" type="checkbox" value="<%= voucher.id %>" class="col-start-1 row-start-1 appearance-none rounded-sm border-0 dark:bg-white/5 ring-1 ring-inset ring-gray-300 dark:ring-white/10 focus:ring-2 focus:ring-sky-600 focus:ring-offset-0">
|
||||||
<label for="voucher-<%= voucher._id %>" class="voucher min-w-0 flex-auto">
|
<label for="voucher-<%= voucher.id %>" class="voucher min-w-0 flex-auto">
|
||||||
<div class="flex items-center gap-x-3">
|
<div class="flex items-center gap-x-3">
|
||||||
<h2 class="min-w-0 text-sm font-semibold leading-6 text-gray-900 dark:text-white">
|
<h2 class="min-w-0 text-sm font-semibold leading-6 text-gray-900 dark:text-white">
|
||||||
<div class="flex gap-x-2">
|
<div class="flex gap-x-2">
|
||||||
<span class="tabular-nums pointer-events-none no-underline text-inherit"><%= voucher.code.slice(0, 5) %>-<%= voucher.code.slice(5) %></span>
|
<span class="tabular-nums pointer-events-none no-underline text-inherit"><%= voucher.code.slice(0, 5) %>-<%= voucher.code.slice(5) %></span>
|
||||||
<% if (voucher.status === 'EXPIRED') { %>
|
<% if (voucher.expired) { %>
|
||||||
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-red-50 text-red-800 ring-red-600/20 dark:text-red-400 dark:bg-red-400/10 dark:ring-red-400/20">
|
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-red-50 text-red-800 ring-red-600/20 dark:text-red-400 dark:bg-red-400/10 dark:ring-red-400/20">
|
||||||
Expired
|
Expired
|
||||||
</div>
|
</div>
|
||||||
<% } else {%>
|
<% } else {%>
|
||||||
<% if(voucher.used > 0) { %>
|
<% if(voucher.authorizedGuestCount > 0) { %>
|
||||||
<div class="rounded-full flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:text-yellow-400 dark:bg-yellow-400/10 dark:ring-yellow-400/20">
|
<div class="rounded-full flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:text-yellow-400 dark:bg-yellow-400/10 dark:ring-yellow-400/20">
|
||||||
In Use
|
In Use
|
||||||
</div>
|
</div>
|
||||||
@@ -64,37 +64,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (notesConvert(voucher.note).note) { %>
|
<% if (voucher.name && notesConvert(voucher.name).note) { %>
|
||||||
<div class="hidden sm:block rounded-md flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-gray-50 text-gray-800 ring-gray-600/20 dark:text-gray-400 dark:bg-gray-400/10 dark:ring-gray-400/20">
|
<div class="hidden sm:block rounded-md flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-gray-50 text-gray-800 ring-gray-600/20 dark:text-gray-400 dark:bg-gray-400/10 dark:ring-gray-400/20">
|
||||||
<%= notesConvert(voucher.note).note %>
|
<%= notesConvert(voucher.name).note %>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex items-center gap-x-2.5 text-xs leading-5 text-gray-600 dark:text-gray-400">
|
<div class="mt-2 flex items-center gap-x-2.5 text-xs leading-5 text-gray-600 dark:text-gray-400">
|
||||||
<p class="whitespace-nowrap"><%= voucher.quota === 1 ? 'Single-use' : voucher.quota === 0 ? 'Multi-use (Unlimited)' : `Multi-use (${voucher.quota}x)` %></p>
|
<p class="whitespace-nowrap"><%= !voucher.authorizedGuestLimit ? 'Multi-use (Unlimited)' : voucher.authorizedGuestLimit === 1 ? 'Single-use' : `Multi-use (${voucher.authorizedGuestLimit}x)` %></p>
|
||||||
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="whitespace-nowrap"><%= timeConvert(voucher.duration) %></p>
|
<p class="whitespace-nowrap"><%= timeConvert(voucher.timeLimitMinutes) %></p>
|
||||||
<% if(voucher.qos_usage_quota) { %>
|
<% if(voucher.dataUsageLimitMBytes) { %>
|
||||||
<svg viewBox="0 0 2 2" class="hidden sm:block h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="hidden sm:block h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="hidden sm:block truncate"><%= bytesConvert(voucher.qos_usage_quota, 2) %> Data Limit</p>
|
<p class="hidden sm:block truncate"><%= bytesConvert(voucher.dataUsageLimitMBytes, 2) %> Data Limit</p>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(voucher.qos_rate_max_down) { %>
|
<% if(voucher.rxRateLimitKbps) { %>
|
||||||
<svg viewBox="0 0 2 2" class="hidden sm:block h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="hidden sm:block h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="hidden sm:block truncate"><%= bytesConvert(voucher.qos_rate_max_down, 1, true) %> Download Limit</p>
|
<p class="hidden sm:block truncate"><%= bytesConvert(voucher.rxRateLimitKbps, 1, true) %> Download Limit</p>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(voucher.qos_rate_max_up) { %>
|
<% if(voucher.txRateLimitKbps) { %>
|
||||||
<svg viewBox="0 0 2 2" class="hidden sm:block h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="hidden sm:block h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class=" hidden sm:block truncate"><%= bytesConvert(voucher.qos_rate_max_up, 1, true) %> Upload Limit</p>
|
<p class=" hidden sm:block truncate"><%= bytesConvert(voucher.txRateLimitKbps, 1, true) %> Upload Limit</p>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -26,12 +26,12 @@
|
|||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Status</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Status</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0">
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0">
|
||||||
<% if (voucher.status === 'EXPIRED') { %>
|
<% if (voucher.expired) { %>
|
||||||
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-red-50 text-red-800 ring-red-600/20 dark:text-red-400 dark:bg-red-400/10 dark:ring-red-400/20">
|
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-red-50 text-red-800 ring-red-600/20 dark:text-red-400 dark:bg-red-400/10 dark:ring-red-400/20">
|
||||||
Expired
|
Expired
|
||||||
</div>
|
</div>
|
||||||
<% } else {%>
|
<% } else {%>
|
||||||
<% if(voucher.used > 0) { %>
|
<% if(voucher.authorizedGuestCount > 0) { %>
|
||||||
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:text-yellow-400 dark:bg-yellow-400/10 dark:ring-yellow-400/20">
|
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:text-yellow-400 dark:bg-yellow-400/10 dark:ring-yellow-400/20">
|
||||||
In Use
|
In Use
|
||||||
</div>
|
</div>
|
||||||
@@ -43,49 +43,49 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% if(notesConvert(voucher.note).note) { %>
|
<% if(voucher.name && notesConvert(voucher.name).note) { %>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Notes</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Notes</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.note).note %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.name).note %></dd>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(notesConvert(voucher.note).source) { %>
|
<% if(voucher.name && notesConvert(voucher.name).source) { %>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Source</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Source</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.note).source %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.name).source %></dd>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(notesConvert(voucher.note).auth_type) { %>
|
<% if(voucher.name && notesConvert(voucher.name).auth_type) { %>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Authentication</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Authentication</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.note).auth_type %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.name).auth_type %></dd>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(notesConvert(voucher.note).auth_oidc_domain) { %>
|
<% if(voucher.name && notesConvert(voucher.name).auth_oidc_domain) { %>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">OIDC Domain</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">OIDC Domain</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.note).auth_oidc_domain %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= notesConvert(voucher.name).auth_oidc_domain %></dd>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Type</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Type</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.quota === 1 ? 'Single-use' : voucher.quota === 0 ? 'Multi-use (Unlimited)' : `Multi-use (${voucher.quota}x)` %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= !voucher.authorizedGuestLimit ? 'Multi-use (Unlimited)' : voucher.authorizedGuestLimit === 1 ? 'Single-use' : `Multi-use (${voucher.authorizedGuestLimit}x)` %></dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Duration</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Duration</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= timeConvert(voucher.duration) %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= timeConvert(voucher.timeLimitMinutes) %></dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Data Limit</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Data Limit</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.qos_usage_quota ? bytesConvert(voucher.qos_usage_quota, 2) : 'Unlimited' %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.dataUsageLimitMBytes ? bytesConvert(voucher.dataUsageLimitMBytes, 2) : 'Unlimited' %></dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Download Limit</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Download Limit</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.qos_rate_max_down ? bytesConvert(voucher.qos_rate_max_down, 1, true) : 'Unlimited' %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.rxRateLimitKbps ? bytesConvert(voucher.rxRateLimitKbps, 1, true) : 'Unlimited' %></dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
<div class="py-2 grid grid-cols-3 gap-4 px-0">
|
||||||
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Upload Limit</dt>
|
<dt class="text-sm font-medium leading-6 text-gray-900 dark:text-white">Upload Limit</dt>
|
||||||
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.qos_rate_max_up ? bytesConvert(voucher.qos_rate_max_up, 1, true) : 'Unlimited' %></dd>
|
<dd class="text-sm leading-6 text-gray-600 dark:text-gray-400 col-span-2 mt-0"><%= voucher.txRateLimitKbps ? bytesConvert(voucher.txRateLimitKbps, 1, true) : 'Unlimited' %></dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="absolute inset-0 overflow-hidden">
|
<div class="absolute inset-0 overflow-hidden">
|
||||||
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
|
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
|
||||||
<div class="pointer-events-auto w-screen max-w-md">
|
<div class="pointer-events-auto w-screen max-w-md">
|
||||||
<form id="email-form" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher._id %>/email" method="post" enctype="multipart/form-data">
|
<form id="email-form" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher.id %>/email" method="post" enctype="multipart/form-data">
|
||||||
<div class="h-0 flex-1 overflow-y-auto">
|
<div class="h-0 flex-1 overflow-y-auto">
|
||||||
<div class="bg-sky-700 px-4 py-6 sm:px-6">
|
<div class="bg-sky-700 px-4 py-6 sm:px-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="absolute inset-0 overflow-hidden">
|
<div class="absolute inset-0 overflow-hidden">
|
||||||
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
|
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
|
||||||
<div class="pointer-events-auto w-screen max-w-md">
|
<div class="pointer-events-auto w-screen max-w-md">
|
||||||
<form id="print-form" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher._id %>/print" method="post" enctype="multipart/form-data">
|
<form id="print-form" class="flex h-full flex-col divide-y divide-black/5 dark:divide-white/5 bg-white dark:bg-gray-900 shadow-xl" action="<%= baseUrl %>/voucher/<%= voucher.id %>/print" method="post" enctype="multipart/form-data">
|
||||||
<div class="h-0 flex-1 overflow-y-auto">
|
<div class="h-0 flex-1 overflow-y-auto">
|
||||||
<div class="bg-sky-700 px-4 py-6 sm:px-6">
|
<div class="bg-sky-700 px-4 py-6 sm:px-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
|||||||
@@ -140,16 +140,16 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
<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.authorizedGuestLimit ? t('multiUse') : voucher.authorizedGuestLimit === 1 ? t('singleUse') : 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, language) %></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.timeLimitMinutes, language) %></p>
|
||||||
<% if(voucher.qos_usage_quota) { %>
|
<% if(voucher.dataUsageLimitMBytes) { %>
|
||||||
<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.dataUsageLimitMBytes, 2) %></p>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(voucher.qos_rate_max_down) { %>
|
<% if(voucher.rxRateLimitKbps) { %>
|
||||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('downloadLimit') %>:</span> <%= bytesConvert(voucher.qos_rate_max_down, 1, true) %></p>
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('downloadLimit') %>:</span> <%= bytesConvert(voucher.rxRateLimitKbps, 1, true) %></p>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(voucher.qos_rate_max_up) { %>
|
<% if(voucher.txRateLimitKbps) { %>
|
||||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('uploadLimit') %>:</span> <%= bytesConvert(voucher.qos_rate_max_up, 1, true) %></p>
|
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><span style="font-weight: bold;"><%= t('uploadLimit') %>:</span> <%= bytesConvert(voucher.txRateLimitKbps, 1, true) %></p>
|
||||||
<% } %>
|
<% } %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -146,17 +146,17 @@
|
|||||||
<ul role="list" class="divide-y divide-black/5 dark:divide-white/5">
|
<ul role="list" class="divide-y divide-black/5 dark:divide-white/5">
|
||||||
<% vouchers.forEach((voucher) => { %>
|
<% vouchers.forEach((voucher) => { %>
|
||||||
<li class="relative flex items-center space-x-4 px-4 py-4 sm:px-6 lg:px-8 hover:bg-gray-200 dark:hover:bg-gray-800 cursor-pointer">
|
<li class="relative flex items-center space-x-4 px-4 py-4 sm:px-6 lg:px-8 hover:bg-gray-200 dark:hover:bg-gray-800 cursor-pointer">
|
||||||
<div class="voucher min-w-0 flex-auto" data-id="<%= voucher._id %>">
|
<div class="voucher min-w-0 flex-auto" data-id="<%= voucher.id %>">
|
||||||
<div class="flex items-center gap-x-3">
|
<div class="flex items-center gap-x-3">
|
||||||
<h2 class="min-w-0 text-sm font-semibold leading-6 text-gray-900 dark:text-white">
|
<h2 class="min-w-0 text-sm font-semibold leading-6 text-gray-900 dark:text-white">
|
||||||
<div class="flex gap-x-2">
|
<div class="flex gap-x-2">
|
||||||
<span class="tabular-nums pointer-events-none no-underline text-inherit"><%= voucher.code.slice(0, 5) %>-<%= voucher.code.slice(5) %></span>
|
<span class="tabular-nums pointer-events-none no-underline text-inherit"><%= voucher.code.slice(0, 5) %>-<%= voucher.code.slice(5) %></span>
|
||||||
<% if (voucher.status === 'EXPIRED') { %>
|
<% if (voucher.expired) { %>
|
||||||
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-red-50 text-red-800 ring-red-600/20 dark:text-red-400 dark:bg-red-400/10 dark:ring-red-400/20">
|
<div class="rounded-full w-fit py-1 px-2 text-xs font-medium ring-1 ring-inset bg-red-50 text-red-800 ring-red-600/20 dark:text-red-400 dark:bg-red-400/10 dark:ring-red-400/20">
|
||||||
Expired
|
Expired
|
||||||
</div>
|
</div>
|
||||||
<% } else {%>
|
<% } else {%>
|
||||||
<% if(voucher.used > 0) { %>
|
<% if(voucher.authorizedGuestCount > 0) { %>
|
||||||
<div class="rounded-full flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:text-yellow-400 dark:bg-yellow-400/10 dark:ring-yellow-400/20">
|
<div class="rounded-full flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:text-yellow-400 dark:bg-yellow-400/10 dark:ring-yellow-400/20">
|
||||||
In Use
|
In Use
|
||||||
</div>
|
</div>
|
||||||
@@ -166,37 +166,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (notesConvert(voucher.note).note) { %>
|
<% if (voucher.name && notesConvert(voucher.name).note) { %>
|
||||||
<div class="hidden sm:block rounded-md flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-gray-50 text-gray-800 ring-gray-600/20 dark:text-gray-400 dark:bg-gray-400/10 dark:ring-gray-400/20">
|
<div class="hidden sm:block rounded-md flex-none py-1 px-2 text-xs font-medium ring-1 ring-inset bg-gray-50 text-gray-800 ring-gray-600/20 dark:text-gray-400 dark:bg-gray-400/10 dark:ring-gray-400/20">
|
||||||
<%= notesConvert(voucher.note).note %>
|
<%= notesConvert(voucher.name).note %>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex items-center gap-x-2.5 text-xs leading-5 text-gray-600 dark:text-gray-400">
|
<div class="mt-2 flex items-center gap-x-2.5 text-xs leading-5 text-gray-600 dark:text-gray-400">
|
||||||
<p class="whitespace-nowrap"><%= voucher.quota === 1 ? 'Single-use' : voucher.quota === 0 ? 'Multi-use (Unlimited)' : `Multi-use (${voucher.quota}x)` %></p>
|
<p class="whitespace-nowrap"><%= !voucher.authorizedGuestLimit ? 'Multi-use (Unlimited)' : voucher.authorizedGuestLimit === 1 ? 'Single-use' : `Multi-use (${voucher.authorizedGuestLimit}x)` %></p>
|
||||||
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="whitespace-nowrap"><%= timeConvert(voucher.duration) %></p>
|
<p class="whitespace-nowrap"><%= timeConvert(voucher.timeLimitMinutes) %></p>
|
||||||
<% if(voucher.qos_usage_quota) { %>
|
<% if(voucher.dataUsageLimitMBytes) { %>
|
||||||
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="truncate"><%= bytesConvert(voucher.qos_usage_quota, 2) %> Data Limit</p>
|
<p class="truncate"><%= bytesConvert(voucher.dataUsageLimitMBytes, 2) %> Data Limit</p>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(voucher.qos_rate_max_down) { %>
|
<% if(voucher.rxRateLimitKbps) { %>
|
||||||
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="truncate"><%= bytesConvert(voucher.qos_rate_max_down, 1, true) %> Download Limit</p>
|
<p class="truncate"><%= bytesConvert(voucher.rxRateLimitKbps, 1, true) %> Download Limit</p>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(voucher.qos_rate_max_up) { %>
|
<% if(voucher.txRateLimitKbps) { %>
|
||||||
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 flex-none fill-gray-500 dark:fill-gray-300">
|
||||||
<circle cx="1" cy="1" r="1" />
|
<circle cx="1" cy="1" r="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="truncate"><%= bytesConvert(voucher.qos_rate_max_up, 1, true) %> Upload Limit</p>
|
<p class="truncate"><%= bytesConvert(voucher.txRateLimitKbps, 1, true) %> Upload Limit</p>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
<path d="M10.5 10.5a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963 5.23 5.23 0 0 0-3.434-1.279h-1.875a.375.375 0 0 1-.375-.375V10.5Z" />
|
<path d="M10.5 10.5a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963 5.23 5.23 0 0 0-3.434-1.279h-1.875a.375.375 0 0 1-.375-.375V10.5Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-id="<%= voucher._id %>" class="voucher-email relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white<%= !email_enabled ? ' hidden' : '' %>">
|
<button type="button" data-id="<%= voucher.id %>" class="voucher-email relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white<%= !email_enabled ? ' hidden' : '' %>">
|
||||||
<span class="absolute -inset-1.5"></span>
|
<span class="absolute -inset-1.5"></span>
|
||||||
<span class="sr-only">Email Voucher Code</span>
|
<span class="sr-only">Email Voucher Code</span>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
@@ -217,14 +217,14 @@
|
|||||||
<path d="M22.5 6.908V6.75a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3v.158l9.714 5.978a1.5 1.5 0 0 0 1.572 0L22.5 6.908Z" />
|
<path d="M22.5 6.908V6.75a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3v.158l9.714 5.978a1.5 1.5 0 0 0 1.572 0L22.5 6.908Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-id="<%= voucher._id %>" class="voucher-print relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white<%= !printer_enabled ? ' hidden' : '' %>">
|
<button type="button" data-id="<%= voucher.id %>" class="voucher-print relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white<%= !printer_enabled ? ' hidden' : '' %>">
|
||||||
<span class="absolute -inset-1.5"></span>
|
<span class="absolute -inset-1.5"></span>
|
||||||
<span class="sr-only">Print Voucher Code</span>
|
<span class="sr-only">Print Voucher Code</span>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
<path fill-rule="evenodd" d="M7.875 1.5C6.839 1.5 6 2.34 6 3.375v2.99c-.426.053-.851.11-1.274.174-1.454.218-2.476 1.483-2.476 2.917v6.294a3 3 0 0 0 3 3h.27l-.155 1.705A1.875 1.875 0 0 0 7.232 22.5h9.536a1.875 1.875 0 0 0 1.867-2.045l-.155-1.705h.27a3 3 0 0 0 3-3V9.456c0-1.434-1.022-2.7-2.476-2.917A48.716 48.716 0 0 0 18 6.366V3.375c0-1.036-.84-1.875-1.875-1.875h-8.25ZM16.5 6.205v-2.83A.375.375 0 0 0 16.125 3h-8.25a.375.375 0 0 0-.375.375v2.83a49.353 49.353 0 0 1 9 0Zm-.217 8.265c.178.018.317.16.333.337l.526 5.784a.375.375 0 0 1-.374.409H7.232a.375.375 0 0 1-.374-.409l.526-5.784a.373.373 0 0 1 .333-.337 41.741 41.741 0 0 1 8.566 0Zm.967-3.97a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H18a.75.75 0 0 1-.75-.75V10.5ZM15 9.75a.75.75 0 0 0-.75.75v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75V10.5a.75.75 0 0 0-.75-.75H15Z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M7.875 1.5C6.839 1.5 6 2.34 6 3.375v2.99c-.426.053-.851.11-1.274.174-1.454.218-2.476 1.483-2.476 2.917v6.294a3 3 0 0 0 3 3h.27l-.155 1.705A1.875 1.875 0 0 0 7.232 22.5h9.536a1.875 1.875 0 0 0 1.867-2.045l-.155-1.705h.27a3 3 0 0 0 3-3V9.456c0-1.434-1.022-2.7-2.476-2.917A48.716 48.716 0 0 0 18 6.366V3.375c0-1.036-.84-1.875-1.875-1.875h-8.25ZM16.5 6.205v-2.83A.375.375 0 0 0 16.125 3h-8.25a.375.375 0 0 0-.375.375v2.83a49.353 49.353 0 0 1 9 0Zm-.217 8.265c.178.018.317.16.333.337l.526 5.784a.375.375 0 0 1-.374.409H7.232a.375.375 0 0 1-.374-.409l.526-5.784a.373.373 0 0 1 .333-.337 41.741 41.741 0 0 1 8.566 0Zm.967-3.97a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75H18a.75.75 0 0 1-.75-.75V10.5ZM15 9.75a.75.75 0 0 0-.75.75v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75V10.5a.75.75 0 0 0-.75-.75H15Z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<a href="<%= baseUrl %>/voucher/<%= voucher._id %>/remove" type="button" class="remove-button relative rounded-full p-1 text-red-500 dark:text-red-400 hover:text-black dark:hover:text-white">
|
<a href="<%= baseUrl %>/voucher/<%= voucher.id %>/remove" type="button" class="remove-button relative rounded-full p-1 text-red-500 dark:text-red-400 hover:text-black dark:hover:text-white">
|
||||||
<span class="absolute -inset-1.5"></span>
|
<span class="absolute -inset-1.5"></span>
|
||||||
<span class="sr-only">Remove Voucher Code</span>
|
<span class="sr-only">Remove Voucher Code</span>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
|||||||
158
utils/fetch.js
Normal file
158
utils/fetch.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Import vendor modules
|
||||||
|
*/
|
||||||
|
const querystring = require('node:querystring');
|
||||||
|
const {Agent} = require('undici');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const log = require('../modules/log');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UniFi Settings
|
||||||
|
*/
|
||||||
|
const controller = {
|
||||||
|
ip: variables.unifiIp,
|
||||||
|
port: variables.unifiPort,
|
||||||
|
username: variables.unifiUsername,
|
||||||
|
password: variables.unifiPassword,
|
||||||
|
token: variables.unifiToken,
|
||||||
|
siteID: variables.unifiSiteId,
|
||||||
|
siteUUID: null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a Controller Site UUID
|
||||||
|
*
|
||||||
|
* @returns {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
const getSiteUUID = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(`https://${controller.ip}:${controller.port}/proxy/network/integration/v1/sites?filter=internalReference.eq('${controller.siteID}')`, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'unifi-voucher-site',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-KEY': controller.token
|
||||||
|
},
|
||||||
|
dispatcher: new Agent({
|
||||||
|
connect: {
|
||||||
|
rejectUnauthorized: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if(response.error) {
|
||||||
|
log.error(`[UniFi] Error while requesting site uuid. Error: ${response.error.message}`);
|
||||||
|
log.debug(response.error);
|
||||||
|
reject(response.error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.statusCode) {
|
||||||
|
log.error(`[UniFi] Error while requesting site uuid. Error: ${response.message}`);
|
||||||
|
log.debug(response);
|
||||||
|
reject(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.data.length < 1) {
|
||||||
|
log.error(`[UniFi] Unknown site id: ${controller.siteID}.`);
|
||||||
|
log.debug(response);
|
||||||
|
reject(`Unknown site id: ${controller.siteID}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(`[UniFi] Site UUID: ${response.data[0].id}`);
|
||||||
|
resolve(response.data[0].id);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log.error('[UniFi] Error while processing request.');
|
||||||
|
log.debug(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch util to get data from a UniFi Controller
|
||||||
|
*
|
||||||
|
* @param endpoint
|
||||||
|
* @param method
|
||||||
|
* @param params
|
||||||
|
* @param data
|
||||||
|
* @returns {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
module.exports = (endpoint, method = 'GET', params = {}, data = null) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
// Auto-resolve siteUUID if not set
|
||||||
|
if(controller.siteUUID === null) {
|
||||||
|
log.debug('[UniFi] Requesting Site UUID...');
|
||||||
|
|
||||||
|
const siteUUID = await getSiteUUID().catch((err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(siteUUID) {
|
||||||
|
controller.siteUUID = siteUUID;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define base request
|
||||||
|
const request = {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'unifi-voucher-site',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-KEY': controller.token
|
||||||
|
},
|
||||||
|
dispatcher: new Agent({
|
||||||
|
connect: {
|
||||||
|
rejectUnauthorized: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add data to body when object is given
|
||||||
|
if(data !== null) {
|
||||||
|
request.body = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`https://${controller.ip}:${controller.port}/proxy/network/integration/v1/sites/${controller.siteUUID}${endpoint}?${querystring.stringify(params)}`, request)
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if(response.error) {
|
||||||
|
log.error(`[UniFi] Error while processing request. Error: ${response.error.message}`);
|
||||||
|
log.debug(response.error);
|
||||||
|
reject(response.error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.statusCode) {
|
||||||
|
log.error(`[UniFi] Error while processing request. Error: ${response.message}`);
|
||||||
|
log.debug(response);
|
||||||
|
reject(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.data) {
|
||||||
|
resolve(response.data);
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log.error(`[UniFi] Error while processing request. Error: ${err}`);
|
||||||
|
log.debug(err);
|
||||||
|
reject(err.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
module.exports = (string) => {
|
module.exports = (string) => {
|
||||||
if(string === null) {
|
if(string === null || typeof string === 'undefined') {
|
||||||
return {
|
return {
|
||||||
note: null,
|
note: null,
|
||||||
source: null,
|
source: null,
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ const variables = require('../modules/variables');
|
|||||||
module.exports = (voucher) => {
|
module.exports = (voucher) => {
|
||||||
let base = variables.unifiSsid !== '' ? variables.unifiSsidPassword !== '' ? 415 : 375 : 260;
|
let base = variables.unifiSsid !== '' ? variables.unifiSsidPassword !== '' ? 415 : 375 : 260;
|
||||||
|
|
||||||
if(voucher.qos_usage_quota) {
|
if(voucher.dataUsageLimitMBytes) {
|
||||||
base += 10;
|
base += 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(voucher.qos_rate_max_down) {
|
if(voucher.rxRateLimitKbps) {
|
||||||
base += 10;
|
base += 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(voucher.qos_rate_max_up) {
|
if(voucher.txRateLimitKbps) {
|
||||||
base += 10;
|
base += 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user