From 8968c797acf7375198803eb3685f3577c7960c15 Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:25:15 +0200 Subject: [PATCH] Implemented right-click context menu on session table to create console windows for interacting with the agent. --- src/client/layout.ini | 110 +++++++++++++++++++++++++--------- src/client/main.nim | 18 ++++-- src/client/views/console.nim | 25 ++++++++ src/client/views/sessions.nim | 45 ++++++++++++-- 4 files changed, 161 insertions(+), 37 deletions(-) create mode 100644 src/client/views/console.nim diff --git a/src/client/layout.ini b/src/client/layout.ini index 2710639..1de9c67 100644 --- a/src/client/layout.ini +++ b/src/client/layout.ini @@ -1,6 +1,6 @@ [Window][Dockspace] Pos=0,0 -Size=2548,1359 +Size=2868,1695 Collapsed=0 [Window][Debug##Default] @@ -9,28 +9,28 @@ Size=449,261 Collapsed=0 [Window][Dear ImGui Demo] -Pos=1852,43 -Size=686,653 +Pos=1884,43 +Size=974,624 Collapsed=0 -DockId=0x00000007,0 +DockId=0x00000006,1 [Window][Agents [Table View]] Pos=10,43 Size=1187,946 Collapsed=0 -DockId=0x00000006,0 +DockId=0x00000005,0 [Window][Agents [Graph View]] Pos=10,43 Size=1888,473 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000005,1 [Window][Eventlog] -Pos=10,698 -Size=2528,651 +Pos=1884,43 +Size=974,624 Collapsed=0 -DockId=0x00000004,0 +DockId=0x00000006,0 [Window][Example: Assets Browser] Pos=60,60 @@ -51,7 +51,7 @@ Collapsed=0 Pos=10,698 Size=2528,651 Collapsed=0 -DockId=0x00000004,1 +DockId=0x00000005,1 [Window][Dear ImGui Metrics/Debugger] Pos=60,60 @@ -60,9 +60,9 @@ Collapsed=0 [Window][Sessions [Table View]] Pos=10,43 -Size=1840,653 +Size=1872,624 Collapsed=0 -DockId=0x00000006,0 +DockId=0x00000005,0 [Window][Dear ImGui Demo/ResizableChild_478B81A3] IsChild=1 @@ -98,6 +98,60 @@ DockId=0x00000002,0 IsChild=1 Size=614,540 +[Window][Listeners] +Pos=10,765 +Size=2848,920 +Collapsed=0 +DockId=0x00000004,0 + +[Window][G1H2I3J4] +Pos=10,866 +Size=2848,819 +Collapsed=0 +DockId=0x00000008,1 + +[Window][C9D8E7F6] +Pos=10,866 +Size=2848,819 +Collapsed=0 +DockId=0x00000008,2 + +[Window][FACEDEAD] +Pos=10,866 +Size=2848,819 +Collapsed=0 +DockId=0x00000008,3 + +[Window][["C9D8E7F6"] charlie@SERVER-03] +Pos=10,866 +Size=2848,819 +Collapsed=0 +DockId=0x00000008,1 + +[Window][[G1H2I3J4] diana@WORKSTATION-04] +Pos=10,669 +Size=2848,1016 +Collapsed=0 +DockId=0x00000008,1 + +[Window][[FACEDEAD] bob@LAPTOP-02] +Pos=10,669 +Size=2848,1016 +Collapsed=0 +DockId=0x00000008,0 + +[Window][[C9D8E7F6] charlie@SERVER-03] +Pos=10,669 +Size=2848,1016 +Collapsed=0 +DockId=0x00000008,2 + +[Window][[DEADBEEF] alice@DESKTOP-01] +Pos=10,669 +Size=2848,1016 +Collapsed=0 +DockId=0x00000008,1 + [Table][0xB6880529,2] RefScale=27 Column 0 Sort=0v @@ -112,14 +166,14 @@ Column 4 Weight=1.0000 Column 5 Width=-1 [Table][0x32886A44,8] -Column 0 Weight=0.7126 -Column 1 Weight=1.0615 -Column 2 Weight=0.7126 -Column 3 Weight=1.2397 -Column 4 Weight=1.6776 -Column 5 Weight=1.2397 -Column 6 Weight=0.3563 -Column 7 Weight=1.5466 +Column 0 Weight=0.6558 +Column 1 Weight=0.9747 +Column 2 Weight=0.6558 +Column 3 Weight=0.8804 +Column 4 Weight=1.8057 +Column 5 Weight=1.1454 +Column 6 Weight=0.3324 +Column 7 Weight=1.5497 [Table][0x51D6F5EA,3] Column 0 Weight=1.0000 @@ -264,11 +318,13 @@ Column 1 Width=119 Column 2 Width=119 [Docking][Data] -DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1306 Split=Y - DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1453,473 Split=Y Selected=0x61E02D75 - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=2528,653 Split=X Selected=0x61E02D75 - DockNode ID=0x00000006 Parent=0x00000003 SizeRef=1840,653 CentralNode=1 Selected=0x61E02D75 - DockNode ID=0x00000007 Parent=0x00000003 SizeRef=686,653 Selected=0x5E5F7166 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=2528,651 Selected=0x0FA43D88 - DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1453,471 Selected=0x6D7E7D39 +DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2848,1642 Split=Y + DockNode ID=0x00000007 Parent=0x85940918 SizeRef=2848,624 Split=Y + DockNode ID=0x00000003 Parent=0x00000007 SizeRef=2848,720 Split=Y + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1453,473 Split=X Selected=0x61E02D75 + DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1872,674 CentralNode=1 Selected=0x61E02D75 + DockNode ID=0x00000006 Parent=0x00000001 SizeRef=974,674 Selected=0x5E5F7166 + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1453,471 Selected=0x6D7E7D39 + DockNode ID=0x00000004 Parent=0x00000007 SizeRef=2848,920 Selected=0x6BE22050 + DockNode ID=0x00000008 Parent=0x85940918 SizeRef=2848,1016 Selected=0xD3469193 diff --git a/src/client/main.nim b/src/client/main.nim index b7d20bc..71920df 100644 --- a/src/client/main.nim +++ b/src/client/main.nim @@ -1,6 +1,6 @@ import tables import ./utils/appImGui -import ./views/[dockspace, sessions, listeners, eventlog] +import ./views/[dockspace, sessions, listeners, eventlog, console] proc main() = var app = createApp(1024, 800, imnodes = true, title = "Conquest", docking = true) @@ -13,6 +13,7 @@ proc main() = showSessionsGraph = false showListeners = false showEventlog = true + consoles: Table[string, ConsoleComponent] views["Sessions [Table View]"] = addr showSessionsTable views["Sessions [Graph View]"] = addr showSessionsGraph @@ -22,12 +23,10 @@ proc main() = # Create components var dockspace = Dockspace() - sessionsTable = SessionsTable("Sessions [Table View]") - sessionsGraph = SessionsTable("Sessions [Graph View]") + sessionsTable = SessionsTable("Sessions [Table View]", addr consoles) listenersTable = ListenersTable("Listeners") eventlog = Eventlog("Eventlog") - let io = igGetIO() # main loop @@ -45,6 +44,17 @@ proc main() = if showListeners: listenersTable.draw(addr showListeners) if showEventlog: eventlog.draw(addr showEventlog) + # Show console windows + var newConsoleTable: Table[string, ConsoleComponent] + for agentId, console in consoles.mpairs(): + if console.showConsole: + console.draw() + newConsoleTable[agentId] = console + + # Update the consoles table with only those sessions that have not been closed yet + # This is done to ensure that closed console windows can be opened again + consoles = newConsoleTable + igShowDemoWindow(nil) # render diff --git a/src/client/views/console.nim b/src/client/views/console.nim new file mode 100644 index 0000000..9d56a32 --- /dev/null +++ b/src/client/views/console.nim @@ -0,0 +1,25 @@ +import strformat +import imguin/[cimgui, glfw_opengl, simple] +import ../utils/appImGui +import ../../common/[types] + +type + ConsoleComponent* = ref object of RootObj + agent: Agent + showConsole*: bool + +proc Console*(agent: Agent): ConsoleComponent = + result = new ConsoleComponent + result.agent = agent + result.showConsole = true + +proc draw*(component: ConsoleComponent) = + igSetNextWindowSize(vec2(800, 600), ImGuiCond_Once.int32) + + # var showComponent = component.showConsole + igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0) + defer: igEnd() + + igText(component.agent.agentId) + + # component.showConsole = showComponent \ No newline at end of file diff --git a/src/client/views/sessions.nim b/src/client/views/sessions.nim index 0061a20..3304ff9 100644 --- a/src/client/views/sessions.nim +++ b/src/client/views/sessions.nim @@ -1,5 +1,7 @@ -import times +import times, tables, strformat import imguin/[cimgui, glfw_opengl, simple] + +import ./console import ../utils/appImGui import ../../common/[types, utils] @@ -8,6 +10,7 @@ type title: string agents: seq[Agent] selection: ptr ImGuiSelectionBasicStorage + consoles: ptr Table[string, ConsoleComponent] let exampleAgents: seq[Agent] = @[ Agent( @@ -80,11 +83,28 @@ let exampleAgents: seq[Agent] = @[ ) ] -proc SessionsTable*(title: string): SessionsTableComponent = +proc SessionsTable*(title: string, consoles: ptr Table[string, ConsoleComponent]): SessionsTableComponent = result = new SessionsTableComponent result.title = title result.agents = exampleAgents result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() + result.consoles = consoles + +proc interact(component: SessionsTableComponent) = + # Open a new console for each selected agent session + var it: pointer = nil + var row: ImGuiID + while ImGuiSelectionBasicStorage_GetNextSelectedItem(component.selection, addr it, addr row): + let agent = component.agents[cast[int](row)] + + # 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}") + proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = igSetNextWindowSize(vec2(800, 600), ImGuiCond_Once.int32) @@ -124,7 +144,7 @@ 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 row in 0..< component.agents.len(): + for row in 0 ..< component.agents.len(): igTableNextRow(ImGuiTableRowFlags_None.int32, 0.0f) let agent = component.agents[row] @@ -134,6 +154,11 @@ proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = 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)) + + # Interact with session on double-click + if igIsMouseDoubleClicked_Nil(ImGui_MouseButton_Left.int32): + component.interact() + if igTableSetColumnIndex(1): igText(agent.ip) if igTableSetColumnIndex(2): @@ -153,15 +178,23 @@ proc draw*(component: SessionsTableComponent, showComponent: ptr bool) = # Right-clicking the table header to hide/show columns or reset the layout is only possible when no sessions are selected if component.selection[].Size > 0 and igBeginPopupContextWindow("TableContextMenu", ImGui_PopupFlags_MouseButtonRight.int32): - if igMenuItem("Interact", "ENTER", false, true): + if igMenuItem("Interact", nil, false, true): + component.interact() igCloseCurrentPopup() - if igMenuItem("Remove", "DELETE", false, true): + if igMenuItem("Remove", nil, false, true): + # Update agents table with only non-selected ones + var newAgents: seq[Agent] = @[] + for i in 0 ..< component.agents.len(): + if not ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)): + newAgents.add(component.agents[i]) + + component.agents = newAgents + ImGuiSelectionBasicStorage_Clear(component.selection) igCloseCurrentPopup() igEndPopup() - multiSelectIO = igEndMultiSelect() ImGuiSelectionBasicStorage_ApplyRequests(component.selection, multiSelectIO)