Files
VnUtest/garrysmod/gamemodes/militaryrp/plugins/serverlogs/cl_plugin.lua
2026-03-31 11:26:49 +03:00

402 lines
15 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("DButton", self.logsList)
logEntry:SetTall(66)
logEntry:Dock(TOP)
logEntry:DockMargin(5, 5, 5, 0)
logEntry:SetText("")
local category = self.LOG_CATEGORIES[log.category]
local categoryColor = category and category.color or color_white
logEntry.Paint = function(s, w, h)
local isHovered = s:IsHovered() or s:IsChildHovered()
local bgCol = isHovered and Color(18, 25, 20) or COLOR_BG_LIGHT
draw.RoundedBox(4, 0, 0, w, h, bgCol)
-- Боковая полоска категории
draw.RoundedBoxEx(4, 0, 0, 5, h, categoryColor, true, false, true, false)
-- Рамка
surface.SetDrawColor(isHovered and COLOR_PRIMARY or 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
-- Поиск SteamID в тексте сообщения
local function findSteamID(text)
if not text then return nil end
return string.match(text, "STEAM_[0-5]:[0-9]:[0-9]+")
end
local extractedID = findSteamID(log.message)
logEntry.DoRightClick = function(s)
local menu = DermaMenu()
-- Опция копирования основного сообщения
menu:AddOption("Копировать сообщение", function()
SetClipboardText(log.message)
ix.util.Notify("Сообщение скопировано")
end):SetIcon("icon16/page_copy.png")
-- Копирование SteamID цели (если есть в метаданных)
if log.target and log.target.steamid and log.target.steamid ~= "N/A" then
menu:AddOption("Копировать SteamID (" .. log.target.name .. ")", function()
SetClipboardText(log.target.steamid)
ix.util.Notify("SteamID скопирован: " .. log.target.steamid)
end):SetIcon("icon16/user_edit.png")
-- Если нет в метаданных, но нашли в тексте (для архива)
elseif extractedID then
menu:AddOption("Копировать найденный SteamID (" .. extractedID .. ")", function()
SetClipboardText(extractedID)
ix.util.Notify("SteamID скопирован: " .. extractedID)
end):SetIcon("icon16/user_magnify.png")
end
-- Копирование SteamID атакующего (для убийств)
if log.data and log.data.attacker and log.data.attacker ~= "world" then
menu:AddOption("Копировать SteamID Атакующего", function()
SetClipboardText(log.data.attacker)
ix.util.Notify("SteamID атакующего скопирован: " .. log.data.attacker)
end):SetIcon("icon16/user_delete.png")
end
menu:AddSpacer()
menu:AddOption("Копировать время (" .. log.timeString .. ")", function()
SetClipboardText(log.timeString)
end):SetIcon("icon16/time.png")
menu:AddOption("Копировать полную строку", function()
local fullLog = string.format("[%s][%s] %s", log.timeString, category and category.name or log.category, log.message)
SetClipboardText(fullLog)
ix.util.Notify("Полная строка скопирована")
end):SetIcon("icon16/page_white_copy.png")
menu:Open()
end
end
end