diff --git a/sys/apps/Network.lua b/sys/apps/Network.lua index 52be626..bc551a4 100644 --- a/sys/apps/Network.lua +++ b/sys/apps/Network.lua @@ -44,7 +44,7 @@ local page = UI.Page { x = -3, dropdown = { { text = 'Port Status', event = 'ports', modem = true }, - { spacer = true }, + { spacer = true }, { text = 'Help', event = 'help', noCheck = true }, }, }, @@ -127,14 +127,12 @@ local function sendCommand(host, command) end end ---[[ function page.ports:eventHandler(event) if event.type == 'grid_select' then - shell.openForegroundTab('sniff ' .. event.selected.port) + shell.openForegroundTab('Sniff ' .. event.selected.port) end return UI.SlideOut.eventHandler(self, event) end -]] function page.ports.grid:update() local transport = network:getTransport() diff --git a/sys/apps/Sniff.lua b/sys/apps/Sniff.lua new file mode 100644 index 0000000..b90bbe8 --- /dev/null +++ b/sys/apps/Sniff.lua @@ -0,0 +1,368 @@ +local UI = require('opus.ui') +local Event = require('opus.event') +local Util = require('opus.util') + +local colors = _G.colors +local device = _G.device +local textutils = _G.textutils +local peripheral = _G.peripheral +local multishell = _ENV.multishell + +local gridColumns = {} +table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' }) +table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' }) +table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' }) +if UI.defaultDevice.width > 50 then table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' }) end +table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' }) + +local page = UI.Page { + paused = false, + index = 1, + notification = UI.Notification { }, + accelerators = { ['control-q'] = 'quit' }, + + menuBar = UI.MenuBar { + buttons = { + { text = 'Pause', event = 'pause_click', name = 'pauseButton' }, + { text = 'Clear', event = 'clear_click' }, + { text = 'Config', event = 'config_click' }, + }, + }, + + packetGrid = UI.ScrollingGrid { + y = 2, + maxPacket = 300, + inverseSort = true, + sortColumn = 'id', + columns = gridColumns, + accelerators = { ['space'] = 'pause_click' }, + }, + + configSlide = UI.SlideOut { + y = -11, + titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' }, + accelerators = { ['backspace'] = 'config_close' }, + configTabs = UI.Tabs { + y = 2, + filterTab = UI.Tab { + tabTitle = 'Filter', + filterGridText = UI.Text { + x = 2, y = 2, + value = 'ID filter', + }, + filterGrid = UI.ScrollingGrid { + x = 2, y = 3, + width = 10, height = 4, + disableHeader = true, + columns = { + { key = 'id', width = 5 }, + }, + }, + filterEntry = UI.TextEntry { + x = 2, y = 8, + width = 7, + shadowText = 'ID', + limit = 5, + accelerators = { enter = 'filter_add' }, + }, + filterAdd = UI.Button { + x = 10, y = 8, + text = '+', + event = 'filter_add', + }, + filterAllCheck = UI.Checkbox { + x = 13, y = 4, + value = false, + }, + filterAddText = UI.Text { + x = 17, y = 4, + value = "Use ID filter", + }, + }, + modemTab = UI.Tab { + tabTitle = 'Modem', + channelGrid = UI.ScrollingGrid { + x = 2, y = 2, + width = 12, height = 5, + autospace = true, + columns = {{ heading = 'Open Ports', key = 'port' }}, + }, + modemGrid = UI.ScrollingGrid { + x = 15, y = 2, + ex = -2, height = 5, + autospace = true, + columns = { + { heading = 'Side', key = 'side' }, + { heading = 'Type', key = 'type' }, + }, + }, + channelEntry = UI.TextEntry { + x = 2, y = 8, + width = 7, + shadowText = 'ID', + limit = 5, + accelerators = { enter = 'channel_add' }, + }, + channelAdd = UI.Button { + x = 10, y = 8, + text = '+', + event = 'channel_add', + }, + }, + }, + }, + + packetSlide = UI.SlideOut { + titleBar = UI.TitleBar { + title = 'Packet Information', + event = 'packet_close', + }, + backgroundColor = colors.cyan, + accelerators = { + ['backspace'] = 'packet_close', + ['left'] = 'prev_packet', + ['right'] = 'next_packet', + }, + packetMeta = UI.Grid { + x = 2, y = 2, + ex = 23, height = 4, + inactive = true, + columns = { + { key = 'text' }, + { key = 'value', align = 'right', textColor = colors.yellow }, + }, + values = { + port = { text = 'Port' }, + reply = { text = 'Reply' }, + dist = { text = 'Distance' }, + } + }, + packetButton = UI.Button { + x = 25, y = 5, + text = 'Open in Lua', + event = 'packet_lua', + }, + packetData = UI.TextArea { + y = 7, ey = -1, + backgroundColor = colors.black, + }, + }, +} + +local filterConfig = page.configSlide.configTabs.filterTab +local modemConfig = page.configSlide.configTabs.modemTab + +function filterConfig:eventHandler(event) + if event.type == 'filter_add' then + local id = tonumber(self.filterEntry.value) + if id then self.filterGrid.values[id] = { id = id } + self.filterGrid:update() + self.filterEntry:reset() + self:draw() + end + + elseif event.type == 'grid_select' then + self.filterGrid.values[event.selected.id] = nil + self.filterGrid:update() + self.filterGrid:draw() + + else return UI.Tab.eventHandler(self, event) + end + return true +end + +function modemConfig:loadChannel() + for chan = 0, 65535 do + self.currentModem.openChannels[chan] = self.currentModem.device.isOpen(chan) and { port = chan } or nil + end + self.channelGrid:setValues(self.currentModem.openChannels) + self.currentModem.loaded = true +end + +function modemConfig:enable() + if not self.currentModem.loaded then + self:loadChannel() + end + + UI.Tab.enable(self) +end + +function modemConfig:eventHandler(event) + if event.type == 'channel_add' then + local id = tonumber(modemConfig.channelEntry.value) + if id then + self.currentModem.openChannels[id] = { port = id } + self.currentModem.device.open(id) + self.channelGrid:setValues(self.currentModem.openChannels) + self.channelGrid:update() + self.channelEntry:reset() + self:draw() + end + + elseif event.type == 'grid_select' then + if event.element == self.channelGrid then + self.currentModem.openChannels[event.selected.port] = nil + self.currentModem.device.close(event.selected.port) + self.channelGrid:setValues(self.currentModem.openChannels) + page.configSlide.configTabs.modemTab.channelGrid:update() + page.configSlide.configTabs.modemTab.channelGrid:draw() + + elseif event.element == self.modemGrid then + self.currentModem = event.selected + page.notification:info("Loading channel list") + page:sync() + modemConfig:loadChannel() + page.notification:success("Now using modem on " .. self.currentModem.side) + self.channelGrid:draw() + end + + else return UI.Tab.eventHandler(self, event) + end + return true +end + +function page.packetSlide:setPacket(packet) + self.currentPacket = packet + local p, res = pcall(textutils.serialize, page.packetSlide.currentPacket.message) + self.packetData.textColor = p and colors.white or colors.red + self.packetData:setText(res) + self.packetMeta.values.port.value = page.packetSlide.currentPacket.portid + self.packetMeta.values.reply.value = page.packetSlide.currentPacket.replyid + self.packetMeta.values.dist.value = Util.round(page.packetSlide.currentPacket.distance, 2) +end + +function page.packetSlide:show(packet) + self:setPacket(packet) + + UI.SlideOut.show(self) +end + +function page.packetSlide:eventHandler(event) + if event.type == 'packet_close' then + self:hide() + page:setFocus(page.packetGrid) + + elseif event.type == 'packet_lua' then + multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true }) + + elseif event.type == 'prev_packet' then + local c = self.currentPacket + local n = page.packetGrid.values[c.id - 1] + if n then + self:setPacket(n) + self:draw() + end + + elseif event.type == 'next_packet' then + local c = self.currentPacket + local n = page.packetGrid.values[c.id + 1] + if n then + self:setPacket(n) + self:draw() + end + + else return UI.SlideOut.eventHandler(self, event) + end + return true +end + +function page.packetGrid:addPacket(packet) + if not page.paused and (not filterConfig.filterAllCheck.value or filterConfig.filterGrid.values[packet.portid]) then + page.index = page.index + 1 + local p, res = pcall(textutils.serialize, packet.message) + packet.packetStr = res:gsub("\n%s*", "") + table.insert(self.values, packet) + end + if #self.values > self.maxPacket then + local t = { } + for i = 10, #self.values do + t[i - 9] = self.values[i] + end + self:setValues(t) + end + + self:update() + self:draw() + page:sync() +end + +function page:enable() + modemConfig.modems = {} + peripheral.find('modem', function(side, dev) + modemConfig.modems[side] = { + type = dev.isWireless() and 'Wireless' or 'Wired', + side = side, + openChannels = { }, + device = dev, + loaded = false + } + end) + modemConfig.currentModem = device.wireless_modem and + modemConfig.modems[device.wireless_modem.side] or + device.wired_modem and + modemConfig.modems[device.wired_modem.side] or + nil + + modemConfig.modemGrid.values = modemConfig.modems + modemConfig.modemGrid:update() + modemConfig.modemGrid:setSelected(modemConfig.currentModem) + + UI.Page.enable(self) +end + + +function page:eventHandler(event) + if event.type == 'pause_click' then + self.paused = not self.paused + self.menuBar.pauseButton.text = self.paused and 'Resume' or 'Pause' + self.notification:success(self.paused and 'Paused' or 'Resumed', 2) + self.menuBar:draw() + + elseif event.type == 'clear_click' then + self.packetGrid:setValues({ }) + self.notification:success('Cleared', 2) + self.packetGrid:draw() + + elseif event.type == 'config_click' then + self.configSlide:show() + self:setFocus(filterConfig.filterEntry) + + elseif event.type == 'config_close' then + self.configSlide:hide() + self:setFocus(self.packetGrid) + + elseif event.type == 'grid_select' then + self.packetSlide:show(event.selected) + + elseif event.type == 'quit' then + Event.exitPullEvents() + + else return UI.Page.eventHandler(self, event) + end + return true +end + +Event.on('modem_message', function(event, side, chan, reply, msg, dist) + if modemConfig.currentModem.side == side then + page.packetGrid:addPacket({ + id = page.index, + portid = chan, + replyid = reply, + message = msg, + distance = dist, + }) + end +end) + +local args = {...} +if args[1] then + local id = tonumber(args[1]) + if id then + filterConfig.filterGrid.values[id] = { id = id } + filterConfig.filterAllCheck:setValue(true) + filterConfig.filterGrid:update() + end +end + +UI:setPage(page) +UI:pullEvents() diff --git a/sys/apps/system/diskusage.lua b/sys/apps/system/diskusage.lua index 3cec732..3749642 100644 --- a/sys/apps/system/diskusage.lua +++ b/sys/apps/system/diskusage.lua @@ -8,146 +8,147 @@ local os = _G.os local peripheral = _G.peripheral local NftImages = { - blank = '\30\56\31\55\153\153\153\153\153\153\153\153\10\30\55\31\56\153\153\153\153\153\153\153\153\10\30\56\31\55\153\153\153\153\153\153\153\153\10\30\55\31\56\153\153\153\153\153\153\153\153\10\30\56\31\55\153\153\153\153\153\153\153\153', - drive = '', - rom = '', - hdd = '', + blank = '\30\56\31\55\153\153\153\153\153\153\153\153\10\30\55\31\56\153\153\153\153\153\153\153\153\10\30\56\31\55\153\153\153\153\153\153\153\153\10\30\55\31\56\153\153\153\153\153\153\153\153\10\30\56\31\55\153\153\153\153\153\153\153\153', + drive = '\30\32\31\32\32\30\98\31\98\128\30\56\31\56\128\128\30\102\149\30\98\149\31\57\139\10\30\32\31\32\32\30\98\31\98\128\128\128\128\128\128\10\30\32\31\32\32\30\98\31\98\128\30\48\31\55\95\95\95\95\30\98\31\98\128\10\30\32\31\32\32\30\98\31\98\128\30\48\31\55\95\95\95\95\30\98\31\98\128', + rom = '\30\57\31\57\128\31\56\144\144\144\144\144\31\57\128\10\30\56\31\57\157\30\55\31\55\128\128\128\128\128\30\57\31\56\145\10\30\57\31\56\136\30\55\31\55\128\30\55\31\48\82\79\77\30\55\128\30\57\31\56\132\10\30\56\31\57\157\30\55\31\55\128\128\128\128\128\30\57\31\56\145\10\30\57\31\57\128\31\56\129\129\129\129\129\31\57\128', + hdd = '\30\32\31\32\32\30\55\31\55\128\30\48\135\131\139\30\55\128\10\30\32\31\32\32\30\48\31\55\149\31\48\128\30\55\131\30\48\128\30\55\149\10\30\32\31\32\32\30\55\31\48\130\30\48\31\55\144\30\56\31\48\133\30\55\159\129\10\30\32\31\32\32\30\56\31\55\149\129\142\159\30\55\128\10\30\32\31\32\32\30\57\31\55\143\143\143\143\143', } local tab = UI.Tab { - tabTitle = 'Disks Usage', - description = 'Visualise HDD and disks usage', + tabTitle = 'Disks Usage', + description = 'Visualise HDD and disks usage', - drives = UI.ScrollingGrid { - x = 2, y = 1, - ex = '47%', ey = 8, - columns = { - { heading = 'Drive', key = 'name' }, - { heading = 'Side' ,key = 'side', textColor = colors.yellow } - }, - sortColumn = 'name', - }, - infos = UI.Grid { - x = '52%', y = 2, - ex = -2, ey = 8, - disableHeader = true, - unfocusedBackgroundSelectedColor = colors.black, - inactive = true, - backgroundSelectedColor = colors.black, - columns = { - { key = 'name' }, - { key = 'value', align = 'right', textColor = colors.yellow }, - } - }, + drives = UI.ScrollingGrid { + x = 2, y = 1, + ex = '47%', ey = 8, + columns = { + { heading = 'Drive', key = 'name' }, + { heading = 'Side' ,key = 'side', textColor = colors.yellow } + }, + sortColumn = 'name', + }, + infos = UI.Grid { + x = '52%', y = 2, + ex = -2, ey = 8, + disableHeader = true, + unfocusedBackgroundSelectedColor = colors.black, + inactive = true, + backgroundSelectedColor = colors.black, + columns = { + { key = 'name' }, + { key = 'value', align = 'right', textColor = colors.yellow }, + } + }, - progress = UI.ProgressBar { - x = 11, y = 10, - ex = -2, - }, - percentage = UI.Text { - x = 11, y = 11, - ex = -2, - align = 'center', - }, - icon = UI.NftImage { - x = 2, y = 10, - image = NFT.parse(NftImages.blank) - }, + progress = UI.ProgressBar { + x = 11, y = 10, + ex = -2, + }, + percentage = UI.Text { + x = 11, y = 11, + ex = -2, + align = 'center', + }, + icon = UI.NftImage { + x = 2, y = 10, + image = NFT.parse(NftImages.blank) + }, } local function getDrives() - local unique = { ['hdd'] = true, ['virt'] = true } - local exclude = {} - local drives = { - {name = 'hdd', side = ''}, - } - for _, drive in pairs(fs.list('/')) do - local side = fs.getDrive(drive) - if side and not unique[side] then - unique[side] = true - exclude[drive] = true - table.insert(drives, {name=drive, side=side}) - end - end - return drives, exclude + local unique = { ['hdd'] = true, ['virt'] = true } + local exclude = {} + local drives = { + {name = 'hdd', side = ''}, + } + for _, drive in pairs(fs.list('/')) do + local side = fs.getDrive(drive) + if side and not unique[side] then + unique[side] = true + exclude[drive] = true + table.insert(drives, {name=drive, side=side}) + end + end + return drives, exclude end local function getDriveInfo(p) - local files, dirs, total = 0, 0, 0 + local files, dirs, total = 0, 0, 0 - if p == "hdd" then p = "/" end - p = fs.combine(p, '') - local drive = fs.getDrive(p) + if p == "hdd" then p = "/" end + p = fs.combine(p, '') + local drive = fs.getDrive(p) - local function recurse(path) - if fs.getDrive(path) == drive then - if fs.isDir(path) then - if path ~= p then - total = total + 500 - dirs = dirs + 1 - end - for _, v in pairs(fs.list(path)) do - recurse(fs.combine(path, v)) - end - else - local sz = fs.getSize(path) + local function recurse(path) + if fs.getDrive(path) == drive then + if fs.isDir(path) then + if path ~= p then + total = total + 500 + dirs = dirs + 1 + end + for _, v in pairs(fs.list(path)) do + recurse(fs.combine(path, v)) + end + else + local sz = fs.getSize(path) + files = files + 1 + if drive == 'rom' then + total = total + sz + else + total = total + math.max(500, sz) + end + end + end + end - files = files + 1 - if drive == 'rom' then - total = total + sz - else - total = total + math.max(500, sz) - end - end - end - end + recurse(p) - recurse(p) - - local info = {} - table.insert(info, { name = 'Type', value = peripheral.getType(drive) or drive }) - table.insert(info, { name = 'Used', value = total }) - table.insert(info, { name = 'Total', value = total + fs.getFreeSpace(p) }) - table.insert(info, { name = 'Free', value = fs.getFreeSpace(p) }) - table.insert(info, { }) - table.insert(info, { name = 'Files', value = files }) - table.insert(info, { name = 'Dirs', value = dirs }) - return info, math.floor((total / (total + fs.getFreeSpace(p))) * 100) + local info = {} + table.insert(info, { name = 'Type', value = peripheral.getType(drive) or drive }) + table.insert(info, { name = 'Used', value = total }) + table.insert(info, { name = 'Total', value = total + fs.getFreeSpace(p) }) + table.insert(info, { name = 'Free', value = fs.getFreeSpace(p) }) + table.insert(info, { }) + table.insert(info, { name = 'Files', value = files }) + table.insert(info, { name = 'Dirs', value = dirs }) + return info, math.floor((total / (total + fs.getFreeSpace(p))) * 100) end function tab:updateInfo() - local selected = self.drives:getSelected() - local info, percent = getDriveInfo(selected and selected.name or self.drives.values[1].name) - self.infos:setValues(info) - self.progress.value = percent - self.percentage.value = ('%#3d%%'):format(percent) - self:draw() + local selected = self.drives:getSelected() + local info, percent = getDriveInfo(selected and selected.name or self.drives.values[1].name) + self.infos:setValues(info) + self.progress.value = percent + self.percentage.value = ('%#3d%%'):format(percent) + self.icon.image = NFT.parse(NftImages[info[1].value] or NftImages.blank) + self:draw() end function tab:updateDrives() - local drives, exclude = getDrives() - self.exclude = exclude - self.drives:setValues(drives) + local drives, exclude = getDrives() + self.exclude = exclude + self.drives:setValues(drives) end function tab:enable() - self:updateDrives() - self:updateInfo() - UI.Tab.enable(self) + self:updateDrives() + self:updateInfo() + UI.Tab.enable(self) end function tab:eventHandler(event) - if event.type == 'grid_focus_row' then - self:updateInfo() - end - return UI.Tab.eventHandler(self, event) + if event.type == 'grid_focus_row' then + self:updateInfo() + else return UI.Tab.eventHandler(self, event) + end + return true end Event.on({ 'disk', 'disk_eject' }, function() - os.sleep(1) - tab:updateDrives() - tab:updateInfo() - tab:sync() + os.sleep(1) + tab:updateDrives() + tab:updateInfo() + tab:sync() end) return tab