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:
Glenn de Haan
2025-08-08 19:38:10 +02:00
parent 21f5be6a0a
commit 68dd918d31
15 changed files with 358 additions and 221 deletions

View File

@@ -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">
<% vouchers.forEach((voucher) => { %>
<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">
<label for="voucher-<%= voucher._id %>" class="voucher min-w-0 flex-auto">
<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">
<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">
<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>
<% 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">
Expired
</div>
<% } 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">
In Use
</div>
@@ -64,37 +64,37 @@
</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">
<%= notesConvert(voucher.note).note %>
<%= notesConvert(voucher.name).note %>
</div>
<% } %>
</div>
</h2>
</div>
<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">
<circle cx="1" cy="1" r="1" />
</svg>
<p class="whitespace-nowrap"><%= timeConvert(voucher.duration) %></p>
<% if(voucher.qos_usage_quota) { %>
<p class="whitespace-nowrap"><%= timeConvert(voucher.timeLimitMinutes) %></p>
<% 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">
<circle cx="1" cy="1" r="1" />
</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">
<circle cx="1" cy="1" r="1" />
</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">
<circle cx="1" cy="1" r="1" />
</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>
</label>

View File

@@ -26,12 +26,12 @@
<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>
<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">
Expired
</div>
<% } 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">
In Use
</div>
@@ -43,49 +43,49 @@
<% } %>
</dd>
</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">
<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>
<% } %>
<% if(notesConvert(voucher.note).source) { %>
<% if(voucher.name && notesConvert(voucher.name).source) { %>
<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>
<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>
<% } %>
<% 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">
<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>
<% } %>
<% 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">
<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 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>
<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 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>
<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 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>
<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 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>
<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 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>
<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>
</dl>
</div>

View File

@@ -5,7 +5,7 @@
<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-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="bg-sky-700 px-4 py-6 sm:px-6">
<div class="flex items-center justify-between">

View File

@@ -5,7 +5,7 @@
<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-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="bg-sky-700 px-4 py-6 sm:px-6">
<div class="flex items-center justify-between">