local ROW_HEIGHT = 20 local COL_WIDTH = 100 local BORDER_WIDTH = 1 local BORDER_COLOR = Color3.new(1,1,1) local STD_BG_COLOR = Color3.new(0.25, 0.75, 0.75) local ALT_BG_COLOR = Color3.new(0.25, 0.25, 0.75) local TEXT_COLOR = Color3.new(1, 1, 1) local SORTED_TITLE_COLOR = Color3.new(1,0,1) local BG_TRANSPARENCY = 0.9 local COL_WIDTHS = {50,50,50,65,120,-335} local COL_KEYS = { "Method", "Status", "Time", "RequestType", "URL"} local COL_TITLES = {"No.", "Method", "Status", "Time(ms)", "RequestType", "URL"} local MAX_ROW = 256 pcall(function () MAX_ROW = tonumber(settings():GetFVariable("HttpAnalyticsMaxHistory")) end) --//////////////////////////////////// -- -- UIListGridItemClass -- a single grid in the list -- it has a 'Frame' to show background and 'TextLabel' to show text -- --//////////////////////////////////// local UIListGridItemClass = {} UIListGridItemClass.__index = UIListGridItemClass function UIListGridItemClass.new(parentFrame, pos, size, bgColor) local self = {} setmetatable(self, UIListGridItemClass) self._parent = parentFrame self._frame = Instance.new("Frame") self._frame.Position = pos self._frame.Size = size self._frame.BorderColor3 = BORDER_COLOR self._frame.BorderSizePixel = BORDER_WIDTH self._frame.BackgroundTransparency = BG_TRANSPARENCY self._frame.ZIndex = self._parent.ZIndex + 1 self._frame.BackgroundColor3 = bgColor self._frame.Parent = self._parent self._label = Instance.new("TextLabel") self._label.TextColor3 = TEXT_COLOR self._label.Position = UDim2.new(0, 5, 0, 0) self._label.Size = UDim2.new(1, -10, 1, 0) self._label.BackgroundTransparency = 1 self._label.TextXAlignment = Enum.TextXAlignment.Left self._label.ZIndex = self._parent.ZIndex + 1 self._label.Parent = self._frame return self end function UIListGridItemClass:setValue( value ) if self._label == nil then return end local valueType = type(value) local text if valueType == "string" then text = value elseif valueType == "number" then if value - math.floor(value) > 0 then text = string.format("%.02f", value) else text = tostring(value) end else text = tostring(value) end self._label.Text = text end function UIListGridItemClass:getFrame() return self._frame end --//////////////////////////////////// -- -- UIListClass -- to show lua tables in UI -- creates and manager UIListGridItemClass instance -- --//////////////////////////////////// local UIListClass = {} UIListClass.__index = UIListClass -- callbackClicked(row, col, frame) 'frame' is the 'Frame' instance be clicked function UIListClass.new(parentFrame, callbackClicked) local self = {} setmetatable(self, UIListClass) self._parent = parentFrame self._frame = Instance.new("Frame") self._frame.Name = "UIListClass" self._frame.Position = UDim2.new(0,1,0,1) self._frame.Size = UDim2.new(1,0,0,0) self._frame.ZIndex = self._parent.ZIndex self._frame.BorderSizePixel = 0 self._frame.Parent = self._parent self._columnWidths = {COL_WIDTH} self._rowHeight = ROW_HEIGHT self._rows = {} self._callbackClicked = callbackClicked return self end function UIListClass:setColumns(names, widths) self:setColumnWidths(widths) self:setRow(1, names) end function UIListClass:setColumnWidths( widths ) self._columnWidths = widths end function UIListClass:setRow( index, values ) if #self._rows + 1 < index then return elseif #self._rows < index then self:addRow(values) else local row = self._rows[index] for i,v in ipairs(row) do v:setValue(values[i]) end end end function UIListClass:addRow( values ) local index = #self._rows+1 local row = {} for i = 1, #values do if i > #self._columnWidths then break end local pos = self:getItemPosition(index, i) local size = self:getItemSize(index,i) local bgColor if (index % 2 == 1) then bgColor = STD_BG_COLOR else bgColor = ALT_BG_COLOR end local item = UIListGridItemClass.new(self._frame, pos, size, bgColor) -- set click callback local itemFrame = item:getFrame() itemFrame.InputBegan:connect(function(input) -- set the frame's background color and call _callbackClicked if input.UserInputType == Enum.UserInputType.MouseButton1 then if self._callbackClicked ~= nil then self._callbackClicked(index, i, itemFrame) end end end) item:setValue(values[i]) table.insert(row, item) end table.insert(self._rows, row) end function UIListClass:getItemPosition( row, col ) local offsetX = 0 local offsetY = 0 for i = 1, col-1 do offsetX = offsetX + self._columnWidths[i] end for i = 1, row-1 do offsetY = offsetY + self._rowHeight end return UDim2.new(0, offsetX, 0, offsetY) end function UIListClass:getItemSize( row, col ) if self._columnWidths[col] < 0 then return UDim2.new(1, -self._columnWidths[col], 0, self._rowHeight) else return UDim2.new(0, self._columnWidths[col], 0, self._rowHeight) end end function UIListClass:getHeightInPix() return self._rowHeight * #self._rows end function UIListClass:getFrame() return self._frame end --//////////////////////////////////// -- -- HttpResultList -- manage http results data and set data to UIListClass then the http results will show in UI -- call HttpResultListClass:addHttpResult to add a row of http result to UI -- --//////////////////////////////////// local HttpResultListClass = {} HttpResultListClass.__index = HttpResultListClass function HttpResultListClass.new( parentFrame, heightChange, callbackClicked) local self = {} setmetatable(self, HttpResultListClass) self._sortedColumn = 1 self._list = UIListClass.new(parentFrame, callbackClicked) self._heightChangedCallback = heightChange self._rowValues = {} self._counter = 0 self._sortedTitleLastColor = nil self._sortedTitleFrame = nil return self end function HttpResultListClass:sort(col,frame) if col == self._sortedColumn then return end if col < 1 or #self._rowValues < 2 or col > #self._rowValues[1] then return end self._sortedColumn = col self._sortedTitleLastColor = frame.BackgroundColor3 if self._sortedTitleFrame ~= nil then self._sortedTitleFrame.BackgroundColor3 = self._sortedTitleLastColor end self._sortedTitleFrame = frame self._sortedTitleFrame.BackgroundColor3 = SORTED_TITLE_COLOR table.sort(self._rowValues, function (row1, row2) return row1[self._sortedColumn] < row2[self._sortedColumn] end) for i,values in ipairs(self._rowValues) do self._list:setRow(i+1, values) end end -- insert 'valuse'. If '_rowValues' is ordered, will keep the order function HttpResultListClass:insert(values) if self._sortedColumn == 0 or #self._rowValues == 0 then table.insert(self._rowValues, values) return else -- insert to an ordered table -- find a right position in the ordered table local posInsert = #self._rowValues + 1 for i=1,#self._rowValues do if self._rowValues[i][self._sortedColumn] > values[self._sortedColumn] then posInsert = i break end end -- inser the 'values' at posInsert -- move the all items after posInsert back off one position table.insert(self._rowValues, posInsert, values) end end -- remove the the oldest item function HttpResultListClass:remove() local removeIndex = 1 if self._sortedColumn > 1 then local removeCounterValue = self._counter - MAX_ROW + 1 for i,v in ipairs(self._rowValues) do if v[1] == removeCounterValue then removeIndex = i break end end -- end for end table.remove(self._rowValues, removeIndex) end function HttpResultListClass:addRow(values) local isHeightUpdate = false if #self._rowValues < MAX_ROW then self:insert(values) isHeightUpdate = true else self:remove() self:insert(values) end for i,values in ipairs(self._rowValues) do self._list:setRow(i+1, values) end if isHeightUpdate and self._heightChangedCallback ~= nil then self._heightChangedCallback(self._list:getHeightInPix()) end end -- add a row of http result to list function HttpResultListClass:addHttpResult(httpResult) if httpResult["Hiden"] == true then return end if #self._rowValues == 0 then self._list:setColumns(COL_TITLES, COL_WIDTHS) end local values = {self._counter+1} for _,v in ipairs(COL_KEYS) do table.insert(values, httpResult[v]) end table.insert(values, httpResult["Response"]) self:addRow(values) self._counter = self._counter+1 end function HttpResultListClass:getHeightInPix() return self._list:getHeightInPix() end function HttpResultListClass:getFrame() return self._list:getFrame() end function HttpResultListClass:getValues(row) return self._rowValues[row] end --//////////////////////////////////// -- -- HttpAnalyticsTable -- how many requests have been made, how many requests have failed, -- average, min, and max response time for per developer facing RequestType -- --//////////////////////////////////// local HttpAnalyticsTableClass = {} HttpAnalyticsTableClass.__index = HttpAnalyticsTableClass local HTTP_ANALYTICS_TITTLES = {"RequestType","RequestCount","FailedCount", "AverageTime(ms)","MinTime(ms)","MaxTime(ms)"} local HTTP_ANALYTICS_KEYS = {"RequestType","RequestCount","FailedCount", "AverageTime","MinTime","MaxTime"} local HTTP_ANALYTICS_WIDTHS = {120, 90, 90, 110, 90, 90} function HttpAnalyticsTableClass.new( parentFrame,callback ) local self = {} setmetatable(self, HttpAnalyticsTableClass) self._list = UIListClass.new(parentFrame) self._tableData = {} self._tableData.size = 0 self._heightChangedCallback = callback return self end -- http analyzer data -- show how many requests have been made, how many requests have failed, -- average, min, and max response time for per developer facing RequestType function HttpAnalyticsTableClass:addHttpResult( httpResult ) if self._tableData.size == 0 then self._list:setColumns(HTTP_ANALYTICS_TITTLES, HTTP_ANALYTICS_WIDTHS) end local record = self._tableData[httpResult.RequestType] local isHeightUpdate = false if record == nil then self._tableData[httpResult.RequestType] = { RequestType = httpResult.RequestType, RequestCount = 1, FailedCount = 0, AverageTime = httpResult.Time, MinTime = httpResult.Time, MaxTime = httpResult.Time, index = self._tableData.size + 1 } self._tableData.size = self._tableData.size + 1 if httpResult.Status >= 400 then self._tableData[httpResult.RequestType].FailedCount = 1 end record = self._tableData[httpResult.RequestType] isHeightUpdate = true else record.RequestCount = record.RequestCount + 1 if httpResult.Status >= 400 then record.FailedCount = record.FailedCount + 1 end record.AverageTime = (record.AverageTime*record.RequestCount + httpResult.Time - record.AverageTime)/record.RequestCount if httpResult.Time < record.MinTime then record.MinTime = httpResult.Time end if httpResult.Time > record.MaxTime then record.MaxTime = httpResult.Time end end local values = {} for _,v in ipairs(HTTP_ANALYTICS_KEYS) do table.insert(values, record[v]) end self._list:setRow(record.index+1, values) if isHeightUpdate and self._heightChangedCallback ~= nil then self._heightChangedCallback(self:getHeightInPix()) end end function HttpAnalyticsTableClass:getHeightInPix() return self._list:getHeightInPix() end function HttpAnalyticsTableClass:getFrame() return self._list:getFrame() end --//////////////////////////////////// -- -- HttpResponseViewClass -- show response header and body -- --//////////////////////////////////// local RESPONSE_TITLE_SIZE = 12 local RESPONSE_TITLE_COLOR = Color3.new(0.8,0.8,1) local RESPONSE_TITLE_INDENTATION = 4 local RESPONSE_TEXT_INDENTATION = 10 local CANT_RENDER_MSG = "\n Some content can't be rendered as text." local BODY_TITTLE = "Response Body:" local HttpResponseViewClass = {} HttpResponseViewClass.__index = HttpResponseViewClass function HttpResponseViewClass.new( parentFrame,callback ) local self = {} setmetatable(self, HttpResponseViewClass) self._frame = Instance.new("Frame") self._frame.Position = UDim2.new(0, 2, 0, 2) self._frame.Size = UDim2.new(1, -4, 10, 0) self._frame.BackgroundTransparency = 1 self._frame.ZIndex = parentFrame.ZIndex+1 self._frame.Visible = false self._frame.InputBegan:connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 then self._bodyTitle.Visible = false self._bodyLabel.Visible = false self._frame.Visible = false callback(false) end end) self._frame.Parent = parentFrame self._bodyTitle = Instance.new("TextLabel") self._bodyTitle.Name = "self._bodyTitle" self._bodyTitle.Position = UDim2.new(0, RESPONSE_TITLE_INDENTATION, 0, RESPONSE_TITLE_INDENTATION) self._bodyTitle.Size = UDim2.new(1, 0, 0, 50) self._bodyTitle.TextSize = RESPONSE_TITLE_SIZE self._bodyTitle.TextColor3 = RESPONSE_TITLE_COLOR self._bodyTitle.TextXAlignment = Enum.TextXAlignment.Left self._bodyTitle.TextYAlignment = Enum.TextYAlignment.Top self._bodyTitle.ZIndex = self._frame.ZIndex self._bodyTitle.BackgroundTransparency = 1 self._bodyTitle.Visible = false self._bodyTitle.TextWrap = true self._bodyTitle.Text = BODY_TITTLE self._bodyTitle.Parent = self._frame self._bodyLabel = Instance.new("TextLabel") self._bodyLabel.Name = "self._bodyLabel" self._bodyLabel.Position = UDim2.new(0, RESPONSE_TEXT_INDENTATION, 0, 29) self._bodyLabel.Size = UDim2.new(1, -2*RESPONSE_TEXT_INDENTATION, 1, 0) self._bodyLabel.TextWrap = true self._bodyLabel.TextXAlignment = Enum.TextXAlignment.Left self._bodyLabel.TextYAlignment = Enum.TextYAlignment.Top self._bodyLabel.TextColor3 = TEXT_COLOR self._bodyLabel.ZIndex = self._frame.ZIndex self._bodyLabel.BackgroundTransparency = 1 self._bodyLabel.Visible = false self._bodyLabel.Parent = self._frame return self end -- show labels and update the postion function HttpResponseViewClass:show(body) local utf8Length,pos= utf8.len(body) if utf8Length == nil then if pos ~= nil then body = body:sub(1, pos-1) end -- tell developers something can't be rendered as text self._bodyTitle.Text = BODY_TITTLE .. CANT_RENDER_MSG else self._bodyTitle.Text = BODY_TITTLE end self._bodyLabel.Text = body self._bodyTitle.Position = UDim2.new(0, RESPONSE_TITLE_INDENTATION, 0, RESPONSE_TITLE_INDENTATION) self._bodyLabel.Position = UDim2.new(0, RESPONSE_TEXT_INDENTATION, 0, self._bodyTitle.Position.Y.Offset + self._bodyTitle.TextBounds.Y) self._bodyTitle.Visible = true self._bodyLabel.Visible = true self._frame.Visible = true end function HttpResponseViewClass:getHeightInPix() return RESPONSE_TITLE_INDENTATION + self._bodyTitle.TextBounds.Y + self._bodyLabel.TextBounds.Y end function HttpResponseViewClass:isVisible() return self._frame.Visible end --//////////////////////////////////// -- -- HttpAnalyzer -- contain the HttpAnalyticsTable and HttpResultList -- get data from uper layer, update the position of HttpResultList -- --//////////////////////////////////// local SPACE_TABLE_LIST = 3 local HttpAnalyzerClass = {} HttpAnalyzerClass.__index = HttpAnalyzerClass function HttpAnalyzerClass.new( parentFrame,callback ) local self = {} setmetatable(self, HttpAnalyzerClass) self._responseView = HttpResponseViewClass.new(parentFrame, function (isShowed, height) if not isShowed then self._httpAnalyticsFrame.Visible = true self._httpResultListFrame.Visible = true end self:heightChange() end) local analyticsTableHeightChange = function (newHeight) self:updatePosition() end local resultListHeightChange = function (newHeight) self:heightChange() end local resultListClicked = function(row, col, frame) if self._responseView:isVisible() then return end if row == 1 then self._httpResultList:sort(col,frame) else local values = self._httpResultList:getValues(row-1) self:showResponse(values) end end self._httpAnalyticsTable = HttpAnalyticsTableClass.new(parentFrame, analyticsTableHeightChange) self._httpResultList = HttpResultListClass.new(parentFrame, resultListHeightChange, resultListClicked) self._httpAnalyticsFrame = self._httpAnalyticsTable:getFrame() self._httpResultListFrame = self._httpResultList:getFrame() self._heightChangedCallback = callback return self end function HttpAnalyzerClass:updatePosition() local tableHeight = self._httpAnalyticsTable:getHeightInPix() self._httpResultListFrame.Position = UDim2.new(0,1,0,tableHeight+SPACE_TABLE_LIST) self:heightChange() end function HttpAnalyzerClass:heightChange() if self._heightChangedCallback ~= nil then self._heightChangedCallback(self:getHeightInPix()) end end -- add a row of http result to list -- and update the analyzer data on table function HttpAnalyzerClass:addHttpResult( httpResult ) self._httpAnalyticsTable:addHttpResult(httpResult) self._httpResultList:addHttpResult(httpResult) end function HttpAnalyzerClass:getHeightInPix() if self._responseView:isVisible() then return self._responseView:getHeightInPix() else return self._httpAnalyticsTable:getHeightInPix() + self._httpResultList:getHeightInPix() + SPACE_TABLE_LIST end end function HttpAnalyzerClass:showResponse(values) self._httpAnalyticsFrame.Visible = false self._httpResultListFrame.Visible = false self._responseView:show(values[#values]) self:heightChange() end return HttpAnalyzerClass