Files
2026-03-31 11:51:03 +03:00

435 lines
16 KiB
Lua
Raw Permalink 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)
if isHovered then
draw.SimpleText("Клик для копирования", "ixSmallFont", w - 10, h - 5, COLOR_TEXT_SECONDARY, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM)
end
end
-- Поиск SteamID в тексте сообщения (SteamID2 и SteamID64)
local function findSteamIDs(text)
if not text then return {} end
local ids = {}
-- Match SteamID2
for id in string.gmatch(text, "STEAM_[0-5]:[0-9]:[1-9][0-9]*") do
table.insert(ids, id)
end
-- Match SteamID64
for id in string.gmatch(text, "7656119[0-9]{10}") do
table.insert(ids, id)
end
return ids
end
local extractedIDs = findSteamIDs(log.message)
logEntry.OnMousePressed = function(s, m)
s:DoRightClick()
end
logEntry.DoRightClick = function(s)
local menu = DermaMenu()
-- Опция копирования основного сообщения
local msgOpt = menu:AddOption("Копировать сообщение", function()
SetClipboardText(log.message)
ix.util.Notify("Сообщение скопировано")
end)
msgOpt:SetIcon("icon16/page_copy.png")
menu:AddSpacer()
-- Копирование SteamID цели (если есть в метаданных)
if log.target and log.target.steamid and log.target.steamid ~= "N/A" then
local targetOpt = menu:AddOption("Копировать SteamID (" .. log.target.name .. ")", function()
SetClipboardText(log.target.steamid)
ix.util.Notify("SteamID скопирован: " .. log.target.steamid)
end)
targetOpt:SetIcon("icon16/user_edit.png")
end
-- Копирование SteamID атакующего (для убийств/урона)
if log.data and log.data.attacker and log.data.attacker ~= "world" then
local attackerOpt = menu:AddOption("Копировать SteamID Атакующего", function()
local sid = log.data.attacker
SetClipboardText(sid)
ix.util.Notify("SteamID атакующего скопирован: " .. sid)
end)
attackerOpt:SetIcon("icon16/user_delete.png")
end
-- Копирование найденных ID из текста
if #extractedIDs > 0 then
for _, eid in ipairs(extractedIDs) do
local extOpt = menu:AddOption("Копировать найденный " .. eid, function()
SetClipboardText(eid)
ix.util.Notify("ID скопирован: " .. eid)
end)
extOpt:SetIcon("icon16/user_magnify.png")
end
end
menu:AddSpacer()
local timeOpt = menu:AddOption("Копировать время (" .. log.timeString .. ")", function()
SetClipboardText(log.timeString)
end)
timeOpt:SetIcon("icon16/time.png")
local fullOpt = 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)
fullOpt:SetIcon("icon16/page_white_copy.png")
menu:Open()
end
end
end