diff --git a/src/client/layout.ini b/src/client/layout.ini index 4b7e7e7..afcf481 100644 --- a/src/client/layout.ini +++ b/src/client/layout.ini @@ -1,18 +1,18 @@ [Window][Sessions [Table View]] Pos=10,43 -Size=1386,340 +Size=1188,394 Collapsed=0 DockId=0x00000003,0 [Window][Listeners] -Pos=10,385 -Size=1888,604 +Pos=10,439 +Size=1888,550 Collapsed=0 DockId=0x00000002,0 [Window][Eventlog] -Pos=1398,43 -Size=500,340 +Pos=1200,43 +Size=698,394 Collapsed=0 DockId=0x00000004,0 @@ -26,70 +26,70 @@ Size=1908,999 Collapsed=0 [Window][[7E248CBA] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002,0 [Window][[7BE69219] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002,2 [Window][[CD44669B] jakob@AURA] -Pos=10,385 -Size=1888,604 -Collapsed=0 -DockId=0x00000002 - -[Window][[F300DB27] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002,1 +[Window][[F300DB27] jakob@AURA] +Pos=10,514 +Size=1888,475 +Collapsed=0 +DockId=0x00000002,3 + [Window][[AB3464CE] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002 [Window][[3FC9903D] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000002,3 [Window][[6CD04F2C] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,1 [Window][[4E93619C] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000002,8 [Window][[0C56BE7D] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000002,7 [Window][[37C08F6C] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,4 [Window][[BCC8B616] jakob@AURA] -Pos=10,186 -Size=1004,604 +Pos=10,315 +Size=1004,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,0 [Window][Debug##Default] Pos=60,60 @@ -97,81 +97,81 @@ Size=400,400 Collapsed=0 [Window][[9592878D] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002,1 [Window][[8F8DC95F] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,0 [Window][[E05185F6] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000002,2 [Window][[022E62E0] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002 [Window][[F5CE46E3] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,6 [Window][[37DAA990] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,439 +Size=1888,550 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,1 [Window][[824B6EC7] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002 [Window][[386BFA92] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,5 [Window][[209155FC] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 DockId=0x00000002,1 [Window][[2498CB04] jakob@AURA] -Pos=10,385 -Size=1888,604 +Pos=10,514 +Size=1888,475 Collapsed=0 -DockId=0x00000002 +DockId=0x00000002,0 [Window][[0EBC9C82] jakob@AURA] -Pos=10,186 -Size=1004,604 +Pos=10,439 +Size=1888,550 Collapsed=0 -DockId=0x00000002,1 +DockId=0x00000002,2 [Table][0x32886A44,9] -Column 0 Weight=0.9944 Visible=1 -Column 1 Weight=1.2029 Visible=1 -Column 2 Weight=0.7801 Visible=1 -Column 3 Weight=0.6312 Visible=1 +Column 0 Weight=0.9882 Visible=1 +Column 1 Weight=1.1970 Visible=1 +Column 2 Weight=0.7725 Visible=1 +Column 3 Weight=0.6681 Visible=1 Column 4 Weight=1.3658 Visible=0 -Column 5 Weight=1.0064 Visible=1 -Column 6 Weight=1.1671 Visible=1 -Column 7 Weight=0.8456 Visible=1 -Column 8 Weight=1.0064 Visible=1 +Column 5 Weight=0.9604 Visible=1 +Column 6 Weight=1.2944 Visible=1 +Column 7 Weight=0.4175 Visible=1 +Column 8 Weight=1.3361 Visible=1 [Table][0x064A67CC,4] Column 0 Weight=1.0000 @@ -181,8 +181,8 @@ Column 3 Weight=1.0000 [Docking][Data] DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y - DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,340 Split=X - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1386,159 CentralNode=1 Selected=0x61E02D75 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=500,159 Selected=0x0FA43D88 - DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,604 Selected=0x6BE22050 + DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,394 Split=X + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1188,159 CentralNode=1 Selected=0x61E02D75 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=698,159 Selected=0x0FA43D88 + DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,550 Selected=0xE5DB7127 diff --git a/src/client/main.nim b/src/client/main.nim index ff6f0c0..f392480 100644 --- a/src/client/main.nim +++ b/src/client/main.nim @@ -73,7 +73,11 @@ proc main() = of CLIENT_AGENT_ADD: let agent = event.data.to(UIAgent) - sessionsTable.agents[agent.agentId] = agent + + # The ImGui Multi Select only works well with seq's, so we maintain a + # separate table of the latest agent heartbeats to have the benefit of quick and direct O(1) access + sessionsTable.agents.add(agent) + sessionsTable.agentActivity[agent.agentId] = agent.latestCheckin # Initialize position of console windows to bottom by drawing them once when they are added # By default, the consoles are attached to the same DockNode as the Listeners table (Default: bottom), @@ -90,7 +94,7 @@ proc main() = consoles[agent.agentId].showConsole = false of CLIENT_AGENT_CHECKIN: - sessionsTable.agents[event.data["agentId"].getStr()].latestCheckin = event.timestamp + sessionsTable.agentActivity[event.data["agentId"].getStr()] = event.timestamp of CLIENT_AGENT_PAYLOAD: discard diff --git a/src/client/views/sessions.nim b/src/client/views/sessions.nim index 339b56b..8fa2a1c 100644 --- a/src/client/views/sessions.nim +++ b/src/client/views/sessions.nim @@ -8,14 +8,16 @@ import ../../common/[types, utils] type SessionsTableComponent* = ref object of RootObj title: string - agents*: Table[string, UIAgent] + agents*: seq[UIAgent] + agentActivity*: Table[string, int64] # Direct O(1) access to latest checkin selection: ptr ImGuiSelectionBasicStorage consoles: ptr Table[string, ConsoleComponent] proc SessionsTable*(title: string, consoles: ptr Table[string, ConsoleComponent]): SessionsTableComponent = result = new SessionsTableComponent result.title = title - result.agents = initTable[string, UIAgent]() + result.agents = @[] + result.agentActivity = initTable[string, int64]() result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.consoles = consoles @@ -24,20 +26,16 @@ proc interact(component: SessionsTableComponent) = var it: pointer = nil var row: ImGuiID while ImGuiSelectionBasicStorage_GetNextSelectedItem(component.selection, addr it, addr row): - var selectedAgent: UIAgent = nil - for agentId, agent in component.agents.mpairs(): - if igGetID_Str(agentId) == row: - selectedAgent = agent + let agent = component.agents[cast[int](row)] - if selectedAgent != nil: - # Create a new console window - if not component.consoles[].hasKey(selectedAgent.agentId): - component.consoles[][selectedAgent.agentId] = Console(selectedAgent) + # Create a new console window + if not component.consoles[].hasKey(agent.agentId): + component.consoles[][agent.agentId] = Console(agent) + + # Focus the existing console window + else: + igSetWindowFocus_Str(fmt"[{agent.agentId}] {agent.username}@{agent.hostname}") - # Focus the existing console window - else: - igSetWindowFocus_Str(fmt"[{selectedAgent.agentId}] {selectedAgent.username}@{selectedAgent.hostname}") - proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = igBegin(component.title, showComponent, 0) defer: igEnd() @@ -75,15 +73,13 @@ proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = var multiSelectIO = igBeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape.int32 or ImGuiMultiSelectFlags_BoxSelect1d.int32, component.selection[].Size, int32(component.agents.len())) ImGuiSelectionBasicStorage_ApplyRequests(component.selection, multiSelectIO) - for agentId, agent in component.agents.mpairs(): + for row, agent in component.agents: igTableNextRow(ImGuiTableRowFlags_None.int32, 0.0f) - let row = igGetID_Str(agentId) - if igTableSetColumnIndex(0): # Enable multi-select functionality - igSetNextItemSelectionUserData(cast[ImGuiSelectionUserData](row)) + igSetNextItemSelectionUserData(row) var isSelected = ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](row)) discard igSelectable_Bool(agent.agentId, isSelected, ImGuiSelectableFlags_SpanAllColumns.int32, vec2(0.0f, 0.0f)) @@ -106,16 +102,14 @@ proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = if igTableSetColumnIndex(7): igText($agent.pid) if igTableSetColumnIndex(8): - let duration = now() - agent.latestCheckin.fromUnix().utc() + let duration = now() - component.agentActivity[agent.agentId].fromUnix().utc() let totalSeconds = duration.inSeconds let hours = totalSeconds div 3600 let minutes = (totalSeconds mod 3600) div 60 let seconds = totalSeconds mod 60 - let dummyTime = dateTime(2000, mJan, 1, hours.int, minutes.int, seconds.int) - let timeText = dummyTime.format("HH:mm:ss") - + let timeText = dateTime(2000, mJan, 1, hours.int, minutes.int, seconds.int).format("HH:mm:ss") igText(fmt"{timeText} ago") # Handle right-click context menu @@ -128,15 +122,12 @@ proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = if igMenuItem("Remove", nil, false, true): # Update agents table with only non-selected ones - var agentsToDelete: seq[string] = @[] - for agentId, agent in component.agents.mpairs(): - let i = igGetID_Str(agentId) - if ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)): - agentsToDelete.add(agentId) - - for agentId in agentsToDelete: - component.agents.del(agentId) + var newAgents: seq[UIAgent] = @[] + for i, agent in component.agents: + if not ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)): + newAgents.add(agent) + component.agents = newAgents ImGuiSelectionBasicStorage_Clear(component.selection) igCloseCurrentPopup() @@ -146,6 +137,3 @@ proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = ImGuiSelectionBasicStorage_ApplyRequests(component.selection, multiSelectIO) igEndTable() - - - \ No newline at end of file