Updated screenshots. Implemented color-scheme script to force browser color-scheme. Implemented print functionality. Updated README.md. Implemented print route.

This commit is contained in:
Glenn de Haan
2024-04-06 21:25:30 +02:00
parent ddc512b77a
commit db0bd72cc2
11 changed files with 1460 additions and 41 deletions

View File

@@ -4,7 +4,7 @@ A small UniFi Voucher Site for simple voucher creation
[![Image Size](https://img.shields.io/docker/image-size/glenndehaan/unifi-voucher-site)](https://hub.docker.com/r/glenndehaan/unifi-voucher-site) [![Image Size](https://img.shields.io/docker/image-size/glenndehaan/unifi-voucher-site)](https://hub.docker.com/r/glenndehaan/unifi-voucher-site)
![Vouchers Overview - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/b501a49c-530c-4a58-8c69-685d5fdf1b88) ![Vouchers Overview - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/2bc18cce-afb2-41e9-a34c-d923fab9f6e4)
## Structure ## Structure
@@ -164,25 +164,43 @@ To install the UniFi Voucher Site add-on for Home Assistant, follow these steps:
4. Once the repository is added, you will find the "UniFi Voucher Site" add-on in the add-on store. Click on it. 4. Once the repository is added, you will find the "UniFi Voucher Site" add-on in the add-on store. Click on it.
5. Click "Install" and wait for the installation to complete. 5. Click "Install" and wait for the installation to complete.
## Print Functionality
The UniFi Voucher Site application includes built-in support for printing vouchers using 80mm receipt printers, offering a convenient way to distribute vouchers in physical format.
### Compatibility
The print functionality is compatible with most 80mm thermal receipt printers commonly used in various industries. These printers typically use thermal printing technology, eliminating the need for ink cartridges and ensuring efficient and cost-effective voucher printing.
### Usage
Once your 80mm receipt printer is configured and connected, you can easily print vouchers directly from the UniFi Voucher Site application. Simply navigate to the voucher within the interface and click on the "Print" button.
The application will automatically format the voucher for 80mm paper width, ensuring optimal printing results. Depending on your printer settings and preferences, you may adjust print quality, paper type, and other printing parameters to suit your needs.
### Example Print PDF
![Example Print PDF](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/e86d0789-47d2-4630-a7fe-291a4fa9502f)
## Screenshots ## Screenshots
### Login (Desktop) ### Login (Desktop)
![Login - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/89289053-50e0-4169-9916-2bce7191bf49) ![Login - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/89289053-50e0-4169-9916-2bce7191bf49)
### Vouchers Overview (Desktop) ### Vouchers Overview (Desktop)
![Vouchers Overview - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/b501a49c-530c-4a58-8c69-685d5fdf1b88) ![Vouchers Overview - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/2bc18cce-afb2-41e9-a34c-d923fab9f6e4)
### Create Voucher (Desktop) ### Create Voucher (Desktop)
![Create Voucher - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/7bc14c84-dcc8-4a04-8bee-f8a75ffac1da) ![Create Voucher - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/32803e88-c3cb-4708-914c-7e7184eae1c6)
### Login (Mobile) ### Login (Mobile)
![Login - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/c236bb65-1ef5-41dd-976f-8f72a98416a3) ![Login - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/c236bb65-1ef5-41dd-976f-8f72a98416a3)
### Vouchers Overview (Mobile) ### Vouchers Overview (Mobile)
![Voucher Overview - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/0b59625e-4e47-49c8-884f-af35fd060911) ![Voucher Overview - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/68656bad-b4a2-495d-baae-61a64ed52e72)
### Create Voucher (Mobile) ### Create Voucher (Mobile)
![Create Voucher - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/6dbf05f6-7632-4140-b023-c8aa9a6f9e49) ![Create Voucher - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/b393c3d9-1e87-40e7-8998-9607872b161f)
## License ## License

1305
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
"js-logger": "^1.6.1", "js-logger": "^1.6.1",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-unifi": "^2.5.1", "node-unifi": "^2.5.1",
"pdfkit": "^0.15.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 40 KiB

113
server.js
View File

@@ -6,6 +6,7 @@ const os = require('os');
const express = require('express'); const express = require('express');
const multer = require('multer'); const multer = require('multer');
const cookieParser = require('cookie-parser'); const cookieParser = require('cookie-parser');
const PDFDocument = require('pdfkit');
/** /**
* Import own modules * Import own modules
@@ -235,6 +236,118 @@ if(webService) {
} }
} }
}); });
app.get('/voucher/:id/print', [authorization.web], async (req, res) => {
const voucher = cache.vouchers.find((e) => {
return e._id === req.params.id;
});
if(voucher) {
const doc = new PDFDocument({
bufferPages: true,
size: [226.77165354330398, 290],
margins : {
top: 20,
bottom: 20,
left: 20,
right: 20
}
});
const buffers = [];
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => {
let pdfData = Buffer.concat(buffers);
res.writeHead(200, {
'Content-Length': Buffer.byteLength(pdfData),
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment;filename=voucher_${req.params.id}.pdf`
}).end(pdfData);
});
doc.image('public/images/logo_grayscale.png', 75, 15, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
doc.font('Helvetica-Bold')
.fontSize(20)
.text(`WiFi Voucher Code`, {
align: 'center'
});
doc.font('Helvetica-Bold')
.fontSize(15)
.text(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`, {
align: 'center'
});
doc.moveDown(2);
doc.font('Helvetica-Bold')
.fontSize(12)
.text(`Voucher Details`);
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`--------------------------------------------------------`);
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Type: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(voucher.quota === 0 ? 'Multi-use' : 'Single-use');
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Duration: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(time(voucher.duration));
if(voucher.qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Data Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${voucher.qos_usage_quota}MB`);
}
if(voucher.qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Download Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${voucher.qos_rate_max_down}kbps`);
}
if(voucher.qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Upload Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${voucher.qos_rate_max_up}kbps`);
}
doc.end();
} else {
res.status(404);
res.render('404', {
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
});
}
});
app.get('/vouchers', [authorization.web], async (req, res) => { app.get('/vouchers', [authorization.web], async (req, res) => {
if(req.query.refresh) { if(req.query.refresh) {
log.info('[Cache] Requesting UniFi Vouchers...'); log.info('[Cache] Requesting UniFi Vouchers...');

View File

@@ -43,5 +43,21 @@
</p> </p>
</div> </div>
</div> </div>
<script type="application/javascript">
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.querySelector('html').setAttribute('style', 'color-scheme: dark;');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
const newColorScheme = event.matches ? "dark" : "light";
if(newColorScheme === 'light') {
document.querySelector('html').removeAttribute('style');
} else {
document.querySelector('html').setAttribute('style', 'color-scheme: dark;');
}
});
</script>
</body> </body>
</html> </html>

View File

@@ -70,5 +70,21 @@
</p> </p>
</div> </div>
</div> </div>
<script type="application/javascript">
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.querySelector('html').setAttribute('style', 'color-scheme: dark;');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
const newColorScheme = event.matches ? "dark" : "light";
if(newColorScheme === 'light') {
document.querySelector('html').removeAttribute('style');
} else {
document.querySelector('html').setAttribute('style', 'color-scheme: dark;');
}
});
</script>
</body> </body>
</html> </html>

View File

@@ -183,6 +183,13 @@
<path fill-rule="evenodd" d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z" clip-rule="evenodd" />
</svg> </svg>
</button> </button>
<a href="<%= baseUrl %>/voucher/<%= voucher._id %>/print" type="button" class="relative rounded-full p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white">
<span class="absolute -inset-1.5"></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">
<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>
</a>
<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>
@@ -300,6 +307,21 @@
</div> </div>
</div> </div>
<script type="application/javascript">
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.querySelector('html').setAttribute('style', 'color-scheme: dark;');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
const newColorScheme = event.matches ? "dark" : "light";
if(newColorScheme === 'light') {
document.querySelector('html').removeAttribute('style');
} else {
document.querySelector('html').setAttribute('style', 'color-scheme: dark;');
}
});
</script>
<script type="application/javascript"> <script type="application/javascript">
const createDialog = document.querySelector('#create-dialog'); const createDialog = document.querySelector('#create-dialog');
const createForum = document.querySelector("#voucher-forum"); const createForum = document.querySelector("#voucher-forum");