update delete log, add confirm device #2
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -422,6 +422,7 @@ mark {
 | 
			
		|||
	display: flex;
 | 
			
		||||
	justify-content: flex-end;
 | 
			
		||||
	gap: 10px;
 | 
			
		||||
	margin-top: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes fadeIn {
 | 
			
		||||
| 
						 | 
				
			
			@ -435,26 +436,6 @@ mark {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-with-badge {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	padding: 8px 14px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-with-badge .badge {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: -6px;
 | 
			
		||||
	right: -10px;
 | 
			
		||||
	background-color: #fff;
 | 
			
		||||
	color: #dc3545;
 | 
			
		||||
	font-size: 12px;
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
	padding: 3px 6px;
 | 
			
		||||
	border-radius: 999px;
 | 
			
		||||
	border: 1px solid #dc3545;
 | 
			
		||||
	min-width: 20px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-dropdown {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
| 
						 | 
				
			
			@ -500,6 +481,15 @@ mark {
 | 
			
		|||
	background-color: #e9ecef;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-dropdown-content .item {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	padding: 10px 16px;
 | 
			
		||||
	border: none;
 | 
			
		||||
	background-color: #f8f9fa;
 | 
			
		||||
	text-align: left;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-dropdown:hover .user-dropdown-content {
 | 
			
		||||
	display: block;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -522,3 +512,9 @@ mark {
 | 
			
		|||
	opacity: 1;
 | 
			
		||||
	bottom: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 768px) {
 | 
			
		||||
	.modal-content {
 | 
			
		||||
		max-width: 90% !important;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,8 @@
 | 
			
		|||
					></button>
 | 
			
		||||
 | 
			
		||||
					<div class="user-dropdown-content">
 | 
			
		||||
						<div id="confirm-count-user" class="item"></div>
 | 
			
		||||
 | 
			
		||||
						<button onclick="logout()">Logout</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -60,14 +62,21 @@
 | 
			
		|||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<button
 | 
			
		||||
					id="logConfirmDeleteBtn"
 | 
			
		||||
					type="button"
 | 
			
		||||
					class="btn-with-badge danger-btn"
 | 
			
		||||
				>
 | 
			
		||||
					Delete
 | 
			
		||||
					<span class="badge" id="logDeleteBadge">0</span>
 | 
			
		||||
				</button>
 | 
			
		||||
				<div>
 | 
			
		||||
					<button
 | 
			
		||||
						id="deviceConfirmBtn"
 | 
			
		||||
						type="button"
 | 
			
		||||
						class="primary-btn"
 | 
			
		||||
					>
 | 
			
		||||
						Confirm
 | 
			
		||||
					</button>
 | 
			
		||||
					<span
 | 
			
		||||
						id="confirmUserText"
 | 
			
		||||
						style="font-size: 12px; display: none"
 | 
			
		||||
					>
 | 
			
		||||
						Confirmed by <strong id="confirmedUserName"></strong>
 | 
			
		||||
					</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div id="errorTableContainer" class="command-section">
 | 
			
		||||
| 
						 | 
				
			
			@ -85,54 +94,76 @@
 | 
			
		|||
			</div>
 | 
			
		||||
			<div id="logSections"></div>
 | 
			
		||||
 | 
			
		||||
			<!-- Log Delete Modal -->
 | 
			
		||||
			<div id="logDeleteModal" class="modal hidden">
 | 
			
		||||
				<div class="modal-content">
 | 
			
		||||
					<h2 class="modal-title">Confirm Deletion</h2>
 | 
			
		||||
 | 
			
		||||
					<table class="delete-table">
 | 
			
		||||
						<thead>
 | 
			
		||||
							<tr>
 | 
			
		||||
								<th>ID</th>
 | 
			
		||||
								<th>PID</th>
 | 
			
		||||
								<th>Version</th>
 | 
			
		||||
								<th>Command</th>
 | 
			
		||||
								<th style="text-align: center">Action</th>
 | 
			
		||||
							</tr>
 | 
			
		||||
						</thead>
 | 
			
		||||
						<tbody id="logDeleteListContainer"></tbody>
 | 
			
		||||
					</table>
 | 
			
		||||
 | 
			
		||||
				<div style="max-width: 30%" class="modal-content">
 | 
			
		||||
					<div id="deleteLogText"></div>
 | 
			
		||||
					<div class="modal-actions">
 | 
			
		||||
						<button id="logCloseModalBtn" class="btn secondary-btn">
 | 
			
		||||
						<button id="logCancelDeleteBtn" class="secondary-btn">
 | 
			
		||||
							Cancel
 | 
			
		||||
						</button>
 | 
			
		||||
						<button id="logSubmitDeleteBtn" class="btn danger-btn">
 | 
			
		||||
						<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>
 | 
			
		||||
					<div class="modal-actions">
 | 
			
		||||
						<button id="deviceCancelBtn" class="secondary-btn">
 | 
			
		||||
							Cancel
 | 
			
		||||
						</button>
 | 
			
		||||
						<button id="submitDeviceConfirmBtn" class="danger-btn">
 | 
			
		||||
							Confirm
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- Authentication and get user info -->
 | 
			
		||||
		<script>
 | 
			
		||||
			if (!localStorage.getItem("token")) {
 | 
			
		||||
				logout();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			document.getElementById(
 | 
			
		||||
				"welcome-user",
 | 
			
		||||
			).innerHTML = `Welcome, <strong>${
 | 
			
		||||
				JSON.parse(localStorage.getItem("user")).name
 | 
			
		||||
			}</strong> ▼`;
 | 
			
		||||
 | 
			
		||||
			function logout() {
 | 
			
		||||
				localStorage.removeItem("token");
 | 
			
		||||
				localStorage.removeItem("user");
 | 
			
		||||
				window.location.href = "/login";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			async function getProfile() {
 | 
			
		||||
				const token = localStorage.getItem("token");
 | 
			
		||||
 | 
			
		||||
				await fetch("/api/profile", {
 | 
			
		||||
					method: "GET",
 | 
			
		||||
					headers: {
 | 
			
		||||
						"Content-Type": "application/json",
 | 
			
		||||
						Authorization: `Bearer ${token}`,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
					.then((res) => res.json())
 | 
			
		||||
					.then((data) => {
 | 
			
		||||
						document.getElementById(
 | 
			
		||||
							"welcome-user",
 | 
			
		||||
						).innerHTML = `Welcome, <strong>${data?.user?.name}</strong> ▼`;
 | 
			
		||||
 | 
			
		||||
						document.getElementById(
 | 
			
		||||
							"confirm-count-user",
 | 
			
		||||
						).innerHTML = `Confirmed: <strong>${data?.user?.confirm_count}</strong>`;
 | 
			
		||||
					})
 | 
			
		||||
					.catch((err) => {
 | 
			
		||||
						console.error(err);
 | 
			
		||||
					});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			getProfile();
 | 
			
		||||
		</script>
 | 
			
		||||
 | 
			
		||||
		<!-- Get PID - Version - Log -->
 | 
			
		||||
		<script>
 | 
			
		||||
			// Get param pid, version from URL
 | 
			
		||||
			const params = new URLSearchParams(window.location.search);
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +177,12 @@
 | 
			
		|||
			const logSections = document.getElementById("logSections");
 | 
			
		||||
			const commands = ["inventory", "version", "license", "logging"];
 | 
			
		||||
 | 
			
		||||
			const confirmUserText = document.getElementById("confirmUserText");
 | 
			
		||||
			const confirmedUserName =
 | 
			
		||||
				document.getElementById("confirmedUserName");
 | 
			
		||||
 | 
			
		||||
			const logsCache = {}; // Store logs and current index for each command
 | 
			
		||||
			let versionsUser = [];
 | 
			
		||||
 | 
			
		||||
			async function loadPIDs(pidParam, versionParam) {
 | 
			
		||||
				const res = await fetch("/api/pids");
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +217,15 @@
 | 
			
		|||
				);
 | 
			
		||||
				const versions = await res.json();
 | 
			
		||||
 | 
			
		||||
				versionsUser = versions
 | 
			
		||||
					.filter((verItem) => verItem.user)
 | 
			
		||||
					.map((verItem) => {
 | 
			
		||||
						return {
 | 
			
		||||
							version: verItem.version,
 | 
			
		||||
							user: verItem.user,
 | 
			
		||||
						};
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
				pidList.innerHTML = `<option value="${pid}">${pid}</option>`;
 | 
			
		||||
				versionList.innerHTML = versions
 | 
			
		||||
					.map((v) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +251,7 @@
 | 
			
		|||
					versionList.dispatchEvent(new Event("change"));
 | 
			
		||||
					currentParams.set("version", versionParam);
 | 
			
		||||
				} else {
 | 
			
		||||
					// ✅ Default chọn version đầu tiên
 | 
			
		||||
					// Default chọn version đầu tiên
 | 
			
		||||
					if (versions.length > 0) {
 | 
			
		||||
						versionList.value = versions[0].version;
 | 
			
		||||
						versionList.dispatchEvent(new Event("change"));
 | 
			
		||||
| 
						 | 
				
			
			@ -266,6 +311,20 @@
 | 
			
		|||
						`?${currentParams.toString()}`,
 | 
			
		||||
					);
 | 
			
		||||
 | 
			
		||||
					// Render Confirm Section
 | 
			
		||||
					const currentVerUser = versionsUser.find(
 | 
			
		||||
						(verItem) => verItem.version === version,
 | 
			
		||||
					);
 | 
			
		||||
					if (currentVerUser) {
 | 
			
		||||
						deviceConfirmBtn.style.display = "none";
 | 
			
		||||
						confirmedUserName.textContent =
 | 
			
		||||
							currentVerUser.user.name;
 | 
			
		||||
						confirmUserText.style.display = "inline";
 | 
			
		||||
					} else {
 | 
			
		||||
						deviceConfirmBtn.style.display = "inline-block";
 | 
			
		||||
						confirmUserText.style.display = "none";
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					loadLogs(pid, version);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
| 
						 | 
				
			
			@ -435,36 +494,11 @@ ${license}`.trim();
 | 
			
		|||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
 | 
			
		||||
		<!-- Handle delete log -->
 | 
			
		||||
		<script>
 | 
			
		||||
			const STORAGE_KEY = "logDeleteList";
 | 
			
		||||
			const logConfirmDeleteBtn = document.getElementById(
 | 
			
		||||
				"logConfirmDeleteBtn",
 | 
			
		||||
			);
 | 
			
		||||
			const logDeleteModal = document.getElementById("logDeleteModal");
 | 
			
		||||
			const logDeleteListContainer = document.getElementById(
 | 
			
		||||
				"logDeleteListContainer",
 | 
			
		||||
			);
 | 
			
		||||
			const logCloseModalBtn =
 | 
			
		||||
				document.getElementById("logCloseModalBtn");
 | 
			
		||||
			const logSubmitDeleteBtn =
 | 
			
		||||
				document.getElementById("logSubmitDeleteBtn");
 | 
			
		||||
			let pendingDeleteLog = null;
 | 
			
		||||
 | 
			
		||||
			function getDeleteList() {
 | 
			
		||||
				return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			function saveDeleteList(list) {
 | 
			
		||||
				localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
 | 
			
		||||
				updateDeleteBtn();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			function updateDeleteBtn() {
 | 
			
		||||
				const deleteList = getDeleteList();
 | 
			
		||||
				const badge = document.getElementById("logDeleteBadge");
 | 
			
		||||
				badge.textContent = deleteList.length;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			document.addEventListener("click", (e) => {
 | 
			
		||||
			document.addEventListener("click", async (e) => {
 | 
			
		||||
				if (e.target.classList.contains("trash-btn")) {
 | 
			
		||||
					const section = e.target.closest(".command-section");
 | 
			
		||||
					const pid = pidList.value;
 | 
			
		||||
| 
						 | 
				
			
			@ -474,103 +508,58 @@ ${license}`.trim();
 | 
			
		|||
					const log =
 | 
			
		||||
						logsCache[`${pid}_${version}`][command].logs[index];
 | 
			
		||||
 | 
			
		||||
					const currentList = getDeleteList();
 | 
			
		||||
					if (!currentList.some((item) => item.id === log.id)) {
 | 
			
		||||
						currentList.push({
 | 
			
		||||
							id: log.id,
 | 
			
		||||
							pid,
 | 
			
		||||
							version,
 | 
			
		||||
							command,
 | 
			
		||||
						});
 | 
			
		||||
						saveDeleteList(currentList);
 | 
			
		||||
					pendingDeleteLog = { id: log.id, command, pid, version };
 | 
			
		||||
 | 
			
		||||
						showToast("Added to delete list");
 | 
			
		||||
					}
 | 
			
		||||
					const text = `Are you sure you want to delete 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;
 | 
			
		||||
					showDeleteModal();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			function renderDeleteModal() {
 | 
			
		||||
				const deleteList = getDeleteList();
 | 
			
		||||
				logDeleteListContainer.innerHTML = "";
 | 
			
		||||
				deleteList.forEach((item, i) => {
 | 
			
		||||
					const tr = document.createElement("tr");
 | 
			
		||||
					tr.innerHTML = `
 | 
			
		||||
				<td>${item.id}</td>
 | 
			
		||||
				<td>${item.pid}</td>
 | 
			
		||||
				<td>${item.version}</td>
 | 
			
		||||
				<td>${item.command}</td>
 | 
			
		||||
				<td style="text-align: center;"><button type="button" class="danger-btn remove-item-btn" data-index="${i}" title="Remove">🗑</button></td>
 | 
			
		||||
			`;
 | 
			
		||||
					logDeleteListContainer.appendChild(tr);
 | 
			
		||||
				});
 | 
			
		||||
			const logDeleteModal = document.getElementById("logDeleteModal");
 | 
			
		||||
			const logCancelDeleteBtn =
 | 
			
		||||
				document.getElementById("logCancelDeleteBtn");
 | 
			
		||||
			const logDeleteBtn = document.getElementById("logDeleteBtn");
 | 
			
		||||
 | 
			
		||||
			function showDeleteModal() {
 | 
			
		||||
				logDeleteModal.classList.remove("hidden");
 | 
			
		||||
			}
 | 
			
		||||
			function hideDeleteModal() {
 | 
			
		||||
				logDeleteModal.classList.add("hidden");
 | 
			
		||||
				pendingDeleteLog = null;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			logDeleteListContainer.addEventListener("click", (e) => {
 | 
			
		||||
				if (e.target.classList.contains("remove-item-btn")) {
 | 
			
		||||
					const index = parseInt(e.target.getAttribute("data-index"));
 | 
			
		||||
					const list = getDeleteList();
 | 
			
		||||
					list.splice(index, 1);
 | 
			
		||||
					saveDeleteList(list);
 | 
			
		||||
					renderDeleteModal();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			logCancelDeleteBtn.addEventListener("click", hideDeleteModal);
 | 
			
		||||
 | 
			
		||||
			// Open delete modal
 | 
			
		||||
			logConfirmDeleteBtn.addEventListener("click", () => {
 | 
			
		||||
				renderDeleteModal();
 | 
			
		||||
				logDeleteModal.classList.remove("hidden");
 | 
			
		||||
			});
 | 
			
		||||
			logDeleteBtn.addEventListener("click", async () => {
 | 
			
		||||
				if (!pendingDeleteLog) return;
 | 
			
		||||
 | 
			
		||||
			// Open close modal
 | 
			
		||||
			logCloseModalBtn.addEventListener("click", () => {
 | 
			
		||||
				logDeleteModal.classList.add("hidden");
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// Submit delete
 | 
			
		||||
			logSubmitDeleteBtn.addEventListener("click", async () => {
 | 
			
		||||
				const deleteList = getDeleteList();
 | 
			
		||||
				if (deleteList.length === 0) {
 | 
			
		||||
					alert("Please select at least one item to delete.");
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (
 | 
			
		||||
					confirm(
 | 
			
		||||
						`Are you sure you want to delete ${deleteList.length} item(s)?`,
 | 
			
		||||
					)
 | 
			
		||||
				) {
 | 
			
		||||
					const deletedItems = deleteList.map((item) => {
 | 
			
		||||
						return {
 | 
			
		||||
							id: item.id,
 | 
			
		||||
							command: item.command,
 | 
			
		||||
						};
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
					const token = localStorage.getItem("token");
 | 
			
		||||
 | 
			
		||||
					await fetch("/api/delete-logs", {
 | 
			
		||||
				const token = localStorage.getItem("token");
 | 
			
		||||
				try {
 | 
			
		||||
					const res = await fetch("/api/delete-log", {
 | 
			
		||||
						method: "POST",
 | 
			
		||||
						headers: {
 | 
			
		||||
							"Content-Type": "application/json",
 | 
			
		||||
							Authorization: `Bearer ${token}`,
 | 
			
		||||
						},
 | 
			
		||||
						body: JSON.stringify({ items: deletedItems }),
 | 
			
		||||
					})
 | 
			
		||||
						.then((res) => res.json())
 | 
			
		||||
						.then((data) => {
 | 
			
		||||
							alert("Deleted successfully!");
 | 
			
		||||
							localStorage.removeItem(STORAGE_KEY);
 | 
			
		||||
							location.reload();
 | 
			
		||||
						})
 | 
			
		||||
						.catch((err) => {
 | 
			
		||||
							console.error(err);
 | 
			
		||||
							alert("Delete failed.");
 | 
			
		||||
						});
 | 
			
		||||
						body: JSON.stringify(pendingDeleteLog),
 | 
			
		||||
					});
 | 
			
		||||
					const data = await res.json();
 | 
			
		||||
					if (data.success) {
 | 
			
		||||
						alert("Deleted successfully!");
 | 
			
		||||
						location.reload();
 | 
			
		||||
					} else {
 | 
			
		||||
						alert("Delete failed.");
 | 
			
		||||
					}
 | 
			
		||||
				} catch (err) {
 | 
			
		||||
					console.error(err);
 | 
			
		||||
					alert("Delete failed.");
 | 
			
		||||
				} finally {
 | 
			
		||||
					hideDeleteModal();
 | 
			
		||||
					pendingDeleteLog = null;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			updateDeleteBtn();
 | 
			
		||||
 | 
			
		||||
			function showToast(message = "Announce something") {
 | 
			
		||||
				const toast = document.getElementById("logToast");
 | 
			
		||||
				toast.textContent = message;
 | 
			
		||||
| 
						 | 
				
			
			@ -583,5 +572,74 @@ ${license}`.trim();
 | 
			
		|||
				}, 2000);
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
 | 
			
		||||
		<!-- Handle confirm device -->
 | 
			
		||||
		<script>
 | 
			
		||||
			const deviceConfirmBtn =
 | 
			
		||||
				document.getElementById("deviceConfirmBtn");
 | 
			
		||||
			const deviceConfirmModal =
 | 
			
		||||
				document.getElementById("deviceConfirmModal");
 | 
			
		||||
			const deviceCancelBtn = document.getElementById("deviceCancelBtn");
 | 
			
		||||
			const submitDeviceConfirmBtn = document.getElementById(
 | 
			
		||||
				"submitDeviceConfirmBtn",
 | 
			
		||||
			);
 | 
			
		||||
			const deviceConfirmText =
 | 
			
		||||
				document.getElementById("deviceConfirmText");
 | 
			
		||||
 | 
			
		||||
			let pendingDevice = null;
 | 
			
		||||
 | 
			
		||||
			deviceConfirmBtn.addEventListener("click", () => {
 | 
			
		||||
				const pid = document.getElementById("pidList").value;
 | 
			
		||||
				const version = document.getElementById("versionList").value;
 | 
			
		||||
 | 
			
		||||
				if (!pid || !version) {
 | 
			
		||||
					alert("PID or version is missing.");
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				pendingDevice = { pid, version };
 | 
			
		||||
 | 
			
		||||
				deviceConfirmText.innerHTML = `
 | 
			
		||||
			Are you sure you want to confirm this device?<br />
 | 
			
		||||
			<strong>${pid} - ${version}</strong>
 | 
			
		||||
		`;
 | 
			
		||||
 | 
			
		||||
				deviceConfirmModal.classList.remove("hidden");
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			deviceCancelBtn.addEventListener("click", () => {
 | 
			
		||||
				deviceConfirmModal.classList.add("hidden");
 | 
			
		||||
				pendingDevice = null;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			submitDeviceConfirmBtn.addEventListener("click", async () => {
 | 
			
		||||
				if (!pendingDevice) return;
 | 
			
		||||
 | 
			
		||||
				const token = localStorage.getItem("token");
 | 
			
		||||
				try {
 | 
			
		||||
					const res = await fetch("/api/confirm-device", {
 | 
			
		||||
						method: "POST",
 | 
			
		||||
						headers: {
 | 
			
		||||
							"Content-Type": "application/json",
 | 
			
		||||
							Authorization: `Bearer ${token}`,
 | 
			
		||||
						},
 | 
			
		||||
						body: JSON.stringify(pendingDevice),
 | 
			
		||||
					});
 | 
			
		||||
					const data = await res.json();
 | 
			
		||||
					if (data.success) {
 | 
			
		||||
						alert(data.message || "Device confirmed successfully!");
 | 
			
		||||
						location.reload();
 | 
			
		||||
					} else {
 | 
			
		||||
						alert(data.message || "Confirmation failed.");
 | 
			
		||||
					}
 | 
			
		||||
				} catch (err) {
 | 
			
		||||
					console.error(err);
 | 
			
		||||
					alert("Confirmation failed.");
 | 
			
		||||
				} finally {
 | 
			
		||||
					deviceConfirmModal.classList.add("hidden");
 | 
			
		||||
					pendingDevice = null;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		</script>
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								route/web.js
								
								
								
								
							
							
						
						
									
										22
									
								
								route/web.js
								
								
								
								
							| 
						 | 
				
			
			@ -2,6 +2,7 @@ const express = require("express");
 | 
			
		|||
const path = require("path");
 | 
			
		||||
const bcrypt = require("bcrypt");
 | 
			
		||||
const { createToken } = require("../utils/jwt");
 | 
			
		||||
const { authenticateToken } = require("../middleware/auth");
 | 
			
		||||
 | 
			
		||||
let router = express.Router();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,5 +49,26 @@ module.exports = (app, db) => {
 | 
			
		|||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	router.get("/api/profile", authenticateToken, async (req, res) => {
 | 
			
		||||
		const userId = req.user?.id;
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			const [rows] = await db.query("SELECT * FROM users WHERE id = ?", [
 | 
			
		||||
				userId,
 | 
			
		||||
			]);
 | 
			
		||||
			const user = rows[0];
 | 
			
		||||
 | 
			
		||||
			return res.status(200).json({
 | 
			
		||||
				user: {
 | 
			
		||||
					name: user.name,
 | 
			
		||||
					confirm_count: user.confirm_count,
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			console.error(err);
 | 
			
		||||
			return res.status(500).json({ message: "Internal Server Error" });
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	app.use("/", router);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										144
									
								
								server.js
								
								
								
								
							
							
						
						
									
										144
									
								
								server.js
								
								
								
								
							| 
						 | 
				
			
			@ -90,9 +90,21 @@ const checkAndInsertAdminUser = async () => {
 | 
			
		|||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(await columnExists("devices", "updated_by"))) {
 | 
			
		||||
	if (await columnExists("devices", "updated_by")) {
 | 
			
		||||
		await db.query(`ALTER TABLE devices DROP COLUMN updated_by`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(await columnExists("devices", "updated_user"))) {
 | 
			
		||||
		await db.query(`
 | 
			
		||||
			ALTER TABLE devices 
 | 
			
		||||
			ADD COLUMN updated_user INT DEFAULT NULL,
 | 
			
		||||
			ADD CONSTRAINT fk_updated_by_user FOREIGN KEY (updated_user) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
 | 
			
		||||
		`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(await columnExists("devices", "is_confirmed"))) {
 | 
			
		||||
		await db.query(
 | 
			
		||||
			`ALTER TABLE devices ADD COLUMN updated_by VARCHAR(100) DEFAULT NULL`,
 | 
			
		||||
			`ALTER TABLE devices ADD COLUMN is_confirmed BOOLEAN DEFAULT FALSE`,
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +156,7 @@ app.get("/api/pid/:pid/versions", async (req, res) => {
 | 
			
		|||
 | 
			
		||||
	// Lấy danh sách các version tương ứng với PID
 | 
			
		||||
	const [devices] = await db.query(
 | 
			
		||||
		"SELECT id, version FROM devices WHERE pid = ?",
 | 
			
		||||
		"SELECT id, version, updated_user FROM devices WHERE pid = ?",
 | 
			
		||||
		[pid],
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +165,12 @@ app.get("/api/pid/:pid/versions", async (req, res) => {
 | 
			
		|||
	for (const device of devices) {
 | 
			
		||||
		const deviceId = device.id;
 | 
			
		||||
		const version = device.version;
 | 
			
		||||
		const userId = device.updated_user;
 | 
			
		||||
 | 
			
		||||
		const [user] = await db.query(
 | 
			
		||||
			"SELECT id, name FROM users WHERE id = ?",
 | 
			
		||||
			[userId],
 | 
			
		||||
		);
 | 
			
		||||
		const [[inv]] = await db.query(
 | 
			
		||||
			"SELECT COUNT(*) AS c FROM inventory_outputs WHERE device_id = ?",
 | 
			
		||||
			[deviceId],
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +189,7 @@ app.get("/api/pid/:pid/versions", async (req, res) => {
 | 
			
		|||
		);
 | 
			
		||||
 | 
			
		||||
		results.push({
 | 
			
		||||
			user: user[0],
 | 
			
		||||
			version,
 | 
			
		||||
			commands: {
 | 
			
		||||
				inventory: inv.c,
 | 
			
		||||
| 
						 | 
				
			
			@ -202,47 +220,33 @@ app.get("/api/device/:pid/:version/:command", async (req, res) => {
 | 
			
		|||
	res.json(logs);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/api/delete-logs", authenticateToken, async (req, res) => {
 | 
			
		||||
	const items = req.body.items;
 | 
			
		||||
	const userId = req.user?.id;
 | 
			
		||||
 | 
			
		||||
	if (!Array.isArray(items)) {
 | 
			
		||||
		return res.status(400).json({ message: "Invalid payload" });
 | 
			
		||||
	}
 | 
			
		||||
app.post("/api/delete-log", authenticateToken, async (req, res) => {
 | 
			
		||||
	const logId = req.body.id;
 | 
			
		||||
	const logCommand = req.body.command;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		for (const item of items) {
 | 
			
		||||
			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(
 | 
			
		||||
				"UPDATE users SET confirm_count = confirm_count + 1 WHERE id = ?",
 | 
			
		||||
				[userId],
 | 
			
		||||
			);
 | 
			
		||||
		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(`DELETE FROM \`${logCommand}_outputs\` WHERE id = ?`, [
 | 
			
		||||
			logId,
 | 
			
		||||
		]);
 | 
			
		||||
 | 
			
		||||
		res.json({ success: true });
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		console.error("Delete error:", error);
 | 
			
		||||
| 
						 | 
				
			
			@ -250,6 +254,66 @@ app.post("/api/delete-logs", authenticateToken, async (req, res) => {
 | 
			
		|||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/api/confirm-device", authenticateToken, async (req, res) => {
 | 
			
		||||
	const { pid, version } = req.body;
 | 
			
		||||
	const updatedBy = req.user?.id;
 | 
			
		||||
 | 
			
		||||
	if (!pid || !version) {
 | 
			
		||||
		return res.status(400).json({
 | 
			
		||||
			success: false,
 | 
			
		||||
			message: "Missing pid or version",
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		// Check if device exists
 | 
			
		||||
		const [rows] = await db.query(
 | 
			
		||||
			`SELECT * FROM devices WHERE pid = ? AND version = ?`,
 | 
			
		||||
			[pid, version],
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (rows.length === 0) {
 | 
			
		||||
			return res.status(404).json({
 | 
			
		||||
				success: false,
 | 
			
		||||
				message: "Device not found.",
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (rows[0].is_confirmed) {
 | 
			
		||||
			return res.status(400).json({
 | 
			
		||||
				success: false,
 | 
			
		||||
				message: "Device is already confirmed.",
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Update device confirmation
 | 
			
		||||
		await db.query(
 | 
			
		||||
			`UPDATE devices
 | 
			
		||||
				SET is_confirmed = true,
 | 
			
		||||
					updated_at = CURRENT_TIMESTAMP,
 | 
			
		||||
					updated_user = ?
 | 
			
		||||
				WHERE pid = ? AND version = ?`,
 | 
			
		||||
			[updatedBy, pid, version],
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		await db.query(
 | 
			
		||||
			"UPDATE users SET confirm_count = confirm_count + 1 WHERE id = ?",
 | 
			
		||||
			[updatedBy],
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		res.json({
 | 
			
		||||
			success: true,
 | 
			
		||||
			message: "Device confirmed successfully!",
 | 
			
		||||
		});
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		console.error("Confirm device error:", error);
 | 
			
		||||
		res.status(500).json({
 | 
			
		||||
			success: false,
 | 
			
		||||
			message: "Server error, please try again!",
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Danh sách regex lọc lỗi
 | 
			
		||||
const errorPatterns = [
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue