update delete log, add confirm device
This commit is contained in:
parent
06554328b1
commit
e8694be0e2
|
|
@ -422,6 +422,7 @@ mark {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@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 {
|
.user-dropdown {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -500,6 +481,15 @@ mark {
|
||||||
background-color: #e9ecef;
|
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 {
|
.user-dropdown:hover .user-dropdown-content {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
@ -522,3 +512,9 @@ mark {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
bottom: 40px;
|
bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.modal-content {
|
||||||
|
max-width: 90% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@
|
||||||
></button>
|
></button>
|
||||||
|
|
||||||
<div class="user-dropdown-content">
|
<div class="user-dropdown-content">
|
||||||
|
<div id="confirm-count-user" class="item"></div>
|
||||||
|
|
||||||
<button onclick="logout()">Logout</button>
|
<button onclick="logout()">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -60,14 +62,21 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div>
|
||||||
id="logConfirmDeleteBtn"
|
<button
|
||||||
type="button"
|
id="deviceConfirmBtn"
|
||||||
class="btn-with-badge danger-btn"
|
type="button"
|
||||||
>
|
class="primary-btn"
|
||||||
Delete
|
>
|
||||||
<span class="badge" id="logDeleteBadge">0</span>
|
Confirm
|
||||||
</button>
|
</button>
|
||||||
|
<span
|
||||||
|
id="confirmUserText"
|
||||||
|
style="font-size: 12px; display: none"
|
||||||
|
>
|
||||||
|
Confirmed by <strong id="confirmedUserName"></strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="errorTableContainer" class="command-section">
|
<div id="errorTableContainer" class="command-section">
|
||||||
|
|
@ -85,54 +94,76 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="logSections"></div>
|
<div id="logSections"></div>
|
||||||
|
|
||||||
<!-- Log Delete Modal -->
|
|
||||||
<div id="logDeleteModal" class="modal hidden">
|
<div id="logDeleteModal" class="modal hidden">
|
||||||
<div class="modal-content">
|
<div style="max-width: 30%" class="modal-content">
|
||||||
<h2 class="modal-title">Confirm Deletion</h2>
|
<div id="deleteLogText"></div>
|
||||||
|
|
||||||
<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 class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button id="logCloseModalBtn" class="btn secondary-btn">
|
<button id="logCancelDeleteBtn" class="secondary-btn">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button id="logSubmitDeleteBtn" class="btn danger-btn">
|
<button id="logDeleteBtn" class="danger-btn">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
|
<!-- Authentication and get user info -->
|
||||||
<script>
|
<script>
|
||||||
if (!localStorage.getItem("token")) {
|
if (!localStorage.getItem("token")) {
|
||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById(
|
|
||||||
"welcome-user",
|
|
||||||
).innerHTML = `Welcome, <strong>${
|
|
||||||
JSON.parse(localStorage.getItem("user")).name
|
|
||||||
}</strong> ▼`;
|
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
localStorage.removeItem("user");
|
localStorage.removeItem("user");
|
||||||
window.location.href = "/login";
|
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>
|
</script>
|
||||||
|
|
||||||
|
<!-- Get PID - Version - Log -->
|
||||||
<script>
|
<script>
|
||||||
// Get param pid, version from URL
|
// Get param pid, version from URL
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -146,7 +177,12 @@
|
||||||
const logSections = document.getElementById("logSections");
|
const logSections = document.getElementById("logSections");
|
||||||
const commands = ["inventory", "version", "license", "logging"];
|
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
|
const logsCache = {}; // Store logs and current index for each command
|
||||||
|
let versionsUser = [];
|
||||||
|
|
||||||
async function loadPIDs(pidParam, versionParam) {
|
async function loadPIDs(pidParam, versionParam) {
|
||||||
const res = await fetch("/api/pids");
|
const res = await fetch("/api/pids");
|
||||||
|
|
@ -181,6 +217,15 @@
|
||||||
);
|
);
|
||||||
const versions = await res.json();
|
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>`;
|
pidList.innerHTML = `<option value="${pid}">${pid}</option>`;
|
||||||
versionList.innerHTML = versions
|
versionList.innerHTML = versions
|
||||||
.map((v) => {
|
.map((v) => {
|
||||||
|
|
@ -206,7 +251,7 @@
|
||||||
versionList.dispatchEvent(new Event("change"));
|
versionList.dispatchEvent(new Event("change"));
|
||||||
currentParams.set("version", versionParam);
|
currentParams.set("version", versionParam);
|
||||||
} else {
|
} else {
|
||||||
// ✅ Default chọn version đầu tiên
|
// Default chọn version đầu tiên
|
||||||
if (versions.length > 0) {
|
if (versions.length > 0) {
|
||||||
versionList.value = versions[0].version;
|
versionList.value = versions[0].version;
|
||||||
versionList.dispatchEvent(new Event("change"));
|
versionList.dispatchEvent(new Event("change"));
|
||||||
|
|
@ -266,6 +311,20 @@
|
||||||
`?${currentParams.toString()}`,
|
`?${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);
|
loadLogs(pid, version);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -435,36 +494,11 @@ ${license}`.trim();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Handle delete log -->
|
||||||
<script>
|
<script>
|
||||||
const STORAGE_KEY = "logDeleteList";
|
let pendingDeleteLog = null;
|
||||||
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");
|
|
||||||
|
|
||||||
function getDeleteList() {
|
document.addEventListener("click", async (e) => {
|
||||||
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) => {
|
|
||||||
if (e.target.classList.contains("trash-btn")) {
|
if (e.target.classList.contains("trash-btn")) {
|
||||||
const section = e.target.closest(".command-section");
|
const section = e.target.closest(".command-section");
|
||||||
const pid = pidList.value;
|
const pid = pidList.value;
|
||||||
|
|
@ -474,103 +508,58 @@ ${license}`.trim();
|
||||||
const log =
|
const log =
|
||||||
logsCache[`${pid}_${version}`][command].logs[index];
|
logsCache[`${pid}_${version}`][command].logs[index];
|
||||||
|
|
||||||
const currentList = getDeleteList();
|
pendingDeleteLog = { id: log.id, command, pid, version };
|
||||||
if (!currentList.some((item) => item.id === log.id)) {
|
|
||||||
currentList.push({
|
|
||||||
id: log.id,
|
|
||||||
pid,
|
|
||||||
version,
|
|
||||||
command,
|
|
||||||
});
|
|
||||||
saveDeleteList(currentList);
|
|
||||||
|
|
||||||
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 logDeleteModal = document.getElementById("logDeleteModal");
|
||||||
const deleteList = getDeleteList();
|
const logCancelDeleteBtn =
|
||||||
logDeleteListContainer.innerHTML = "";
|
document.getElementById("logCancelDeleteBtn");
|
||||||
deleteList.forEach((item, i) => {
|
const logDeleteBtn = document.getElementById("logDeleteBtn");
|
||||||
const tr = document.createElement("tr");
|
|
||||||
tr.innerHTML = `
|
function showDeleteModal() {
|
||||||
<td>${item.id}</td>
|
logDeleteModal.classList.remove("hidden");
|
||||||
<td>${item.pid}</td>
|
}
|
||||||
<td>${item.version}</td>
|
function hideDeleteModal() {
|
||||||
<td>${item.command}</td>
|
logDeleteModal.classList.add("hidden");
|
||||||
<td style="text-align: center;"><button type="button" class="danger-btn remove-item-btn" data-index="${i}" title="Remove">🗑</button></td>
|
pendingDeleteLog = null;
|
||||||
`;
|
|
||||||
logDeleteListContainer.appendChild(tr);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logDeleteListContainer.addEventListener("click", (e) => {
|
logCancelDeleteBtn.addEventListener("click", hideDeleteModal);
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open delete modal
|
logDeleteBtn.addEventListener("click", async () => {
|
||||||
logConfirmDeleteBtn.addEventListener("click", () => {
|
if (!pendingDeleteLog) return;
|
||||||
renderDeleteModal();
|
|
||||||
logDeleteModal.classList.remove("hidden");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open close modal
|
const token = localStorage.getItem("token");
|
||||||
logCloseModalBtn.addEventListener("click", () => {
|
try {
|
||||||
logDeleteModal.classList.add("hidden");
|
const res = await fetch("/api/delete-log", {
|
||||||
});
|
|
||||||
|
|
||||||
// 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", {
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ items: deletedItems }),
|
body: JSON.stringify(pendingDeleteLog),
|
||||||
})
|
});
|
||||||
.then((res) => res.json())
|
const data = await res.json();
|
||||||
.then((data) => {
|
if (data.success) {
|
||||||
alert("Deleted successfully!");
|
alert("Deleted successfully!");
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
location.reload();
|
||||||
location.reload();
|
} else {
|
||||||
})
|
alert("Delete failed.");
|
||||||
.catch((err) => {
|
}
|
||||||
console.error(err);
|
} catch (err) {
|
||||||
alert("Delete failed.");
|
console.error(err);
|
||||||
});
|
alert("Delete failed.");
|
||||||
|
} finally {
|
||||||
|
hideDeleteModal();
|
||||||
|
pendingDeleteLog = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateDeleteBtn();
|
|
||||||
|
|
||||||
function showToast(message = "Announce something") {
|
function showToast(message = "Announce something") {
|
||||||
const toast = document.getElementById("logToast");
|
const toast = document.getElementById("logToast");
|
||||||
toast.textContent = message;
|
toast.textContent = message;
|
||||||
|
|
@ -583,5 +572,74 @@ ${license}`.trim();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
22
route/web.js
22
route/web.js
|
|
@ -2,6 +2,7 @@ const express = require("express");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
const { createToken } = require("../utils/jwt");
|
const { createToken } = require("../utils/jwt");
|
||||||
|
const { authenticateToken } = require("../middleware/auth");
|
||||||
|
|
||||||
let router = express.Router();
|
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);
|
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(
|
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
|
// Lấy danh sách các version tương ứng với PID
|
||||||
const [devices] = await db.query(
|
const [devices] = await db.query(
|
||||||
"SELECT id, version FROM devices WHERE pid = ?",
|
"SELECT id, version, updated_user FROM devices WHERE pid = ?",
|
||||||
[pid],
|
[pid],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -153,7 +165,12 @@ app.get("/api/pid/:pid/versions", async (req, res) => {
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
const deviceId = device.id;
|
const deviceId = device.id;
|
||||||
const version = device.version;
|
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(
|
const [[inv]] = await db.query(
|
||||||
"SELECT COUNT(*) AS c FROM inventory_outputs WHERE device_id = ?",
|
"SELECT COUNT(*) AS c FROM inventory_outputs WHERE device_id = ?",
|
||||||
[deviceId],
|
[deviceId],
|
||||||
|
|
@ -172,6 +189,7 @@ app.get("/api/pid/:pid/versions", async (req, res) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
|
user: user[0],
|
||||||
version,
|
version,
|
||||||
commands: {
|
commands: {
|
||||||
inventory: inv.c,
|
inventory: inv.c,
|
||||||
|
|
@ -202,47 +220,33 @@ app.get("/api/device/:pid/:version/:command", async (req, res) => {
|
||||||
res.json(logs);
|
res.json(logs);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/delete-logs", authenticateToken, async (req, res) => {
|
app.post("/api/delete-log", authenticateToken, async (req, res) => {
|
||||||
const items = req.body.items;
|
const logId = req.body.id;
|
||||||
const userId = req.user?.id;
|
const logCommand = req.body.command;
|
||||||
|
|
||||||
if (!Array.isArray(items)) {
|
|
||||||
return res.status(400).json({ message: "Invalid payload" });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const item of items) {
|
if (!logCommand || typeof logId !== "number") {
|
||||||
const { command, id } = item;
|
return res
|
||||||
|
.status(400)
|
||||||
if (!command || typeof id !== "number") continue;
|
.json({ success: false, message: "Invalid input" });
|
||||||
|
|
||||||
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],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Delete error:", 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
|
// Danh sách regex lọc lỗi
|
||||||
const errorPatterns = [
|
const errorPatterns = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue