Добавлено логгирование
This commit is contained in:
parent
fb81e914a4
commit
d23bdc1bfa
50
backend/migrations/20251012140844-create-activity-logs.js
Normal file
50
backend/migrations/20251012140844-create-activity-logs.js
Normal file
@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable("ActivityLogs", {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER,
|
||||
},
|
||||
userId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: "Users",
|
||||
key: "id",
|
||||
},
|
||||
onUpdate: "CASCADE",
|
||||
onDelete: "CASCADE",
|
||||
},
|
||||
action: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
details: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
timestamp: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable("ActivityLogs");
|
||||
},
|
||||
};
|
||||
44
backend/models/activitylog.js
Normal file
44
backend/models/activitylog.js
Normal file
@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
const { Model } = require("sequelize");
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
class ActivityLog extends Model {
|
||||
/**
|
||||
* Helper method for defining associations.
|
||||
* This method is not a part of Sequelize lifecycle.
|
||||
* The `models/index` file will call this method automatically.
|
||||
*/
|
||||
static associate(models) {
|
||||
this.belongsTo(models.User, { foreignKey: "userId" });
|
||||
}
|
||||
}
|
||||
ActivityLog.init(
|
||||
{
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: "Users",
|
||||
key: "id",
|
||||
},
|
||||
},
|
||||
action: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
details: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
timestamp: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: "ActivityLog",
|
||||
}
|
||||
);
|
||||
return ActivityLog;
|
||||
};
|
||||
@ -10,6 +10,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
*/
|
||||
static associate(models) {
|
||||
this.hasMany(models.TimeEntry, { foreignKey: "userId" });
|
||||
this.hasMany(models.ActivityLog, { foreignKey: "userId" });
|
||||
}
|
||||
|
||||
// Instance method to check password
|
||||
|
||||
72
backend/routes/activityLogs.js
Normal file
72
backend/routes/activityLogs.js
Normal file
@ -0,0 +1,72 @@
|
||||
const express = require("express");
|
||||
const { ActivityLog, User } = require("../models");
|
||||
const { authenticate } = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all activity logs (admin only)
|
||||
router.get("/", authenticate, async (req, res) => {
|
||||
try {
|
||||
if (req.user.role !== "admin") {
|
||||
return res.status(403).json({ error: "Access denied" });
|
||||
}
|
||||
|
||||
const logs = await ActivityLog.findAll({
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
attributes: ["id", "username", "role"],
|
||||
},
|
||||
],
|
||||
order: [["timestamp", "DESC"]],
|
||||
});
|
||||
|
||||
res.json(logs);
|
||||
} catch (error) {
|
||||
console.error("Error fetching activity logs:", error);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Get activity logs for managers and users (manager only)
|
||||
router.get("/managed", authenticate, async (req, res) => {
|
||||
try {
|
||||
if (req.user.role !== "manager" && req.user.role !== "admin") {
|
||||
return res.status(403).json({ error: "Access denied" });
|
||||
}
|
||||
|
||||
const logs = await ActivityLog.findAll({
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
attributes: ["id", "username", "role"],
|
||||
},
|
||||
],
|
||||
where: {
|
||||
"$User.role$": ["manager", "user"],
|
||||
},
|
||||
order: [["timestamp", "DESC"]],
|
||||
});
|
||||
|
||||
res.json(logs);
|
||||
} catch (error) {
|
||||
console.error("Error fetching managed activity logs:", error);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Log an activity (internal function)
|
||||
const logActivity = async (userId, action, details = null) => {
|
||||
try {
|
||||
await ActivityLog.create({
|
||||
userId,
|
||||
action,
|
||||
details,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error logging activity:", error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { router, logActivity };
|
||||
@ -1,5 +1,6 @@
|
||||
const express = require("express");
|
||||
const { TimeEntry, User } = require("../models");
|
||||
const { logActivity } = require("./activityLogs");
|
||||
const {
|
||||
authenticate,
|
||||
authorizeAdmin,
|
||||
@ -28,6 +29,14 @@ router.get("/all", authenticate, authorizeManager, async (req, res) => {
|
||||
include: [{ model: User, attributes: ["username"] }],
|
||||
order: [["date", "DESC"]],
|
||||
});
|
||||
|
||||
// Log the export action
|
||||
await logActivity(
|
||||
req.user.id,
|
||||
"Экспорт общей таблицы",
|
||||
`Просмотрена общая таблица записей времени`
|
||||
);
|
||||
|
||||
res.json(entries);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Server error" });
|
||||
@ -48,6 +57,18 @@ router.get(
|
||||
order: [["date", "DESC"]],
|
||||
});
|
||||
console.log("Found entries:", entries);
|
||||
|
||||
// Get username for logging
|
||||
const user = await User.findByPk(userId);
|
||||
const username = user ? user.username : "Unknown";
|
||||
|
||||
// Log the export action for specific user
|
||||
await logActivity(
|
||||
req.user.id,
|
||||
"Экспорт таблицы пользователя",
|
||||
`Просмотрена таблица записей времени для пользователя: ${username}`
|
||||
);
|
||||
|
||||
res.json(entries);
|
||||
} catch (error) {
|
||||
console.error("Error fetching user entries:", error);
|
||||
@ -100,6 +121,14 @@ router.delete("/delete-all", authenticate, async (req, res) => {
|
||||
const deletedCount = await TimeEntry.destroy({
|
||||
where: { userId: req.user.id },
|
||||
});
|
||||
|
||||
// Log the delete all action
|
||||
await logActivity(
|
||||
req.user.id,
|
||||
"Удаление всех записей",
|
||||
`Удалены все записи времени (${deletedCount} записей)`
|
||||
);
|
||||
|
||||
res.json({ message: "All entries deleted", deletedCount });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Server error" });
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const express = require("express");
|
||||
const { User } = require("../models");
|
||||
const { logActivity } = require("./activityLogs");
|
||||
const {
|
||||
authenticate,
|
||||
authorizeAdmin,
|
||||
@ -48,6 +49,14 @@ router.post("/", authenticate, authorizeManager, async (req, res) => {
|
||||
password,
|
||||
role: userRole,
|
||||
});
|
||||
|
||||
// Log the creation action
|
||||
await logActivity(
|
||||
req.user.id,
|
||||
"Создание пользователя",
|
||||
`Создан новый пользователь: ${username} с ролью ${userRole}`
|
||||
);
|
||||
|
||||
res
|
||||
.status(201)
|
||||
.json({ id: user.id, username: user.username, role: user.role });
|
||||
@ -73,6 +82,14 @@ router.put(
|
||||
}
|
||||
user.password = password;
|
||||
await user.save();
|
||||
|
||||
// Log the password reset action
|
||||
await logActivity(
|
||||
req.user.id,
|
||||
"Сброс пароля",
|
||||
`Сброшен пароль для пользователя: ${user.username}`
|
||||
);
|
||||
|
||||
res.json({ message: "Password updated" });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Server error" });
|
||||
@ -88,6 +105,14 @@ router.delete("/:id", authenticate, authorizeManager, async (req, res) => {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
await user.destroy();
|
||||
|
||||
// Log the deletion action
|
||||
await logActivity(
|
||||
req.user.id,
|
||||
"Удаление пользователя",
|
||||
`Удален пользователь: ${user.username}`
|
||||
);
|
||||
|
||||
res.json({ message: "User deleted" });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Server error" });
|
||||
|
||||
@ -82,6 +82,8 @@ sequelize
|
||||
app.use("/api/auth", require("./routes/auth"));
|
||||
app.use("/api/users", require("./routes/users"));
|
||||
app.use("/api/time-entries", require("./routes/timeEntries"));
|
||||
const { router: activityLogsRouter } = require("./routes/activityLogs");
|
||||
app.use("/api/activity-logs", activityLogsRouter);
|
||||
|
||||
// Health check
|
||||
app.get("/health", (req, res) => res.status(200).json({ status: "OK" }));
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { usersAPI, timeEntriesAPI } from "../services/api";
|
||||
import { usersAPI, timeEntriesAPI, activityLogsAPI } from "../services/api";
|
||||
import UserTimeEntriesModal from "./UserTimeEntriesModal";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
@ -16,6 +16,9 @@ const AdminPanel = () => {
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [deleteUserId, setDeleteUserId] = useState(null);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
const [sortConfig, setSortConfig] = useState({ key: null, direction: "asc" });
|
||||
const [activityLogs, setActivityLogs] = useState([]);
|
||||
const [showLogs, setShowLogs] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsers();
|
||||
@ -30,6 +33,15 @@ const AdminPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchActivityLogs = async () => {
|
||||
try {
|
||||
const response = await activityLogsAPI.getLogs();
|
||||
setActivityLogs(response.data);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch activity logs:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportAll = async () => {
|
||||
try {
|
||||
const response = await timeEntriesAPI.getAllEntries();
|
||||
@ -153,6 +165,27 @@ const AdminPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = (key) => {
|
||||
let direction = "asc";
|
||||
if (sortConfig.key === key && sortConfig.direction === "asc") {
|
||||
direction = "desc";
|
||||
}
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
const sortedUsers = [...users].sort((a, b) => {
|
||||
if (sortConfig.key === null) return 0;
|
||||
const aValue = a[sortConfig.key];
|
||||
const bValue = b[sortConfig.key];
|
||||
if (aValue < bValue) {
|
||||
return sortConfig.direction === "asc" ? -1 : 1;
|
||||
}
|
||||
if (aValue > bValue) {
|
||||
return sortConfig.direction === "asc" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-100 mt-5 px-0">
|
||||
<h3>Админ панель</h3>
|
||||
@ -169,6 +202,15 @@ const AdminPanel = () => {
|
||||
<button className="btn btn-info" onClick={handleExportAll}>
|
||||
Экспорт общей таблицы
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-warning"
|
||||
onClick={() => {
|
||||
setShowLogs(!showLogs);
|
||||
if (!showLogs) fetchActivityLogs();
|
||||
}}
|
||||
>
|
||||
{showLogs ? "Скрыть логи" : "Показать логи"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showCreateForm && (
|
||||
@ -231,14 +273,56 @@ const AdminPanel = () => {
|
||||
<table className="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Имя пользователя</th>
|
||||
<th>Роль</th>
|
||||
<th
|
||||
onClick={() => handleSort("id")}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
sortConfig.key === "id" ? "#f0f0f0" : "inherit",
|
||||
color: sortConfig.key === "id" ? "#007bff" : "inherit",
|
||||
}}
|
||||
title="Кликни для сортировки"
|
||||
>
|
||||
ID{" "}
|
||||
{sortConfig.key === "id" &&
|
||||
(sortConfig.direction === "asc" ? "↑" : "↓")}
|
||||
{sortConfig.key !== "id" && "↕"}
|
||||
</th>
|
||||
<th
|
||||
onClick={() => handleSort("username")}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
sortConfig.key === "username" ? "#f0f0f0" : "inherit",
|
||||
color: sortConfig.key === "username" ? "#007bff" : "inherit",
|
||||
}}
|
||||
title="Кликни для сортировки"
|
||||
>
|
||||
Имя пользователя{" "}
|
||||
{sortConfig.key === "username" &&
|
||||
(sortConfig.direction === "asc" ? "↑" : "↓")}
|
||||
{sortConfig.key !== "username" && "↕"}
|
||||
</th>
|
||||
<th
|
||||
onClick={() => handleSort("role")}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
sortConfig.key === "role" ? "#f0f0f0" : "inherit",
|
||||
color: sortConfig.key === "role" ? "#007bff" : "inherit",
|
||||
}}
|
||||
title="Кликни для сортировки"
|
||||
>
|
||||
Роль{" "}
|
||||
{sortConfig.key === "role" &&
|
||||
(sortConfig.direction === "asc" ? "↑" : "↓")}
|
||||
{sortConfig.key !== "role" && "↕"}
|
||||
</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
{sortedUsers.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td>{user.id}</td>
|
||||
<td>
|
||||
@ -393,6 +477,45 @@ const AdminPanel = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showLogs && (
|
||||
<div className="mb-4">
|
||||
<h4>Логи действий</h4>
|
||||
<div className="table-responsive w-100">
|
||||
<table className="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Время</th>
|
||||
<th>Пользователь</th>
|
||||
<th>Роль</th>
|
||||
<th>Действие</th>
|
||||
<th>Детали</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{activityLogs.map((log) => (
|
||||
<tr key={log.id}>
|
||||
<td>{new Date(log.timestamp).toLocaleString("ru-RU")}</td>
|
||||
<td>{log.User?.username || "Unknown"}</td>
|
||||
<td>
|
||||
{log.User?.role === "manager"
|
||||
? "Руководство"
|
||||
: log.User?.role === "admin"
|
||||
? "Админ"
|
||||
: "Пользователь"}
|
||||
</td>
|
||||
<td>{log.action}</td>
|
||||
<td>{log.details || "-"}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{activityLogs.length === 0 && (
|
||||
<div className="text-center text-muted">Нет записей логов</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedUser && (
|
||||
<UserTimeEntriesModal
|
||||
show={!!selectedUser}
|
||||
|
||||
@ -16,6 +16,7 @@ const ManagerPanel = () => {
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [deleteUserId, setDeleteUserId] = useState(null);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
const [sortConfig, setSortConfig] = useState({ key: null, direction: "asc" });
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsers();
|
||||
@ -153,6 +154,27 @@ const ManagerPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = (key) => {
|
||||
let direction = "asc";
|
||||
if (sortConfig.key === key && sortConfig.direction === "asc") {
|
||||
direction = "desc";
|
||||
}
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
const sortedUsers = [...users].sort((a, b) => {
|
||||
if (sortConfig.key === null) return 0;
|
||||
const aValue = a[sortConfig.key];
|
||||
const bValue = b[sortConfig.key];
|
||||
if (aValue < bValue) {
|
||||
return sortConfig.direction === "asc" ? -1 : 1;
|
||||
}
|
||||
if (aValue > bValue) {
|
||||
return sortConfig.direction === "asc" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-100 mt-5 px-0">
|
||||
<h3>Менеджер панель</h3>
|
||||
@ -230,14 +252,56 @@ const ManagerPanel = () => {
|
||||
<table className="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Имя пользователя</th>
|
||||
<th>Роль</th>
|
||||
<th
|
||||
onClick={() => handleSort("id")}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
sortConfig.key === "id" ? "#f0f0f0" : "inherit",
|
||||
color: sortConfig.key === "id" ? "#007bff" : "inherit",
|
||||
}}
|
||||
title="Кликни для сортировки"
|
||||
>
|
||||
ID{" "}
|
||||
{sortConfig.key === "id" &&
|
||||
(sortConfig.direction === "asc" ? "↑" : "↓")}
|
||||
{sortConfig.key !== "id" && "↕"}
|
||||
</th>
|
||||
<th
|
||||
onClick={() => handleSort("username")}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
sortConfig.key === "username" ? "#f0f0f0" : "inherit",
|
||||
color: sortConfig.key === "username" ? "#007bff" : "inherit",
|
||||
}}
|
||||
title="Кликни для сортировки"
|
||||
>
|
||||
Имя пользователя{" "}
|
||||
{sortConfig.key === "username" &&
|
||||
(sortConfig.direction === "asc" ? "↑" : "↓")}
|
||||
{sortConfig.key !== "username" && "↕"}
|
||||
</th>
|
||||
<th
|
||||
onClick={() => handleSort("role")}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
backgroundColor:
|
||||
sortConfig.key === "role" ? "#f0f0f0" : "inherit",
|
||||
color: sortConfig.key === "role" ? "#007bff" : "inherit",
|
||||
}}
|
||||
title="Кликни для сортировки"
|
||||
>
|
||||
Роль{" "}
|
||||
{sortConfig.key === "role" &&
|
||||
(sortConfig.direction === "asc" ? "↑" : "↓")}
|
||||
{sortConfig.key !== "role" && "↕"}
|
||||
</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
{sortedUsers.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td>{user.id}</td>
|
||||
<td>
|
||||
@ -257,24 +321,28 @@ const ManagerPanel = () => {
|
||||
</td>
|
||||
<td>
|
||||
<div className="d-flex gap-1 flex-column flex-lg-row">
|
||||
<button
|
||||
className="btn btn-sm btn-warning"
|
||||
onClick={() => setResetPasswordId(user.id)}
|
||||
>
|
||||
Сбросить пароль
|
||||
</button>
|
||||
{user.role !== "admin" && (
|
||||
<button
|
||||
className="btn btn-sm btn-warning"
|
||||
onClick={() => setResetPasswordId(user.id)}
|
||||
>
|
||||
Сбросить пароль
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-sm btn-success"
|
||||
onClick={() => handleExportUser(user.id, user.username)}
|
||||
>
|
||||
Экспорт
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={() => setDeleteUserId(user.id)}
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
{user.role !== "admin" && (
|
||||
<button
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={() => setDeleteUserId(user.id)}
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -38,4 +38,9 @@ export const timeEntriesAPI = {
|
||||
deleteAllEntries: () => api.delete("/time-entries/delete-all"),
|
||||
};
|
||||
|
||||
export const activityLogsAPI = {
|
||||
getLogs: () => api.get("/activity-logs"),
|
||||
getManagedLogs: () => api.get("/activity-logs/managed"),
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
64
test-api.js
Normal file
64
test-api.js
Normal file
@ -0,0 +1,64 @@
|
||||
const http = require("http");
|
||||
|
||||
function makeRequest(options, postData = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
try {
|
||||
resolve({
|
||||
statusCode: res.statusCode,
|
||||
data: JSON.parse(data),
|
||||
});
|
||||
} catch (e) {
|
||||
resolve({
|
||||
statusCode: res.statusCode,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
if (postData) {
|
||||
req.write(postData);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function testAPI() {
|
||||
try {
|
||||
console.log("Testing health endpoint...");
|
||||
const healthResponse = await makeRequest({
|
||||
hostname: "localhost",
|
||||
port: 5000,
|
||||
path: "/health",
|
||||
method: "GET",
|
||||
});
|
||||
console.log("Health Status:", healthResponse.statusCode);
|
||||
console.log("Health Data:", healthResponse.data);
|
||||
|
||||
console.log(
|
||||
"\nTesting activity logs endpoint (should return 401 without auth)..."
|
||||
);
|
||||
const logsResponse = await makeRequest({
|
||||
hostname: "localhost",
|
||||
port: 5000,
|
||||
path: "/api/activity-logs",
|
||||
method: "GET",
|
||||
});
|
||||
console.log("Activity Logs Status:", logsResponse.statusCode);
|
||||
console.log("Activity Logs Data:", logsResponse.data);
|
||||
} catch (error) {
|
||||
console.error("Error:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testAPI();
|
||||
40
test-log.js
Normal file
40
test-log.js
Normal file
@ -0,0 +1,40 @@
|
||||
require("dotenv").config();
|
||||
const { ActivityLog, User } = require("./backend/models");
|
||||
|
||||
async function createTestLog() {
|
||||
try {
|
||||
// Найти админа
|
||||
const admin = await User.findOne({ where: { role: "admin" } });
|
||||
if (!admin) {
|
||||
console.log("Admin not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Создать тестовую запись лога
|
||||
const log = await ActivityLog.create({
|
||||
userId: admin.id,
|
||||
action: "Тестовое действие",
|
||||
details: "Это тестовая запись лога для проверки системы",
|
||||
timestamp: new Date(),
|
||||
});
|
||||
|
||||
console.log("Test log created:", log.toJSON());
|
||||
|
||||
// Получить все логи
|
||||
const logs = await ActivityLog.findAll({
|
||||
include: [{ model: User, attributes: ["username", "role"] }],
|
||||
order: [["timestamp", "DESC"]],
|
||||
});
|
||||
|
||||
console.log("\nAll logs:");
|
||||
logs.forEach((log) => {
|
||||
console.log(
|
||||
`- ${log.User.username} (${log.User.role}): ${log.action} - ${log.details}`
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
createTestLog();
|
||||
Loading…
x
Reference in New Issue
Block a user