update delete log, authentication, add export device #4
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -94,20 +94,6 @@
 | 
			
		|||
			</div>
 | 
			
		||||
			<div id="logSections"></div>
 | 
			
		||||
 | 
			
		||||
			<div id="logDeleteModal" class="modal hidden">
 | 
			
		||||
				<div style="max-width: 30%" class="modal-content">
 | 
			
		||||
					<div id="deleteLogText"></div>
 | 
			
		||||
					<div class="modal-actions">
 | 
			
		||||
						<button id="logCancelDeleteBtn" class="secondary-btn">
 | 
			
		||||
							Cancel
 | 
			
		||||
						</button>
 | 
			
		||||
						<button id="logDeleteBtn" class="danger-btn">
 | 
			
		||||
							Delete
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div id="deviceConfirmModal" class="modal hidden">
 | 
			
		||||
				<div style="max-width: 30%" class="modal-content">
 | 
			
		||||
					<div id="deviceConfirmText"></div>
 | 
			
		||||
| 
						 | 
				
			
			@ -298,6 +284,7 @@
 | 
			
		|||
			versionList.addEventListener("change", () => {
 | 
			
		||||
				const pid = pidList.value;
 | 
			
		||||
				const version = versionList.value;
 | 
			
		||||
				deletedLogs = [];
 | 
			
		||||
				if (pid && version) {
 | 
			
		||||
					// Set param pid, version to URL
 | 
			
		||||
					const currentParams = new URLSearchParams(
 | 
			
		||||
| 
						 | 
				
			
			@ -354,27 +341,31 @@
 | 
			
		|||
					section.className = "command-section";
 | 
			
		||||
					section.id = `section-${cmd}`;
 | 
			
		||||
					section.innerHTML = `
 | 
			
		||||
            <h3>
 | 
			
		||||
                ${"show " + cmd} (${data.length})
 | 
			
		||||
                <div>
 | 
			
		||||
                <button class="next-btn" id="btn-prev-${cmd}">Previous</button>
 | 
			
		||||
                <span id="page-info-${cmd}">Output 1/${data.length}</span>
 | 
			
		||||
                <button class="next-btn" id="btn-next-${cmd}">Next</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </h3>
 | 
			
		||||
            <div class="log-block ${
 | 
			
		||||
				data[0].is_deleted ? "soft-deleted" : ""
 | 
			
		||||
			}" id="log-${cmd}">
 | 
			
		||||
		${
 | 
			
		||||
			currentVerUser
 | 
			
		||||
				? ""
 | 
			
		||||
				: data[0].is_deleted
 | 
			
		||||
				? `<button type="button" class="secondary-btn cancel-trash-btn" title="Cancel soft delete">✖️</button>`
 | 
			
		||||
				: `<button type="button" class="danger-btn trash-btn" title="Soft delete">🗑</button>`
 | 
			
		||||
		}
 | 
			
		||||
                <b>${data[0].filename}</b>\n${data[0].output}
 | 
			
		||||
            </div>
 | 
			
		||||
            `;
 | 
			
		||||
						<h3>
 | 
			
		||||
							${"show " + cmd} (${data.length})
 | 
			
		||||
							<div>
 | 
			
		||||
							<button class="next-btn" id="btn-prev-${cmd}">Previous</button>
 | 
			
		||||
							<span id="page-info-${cmd}">Output 1/${data.length}</span>
 | 
			
		||||
							<button class="next-btn" id="btn-next-${cmd}">Next</button>
 | 
			
		||||
							</div>
 | 
			
		||||
						</h3>
 | 
			
		||||
						<div class="log-block ${
 | 
			
		||||
							deletedLogs.some((item) => item.id === data[0].id)
 | 
			
		||||
								? "soft-deleted"
 | 
			
		||||
								: ""
 | 
			
		||||
						}" id="log-${cmd}">
 | 
			
		||||
							${
 | 
			
		||||
								currentVerUser
 | 
			
		||||
									? ""
 | 
			
		||||
									: deletedLogs.some(
 | 
			
		||||
											(item) => item.id === data[0].id,
 | 
			
		||||
									  )
 | 
			
		||||
									? `<button type="button" class="secondary-btn cancel-trash-btn" title="Remove from delete list">✖️</button>`
 | 
			
		||||
									: `<button type="button" class="danger-btn trash-btn" title="Add to delete list">🗑</button>`
 | 
			
		||||
							}
 | 
			
		||||
							<b>${data[0].filename}</b><br>${data[0].output}
 | 
			
		||||
						</div>
 | 
			
		||||
					`;
 | 
			
		||||
 | 
			
		||||
					logSections.appendChild(section);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -382,29 +373,7 @@
 | 
			
		|||
					const updateDisplay = () => {
 | 
			
		||||
						const info = logsCache[`${pid}_${version}`][cmd];
 | 
			
		||||
						const curr = info.logs[info.index];
 | 
			
		||||
 | 
			
		||||
						const logBlock = document.getElementById(`log-${cmd}`);
 | 
			
		||||
						logBlock.className = `log-block ${
 | 
			
		||||
							curr.is_deleted ? "soft-deleted" : ""
 | 
			
		||||
						}`;
 | 
			
		||||
 | 
			
		||||
						const currentVerUser = versionsUser.find(
 | 
			
		||||
							(verItem) => verItem.version === version,
 | 
			
		||||
						);
 | 
			
		||||
 | 
			
		||||
						if (!currentVerUser) {
 | 
			
		||||
							if (curr.is_deleted) {
 | 
			
		||||
								logBlock.innerHTML = `
 | 
			
		||||
									<button type="button" class="secondary-btn cancel-trash-btn" title="Cancel soft delete">✖️</button>
 | 
			
		||||
									<b>${curr.filename}</b>\n${curr.output}
 | 
			
		||||
								`;
 | 
			
		||||
							} else {
 | 
			
		||||
								logBlock.innerHTML = `
 | 
			
		||||
									<button type="button" class="danger-btn trash-btn" title="Soft delete">🗑</button>
 | 
			
		||||
									<b>${curr.filename}</b>\n${curr.output}
 | 
			
		||||
								`;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						updateDisplayActionDelete(curr, cmd, version);
 | 
			
		||||
 | 
			
		||||
						document.getElementById(
 | 
			
		||||
							`page-info-${cmd}`,
 | 
			
		||||
| 
						 | 
				
			
			@ -437,6 +406,73 @@
 | 
			
		|||
 | 
			
		||||
			loadPIDs(pidParam, versionParam);
 | 
			
		||||
 | 
			
		||||
			// Handle delete and cancel
 | 
			
		||||
			document.addEventListener("click", async (e) => {
 | 
			
		||||
				const pid = pidList.value;
 | 
			
		||||
				const version = versionList.value;
 | 
			
		||||
 | 
			
		||||
				if (!pid || !version) return;
 | 
			
		||||
 | 
			
		||||
				// Find command section
 | 
			
		||||
				const section = e.target.closest(".command-section");
 | 
			
		||||
				if (!section) return;
 | 
			
		||||
 | 
			
		||||
				const command = section.id.replace("section-", "");
 | 
			
		||||
				const info = logsCache[`${pid}_${version}`][command];
 | 
			
		||||
				const index = info.index;
 | 
			
		||||
				const log = info.logs[index];
 | 
			
		||||
 | 
			
		||||
				const isDeleted = deletedLogs.some(
 | 
			
		||||
					(item) => item.id === log.id,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				// Add to delete
 | 
			
		||||
				if (e.target.classList.contains("trash-btn")) {
 | 
			
		||||
					if (!isDeleted) {
 | 
			
		||||
						deletedLogs.push({
 | 
			
		||||
							id: log.id,
 | 
			
		||||
							command: command,
 | 
			
		||||
						});
 | 
			
		||||
						updateDisplayActionDelete(log, command, version);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Cancel add to delete
 | 
			
		||||
				if (e.target.classList.contains("cancel-trash-btn")) {
 | 
			
		||||
					deletedLogs = deletedLogs.filter(
 | 
			
		||||
						(item) => item.id !== log.id,
 | 
			
		||||
					);
 | 
			
		||||
					updateDisplayActionDelete(log, command, version);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// Update UI delete and cancel button
 | 
			
		||||
			const updateDisplayActionDelete = (log, command, version) => {
 | 
			
		||||
				const logBlock = document.getElementById(`log-${command}`);
 | 
			
		||||
				const isDeleted = deletedLogs.some(
 | 
			
		||||
					(item) => item.id === log.id,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				logBlock.className = `log-block ${
 | 
			
		||||
					isDeleted ? "soft-deleted" : ""
 | 
			
		||||
				}`;
 | 
			
		||||
 | 
			
		||||
				const currentVerUser = versionsUser.find(
 | 
			
		||||
					(verItem) => verItem.version === version,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				if (!currentVerUser) {
 | 
			
		||||
					logBlock.innerHTML = `
 | 
			
		||||
			${
 | 
			
		||||
				isDeleted
 | 
			
		||||
					? `<button type="button" class="secondary-btn cancel-trash-btn" title="Remove from delete list">✖️</button>`
 | 
			
		||||
					: `<button type="button" class="danger-btn trash-btn" title="Add to delete list">🗑</button>`
 | 
			
		||||
			}
 | 
			
		||||
			<b>${log.filename}</b>\n${log.output}
 | 
			
		||||
		`;
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			const copyBtn = document.getElementById("copyBtn");
 | 
			
		||||
			const downloadBtn = document.getElementById("downloadBtn");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -522,116 +558,6 @@ ${license}`.trim();
 | 
			
		|||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
 | 
			
		||||
		<!-- Handle soft delete log -->
 | 
			
		||||
		<script>
 | 
			
		||||
			let pendingDeleteLog = null;
 | 
			
		||||
 | 
			
		||||
			const logDeleteModal = document.getElementById("logDeleteModal");
 | 
			
		||||
			const logCancelDeleteBtn =
 | 
			
		||||
				document.getElementById("logCancelDeleteBtn");
 | 
			
		||||
			const logDeleteBtn = document.getElementById("logDeleteBtn");
 | 
			
		||||
 | 
			
		||||
			document.addEventListener("click", async (e) => {
 | 
			
		||||
				if (e.target.classList.contains("trash-btn")) {
 | 
			
		||||
					const section = e.target.closest(".command-section");
 | 
			
		||||
					const pid = pidList.value;
 | 
			
		||||
					const version = versionList.value;
 | 
			
		||||
					const command = section.id.replace("section-", "");
 | 
			
		||||
					const index = logsCache[`${pid}_${version}`][command].index;
 | 
			
		||||
					const log =
 | 
			
		||||
						logsCache[`${pid}_${version}`][command].logs[index];
 | 
			
		||||
 | 
			
		||||
					pendingDeleteLog = {
 | 
			
		||||
						id: log.id,
 | 
			
		||||
						command,
 | 
			
		||||
						pid,
 | 
			
		||||
						version,
 | 
			
		||||
						is_deleted: true,
 | 
			
		||||
					};
 | 
			
		||||
 | 
			
		||||
					const text = `Are you sure you want to <strong>soft delete</strong> this log? <br /><code>${pid} - ${version} - ${command}</code> <br /> <div style="font-size: 12px; font-weight: 700" >${log.filename}</div>`;
 | 
			
		||||
					document.getElementById("deleteLogText").innerHTML = text;
 | 
			
		||||
					logDeleteBtn.innerHTML = "Delete";
 | 
			
		||||
					showDeleteModal();
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (e.target.classList.contains("cancel-trash-btn")) {
 | 
			
		||||
					const section = e.target.closest(".command-section");
 | 
			
		||||
					const pid = pidList.value;
 | 
			
		||||
					const version = versionList.value;
 | 
			
		||||
					const command = section.id.replace("section-", "");
 | 
			
		||||
					const index = logsCache[`${pid}_${version}`][command].index;
 | 
			
		||||
					const log =
 | 
			
		||||
						logsCache[`${pid}_${version}`][command].logs[index];
 | 
			
		||||
 | 
			
		||||
					pendingDeleteLog = {
 | 
			
		||||
						id: log.id,
 | 
			
		||||
						command,
 | 
			
		||||
						pid,
 | 
			
		||||
						version,
 | 
			
		||||
						is_deleted: false,
 | 
			
		||||
					};
 | 
			
		||||
 | 
			
		||||
					const text = `Are you sure you want to <strong>cancel soft delete</strong> this log? <br /><code>${pid} - ${version} - ${command}</code> <br /> <div style="font-size: 12px; font-weight: 700" >${log.filename}</div>`;
 | 
			
		||||
					document.getElementById("deleteLogText").innerHTML = text;
 | 
			
		||||
 | 
			
		||||
					logDeleteBtn.innerHTML = "Cancel Delete";
 | 
			
		||||
					showDeleteModal();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			function showDeleteModal() {
 | 
			
		||||
				logDeleteModal.classList.remove("hidden");
 | 
			
		||||
			}
 | 
			
		||||
			function hideDeleteModal() {
 | 
			
		||||
				logDeleteModal.classList.add("hidden");
 | 
			
		||||
				pendingDeleteLog = null;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			logCancelDeleteBtn.addEventListener("click", hideDeleteModal);
 | 
			
		||||
 | 
			
		||||
			logDeleteBtn.addEventListener("click", async () => {
 | 
			
		||||
				if (!pendingDeleteLog) return;
 | 
			
		||||
 | 
			
		||||
				const token = localStorage.getItem("token");
 | 
			
		||||
				try {
 | 
			
		||||
					const res = await fetch("/api/soft-delete-log", {
 | 
			
		||||
						method: "POST",
 | 
			
		||||
						headers: {
 | 
			
		||||
							"Content-Type": "application/json",
 | 
			
		||||
							Authorization: `Bearer ${token}`,
 | 
			
		||||
						},
 | 
			
		||||
						body: JSON.stringify(pendingDeleteLog),
 | 
			
		||||
					});
 | 
			
		||||
					const data = await res.json();
 | 
			
		||||
					if (data.success) {
 | 
			
		||||
						alert(data.message || "Soft deleted successfully!");
 | 
			
		||||
						location.reload();
 | 
			
		||||
					} else {
 | 
			
		||||
						alert(data.message || "Delete failed.");
 | 
			
		||||
					}
 | 
			
		||||
				} catch (err) {
 | 
			
		||||
					console.error(err);
 | 
			
		||||
					alert("Delete failed.");
 | 
			
		||||
				} finally {
 | 
			
		||||
					hideDeleteModal();
 | 
			
		||||
					pendingDeleteLog = null;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			function showToast(message = "Announce something") {
 | 
			
		||||
				const toast = document.getElementById("logToast");
 | 
			
		||||
				toast.textContent = message;
 | 
			
		||||
				toast.classList.add("show");
 | 
			
		||||
				toast.classList.remove("hidden");
 | 
			
		||||
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					toast.classList.remove("show");
 | 
			
		||||
					toast.classList.add("hidden");
 | 
			
		||||
				}, 2000);
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
 | 
			
		||||
		<!-- Handle confirm device -->
 | 
			
		||||
		<script>
 | 
			
		||||
			const deviceConfirmBtn =
 | 
			
		||||
| 
						 | 
				
			
			@ -646,6 +572,7 @@ ${license}`.trim();
 | 
			
		|||
				document.getElementById("deviceConfirmText");
 | 
			
		||||
 | 
			
		||||
			let pendingDevice = null;
 | 
			
		||||
			let deletedLogs = [];
 | 
			
		||||
 | 
			
		||||
			deviceConfirmBtn.addEventListener("click", () => {
 | 
			
		||||
				const pid = document.getElementById("pidList").value;
 | 
			
		||||
| 
						 | 
				
			
			@ -656,12 +583,12 @@ ${license}`.trim();
 | 
			
		|||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				pendingDevice = { pid, version };
 | 
			
		||||
				pendingDevice = { pid, version, deletedLogs };
 | 
			
		||||
 | 
			
		||||
				deviceConfirmText.innerHTML = `
 | 
			
		||||
			Are you sure you want to <strong>save</strong> these changes?<br />
 | 
			
		||||
			<strong>${pid} - ${version}</strong>
 | 
			
		||||
		`;
 | 
			
		||||
					Are you sure you want to <strong>save</strong> these changes?<br />
 | 
			
		||||
					<strong>${pid} - ${version}</strong>
 | 
			
		||||
				`;
 | 
			
		||||
 | 
			
		||||
				deviceConfirmModal.classList.remove("hidden");
 | 
			
		||||
			});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										84
									
								
								server.js
								
								
								
								
							
							
						
						
									
										84
									
								
								server.js
								
								
								
								
							| 
						 | 
				
			
			@ -226,54 +226,8 @@ app.get("/api/device/:pid/:version/:command", async (req, res) => {
 | 
			
		|||
	res.json(logs);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/api/soft-delete-log", authenticateToken, async (req, res) => {
 | 
			
		||||
	const logId = req.body.id;
 | 
			
		||||
	const logCommand = req.body.command;
 | 
			
		||||
	const isDeleted = req.body.is_deleted;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		if (!logCommand || typeof logId !== "number") {
 | 
			
		||||
			return res
 | 
			
		||||
				.status(400)
 | 
			
		||||
				.json({ success: false, message: "Invalid input." });
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const [rows] = await db.query(
 | 
			
		||||
			`SELECT id FROM \`${logCommand}_outputs\` WHERE id = ?`,
 | 
			
		||||
			[logId],
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (rows.length === 0) {
 | 
			
		||||
			return res.status(404).json({
 | 
			
		||||
				success: false,
 | 
			
		||||
				message: "Log not found.",
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await db.query(
 | 
			
		||||
			`UPDATE \`${logCommand}_outputs\` SET is_deleted = ? WHERE id = ?`,
 | 
			
		||||
			[isDeleted, logId],
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (isDeleted) {
 | 
			
		||||
			res.json({ success: true, message: "Soft deleted successfully!" });
 | 
			
		||||
		} else {
 | 
			
		||||
			res.json({
 | 
			
		||||
				success: true,
 | 
			
		||||
				message: "Cancel soft deleted successfully!",
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		console.error("Delete error:", error);
 | 
			
		||||
		res.status(500).json({
 | 
			
		||||
			success: false,
 | 
			
		||||
			message: "Server error, please try again!",
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/api/confirm-device", authenticateToken, async (req, res) => {
 | 
			
		||||
	const { pid, version } = req.body;
 | 
			
		||||
	const { pid, version, deletedLogs } = req.body;
 | 
			
		||||
	const updatedBy = req.user?.id;
 | 
			
		||||
 | 
			
		||||
	if (!pid || !version) {
 | 
			
		||||
| 
						 | 
				
			
			@ -297,7 +251,6 @@ app.post("/api/confirm-device", authenticateToken, async (req, res) => {
 | 
			
		|||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const deviceId = rows[0].id;
 | 
			
		||||
		if (rows[0].is_confirmed) {
 | 
			
		||||
			return res.status(400).json({
 | 
			
		||||
				success: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -316,17 +269,32 @@ app.post("/api/confirm-device", authenticateToken, async (req, res) => {
 | 
			
		|||
		);
 | 
			
		||||
 | 
			
		||||
		// Delete Log
 | 
			
		||||
		const tables = [
 | 
			
		||||
			"inventory_outputs",
 | 
			
		||||
			"version_outputs",
 | 
			
		||||
			"license_outputs",
 | 
			
		||||
			"logging_outputs",
 | 
			
		||||
		];
 | 
			
		||||
		for (const table of tables) {
 | 
			
		||||
			await db.query(
 | 
			
		||||
				`DELETE FROM ${table} WHERE device_id = ? AND is_deleted = TRUE`,
 | 
			
		||||
				[deviceId],
 | 
			
		||||
		for (const item of deletedLogs) {
 | 
			
		||||
			const { command, id } = item;
 | 
			
		||||
 | 
			
		||||
			if (!command || typeof id !== "number") continue;
 | 
			
		||||
 | 
			
		||||
			const allowedCommands = [
 | 
			
		||||
				"inventory",
 | 
			
		||||
				"version",
 | 
			
		||||
				"license",
 | 
			
		||||
				"logging",
 | 
			
		||||
			];
 | 
			
		||||
			if (!allowedCommands.includes(command)) continue;
 | 
			
		||||
 | 
			
		||||
			const [rows] = await db.query(
 | 
			
		||||
				`SELECT id FROM \`${command}_outputs\` WHERE id = ?`,
 | 
			
		||||
				[id],
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			if (rows.length === 0) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Delete log
 | 
			
		||||
			await db.query(`DELETE FROM \`${command}_outputs\` WHERE id = ?`, [
 | 
			
		||||
				id,
 | 
			
		||||
			]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await db.query(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue