Compare commits

..

4 Commits
v1.1.0 ... main

Author SHA1 Message Date
6037a6a73f . 2024-10-25 22:33:28 -04:00
c111c1a23e Fix how NAS gets mounted to be more generic 2024-10-08 21:31:20 -04:00
626aab90f4 Better aesthetics 2024-09-25 17:45:24 -04:00
470a81a809 MVP 2024-09-25 17:43:32 -04:00
13 changed files with 274 additions and 392 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
mccs-it/data
node_modules
package-lock.json
*.DS_Store*
*DS_Store*

157
bot-control Normal file
View File

@ -0,0 +1,157 @@
#!/bin/bash
# Path variables
NODBOT_PATH="/srv/docker/nodbot"
NODBOT_PE_PATH="/srv/docker/nodbot-pe"
SILVANUS_PATH="/srv/docker/silvanus"
SILVANUS_FE_PATH="/srv/docker/silvanus-fe"
# Check for root or docker group
if [ "$EUID" -ne 0 ] && [ -z "$(groups | grep docker)" ]; then
echo "Please run as root or add user to docker group"
exit
fi
# Prompt for input
echo "##########################################"
echo "# Bot Control Panel #"
echo "##########################################"
echo "# 1. NodBot #"
echo "# 2. NodBot PE #"
echo "# 3. Silvanus #"
echo "# 4. Silvanus FE #"
echo "##########################################"
# Read user input
read -p "Enter your choice: " choice
# Switch case on input
case $choice in
1)
clear
echo "Selecting NodBot..."
echo "##########################################"
echo "# 1. Start NodBot #"
echo "# 2. Stop NodBot #"
echo "# 3. Restart NodBot #"
echo "# 4. View NodBot Logs #"
echo "##########################################"
# Read user input
read -p "Enter your choice: " nodbot_choice
case $nodbot_choice in
1)
echo "Starting NodBot..."
docker-compose -f $NODBOT_PATH/docker-compose.yml up -d
;;
2)
echo "Stopping NodBot..."
docker-compose -f $NODBOT_PATH/docker-compose.yml down
;;
3)
echo "Restarting NodBot..."
docker-compose -f $NODBOT_PATH/docker-compose.yml down
docker-compose -f $NODBOT_PATH/docker-compose.yml up -d
;;
4)
echo "Viewing NodBot Logs..."
docker-compose -f $NODBOT_PATH/docker-compose.yml logs -f
;;
esac
;;
2)
clear
echo "Selecting NodBot PE..."
echo "##########################################"
echo "# 1. Start NodBot PE #"
echo "# 2. Stop NodBot PE #"
echo "# 3. Restart NodBot PE #"
echo "# 4. View NodBot PE Logs #"
echo "##########################################"
# Read user input
read -p "Enter your choice: " nodbot_pe_choice
case $nodbot_pe_choice in
1)
echo "Starting NodBot PE..."
docker-compose -f $NODBOT_PE_PATH/docker-compose.yml up -d
;;
2)
echo "Stopping NodBot PE..."
docker-compose -f $NODBOT_PE_PATH/docker-compose.yml down
;;
3)
echo "Restarting NodBot PE..."
docker-compose -f $NODBOT_PE_PATH/docker-compose.yml down
docker-compose -f $NODBOT_PE_PATH/docker-compose.yml up -d
;;
4)
echo "Viewing NodBot PE Logs..."
docker-compose -f $NODBOT_PE_PATH/docker-compose.yml logs -f
;;
esac
;;
3)
clear
echo "Selecting Silvanus..."
echo "##########################################"
echo "# 1. Start Silvanus #"
echo "# 2. Stop Silvanus #"
echo "# 3. Restart Silvanus #"
echo "# 4. View Silvanus Logs #"
echo "##########################################"
# Read user input
read -p "Enter your choice: " silvanus_choice
case $silvanus_choice in
1)
echo "Starting Silvanus..."
docker-compose -f $SILVANUS_PATH/docker-compose.yml up -d
;;
2)
echo "Stopping Silvanus..."
docker-compose -f $SILVANUS_PATH/docker-compose.yml down
;;
3)
echo "Restarting Silvanus..."
docker-compose -f $SILVANUS_PATH/docker-compose.yml down
docker-compose -f $SILVANUS_PATH/docker-compose.yml up -d
;;
4)
echo "Viewing Silvanus Logs..."
docker-compose -f $SILVANUS_PATH/docker-compose.yml logs -f
;;
esac
;;
4)
clear
echo "Selecting Silvanus FE..."
echo "##########################################"
echo "# 1. Start Silvanus FE #"
echo "# 2. Stop Silvanus FE #"
echo "# 3. Restart Silvanus FE #"
echo "# 4. View Silvanus FE Logs #"
echo "##########################################"
# Read user input
read -p "Enter your choice: " silvanus_fe_choice
case $silvanus_fe_choice in
1)
echo "Starting Silvanus FE..."
docker-compose -f $SILVANUS_FE_PATH/docker-compose.yml up -d
;;
2)
echo "Stopping Silvanus FE..."
docker-compose -f $SILVANUS_FE_PATH/docker-compose.yml down
;;
3)
echo "Restarting Silvanus FE..."
docker-compose -f $SILVANUS_FE_PATH/docker-compose.yml down
docker-compose -f $SILVANUS_FE_PATH/docker-compose.yml up -d
;;
4)
echo "Viewing Silvanus FE Logs..."
docker-compose -f $SILVANUS_FE_PATH/docker-compose.yml logs -f
;;
esac
;;
*)
echo "Invalid choice"
;;
esac

