add sborka
This commit is contained in:
343
garrysmod/gamemodes/militaryrp/plugins/serverlogs/cl_plugin.lua
Normal file
343
garrysmod/gamemodes/militaryrp/plugins/serverlogs/cl_plugin.lua
Normal file
@@ -0,0 +1,343 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
-- Цветовая схема
|
||||
local COLOR_BG_DARK = Color(3, 5, 4)
|
||||
local COLOR_BG_MEDIUM = Color(8, 12, 10)
|
||||
local COLOR_BG_LIGHT = Color(12, 18, 14)
|
||||
local COLOR_PRIMARY = Color(27, 94, 32)
|
||||
local COLOR_PRIMARY_DARK = Color(15, 60, 18)
|
||||
local COLOR_ACCENT = Color(56, 102, 35)
|
||||
local COLOR_TEXT_PRIMARY = Color(165, 214, 167)
|
||||
local COLOR_TEXT_SECONDARY = Color(102, 187, 106)
|
||||
local COLOR_BORDER = Color(15, 60, 18, 60)
|
||||
|
||||
-- Локальное хранилище логов
|
||||
local clientLogs = {}
|
||||
local isLoadingLogs = false
|
||||
|
||||
-- Сетевой получатель для открытия меню (из команды Helix)
|
||||
net.Receive("ixServerLogs_OpenMenu", function()
|
||||
PLUGIN:OpenLogsMenu()
|
||||
end)
|
||||
|
||||
-- Получение логов с сервера
|
||||
function PLUGIN:RequestLogs(filters)
|
||||
if isLoadingLogs then return end
|
||||
|
||||
isLoadingLogs = true
|
||||
clientLogs = {}
|
||||
|
||||
net.Start("ixServerLogs_Request")
|
||||
net.WriteTable(filters or {})
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
-- Получение ответа с логами
|
||||
net.Receive("ixServerLogs_Response", function()
|
||||
local isFirst = net.ReadUInt(2) == 1
|
||||
local logs = net.ReadTable()
|
||||
|
||||
if isFirst then
|
||||
clientLogs = {}
|
||||
end
|
||||
|
||||
for _, log in ipairs(logs) do
|
||||
table.insert(clientLogs, log)
|
||||
end
|
||||
|
||||
isLoadingLogs = false
|
||||
|
||||
-- Обновляем UI если открыто
|
||||
if IsValid(PLUGIN.logsFrame) and IsValid(PLUGIN.logsList) then
|
||||
PLUGIN:UpdateLogsList()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Главное меню логов
|
||||
function PLUGIN:OpenLogsMenu()
|
||||
if IsValid(self.logsFrame) then
|
||||
self.logsFrame:Remove()
|
||||
end
|
||||
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetSize(scrW * 0.9, scrH * 0.85)
|
||||
frame:Center()
|
||||
frame:SetTitle("")
|
||||
frame:SetDraggable(true)
|
||||
frame:ShowCloseButton(false)
|
||||
frame:MakePopup()
|
||||
|
||||
frame.Paint = function(s, w, h)
|
||||
-- Основной фон
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
|
||||
|
||||
-- Верхняя панель
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 60, COLOR_BG_MEDIUM, true, true, false, false)
|
||||
|
||||
-- Акцентная линия
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawRect(0, 60, w, 3)
|
||||
|
||||
-- Заголовок
|
||||
draw.SimpleText("◆ ЛОГИ СЕРВЕРА ◆", "ixMenuButtonFont", w/2, 20, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("СИСТЕМА МОНИТОРИНГА СОБЫТИЙ", "ixSmallFont", w/2, 43, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Рамка
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
-- Кнопка закрытия
|
||||
local closeBtn = vgui.Create("DButton", frame)
|
||||
closeBtn:SetSize(40, 40)
|
||||
closeBtn:SetPos(frame:GetWide() - 50, 10)
|
||||
closeBtn:SetText("")
|
||||
closeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and Color(200, 50, 50) or Color(150, 50, 50)
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
|
||||
surface.SetDrawColor(255, 255, 255)
|
||||
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
|
||||
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
|
||||
end
|
||||
closeBtn.DoClick = function()
|
||||
frame:Close()
|
||||
end
|
||||
|
||||
-- Панель фильтров
|
||||
local filterPanel = vgui.Create("DPanel", frame)
|
||||
filterPanel:SetSize(250, frame:GetTall() - 80)
|
||||
filterPanel:SetPos(10, 70)
|
||||
filterPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
-- Заголовок фильтров
|
||||
local filterHeader = vgui.Create("DLabel", filterPanel)
|
||||
filterHeader:SetPos(10, 10)
|
||||
filterHeader:SetFont("ixSmallFont")
|
||||
filterHeader:SetText("⚙ ФИЛЬТРЫ")
|
||||
filterHeader:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
filterHeader:SizeToContents()
|
||||
|
||||
-- Категории
|
||||
local categoryLabel = vgui.Create("DLabel", filterPanel)
|
||||
categoryLabel:SetPos(10, 40)
|
||||
categoryLabel:SetFont("DermaDefault")
|
||||
categoryLabel:SetText("Категория:")
|
||||
categoryLabel:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
categoryLabel:SizeToContents()
|
||||
|
||||
local categoryBox = vgui.Create("DComboBox", filterPanel)
|
||||
categoryBox:SetPos(10, 60)
|
||||
categoryBox:SetSize(230, 30)
|
||||
categoryBox:SetValue("Все категории")
|
||||
categoryBox:AddChoice("Все категории")
|
||||
|
||||
for catID, cat in SortedPairs(self.LOG_CATEGORIES) do
|
||||
categoryBox:AddChoice(cat.icon .. " " .. cat.name, catID)
|
||||
end
|
||||
|
||||
categoryBox.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
-- Поиск
|
||||
local searchLabel = vgui.Create("DLabel", filterPanel)
|
||||
searchLabel:SetPos(10, 100)
|
||||
searchLabel:SetFont("DermaDefault")
|
||||
searchLabel:SetText("Поиск:")
|
||||
searchLabel:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
searchLabel:SizeToContents()
|
||||
|
||||
local searchBox = vgui.Create("DTextEntry", filterPanel)
|
||||
searchBox:SetPos(10, 120)
|
||||
searchBox:SetSize(230, 30)
|
||||
searchBox:SetPlaceholderText("Введите текст для поиска...")
|
||||
searchBox.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
s:DrawTextEntryText(COLOR_TEXT_PRIMARY, COLOR_ACCENT, COLOR_TEXT_PRIMARY)
|
||||
end
|
||||
|
||||
-- Лимит
|
||||
local limitLabel = vgui.Create("DLabel", filterPanel)
|
||||
limitLabel:SetPos(10, 160)
|
||||
limitLabel:SetFont("DermaDefault")
|
||||
limitLabel:SetText("Количество записей:")
|
||||
limitLabel:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
limitLabel:SizeToContents()
|
||||
|
||||
local limitSlider = vgui.Create("DNumSlider", filterPanel)
|
||||
limitSlider:SetPos(10, 180)
|
||||
limitSlider:SetSize(230, 30)
|
||||
limitSlider:SetMin(10)
|
||||
limitSlider:SetMax(500)
|
||||
limitSlider:SetDecimals(0)
|
||||
limitSlider:SetValue(100)
|
||||
limitSlider:SetText("")
|
||||
|
||||
-- Кнопка применить фильтры
|
||||
local applyBtn = vgui.Create("DButton", filterPanel)
|
||||
applyBtn:SetPos(10, 230)
|
||||
applyBtn:SetSize(230, 40)
|
||||
applyBtn:SetText("")
|
||||
applyBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("🔍 ПРИМЕНИТЬ", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
applyBtn.DoClick = function()
|
||||
local filters = {
|
||||
limit = limitSlider:GetValue()
|
||||
}
|
||||
|
||||
local _, catID = categoryBox:GetSelected()
|
||||
if catID then
|
||||
filters.category = catID
|
||||
end
|
||||
|
||||
local searchText = searchBox:GetValue()
|
||||
if searchText and searchText ~= "" then
|
||||
filters.search = searchText
|
||||
end
|
||||
|
||||
self:RequestLogs(filters)
|
||||
end
|
||||
|
||||
-- Кнопка очистить логи
|
||||
local clearBtn = vgui.Create("DButton", filterPanel)
|
||||
clearBtn:SetPos(10, 280)
|
||||
clearBtn:SetSize(230, 40)
|
||||
clearBtn:SetText("")
|
||||
clearBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and Color(200, 50, 50) or Color(150, 50, 50)
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("🗑 ОЧИСТИТЬ ЛОГИ", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
clearBtn.DoClick = function()
|
||||
Derma_Query(
|
||||
"Вы уверены что хотите очистить все логи?",
|
||||
"Подтверждение",
|
||||
"Да",
|
||||
function()
|
||||
net.Start("ixServerLogs_Clear")
|
||||
net.SendToServer()
|
||||
clientLogs = {}
|
||||
self:UpdateLogsList()
|
||||
end,
|
||||
"Нет"
|
||||
)
|
||||
end
|
||||
|
||||
-- Панель логов
|
||||
local logsPanel = vgui.Create("DPanel", frame)
|
||||
logsPanel:SetSize(frame:GetWide() - 280, frame:GetTall() - 80)
|
||||
logsPanel:SetPos(270, 70)
|
||||
logsPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
-- Заголовок логов
|
||||
local logsHeader = vgui.Create("DPanel", logsPanel)
|
||||
logsHeader:SetSize(logsPanel:GetWide(), 40)
|
||||
logsHeader:Dock(TOP)
|
||||
logsHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBoxEx(6, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
|
||||
draw.SimpleText("📋 ЗАПИСИ ЛОГОВ", "ixSmallFont", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("Всего: " .. #clientLogs, "DermaDefault", w - 10, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
-- Список логов
|
||||
local logsList = vgui.Create("DScrollPanel", logsPanel)
|
||||
logsList:Dock(FILL)
|
||||
logsList:DockMargin(5, 5, 5, 5)
|
||||
|
||||
local sbar = logsList:GetVBar()
|
||||
sbar:SetHideButtons(true)
|
||||
function sbar:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function sbar.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
self.logsFrame = frame
|
||||
self.logsList = logsList
|
||||
self.logsHeader = logsHeader
|
||||
|
||||
-- Загружаем логи
|
||||
self:RequestLogs({limit = 100})
|
||||
end
|
||||
|
||||
-- Обновление списка логов
|
||||
function PLUGIN:UpdateLogsList()
|
||||
if not IsValid(self.logsList) then return end
|
||||
|
||||
self.logsList:Clear()
|
||||
|
||||
if #clientLogs == 0 then
|
||||
local noLogs = vgui.Create("DLabel", self.logsList)
|
||||
noLogs:SetText("Логи не найдены")
|
||||
noLogs:SetFont("ixSmallFont")
|
||||
noLogs:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
noLogs:SizeToContents()
|
||||
noLogs:Dock(TOP)
|
||||
noLogs:DockMargin(10, 10, 10, 0)
|
||||
return
|
||||
end
|
||||
|
||||
-- Обновляем заголовок
|
||||
if IsValid(self.logsHeader) then
|
||||
self.logsHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBoxEx(6, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
|
||||
draw.SimpleText("📋 ЗАПИСИ ЛОГОВ", "ixSmallFont", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("Всего: " .. #clientLogs, "DermaDefault", w - 10, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
for _, log in ipairs(clientLogs) do
|
||||
local logEntry = vgui.Create("DPanel", self.logsList)
|
||||
logEntry:SetTall(60)
|
||||
logEntry:Dock(TOP)
|
||||
logEntry:DockMargin(5, 5, 5, 0)
|
||||
|
||||
local category = self.LOG_CATEGORIES[log.category]
|
||||
local categoryColor = category and category.color or color_white
|
||||
|
||||
logEntry.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
|
||||
-- Боковая полоска категории
|
||||
draw.RoundedBoxEx(4, 0, 0, 5, h, categoryColor, true, false, true, false)
|
||||
|
||||
-- Рамка
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
-- Время
|
||||
draw.SimpleText(log.timeString, "DermaDefault", 15, 8, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
-- Категория
|
||||
local catText = category and (category.icon .. " " .. category.name) or log.category
|
||||
draw.SimpleText(catText, "DermaDefault", 15, 25, categoryColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
-- Сообщение
|
||||
draw.SimpleText(log.message, "ixSmallFont", 15, 43, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
end
|
||||
end
|
||||
end
|
||||
161
garrysmod/gamemodes/militaryrp/plugins/serverlogs/sh_plugin.lua
Normal file
161
garrysmod/gamemodes/militaryrp/plugins/serverlogs/sh_plugin.lua
Normal file
@@ -0,0 +1,161 @@
|
||||
PLUGIN.name = "Server Logs"
|
||||
PLUGIN.author = "Server"
|
||||
PLUGIN.description = "Система логирования событий сервера для администраторов"
|
||||
|
||||
-- Категории логов
|
||||
PLUGIN.LOG_CATEGORIES = {
|
||||
COMBAT = {
|
||||
name = "Боевые действия",
|
||||
color = Color(255, 100, 100),
|
||||
icon = "⚔"
|
||||
},
|
||||
ADMIN = {
|
||||
name = "Администрирование",
|
||||
color = Color(255, 200, 50),
|
||||
icon = "⚙"
|
||||
},
|
||||
ECONOMY = {
|
||||
name = "Экономика",
|
||||
color = Color(100, 200, 100),
|
||||
icon = "💰"
|
||||
},
|
||||
CHAT = {
|
||||
name = "Чат",
|
||||
color = Color(150, 150, 255),
|
||||
icon = "💬"
|
||||
},
|
||||
CONNECTION = {
|
||||
name = "Подключения",
|
||||
color = Color(200, 200, 200),
|
||||
icon = "🔌"
|
||||
},
|
||||
INVENTORY = {
|
||||
name = "Инвентарь",
|
||||
color = Color(255, 150, 100),
|
||||
icon = "🎒"
|
||||
},
|
||||
VEHICLE = {
|
||||
name = "Техника",
|
||||
color = Color(100, 150, 255),
|
||||
icon = "🚗"
|
||||
},
|
||||
CHARACTER = {
|
||||
name = "Персонажи",
|
||||
color = Color(150, 255, 150),
|
||||
icon = "👤"
|
||||
}
|
||||
}
|
||||
|
||||
-- Типы событий
|
||||
PLUGIN.LOG_TYPES = {
|
||||
-- Боевые
|
||||
PLAYER_DEATH = {category = "COMBAT", description = "Смерть игрока"},
|
||||
PLAYER_HURT = {category = "COMBAT", description = "Получение урона"},
|
||||
NPC_DEATH = {category = "COMBAT", description = "Смерть NPC"},
|
||||
|
||||
-- Административные
|
||||
ADMIN_COMMAND = {category = "ADMIN", description = "Админ команда"},
|
||||
PLAYER_KICK = {category = "ADMIN", description = "Кик игрока"},
|
||||
PLAYER_BAN = {category = "ADMIN", description = "Бан игрока"},
|
||||
PLAYER_WARN = {category = "ADMIN", description = "Предупреждение"},
|
||||
PLAYER_ARREST = {category = "ADMIN", description = "Арест игрока"},
|
||||
PLAYER_TIE = {category = "ADMIN", description = "Связывание игрока"},
|
||||
HANDCUFF_RESTRAIN = {category = "ADMIN", description = "Надевание наручников"},
|
||||
HANDCUFF_UNRESTRAIN = {category = "ADMIN", description = "Снятие наручников"},
|
||||
PLAYER_JAIL = {category = "ADMIN", description = "Отправка в тюрьму"},
|
||||
PLAYER_RELEASE = {category = "ADMIN", description = "Освобождение из тюрьмы"},
|
||||
PLAYER_UNARREST = {category = "ADMIN", description = "Снятие ареста"},
|
||||
|
||||
-- Экономика
|
||||
MONEY_TRANSACTION = {category = "ECONOMY", description = "Денежная транзакция"},
|
||||
ITEM_PURCHASE = {category = "ECONOMY", description = "Покупка предмета"},
|
||||
SUPPLY_CHANGE = {category = "ECONOMY", description = "Изменение снабжения"},
|
||||
FACTION_SUPPLY_ADD = {category = "ECONOMY", description = "Пополнение снабжения фракции"},
|
||||
FACTION_SUPPLY_USE = {category = "ECONOMY", description = "Расход снабжения фракции"},
|
||||
VEHICLE_POINTS_ADD = {category = "ECONOMY", description = "Пополнение баллов техники"},
|
||||
VEHICLE_POINTS_USE = {category = "ECONOMY", description = "Расход баллов техники"},
|
||||
|
||||
-- Чат
|
||||
CHAT_MESSAGE = {category = "CHAT", description = "Сообщение в чат"},
|
||||
COMMAND_EXECUTED = {category = "CHAT", description = "Выполнена команда"},
|
||||
|
||||
-- Подключения
|
||||
PLAYER_CONNECT = {category = "CONNECTION", description = "Подключение"},
|
||||
PLAYER_DISCONNECT = {category = "CONNECTION", description = "Отключение"},
|
||||
PLAYER_SPAWN = {category = "CONNECTION", description = "Спавн игрока"},
|
||||
|
||||
-- Инвентарь
|
||||
ITEM_PICKUP = {category = "INVENTORY", description = "Поднятие предмета"},
|
||||
ITEM_DROP = {category = "INVENTORY", description = "Выброс предмета"},
|
||||
ITEM_USE = {category = "INVENTORY", description = "Использование предмета"},
|
||||
WEAPON_SPAWN = {category = "INVENTORY", description = "Выдача оружия из арсенала"},
|
||||
WEAPON_DONATE = {category = "INVENTORY", description = "Выдача донат оружия"},
|
||||
|
||||
-- Техника
|
||||
VEHICLE_SPAWN = {category = "VEHICLE", description = "Вызов техники"},
|
||||
VEHICLE_RETURN = {category = "VEHICLE", description = "Возврат техники"},
|
||||
|
||||
-- Персонажи
|
||||
CHARACTER_CREATE = {category = "CHARACTER", description = "Создание персонажа"},
|
||||
CHARACTER_DELETE = {category = "CHARACTER", description = "Удаление персонажа"},
|
||||
CHARACTER_SWITCH = {category = "CHARACTER", description = "Смена персонажа"},
|
||||
|
||||
-- Захват точек
|
||||
CAPTURE_START = {category = "COMBAT", description = "Начало захвата точки"},
|
||||
CAPTURE_COMPLETE = {category = "COMBAT", description = "Точка захвачена"},
|
||||
CAPTURE_LOST = {category = "COMBAT", description = "Точка потеряна"},
|
||||
CAPTURE_REWARD = {category = "ECONOMY", description = "Награда за захват"},
|
||||
|
||||
-- Отряды
|
||||
SQUAD_CREATE = {category = "CHARACTER", description = "Создание отряда"},
|
||||
SQUAD_DELETE = {category = "CHARACTER", description = "Расформирование отряда"},
|
||||
SQUAD_INVITE = {category = "CHARACTER", description = "Приглашение в отряд"},
|
||||
SQUAD_JOIN = {category = "CHARACTER", description = "Вступление в отряд"},
|
||||
SQUAD_LEAVE = {category = "CHARACTER", description = "Выход из отряда"},
|
||||
SQUAD_KICK = {category = "CHARACTER", description = "Исключение из отряда"}
|
||||
}
|
||||
|
||||
ix.log = ix.log or {}
|
||||
ix.log.stored = ix.log.stored or {}
|
||||
|
||||
-- Конфигурация
|
||||
ix.config.Add("logMaxEntries", 1000, "Максимальное количество логов в памяти", nil, {
|
||||
data = {min = 100, max = 5000},
|
||||
category = "server"
|
||||
})
|
||||
|
||||
ix.config.Add("logSaveToFile", true, "Сохранять логи в файл", nil, {
|
||||
category = "server"
|
||||
})
|
||||
|
||||
ix.config.Add("logFilePath", "helix_logs", "Папка для сохранения логов", nil, {
|
||||
category = "server"
|
||||
})
|
||||
|
||||
-- Локализация команд
|
||||
if CLIENT then
|
||||
ix.lang.AddTable("russian", {
|
||||
serverLogsOpened = "Панель логов открыта",
|
||||
serverLogsCleared = "Логи сервера очищены",
|
||||
serverLogsDisplayed = "Показано логов: %s",
|
||||
serverLogsSearchResults = "Найдено логов: %s",
|
||||
cmdViewLogs = "Открыть панель логов",
|
||||
cmdClearLogs = "Очистить логи",
|
||||
cmdLastLogs = "Показать последние логи",
|
||||
cmdSearchLogs = "Поиск в логах"
|
||||
})
|
||||
|
||||
ix.lang.AddTable("english", {
|
||||
serverLogsOpened = "Logs panel opened",
|
||||
serverLogsCleared = "Server logs cleared",
|
||||
serverLogsDisplayed = "Logs displayed: %s",
|
||||
serverLogsSearchResults = "Logs found: %s",
|
||||
cmdViewLogs = "Open logs panel",
|
||||
cmdClearLogs = "Clear logs",
|
||||
cmdLastLogs = "Show recent logs",
|
||||
cmdSearchLogs = "Search logs"
|
||||
})
|
||||
end
|
||||
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
455
garrysmod/gamemodes/militaryrp/plugins/serverlogs/sv_plugin.lua
Normal file
455
garrysmod/gamemodes/militaryrp/plugins/serverlogs/sv_plugin.lua
Normal file
@@ -0,0 +1,455 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
util.AddNetworkString("ixServerLogs_Request")
|
||||
util.AddNetworkString("ixServerLogs_Response")
|
||||
util.AddNetworkString("ixServerLogs_Clear")
|
||||
util.AddNetworkString("ixServerLogs_OpenMenu")
|
||||
|
||||
-- Функция проверки доступа к логам через CAMI
|
||||
local function HasLogAccess(client)
|
||||
return AdminPrivs[client:GetUserGroup()]
|
||||
end
|
||||
|
||||
-- Инициализация системы логов
|
||||
function PLUGIN:InitializeLogSystem()
|
||||
self.logs = self.logs or {}
|
||||
self.logFile = nil
|
||||
|
||||
-- Создаем папку для логов
|
||||
if ix.config.Get("logSaveToFile", true) then
|
||||
local logPath = ix.config.Get("logFilePath", "helix_logs")
|
||||
if not file.Exists(logPath, "DATA") then
|
||||
file.CreateDir(logPath)
|
||||
end
|
||||
|
||||
-- Создаем файл для текущей сессии
|
||||
local date = os.date("%Y-%m-%d_%H-%M-%S")
|
||||
self.logFile = logPath .. "/log_" .. date .. ".txt"
|
||||
|
||||
file.Write(self.logFile, "=== Server Logs Started: " .. os.date("%Y-%m-%d %H:%M:%S") .. " ===\n")
|
||||
end
|
||||
end
|
||||
|
||||
-- Добавление лога
|
||||
function PLUGIN:AddLog(logType, message, target, data)
|
||||
if not self.LOG_TYPES[logType] then
|
||||
print("[ServerLogs] Unknown log type: " .. tostring(logType))
|
||||
return
|
||||
end
|
||||
|
||||
local logEntry = {
|
||||
id = #self.logs + 1,
|
||||
type = logType,
|
||||
category = self.LOG_TYPES[logType].category,
|
||||
message = message,
|
||||
target = target and {
|
||||
name = IsValid(target) and target:Nick() or tostring(target),
|
||||
steamid = IsValid(target) and target:SteamID() or "N/A"
|
||||
} or nil,
|
||||
data = data or {},
|
||||
timestamp = os.time(),
|
||||
timeString = os.date("%H:%M:%S")
|
||||
}
|
||||
|
||||
table.insert(self.logs, logEntry)
|
||||
|
||||
-- Ограничиваем количество логов в памяти
|
||||
local maxEntries = ix.config.Get("logMaxEntries", 1000)
|
||||
if #self.logs > maxEntries then
|
||||
table.remove(self.logs, 1)
|
||||
end
|
||||
|
||||
-- Сохраняем в файл
|
||||
if self.logFile then
|
||||
local logString = string.format("[%s][%s] %s\n",
|
||||
logEntry.timeString,
|
||||
self.LOG_CATEGORIES[logEntry.category].name,
|
||||
message
|
||||
)
|
||||
file.Append(self.logFile, logString)
|
||||
end
|
||||
|
||||
-- Выводим в консоль
|
||||
MsgC(self.LOG_CATEGORIES[logEntry.category].color, "[LOG] ", Color(255, 255, 255), message .. "\n")
|
||||
end
|
||||
|
||||
-- Получение логов с фильтрацией
|
||||
function PLUGIN:GetLogs(filters)
|
||||
filters = filters or {}
|
||||
local result = {}
|
||||
|
||||
for i = #self.logs, 1, -1 do
|
||||
local log = self.logs[i]
|
||||
local match = true
|
||||
|
||||
-- Фильтр по категории
|
||||
if filters.category and log.category ~= filters.category then
|
||||
match = false
|
||||
end
|
||||
|
||||
-- Фильтр по типу
|
||||
if filters.type and log.type ~= filters.type then
|
||||
match = false
|
||||
end
|
||||
|
||||
-- Фильтр по игроку
|
||||
if filters.player and log.target and log.target.steamid ~= filters.player then
|
||||
match = false
|
||||
end
|
||||
|
||||
-- Фильтр по времени
|
||||
if filters.startTime and log.timestamp < filters.startTime then
|
||||
match = false
|
||||
end
|
||||
|
||||
if filters.endTime and log.timestamp > filters.endTime then
|
||||
match = false
|
||||
end
|
||||
|
||||
-- Фильтр по тексту
|
||||
if filters.search and not string.find(string.lower(log.message), string.lower(filters.search)) then
|
||||
match = false
|
||||
end
|
||||
|
||||
if match then
|
||||
table.insert(result, log)
|
||||
|
||||
-- Ограничиваем количество результатов
|
||||
if filters.limit and #result >= filters.limit then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Сетевая обработка запроса логов
|
||||
net.Receive("ixServerLogs_Request", function(len, client)
|
||||
if not HasLogAccess(client) then return end
|
||||
|
||||
local filters = net.ReadTable()
|
||||
local logs = PLUGIN:GetLogs(filters)
|
||||
|
||||
-- Отправляем логи порциями
|
||||
local chunkSize = 50
|
||||
for i = 1, #logs, chunkSize do
|
||||
local chunk = {}
|
||||
for j = i, math.min(i + chunkSize - 1, #logs) do
|
||||
table.insert(chunk, logs[j])
|
||||
end
|
||||
|
||||
net.Start("ixServerLogs_Response")
|
||||
net.WriteUInt(i == 1 and 1 or 0, 2) -- Первый пакет или нет
|
||||
net.WriteTable(chunk)
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
-- Отправляем финальный пакет если логов нет
|
||||
if #logs == 0 then
|
||||
net.Start("ixServerLogs_Response")
|
||||
net.WriteUInt(1, 2)
|
||||
net.WriteTable({})
|
||||
net.Send(client)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Очистка логов
|
||||
net.Receive("ixServerLogs_Clear", function(len, client)
|
||||
if not client:IsSuperAdmin() then return end
|
||||
|
||||
PLUGIN.logs = {}
|
||||
PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client)
|
||||
end)
|
||||
|
||||
-- ======================
|
||||
-- ХУКИ ДЛЯ ЛОГИРОВАНИЯ
|
||||
-- ======================
|
||||
|
||||
-- Смерть игрока
|
||||
function PLUGIN:PlayerDeath(victim, inflictor, attacker)
|
||||
local attackerName = "Мир"
|
||||
local attackerSteamID = "world"
|
||||
local weapon = "unknown"
|
||||
|
||||
if IsValid(attacker) and attacker:IsPlayer() then
|
||||
attackerName = attacker:Nick()
|
||||
attackerSteamID = attacker:SteamID()
|
||||
weapon = IsValid(attacker:GetActiveWeapon()) and attacker:GetActiveWeapon():GetClass() or "unknown"
|
||||
end
|
||||
|
||||
local message = ""
|
||||
|
||||
if IsValid(attacker) and attacker:IsPlayer() then
|
||||
if attacker == victim then
|
||||
message = victim:Nick() .. " покончил с собой"
|
||||
else
|
||||
message = attackerName .. " убил " .. victim:Nick() .. " используя " .. weapon
|
||||
end
|
||||
else
|
||||
message = victim:Nick() .. " умер"
|
||||
end
|
||||
|
||||
self:AddLog("PLAYER_DEATH", message, victim, {
|
||||
attacker = attackerSteamID,
|
||||
weapon = weapon
|
||||
})
|
||||
end
|
||||
|
||||
-- Получение урона
|
||||
function PLUGIN:PlayerHurt(victim, attacker, health, damage)
|
||||
if not IsValid(attacker) or not attacker:IsPlayer() or attacker == victim then return end
|
||||
|
||||
local message = string.format("%s нанёс %d урона игроку %s (осталось %d HP)",
|
||||
attacker:Nick(), damage, victim:Nick(), health)
|
||||
|
||||
self:AddLog("PLAYER_HURT", message, victim, {
|
||||
attacker = attacker:SteamID(),
|
||||
damage = damage,
|
||||
health = health
|
||||
})
|
||||
end
|
||||
|
||||
-- Подключение игрока
|
||||
function PLUGIN:PlayerInitialSpawn(client)
|
||||
local message = string.format("%s подключился к серверу (SteamID: %s)",
|
||||
client:Nick(), client:SteamID())
|
||||
|
||||
self:AddLog("PLAYER_CONNECT", message, client)
|
||||
end
|
||||
|
||||
-- Отключение игрока
|
||||
function PLUGIN:PlayerDisconnected(client)
|
||||
local message = string.format("%s отключился от сервера", client:Nick())
|
||||
|
||||
self:AddLog("PLAYER_DISCONNECT", message, client)
|
||||
end
|
||||
|
||||
-- Спавн игрока
|
||||
function PLUGIN:PlayerSpawn(client)
|
||||
if not client:GetCharacter() then return end
|
||||
|
||||
local message = string.format("%s заспавнился", client:Nick())
|
||||
|
||||
self:AddLog("PLAYER_SPAWN", message, client)
|
||||
end
|
||||
|
||||
-- Сообщения в чат
|
||||
function PLUGIN:PlayerSay(client, text, teamChat)
|
||||
local chatType = teamChat and "[TEAM]" or "[ALL]"
|
||||
local message = string.format("%s %s: %s", chatType, client:Nick(), text)
|
||||
|
||||
self:AddLog("CHAT_MESSAGE", message, client, {
|
||||
text = text,
|
||||
team = teamChat
|
||||
})
|
||||
end
|
||||
|
||||
-- Создание персонажа
|
||||
function PLUGIN:OnCharacterCreated(client, character)
|
||||
local message = string.format("%s создал персонажа '%s'",
|
||||
client:Nick(), character:GetName())
|
||||
|
||||
self:AddLog("CHARACTER_CREATE", message, client, {
|
||||
characterID = character:GetID(),
|
||||
characterName = character:GetName()
|
||||
})
|
||||
end
|
||||
|
||||
-- Удаление персонажа
|
||||
function PLUGIN:PreCharacterDeleted(client, character)
|
||||
local message = string.format("%s удалил персонажа '%s'",
|
||||
client:Nick(), character:GetName())
|
||||
|
||||
self:AddLog("CHARACTER_DELETE", message, client, {
|
||||
characterID = character:GetID(),
|
||||
characterName = character:GetName()
|
||||
})
|
||||
end
|
||||
|
||||
-- Смена персонажа
|
||||
function PLUGIN:PlayerLoadedCharacter(client, character, oldCharacter)
|
||||
if oldCharacter then
|
||||
local message = string.format("%s сменил персонажа с '%s' на '%s'",
|
||||
client:Nick(), oldCharacter:GetName(), character:GetName())
|
||||
|
||||
self:AddLog("CHARACTER_SWITCH", message, client, {
|
||||
oldCharacter = oldCharacter:GetName(),
|
||||
newCharacter = character:GetName()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Покупка предмета
|
||||
function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell)
|
||||
local action = isSell and "продал" or "купил"
|
||||
local message = string.format("%s %s предмет за %d",
|
||||
client:Nick(), action, price)
|
||||
|
||||
self:AddLog("ITEM_PURCHASE", message, client, {
|
||||
vendor = vendor,
|
||||
price = price,
|
||||
sell = isSell
|
||||
})
|
||||
end
|
||||
|
||||
-- Использование предмета
|
||||
function PLUGIN:OnItemTransferred(item, curInv, inventory)
|
||||
if item.player then
|
||||
local message = string.format("%s переместил предмет '%s'",
|
||||
item.player:Nick(), item:GetName())
|
||||
|
||||
self:AddLog("INVENTORY", message, item.player, {
|
||||
item = item:GetName(),
|
||||
itemID = item:GetID()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Инициализация при загрузке плагина
|
||||
function PLUGIN:InitPostEntity()
|
||||
self:InitializeLogSystem()
|
||||
self:AddLog("ADMIN_COMMAND", "Система логов инициализирована")
|
||||
end
|
||||
|
||||
-- ======================
|
||||
-- КОМАНДЫ HELIX
|
||||
-- ======================
|
||||
|
||||
-- Регистрация CAMI разрешения
|
||||
if CAMI then
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - View Server Logs",
|
||||
MinAccess = "admin",
|
||||
Description = "Позволяет просматривать логи сервера"
|
||||
})
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Clear Server Logs",
|
||||
MinAccess = "superadmin",
|
||||
Description = "Позволяет очищать логи сервера"
|
||||
})
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Use Handcuffs",
|
||||
MinAccess = "admin",
|
||||
Description = "Позволяет использовать наручники"
|
||||
})
|
||||
end
|
||||
|
||||
-- Команда для просмотра логов
|
||||
ix.command.Add("ViewLogs", {
|
||||
description = "Открыть панель просмотра логов сервера",
|
||||
OnCheckAccess = function(self, client)
|
||||
return HasLogAccess(client)
|
||||
end,
|
||||
OnRun = function(self, client)
|
||||
-- Отправляем команду на открытие меню на клиенте
|
||||
net.Start("ixServerLogs_OpenMenu")
|
||||
net.Send(client)
|
||||
|
||||
return "@serverLogsOpened"
|
||||
end
|
||||
})
|
||||
|
||||
-- Альтернативная команда
|
||||
ix.command.Add("ServerLogs", {
|
||||
description = "Открыть панель просмотра логов сервера",
|
||||
OnCheckAccess = function(self, client)
|
||||
return HasLogAccess(client)
|
||||
end,
|
||||
OnRun = function(self, client)
|
||||
-- Отправляем команду на открытие меню на клиенте
|
||||
net.Start("ixServerLogs_OpenMenu")
|
||||
net.Send(client)
|
||||
|
||||
return "@serverLogsOpened"
|
||||
end
|
||||
})
|
||||
|
||||
-- Команда для очистки логов
|
||||
ix.command.Add("ClearLogs", {
|
||||
description = "Очистить все логи сервера",
|
||||
superAdminOnly = true,
|
||||
OnCheckAccess = function(self, client)
|
||||
-- Проверка через CAMI
|
||||
if CAMI then
|
||||
return CAMI.PlayerHasAccess(client, "Helix - Clear Server Logs", function(allowed)
|
||||
return allowed
|
||||
end)
|
||||
end
|
||||
-- Fallback на стандартную проверку суперадмина
|
||||
return client:IsSuperAdmin()
|
||||
end,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN.logs = {}
|
||||
PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client)
|
||||
return "@serverLogsCleared"
|
||||
end
|
||||
})
|
||||
|
||||
-- Команда для просмотра последних логов в консоли
|
||||
ix.command.Add("LastLogs", {
|
||||
description = "Показать последние 20 логов в консоли",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.number,
|
||||
},
|
||||
argumentNames = {"количество"},
|
||||
OnCheckAccess = function(self, client)
|
||||
-- Проверка через CAMI
|
||||
if CAMI then
|
||||
return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed)
|
||||
return allowed
|
||||
end)
|
||||
end
|
||||
return client:IsAdmin()
|
||||
end,
|
||||
OnRun = function(self, client, count)
|
||||
count = math.Clamp(count or 20, 1, 100)
|
||||
|
||||
local logs = PLUGIN:GetLogs({limit = count})
|
||||
|
||||
client:ChatPrint("=== Последние " .. #logs .. " логов ===")
|
||||
for i, log in ipairs(logs) do
|
||||
local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name
|
||||
local targetInfo = log.target and (" [" .. log.target.name .. "]") or ""
|
||||
client:ChatPrint(string.format("[%s][%s]%s %s",
|
||||
log.timeString, categoryName, targetInfo, log.message))
|
||||
end
|
||||
|
||||
return "@serverLogsDisplayed", #logs
|
||||
end
|
||||
})
|
||||
|
||||
-- Команда для поиска в логах
|
||||
ix.command.Add("SearchLogs", {
|
||||
description = "Найти логи по тексту или игроку",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.text,
|
||||
},
|
||||
argumentNames = {"поисковый запрос"},
|
||||
OnCheckAccess = function(self, client)
|
||||
-- Проверка через CAMI
|
||||
if CAMI then
|
||||
return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed)
|
||||
return allowed
|
||||
end)
|
||||
end
|
||||
return client:IsAdmin()
|
||||
end,
|
||||
OnRun = function(self, client, search)
|
||||
local logs = PLUGIN:GetLogs({search = search, limit = 50})
|
||||
|
||||
client:ChatPrint("=== Найдено логов: " .. #logs .. " ===")
|
||||
for i, log in ipairs(logs) do
|
||||
local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name
|
||||
local targetInfo = log.target and (" [" .. log.target.name .. "]") or ""
|
||||
client:ChatPrint(string.format("[%s][%s]%s %s",
|
||||
log.timeString, categoryName, targetInfo, log.message))
|
||||
end
|
||||
|
||||
return "@serverLogsSearchResults", #logs
|
||||
end
|
||||
})
|
||||
Reference in New Issue
Block a user