View File

@ -42,8 +42,9 @@ SD_MOUNT_POINT[1]="/Volumes/Untitled" # Drone
declare -a SD_SRC_PATH
SD_SRC_PATH[0]="/DCIM/100MSDCF" # Camera
SD_SRC_PATH[1]="/DCIM/100MEDIA" # Drone
# NAS Network Address
NAS_ADDRESS="//GUEST:@192.168.0.2/media"
# NAS Mount address as found with `mount`
NAS_ADDRESS="//voidf1sh@voidNAS"
declare NAS_MOUNT_POINT # Will be set later
# Folder where ingests are stored on the Mac
declare -a MAC_INGEST_FOLDER
@ -147,10 +148,10 @@ check_sd() {
check_nas() {
if mount | grep -q "$NAS_ADDRESS"; then
# Get the NAS mount point since it can vary
NAS_MOUNT_POINT=$(mount | grep "$NAS_ADDRESS" | awk '{print $3}' | grep -E '^/Volumes/media(-1)?$')
NAS_MOUNT_POINT=$(mount | grep "$NAS_ADDRESS" | awk '{print $3}' | grep -E '^/[^/]+')
echo "NAS Mounted at $NAS_MOUNT_POINT."
else
echo "ERR: NAS Not mounted!"
exit 1
fi
}
@ -252,6 +253,7 @@ ingest_nas() {
fi
echo "Beginning copy..."
echo "Syncing ingested images with NAS..."
echo "Debug: NAS_MOUNT_POINT inside function: $NAS_MOUNT_POINT"
echo "Source: ${MAC_INGEST_FOLDER[$INGEST_MODE]}/"
echo "Destination: $NAS_MOUNT_POINT${NAS_INGEST_FOLDER[$INGEST_MODE]}/"
echo "Press any key to continue..."
@ -268,6 +270,7 @@ ingest_nas() {
######################################################################################
check_sd
check_nas
######################################################################################
# Mode Prompt
@ -291,7 +294,8 @@ while true; do
echo ""
echo "Ingest Source Path: ${SD_MOUNT_POINT[$INGEST_MODE]}${SD_SRC_PATH[$INGEST_MODE]}"
echo "Mac Ingest Path: $MAC_INGEST_PATH"
echo "NAS Ingest Path: $NAS_MOUNT_POINT$NAS_INGEST_PATH"
echo "NAS Mount Path: $NAS_MOUNT_POINT"
echo "NAS Ingest Path: $NAS_INGEST_PATH"
echo ""
echo "Please enter an option from below:"
echo ""

View File

@ -2,7 +2,7 @@ const express = require('express');
const Thermal = require('./modules/Thermal.js');
const app = express();
const port = 3000;
const port = 80;
// Middleware to serve static files (HTML)
app.use(express.static('public'));
@ -10,91 +10,20 @@ app.use(express.static('public'));
// Middleware to parse JSON request bodies
app.use(express.json());
// Print credentials
app.post('/print-credentials', async (req, res) => {
// Test print function
app.post('/print', async (req, res) => {
try {
const { username, studentId } = req.body;
if (!username || !studentId) {
return res.status(400).json({ message: 'Username and Student ID are required.' });
}
// Generate the information string
const infoArray = Thermal.generateInfo.credentials(username, studentId);
// Generate the PDF, saved to disk
Thermal.generatePdf(infoArray).then((result) => {
res.send('PDF file written successfully.');
}).catch(e => {
console.error(e);
res.status(500).json({ message: 'Error writing PDF file', error: e.message });
});
} catch (e) {
console.error('Print Error:', e);
res.status(500).json({ message: 'Print Error', error: e.message });
}
});
// Print temp password
app.post('/print-temp-pw', async (req, res) => {
try {
// Grab the password from the request body
const { password } = req.body;
// Check that the password is actually there
if (!password) {
return res.status(400).json({ message: 'Password is required.' });
}
// Generate the information string
const infoArray = Thermal.generateInfo.tempPassword(password);
// Generate the PDF, saved to disk
Thermal.generatePdf(infoArray).then((result) => {
res.send('PDF file written successfully.');
}).catch(e => {
console.error(e);
res.status(500).json({ message: 'Error writing PDF file', error: err.message });
});
await Thermal.printCredentials(username, studentId, req, res);
} catch (error) {
console.error('Print Error:', error);
res.status(500).json({ message: 'Print Error', error: error.message });
}
});
// Print Student MaineCC Info
app.post('/print-mcc', async (req, res) => {
try {
// Generate the information string
const infoArray = Thermal.generateInfo.maineCC();
// Generate the PDF, saved to disk
Thermal.generatePdf(infoArray).then((result) => {
res.send('PDF file written successfully.');
}).catch(e => {
console.error(e);
res.status(500).json({ message: 'Error writing PDF file', error: e.message });
});
} catch (e) {
console.error('Print Error:', e);
res.status(500).json({ message: 'Print Error', error: e.message });
}
});
// Print Student MaineCC Info
app.post('/print', async (req, res) => {
try {
// Print the document
Thermal.print().then(result => {
res.send('PDF sent to printer successfully.');
}).catch(e => {
console.error('Failed to start printing process:', err);
res.status(500).json({ message: 'Failed to start printing process', error: err.message });
});
} catch (e) {
console.error('Print Error:', e);
res.status(500).json({ message: 'Print Error', error: e.message });
}
});
// Start the server
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);

View File

@ -3,60 +3,24 @@ const fs = require('fs');
const { spawn } = require('child_process');
const path = require('path');
const info = {
deptName: "IT Support",
supportEmail: "ITHelp@MaineCC.edu",
supportPhone: "(207) 453-5079"
}
const receiptFooter = `For other technical assistance, contact ${info.deptName}:\n${info.supportEmail}\nor ${info.supportPhone}.`;
const mccContractor = {
name: "Contracting Company",
email: "helpme@contractor.com",
phone: "1 (800) 234-5678",
site: "www.google.com"
};
module.exports = {
generateInfo: {
credentials(username, studentId) {
printCredentials(username, studentId, req, res) {
// Create the output text
return [
{ font: 'public/Outfit-Regular.ttf', text: `Username:`, moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${username}`, moveDownAfter: true },
{ font: 'public/Outfit-Regular.ttf', text: 'Email:', moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${username}@kvcc.me.edu`, moveDownAfter: true },
{ font: 'public/Outfit-Regular.ttf', text: `Student ID:`, moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${studentId}`, moveDownAfter: true },
{ font: 'public/Outfit-Regular.ttf', text: 'Note: the MyKV Portal uses your username to sign in, not email.', moveDownAfter: true },
]
},
maineCC() {
// Create the output text
return [
{ font: 'public/Outfit-Regular.ttf', text: 'Kennebec Valley Community College is migrating email domains from @kvcc.me.edu to @mainecc.edu.', moveDownAfter: true },
{ font: 'public/Outfit-Regular.ttf', text: `For support with this transition, please contact:`, moveDownAfter: true },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${mccContractor.name}`, moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${mccContractor.phone}`, moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${mccContractor.email}`, moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${mccContractor.site}`, moveDownAfter: true }
const infoArray = [
`Username: ${username}`,
'',
'Email:',
`${username}@kvcc.me.edu`,
'',
`Student ID: ${studentId}`,
'',
'Note: the MyKV Portal uses your username to sign in, not email.',
'For technical support contact IT Support at ITHelp@MaineCC.edu or (207)453-5079'
];
},
tempPassword(password) {
// Create the output text
return [
{ font: 'public/Outfit-Regular.ttf', text: 'We have changed your password to a temporary password, shown below. Next time you log into Microsoft you will be prompted to change your password.\n', moveDownAfter: true },
{ font: 'public/Outfit-Regular.ttf', text: 'Temporary Password:', moveDownAfter: false },
{ font: 'public/RobotoMono-SemiBold.ttf', text: `${password}\n`, moveDownAfter: true },
{ font: 'public/Outfit-Regular.ttf', text: 'Password requirements:\n8+ characters\n1+ Uppercase\n1+ Lowercase\n1+ Special (!/_=.,?)\n', moveDownAfter: true }
]
}
},
generatePdf(infoArray) {
return new Promise((resolve, reject) => {
const infoString = infoArray.join('\n');
// Define the path for the PDF file
const pdfFilePath = path.join(__dirname, '../public/output.pdf');
const pdfFilePath = path.join(__dirname, 'output.pdf');
// Create a new PDF document
const doc = new PDFDocument({
@ -75,7 +39,6 @@ module.exports = {
valign: 'top',
});
// Move down below the Logo/Banner image
doc.moveDown();
doc.moveDown();
doc.moveDown();
@ -83,23 +46,7 @@ module.exports = {
doc.moveDown();
doc.moveDown();
doc.moveDown();
// Write the text from infoString
doc.fontSize(12)
// doc.font('public/Outfit-Regular.ttf').text(infoString, {
// align: 'left',
// });
for (row of infoArray) {
doc.font(row.font).text(row.text, {
align: 'left',
});
if (row.moveDownAfter) {
doc.moveDown();
}
}
doc.font('public/Outfit-Regular.ttf').text(receiptFooter, {
doc.fontSize(12).text(infoString, {
align: 'left',
});
@ -107,35 +54,29 @@ module.exports = {
doc.end();
writeStream.on('finish', () => {
resolve();
});
// Handle any errors during file writing
writeStream.on('error', (err) => {
reject(err);
});
})
},
print() {
return new Promise((resolve, reject) => {
// Define the path for the PDF file
const pdfFilePath = path.join(__dirname, '../public/output.pdf');
// Start the print command
const printer = spawn('lp', ['-d', 'ITThermal', pdfFilePath]);
// PDF file has been saved, now print it using lp command
const printer = spawn('lp', ['-d', 'ITThermal', pdfFilePath]); // Replace 'ITThermal' with your actual printer name
// Handle error if the printing process fails
printer.on('error', (err) => {
reject(err);
console.error('Failed to start printing process:', err);
res.status(500).json({ message: 'Failed to start printing process', error: err.message });
});
// Handle the process exit event
printer.on('close', (code) => {
if (code === 0) {
resolve()
res.send('PDF sent to printer successfully.');
} else {
reject("lp process exited with non-zero result.");
res.status(500).json({ message: 'Failed to print PDF' });
}
});
})
});
// Handle any errors during file writing
writeStream.on('error', (err) => {
console.error('Error writing PDF file:', err);
res.status(500).json({ message: 'Error writing PDF file', error: err.message });
});
}
}

View File

@ -1,6 +1,6 @@
{
"name": "thermal",
"version": "1.1.0",
"version": "1.0.0",
"description": "Web portal to print to a TSP100 thermal printer.",
"main": "app.js",
"scripts": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -3,170 +3,44 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ITS Thermal Printer</title>
<title>Credential Printer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
<body class="bg-gray-100 font-sans leading-normal tracking-normal">
<!-- Parent container to hold two columns -->
<div class="flex justify-center mt-10 space-x-4">
<!-- Left column containing the three original sections -->
<div class="flex flex-col space-y-4 max-w-lg">
<!-- ITS Thermal Printer Panel -->
<div class="bg-white p-8 rounded-lg shadow-lg">
<div class="max-w-lg mx-auto mt-10 bg-white p-8 rounded-lg shadow-lg">
<div class="flex justify-center mb-6">
<img src="./itslogo_color.png" alt="ITS Logo" class="h-24 w-auto">
</div>
<h1 class="text-2xl font-bold text-center mb-4" style="color: #1F3C70;">ITS Thermal Printer Panel</h1>
<h1 class="text-2xl font-bold text-center mb-4" style="color: #1F3C70;">ITS Credential Printer</h1>
<p class="text-center text-gray-600 mb-6">
Provide the username and student ID in the fields below, then click the Generate button.<br>
A pre-formatted receipt will generate with the information. Click Print at the bottom of the page to print it.
Provide the username and student ID in the fields below, then click the Print button.
A pre-formatted receipt will print with the information from the thermal printer.
</p>
<form id="printForm" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-gray-700">Username:</label>
<input type="text" id="username" name="username" required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<label for="studentId" class="block text-sm font-medium text-gray-700">Student ID:</label>
<input type="text" id="studentId" name="studentId" required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="text-center">
<button type="submit" style="background-color: #1F3C70;"
<button type="submit"
style="background-color: #1F3C70;"
class="w-full text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50">
Generate Receipt
Print
</button>
</div>
</form>
</div>
<!-- Temporary Password Printer -->
<div class="bg-white p-8 rounded-lg shadow-lg">
<h1 class="text-2xl font-bold text-center mb-4" style="color: #1F3C70;">Temporary Password Printer</h1>
<p class="text-center text-gray-600 mb-6">
Provide the temporary password in the field below, then click the Generate button.<br>
A pre-formatted receipt will generate with the information. Click Print at the bottom of the page to print it.
</p>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Temp Password:</label>
<input type="text" id="password" name="password" required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div class="text-center">
<button type="submit" id="printTempPw" style="background-color: #1F3C70;"
class="w-full text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50 mt-4">
Generate Receipt
</button>
</div>
</div>
</div>
<!-- Right column containing the PDF Viewer -->
<div class="flex flex-col space-y-4 max-w-lg">
<div id="pdfContainer" class="max-w-lg bg-white p-8 rounded-lg shadow-lg text-center">
<h1 class="text-2xl font-bold mb-4" style="color: #1F3C70;">Receipt Preview</h1>
<!-- Refresh Button -->
<button id="refreshPdf"
class="bg-blue-200 hover:bg-blue-700 hover:text-white text-black font-bold py-2 px-4 rounded mb-4">
Refresh PDF
</button>
<!-- Print Button -->
<button id="print"
class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded mb-4">
Print Receipt
</button>
<!-- PDF Viewer -->
<div id="pdfViewer" class="border border-gray-300 p-4 rounded-md"></div>
</div>
<!-- Quick Prints -->
<div class="bg-white p-8 rounded-lg shadow-lg">
<h1 class="text-2xl font-bold text-center mb-4" style="color: #1F3C70;">Quick Prints</h1>
<p class="text-center text-gray-600 mb-6">
Click the button to generate the relevant receipt.<br>
Click Print at the bottom of the page to print it.
</p>
<button id="print-mcc" class="w-full bg-gray-200 text-black py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50 mt-2">
Contractor Information
</button>
</div>
</div>
</div>
<script>
function loadPDF() {
fetch('./output.pdf')
.then(response => {
const pdfViewer = document.getElementById('pdfViewer');
pdfViewer.innerHTML = ''; // Clear any previous content
console.log('Emptied PDF Viewer');
if (response.ok) {
// If the PDF exists, reload the iframe
const iframe = document.createElement('iframe');
iframe.src = './output.pdf?' + new Date().getTime(); // Add timestamp to avoid caching
iframe.width = '100%';
iframe.height = '500px'; // Set the height as needed
iframe.classList.add('rounded-md');
pdfViewer.appendChild(iframe);
} else {
// If the PDF does not exist, display a message
pdfViewer.textContent = 'No PDF available';
}
})
.catch(() => {
// Handle errors, such as network issues
document.getElementById('pdfViewer').textContent = 'Error loading PDF';
});
}
// Load the PDF when the page loads
window.onload = loadPDF;
// Add event listener for the "Refresh PDF" button
document.getElementById('refreshPdf').addEventListener('click', loadPDF);
document.getElementById('print-mcc').addEventListener('click', (event) => {
// Send a POST request with the form data to the server
fetch('/print-mcc', {
method: 'POST'
})
.then(response => {
if (response.ok) {
return response.text(); // or .json() if your server returns JSON
} else {
throw new Error('Failed to send print request');
}
})
.catch(error => alert('Error: ' + error));
setTimeout(loadPDF, 100);
});
document.getElementById('print').addEventListener('click', (event) => {
// Send a POST request with the form data to the server
fetch('/print', {
method: 'POST'
})
.then(response => {
if (response.ok) {
return response.text(); // or .json() if your server returns JSON
} else {
throw new Error('Failed to send print request');
}
})
.then(message => alert(message))
.catch(error => alert('Error: ' + error));
setTimeout(loadPDF, 100);
});
document.getElementById('printForm').addEventListener('submit', (event) => {
event.preventDefault(); // Prevent the form from submitting normally
@ -176,7 +50,7 @@
const studentId = formData.get('studentId');
// Send a POST request with the form data to the server
fetch('/print-credentials', {
fetch('/print', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -193,25 +67,8 @@
throw new Error('Failed to send print request');
}
})
.then(message => alert(message))
.catch(error => alert('Error: ' + error));
setTimeout(loadPDF, 100);
});
document.getElementById('printTempPw').addEventListener('click', (event) => {
const password = document.getElementById('password').value;
// Send a POST request with the form data to the server
fetch('/print-temp-pw', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: password
}),
})
.catch(error => alert('Error: ' + error));
setTimeout(loadPDF, 100);
});
</script>
</body>

View File

@ -1,8 +0,0 @@
@font-face {
font-family: Outfit;
src: url('Outfit-Regular.ttf');
}
body{
font-family: 'Outfit';
}