Skip to content
API Documentation
BastionGuard™
ConnectionsPage Class Reference

#include <ConnectionsPage.hpp>

Inheritance diagram for ConnectionsPage:

Classes

struct  DetailColumns
struct  AppColumns
struct  ComboCol
struct  AppStats
struct  DetailEvent

Public Member Functions

 ConnectionsPage (UIServer &uiServer)
 ~ConnectionsPage () override
void onNewConnection (const ConnectionInfo &info, bool pending=true)
void setLastVerdict (const std::string &verdict)
void saveLog () const
void loadLog ()
void clearLog ()

Static Public Member Functions

static std::string logPath ()

Static Public Attributes

static constexpr uint64_t kEstimatedBytesPerConn = 4096

Private Member Functions

void buildVerdictModel ()
void buildToolbar ()
void buildAppTreeView ()
void buildDetailTreeView ()
void buildSparkline ()
void showAppsView ()
void showDetailView (const std::string &app)
void onAppActivated (const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column)
void onBackClicked ()
void onVerdictEdited (const Glib::ustring &pathStr, const Glib::ustring &newText)
void onClear ()
void addOrUpdateDetailRow (const ConnectionInfo &info, const std::string &app_display, const std::string &argsStr, bool pending)
void updateAppAggregate (const std::string &app_display)
void pushDetailEvent (const std::string &app_display, const DetailEvent &ev)
void populateDetailFromRing (const std::string &app_display)
bool onSparkTick ()
void onSparkDraw (const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
std::string resolveIconName (const std::string &app_display)
void buildDesktopIndex ()
std::string currentTime ()
ReplyAction parseReplyAction (const Glib::ustring &text) const

Static Private Member Functions

static std::string groupKeyFor (const std::string &app, const std::string &protocol, const std::string &host, const std::string &ip, uint32_t port)
static std::string formatBytes (uint64_t n)
static std::string formatEpochTime (int64_t epoch)
static std::string jsonEscapeStr (const std::string &s)
static std::string jsonFieldStr (const std::string &jsonjson, const std::string &key)
static uint32_t jsonFieldUint (const std::string &jsonjson, const std::string &key)
static bool jsonFieldBool (const std::string &jsonjson, const std::string &key)

Private Attributes

std::unordered_map< std::string, std::deque< DetailEvent > > m_detailRings
std::deque< uint64_t > m_sparkBuckets
uint64_t m_currentBucket = 0
sigc::connection m_sparkTick
DetailColumns m_columns
AppColumns m_appColumns
ComboCol m_comboCol
Glib::RefPtr< Gtk::ListStore > m_verdictModel
Glib::RefPtr< Gtk::ListStore > m_store
Glib::RefPtr< Gtk::ListStore > m_appStore
Gtk::TreeView m_treeView
Gtk::TreeView m_appTreeView
Gtk::ScrolledWindow m_scroll
Gtk::ScrolledWindow m_appScroll
Gtk::Stack m_stack
Gtk::Box m_toolbar { Gtk::Orientation::HORIZONTAL, 6 }
Gtk::Button m_clearBtn
Gtk::Button m_backBtn
Gtk::Label m_detailTitle
Gtk::SearchEntry m_search
Gtk::Frame m_sparkFrame
Gtk::Box m_sparkBox { Gtk::Orientation::VERTICAL, 4 }
Gtk::Label m_sparkLabel
Gtk::DrawingArea m_sparkArea
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_groupIndex
std::unordered_map< std::string, AppStats > m_appStats
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_appIndex
std::string m_currentAppFilter
std::unordered_map< std::string, std::string > m_iconCache
std::unordered_map< std::string, std::string > m_desktopIndex
bool m_desktopIndexBuilt = false
UIServer & m_uiServer

Static Private Attributes

static constexpr size_t kDetailRingMax = 500
static constexpr int64_t kRetentionSeconds = 7 * 24 * 3600
static constexpr int kSparkBucketMs = 500
static constexpr int kSparkBucketsMax = 120

Detailed Description

Definition at line 40 of file ConnectionsPage.hpp.

Constructor & Destructor Documentation

◆ ConnectionsPage()

ConnectionsPage::ConnectionsPage ( UIServer & uiServer)
explicit

Definition at line 36 of file ConnectionsPage.cpp.

37: Gtk::Box(Gtk::Orientation::VERTICAL, 0)
38, m_uiServer(uiServer)
39{
45
46 // Stack con due pagine. Default: apps.
47 m_appScroll.set_child(m_appTreeView);
48 m_appScroll.set_vexpand(true);
49 m_scroll.set_child(m_treeView);
50 m_scroll.set_vexpand(true);
51
52 m_stack.add(m_appScroll, "apps", _("Applicazioni"));
53 m_stack.add(m_scroll, "detail", _("Dettagli connessioni"));
54 m_stack.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
55 m_stack.set_transition_duration(220);
56 m_stack.set_vexpand(true);
57 m_stack.set_visible_child("apps");
58
59 append(m_toolbar);
60 append(m_stack);
61 append(m_sparkFrame);
62
63 // Al primo avvio siamo in apps → backBtn nascosto, detailTitle nascosto.
65
66 // Avvia il timer dello sparkline
67 m_sparkTick = Glib::signal_timeout().connect(
68 sigc::mem_fun(*this, &ConnectionsPage::onSparkTick),
70}
#define _(s)
Gtk::TreeView m_appTreeView
Gtk::ScrolledWindow m_scroll
Gtk::TreeView m_treeView
Gtk::ScrolledWindow m_appScroll
sigc::connection m_sparkTick
static constexpr int kSparkBucketMs

References _, buildAppTreeView(), buildDetailTreeView(), buildSparkline(), buildToolbar(), buildVerdictModel(), kSparkBucketMs, m_appScroll, m_appTreeView, m_scroll, m_sparkFrame, m_sparkTick, m_stack, m_toolbar, m_treeView, m_uiServer, onSparkTick(), and showAppsView().

◆ ~ConnectionsPage()

ConnectionsPage::~ConnectionsPage ( )
override

Definition at line 72 of file ConnectionsPage.cpp.

73{
74 // Disconnetti il timer prima della distruzione: Glib::signal_timeout()
75 // e' un source globale; se la pagina viene distrutta (es. cambio
76 // finestra principale) il callback risulterebbe in use-after-free.
77 m_sparkTick.disconnect();
78}

References m_sparkTick.

Member Function Documentation

◆ addOrUpdateDetailRow()

void ConnectionsPage::addOrUpdateDetailRow ( const ConnectionInfo & info,
const std::string & app_display,
const std::string & argsStr,
bool pending )
private

Definition at line 633 of file ConnectionsPage.cpp.

637{
638 const std::string key = groupKeyFor(app_display, info.protocol,
639 info.dest_host, info.dest_ip,
640 info.dest_port);
641
642 // Dedup solo per righe non-pending
643 if (!key.empty() && !pending) {
644 auto it_idx = m_groupIndex.find(key);
645 if (it_idx != m_groupIndex.end()) {
646 auto it = it_idx->second;
647 if (it && m_store->iter_is_valid(it)) {
648 const bool row_pending = (*it)[m_columns.col_pending];
649 if (!row_pending) {
650 const uint32_t new_count = static_cast<uint32_t>((*it)[m_columns.col_count]) + 1;
651 const Glib::ustring v_app = (*it)[m_columns.col_app];
652 const Glib::ustring v_verdict = (*it)[m_columns.col_verdict];
653 const Glib::ustring v_proto = (*it)[m_columns.col_protocol];
654 const Glib::ustring v_src_ip = (*it)[m_columns.col_src_ip];
655 const Glib::ustring v_host = (*it)[m_columns.col_host];
656 const Glib::ustring v_ip = (*it)[m_columns.col_ip];
657 const uint32_t v_port = (*it)[m_columns.col_port];
658 const Glib::ustring v_args = (*it)[m_columns.col_args];
659 const Glib::ustring v_gkey = (*it)[m_columns.col_group_key];
660
661 m_store->erase(it);
662
663 auto newIt = m_store->prepend();
664 auto newRow = *newIt;
665 newRow[m_columns.col_time] = currentTime();
666 newRow[m_columns.col_app] = v_app;
667 newRow[m_columns.col_verdict] = v_verdict;
668 newRow[m_columns.col_count] = new_count;
669 newRow[m_columns.col_pid] = info.pid;
670 newRow[m_columns.col_protocol] = v_proto;
671 newRow[m_columns.col_src_ip] = v_src_ip;
672 newRow[m_columns.col_host] = v_host;
673 newRow[m_columns.col_ip] = v_ip;
674 newRow[m_columns.col_port] = v_port;
675 newRow[m_columns.col_args] = v_args;
676 newRow[m_columns.col_pending] = false;
677 newRow[m_columns.col_group_key] = v_gkey;
678
679 m_groupIndex[key] = newIt;
680 return;
681 }
682 } else {
683 m_groupIndex.erase(it_idx);
684 }
685 }
686 }
687
688 // Nuova riga
689 auto it = m_store->prepend();
690 auto row = *it;
692 row[m_columns.col_app] = app_display;
693 row[m_columns.col_verdict] = pending ? _("In attesa...") : "";
694 row[m_columns.col_count] = 1u;
695 row[m_columns.col_pid] = info.pid;
696 row[m_columns.col_protocol] = info.protocol;
697 row[m_columns.col_src_ip] = info.src_ip;
698 row[m_columns.col_host] = info.dest_host;
699 row[m_columns.col_ip] = info.dest_ip;
700 row[m_columns.col_port] = info.dest_port;
701 row[m_columns.col_args] = argsStr;
702 row[m_columns.col_pending] = pending;
703 row[m_columns.col_group_key] = key;
704
705 if (!key.empty()) m_groupIndex[key] = it;
706}
DetailColumns m_columns
static std::string groupKeyFor(const std::string &app, const std::string &protocol, const std::string &host, const std::string &ip, uint32_t port)
std::string currentTime()
Glib::RefPtr< Gtk::ListStore > m_store
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_groupIndex
uint32_t pid
Definition UIServer.hpp:31
std::string src_ip
Definition UIServer.hpp:35
std::string protocol
Definition UIServer.hpp:34
std::string dest_host
Definition UIServer.hpp:37
uint32_t dest_port
Definition UIServer.hpp:39
std::string dest_ip
Definition UIServer.hpp:38
Gtk::TreeModelColumn< Glib::ustring > col_protocol
Gtk::TreeModelColumn< Glib::ustring > col_ip
Gtk::TreeModelColumn< Glib::ustring > col_src_ip
Gtk::TreeModelColumn< Glib::ustring > col_group_key
Gtk::TreeModelColumn< uint32_t > col_port
Gtk::TreeModelColumn< Glib::ustring > col_time
Gtk::TreeModelColumn< Glib::ustring > col_args
Gtk::TreeModelColumn< Glib::ustring > col_host
Gtk::TreeModelColumn< bool > col_pending
Gtk::TreeModelColumn< Glib::ustring > col_verdict
Gtk::TreeModelColumn< Glib::ustring > col_app
Gtk::TreeModelColumn< uint32_t > col_count
Gtk::TreeModelColumn< uint32_t > col_pid

References _, ConnectionsPage::DetailColumns::col_app, ConnectionsPage::DetailColumns::col_args, ConnectionsPage::DetailColumns::col_count, ConnectionsPage::DetailColumns::col_group_key, ConnectionsPage::DetailColumns::col_host, ConnectionsPage::DetailColumns::col_ip, ConnectionsPage::DetailColumns::col_pending, ConnectionsPage::DetailColumns::col_pid, ConnectionsPage::DetailColumns::col_port, ConnectionsPage::DetailColumns::col_protocol, ConnectionsPage::DetailColumns::col_src_ip, ConnectionsPage::DetailColumns::col_time, ConnectionsPage::DetailColumns::col_verdict, currentTime(), ConnectionInfo::dest_host, ConnectionInfo::dest_ip, ConnectionInfo::dest_port, groupKeyFor(), m_columns, m_groupIndex, m_store, ConnectionInfo::pid, ConnectionInfo::protocol, and ConnectionInfo::src_ip.

Referenced by onNewConnection().

◆ buildAppTreeView()

void ConnectionsPage::buildAppTreeView ( )
private

Definition at line 128 of file ConnectionsPage.cpp.

129{
130 m_appStore = Gtk::ListStore::create(m_appColumns);
131 m_appTreeView.set_model(m_appStore);
132 m_appTreeView.get_style_context()->add_class("bsc-treeview");
133 m_appTreeView.set_grid_lines(Gtk::TreeView::GridLines::HORIZONTAL);
134
135 // ── Colonna "Applicazione": icona + testo ─────────────────────────
136 // Composta da due renderer affiancati: CellRendererPixbuf (icona dal
137 // tema) + CellRendererText (nome processo). Lookup dell'icona via
138 // DesktopAppInfo — vedi resolveIconName().
139 {
140 auto* col = Gtk::make_managed<Gtk::TreeViewColumn>(_("Applicazione"));
141 auto* iconCell = Gtk::make_managed<Gtk::CellRendererPixbuf>();
142 auto* textCell = Gtk::make_managed<Gtk::CellRendererText>();
143
144 // Icona 24×24 — stessa scala usata da Nautilus/Files in lista.
145 iconCell->property_icon_size() = Gtk::IconSize::NORMAL;
146 iconCell->set_padding(2, 0);
147 textCell->set_padding(4, 0);
148
149 col->pack_start(*iconCell, /*expand=*/false);
150 col->pack_start(*textCell, /*expand=*/true);
151 col->add_attribute(iconCell->property_icon_name(), m_appColumns.col_icon_name);
152 col->add_attribute(textCell->property_text(), m_appColumns.col_app);
153 col->set_sort_column(m_appColumns.col_app);
154 m_appTreeView.append_column(*col);
155 }
156
157 m_appTreeView.append_column(_("Connessioni"), m_appColumns.col_conn_count);
158 m_appTreeView.append_column(_("Dati stimati"), m_appColumns.col_bytes_str);
159 m_appTreeView.append_column(_("Ultimo accesso"), m_appColumns.col_last_seen);
160
161 // Larghezze minime
162 const int minW[] = { 260, 110, 130, 130 };
163 for (int i = 0; i < 4; ++i) {
164 if (auto* c = m_appTreeView.get_column(i)) {
165 c->set_min_width(minW[i]);
166 c->set_resizable(true);
167 }
168 }
169
170 // Rendi le colonne ordinabili con lo stesso campo "grezzo" quando serve.
171 // Colonna "Connessioni": ordina direttamente sul uint32.
172 if (auto* c = m_appTreeView.get_column(1)) {
173 c->set_sort_column(m_appColumns.col_conn_count);
174 }
175 // Colonna "Dati stimati": ordina sul valore raw byte, non sulla stringa
176 // formattata (altrimenti "900 KB" > "1.2 MB" perche' confronto lessicale).
177 if (auto* c = m_appTreeView.get_column(2)) {
178 c->set_sort_column(m_appColumns.col_bytes_raw);
179 }
180 // Colonna "Ultimo accesso": ordina su epoch, non su stringa HH:MM:SS.
181 if (auto* c = m_appTreeView.get_column(3)) {
182 c->set_sort_column(m_appColumns.col_last_ts);
183 }
184 // Sort iniziale: piu' recenti in cima.
185 m_appStore->set_sort_column(m_appColumns.col_last_ts,
186 Gtk::SortType::DESCENDING);
187
188 // Doppio click sull'app → entra nella vista detail filtrata.
189 m_appTreeView.signal_row_activated().connect(
190 sigc::mem_fun(*this, &ConnectionsPage::onAppActivated));
191}
void onAppActivated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column)
Glib::RefPtr< Gtk::ListStore > m_appStore
Gtk::TreeModelColumn< Glib::ustring > col_icon_name
Gtk::TreeModelColumn< int64_t > col_last_ts
Gtk::TreeModelColumn< Glib::ustring > col_last_seen
Gtk::TreeModelColumn< uint32_t > col_conn_count
Gtk::TreeModelColumn< uint64_t > col_bytes_raw
Gtk::TreeModelColumn< Glib::ustring > col_bytes_str
Gtk::TreeModelColumn< Glib::ustring > col_app

References _, ConnectionsPage::AppColumns::col_app, ConnectionsPage::AppColumns::col_bytes_raw, ConnectionsPage::AppColumns::col_bytes_str, ConnectionsPage::AppColumns::col_conn_count, ConnectionsPage::AppColumns::col_icon_name, ConnectionsPage::AppColumns::col_last_seen, ConnectionsPage::AppColumns::col_last_ts, m_appColumns, m_appStore, m_appTreeView, and onAppActivated().

Referenced by ConnectionsPage().

◆ buildDesktopIndex()

void ConnectionsPage::buildDesktopIndex ( )
private

Definition at line 353 of file ConnectionsPage.cpp.

354{
355 if (m_desktopIndexBuilt) return;
356 m_desktopIndexBuilt = true;
357
358 // Scansione di tutti i .desktop visibili nel sistema. Per ciascuno
359 // estraiamo il nome del binario (executable basename) e lo usiamo
360 // come chiave lowercase. Es: /usr/share/applications/firefox.desktop
361 // con Exec="firefox %u" → mappa "firefox" → "firefox" (icon name).
362 //
363 // Questo copre la stragrande maggioranza delle applicazioni GUI.
364 // I daemon di sistema (dnsmasq, avahi-daemon, irq handlers) non
365 // hanno .desktop, quindi cadono sul fallback generico.
366 auto apps = Gio::AppInfo::get_all();
367 for (const auto& ai : apps) {
368 auto dai = std::dynamic_pointer_cast<Gio::DesktopAppInfo>(ai);
369 if (!dai) continue;
370
371 // Icon name: il nome canonico dell'icona nel tema
372 Glib::ustring icon_name;
373 auto icon = dai->get_icon();
374 if (icon) {
375 auto themed = std::dynamic_pointer_cast<Gio::ThemedIcon>(icon);
376 if (themed) {
377 const auto names = themed->get_names();
378 if (!names.empty()) icon_name = names[0];
379 }
380 }
381 if (icon_name.empty()) continue;
382
383 // Exec line: prendiamo il primo token (path) e il basename.
384 const std::string exec = dai->get_commandline();
385 if (exec.empty()) continue;
386 // Primo token (fino a spazio)
387 auto sp = exec.find(' ');
388 std::string first = (sp == std::string::npos) ? exec : exec.substr(0, sp);
389 // Basename
390 auto sl = first.rfind('/');
391 std::string base = (sl == std::string::npos) ? first : first.substr(sl + 1);
392 // Lowercase per lookup case-insensitive
393 for (auto& c : base) c = std::tolower(static_cast<unsigned char>(c));
394 if (base.empty()) continue;
395
396 // Non sovrascrivere: se esistono piu' .desktop con stesso exec
397 // (es. "firefox" + "firefox-esr" entrambi con Exec=firefox) il
398 // primo vince. Ordine deterministico non garantito ma in
399 // pratica va bene.
400 m_desktopIndex.emplace(base, icon_name.raw());
401
402 // Indicizza anche il nome "StartupWMClass" se presente — alcune
403 // app (Discord, Slack) usano questo come identificatore.
404 const std::string wm = dai->get_startup_wm_class();
405 if (!wm.empty()) {
406 std::string wmlc = wm;
407 for (auto& c : wmlc) c = std::tolower(static_cast<unsigned char>(c));
408 m_desktopIndex.emplace(wmlc, icon_name.raw());
409 }
410 }
411}
std::unordered_map< std::string, std::string > m_desktopIndex

References m_desktopIndex, and m_desktopIndexBuilt.

◆ buildDetailTreeView()

void ConnectionsPage::buildDetailTreeView ( )
private

Definition at line 199 of file ConnectionsPage.cpp.

200{
201 m_store = Gtk::ListStore::create(m_columns);
202 m_treeView.set_model(m_store);
203 m_treeView.get_style_context()->add_class("bsc-treeview");
204 m_treeView.set_grid_lines(Gtk::TreeView::GridLines::HORIZONTAL);
205
206 m_treeView.append_column(_("Ora"), m_columns.col_time);
207 m_treeView.append_column(_("Applicazione"), m_columns.col_app);
208
209 // Colonna Esito — CellRendererCombo (subito dopo Applicazione)
210 {
211 auto* col = Gtk::make_managed<Gtk::TreeViewColumn>(_("Esito"));
212 auto* cell = Gtk::make_managed<Gtk::CellRendererCombo>();
213 cell->property_model() = m_verdictModel;
214 cell->property_text_column() = 0;
215 cell->property_editable() = true;
216 cell->property_has_entry() = false;
217 cell->signal_edited().connect(
218 sigc::mem_fun(*this, &ConnectionsPage::onVerdictEdited));
219 col->pack_start(*cell, true);
220 col->add_attribute(cell->property_text(), m_columns.col_verdict);
221
222 col->set_cell_data_func(*cell,
223 [this](Gtk::CellRenderer* cr, const Gtk::TreeModel::const_iterator& it) {
224 auto* c = static_cast<Gtk::CellRendererCombo*>(cr);
225 bool pending = (*it)[m_columns.col_pending];
226 c->property_foreground() = pending ? "#f0c040" : "";
227 c->property_weight() = pending
228 ? static_cast<int>(Pango::Weight::BOLD)
229 : static_cast<int>(Pango::Weight::NORMAL);
230 });
231
232 m_treeView.append_column(*col);
233 }
234
235 m_treeView.append_column(_("#"), m_columns.col_count);
236 m_treeView.append_column(_("PID"), m_columns.col_pid);
237 m_treeView.append_column(_("Protocollo"), m_columns.col_protocol);
238 m_treeView.append_column(_("IP Sorgente"), m_columns.col_src_ip);
239 m_treeView.append_column(_("Host"), m_columns.col_host);
240 m_treeView.append_column(_("IP Dest."), m_columns.col_ip);
241 m_treeView.append_column(_("Porta"), m_columns.col_port);
242 m_treeView.append_column(_("Argomenti"), m_columns.col_args);
243
244 const int minW[] = { 75, 180, 140, 40, 55, 75, 110, 160, 110, 55, 180 };
245 for (int i = 0; i < 11; ++i) {
246 if (auto* c = m_treeView.get_column(i)) {
247 c->set_min_width(minW[i]);
248 c->set_resizable(true);
249 }
250 }
251}
void onVerdictEdited(const Glib::ustring &pathStr, const Glib::ustring &newText)
Glib::RefPtr< Gtk::ListStore > m_verdictModel

References _, ConnectionsPage::DetailColumns::col_app, ConnectionsPage::DetailColumns::col_args, ConnectionsPage::DetailColumns::col_count, ConnectionsPage::DetailColumns::col_host, ConnectionsPage::DetailColumns::col_ip, ConnectionsPage::DetailColumns::col_pending, ConnectionsPage::DetailColumns::col_pid, ConnectionsPage::DetailColumns::col_port, ConnectionsPage::DetailColumns::col_protocol, ConnectionsPage::DetailColumns::col_src_ip, ConnectionsPage::DetailColumns::col_time, ConnectionsPage::DetailColumns::col_verdict, m_columns, m_store, m_treeView, m_verdictModel, and onVerdictEdited().

Referenced by ConnectionsPage().

◆ buildSparkline()

void ConnectionsPage::buildSparkline ( )
private

Definition at line 257 of file ConnectionsPage.cpp.

258{
259 m_sparkFrame.set_margin_top(4);
260 m_sparkFrame.set_margin_bottom(4);
261 m_sparkFrame.set_margin_start(8);
262 m_sparkFrame.set_margin_end(8);
263
264 m_sparkLabel.set_markup(
265 _("<b>Throughput stimato</b> <small>(ultimi 60 s — stima basata sul numero di connessioni)</small>"));
266 m_sparkLabel.set_halign(Gtk::Align::START);
267 m_sparkLabel.set_margin_start(8);
268 m_sparkLabel.set_margin_top(4);
269
270 m_sparkArea.set_content_height(72);
271 m_sparkArea.set_hexpand(true);
272 m_sparkArea.set_draw_func(
273 sigc::mem_fun(*this, &ConnectionsPage::onSparkDraw));
274
275 m_sparkBox.append(m_sparkLabel);
276 m_sparkBox.append(m_sparkArea);
277 m_sparkFrame.set_child(m_sparkBox);
278
279 // Prealloca 120 bucket a zero: evita saltelli all'avvio della UI.
280 for (int i = 0; i < kSparkBucketsMax; ++i) m_sparkBuckets.push_back(0);
281}
std::deque< uint64_t > m_sparkBuckets
void onSparkDraw(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
Gtk::DrawingArea m_sparkArea
static constexpr int kSparkBucketsMax

References _, kSparkBucketsMax, m_sparkArea, m_sparkBox, m_sparkBuckets, m_sparkFrame, m_sparkLabel, and onSparkDraw().

Referenced by ConnectionsPage().

◆ buildToolbar()

void ConnectionsPage::buildToolbar ( )
private

Definition at line 93 of file ConnectionsPage.cpp.

94{
95 m_toolbar.set_margin_top(8);
96 m_toolbar.set_margin_bottom(8);
97 m_toolbar.set_margin_start(8);
98 m_toolbar.set_margin_end(8);
99
100 // Pulsante "Indietro" — visibile solo in vista detail
101 m_backBtn.set_label("←");
102 m_backBtn.set_tooltip_text(_("Torna all'elenco applicazioni"));
103 m_backBtn.signal_clicked().connect(
104 sigc::mem_fun(*this, &ConnectionsPage::onBackClicked));
105 m_toolbar.append(m_backBtn);
106
107 // Titolo centrale (visibile solo in detail)
108 m_detailTitle.set_halign(Gtk::Align::START);
109 m_detailTitle.set_valign(Gtk::Align::CENTER);
110 m_detailTitle.get_style_context()->add_class("heading");
111 m_toolbar.append(m_detailTitle);
112
113 m_search.set_placeholder_text(_("Filtra per app o host..."));
114 m_search.set_hexpand(true);
115 m_toolbar.append(m_search);
116
117 m_clearBtn.set_label(_("🗑 Pulisci"));
118 m_clearBtn.signal_clicked().connect(
119 sigc::mem_fun(*this, &ConnectionsPage::onClear));
120 m_toolbar.append(m_clearBtn);
121}
Gtk::SearchEntry m_search

References _, m_backBtn, m_clearBtn, m_detailTitle, m_search, m_toolbar, onBackClicked(), and onClear().

Referenced by ConnectionsPage().

◆ buildVerdictModel()

void ConnectionsPage::buildVerdictModel ( )
private

Definition at line 80 of file ConnectionsPage.cpp.

81{
82 m_verdictModel = Gtk::ListStore::create(m_comboCol);
83 for (int i = 0; kVerdictOptions[i]; ++i) {
84 auto row = *m_verdictModel->append();
86 }
87}
static const char * kVerdictOptions[]
Gtk::TreeModelColumn< Glib::ustring > val

References kVerdictOptions, m_comboCol, m_verdictModel, and ConnectionsPage::ComboCol::val.

Referenced by ConnectionsPage().

◆ clearLog()

void ConnectionsPage::clearLog ( )

Definition at line 1064 of file ConnectionsPage.cpp.

1065{
1066 m_store->clear();
1067 m_groupIndex.clear();
1068 m_appStore->clear();
1069 m_appIndex.clear();
1070 m_appStats.clear();
1071 m_detailRings.clear();
1072 std::error_code ec;
1073 std::filesystem::remove(logPath(), ec);
1074}
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_appIndex
std::unordered_map< std::string, std::deque< DetailEvent > > m_detailRings
std::unordered_map< std::string, AppStats > m_appStats
static std::string logPath()

References logPath(), m_appIndex, m_appStats, m_appStore, m_detailRings, m_groupIndex, and m_store.

Referenced by onClear().

◆ currentTime()

std::string ConnectionsPage::currentTime ( )
private

Definition at line 847 of file ConnectionsPage.cpp.

848{
849 auto now = Glib::DateTime::create_now_local();
850 return now.format("%H:%M:%S");
851}

Referenced by addOrUpdateDetailRow(), and onNewConnection().

◆ formatBytes()

std::string ConnectionsPage::formatBytes ( uint64_t n)
staticprivate

Definition at line 440 of file ConnectionsPage.cpp.

441{
442 char buf[32];
443 if (n < 1024ULL) {
444 std::snprintf(buf, sizeof(buf), "%lu B", (unsigned long)n);
445 } else if (n < 1024ULL * 1024ULL) {
446 std::snprintf(buf, sizeof(buf), "%.1f KB", n / 1024.0);
447 } else if (n < 1024ULL * 1024ULL * 1024ULL) {
448 std::snprintf(buf, sizeof(buf), "%.1f MB", n / (1024.0 * 1024.0));
449 } else {
450 std::snprintf(buf, sizeof(buf), "%.2f GB",
451 n / (1024.0 * 1024.0 * 1024.0));
452 }
453 return std::string(buf);
454}

Referenced by loadLog(), onSparkTick(), and updateAppAggregate().

◆ formatEpochTime()

std::string ConnectionsPage::formatEpochTime ( int64_t epoch)
staticprivate

Definition at line 456 of file ConnectionsPage.cpp.

457{
458 if (epoch <= 0) return "";
459 std::time_t t = static_cast<std::time_t>(epoch);
460 std::tm tmv{};
461 localtime_r(&t, &tmv);
462 char buf[16];
463 std::strftime(buf, sizeof(buf), "%H:%M:%S", &tmv);
464 return std::string(buf);
465}

Referenced by loadLog(), and updateAppAggregate().

◆ groupKeyFor()

std::string ConnectionsPage::groupKeyFor ( const std::string & app,
const std::string & protocol,
const std::string & host,
const std::string & ip,
uint32_t port )
staticprivate

Definition at line 425 of file ConnectionsPage.cpp.

430{
431 if (app.empty()) return std::string{};
432 const std::string h = !host.empty() ? host : ip;
433 return app + "|" + protocol + "|" + h + "|" + std::to_string(port);
434}

Referenced by addOrUpdateDetailRow().

◆ jsonEscapeStr()

std::string ConnectionsPage::jsonEscapeStr ( const std::string & s)
staticprivate

Definition at line 863 of file ConnectionsPage.cpp.

864{
865 std::string out;
866 for (unsigned char c : s) {
867 switch (c) {
868 case '"': out += "\\\""; break;
869 case '\\': out += "\\\\"; break;
870 case '\n': out += "\\n"; break;
871 case '\r': out += "\\r"; break;
872 case '\t': out += "\\t"; break;
873 default:
874 if (c < 0x20) { char b[8]; std::snprintf(b, sizeof(b), "\\u%04x", c); out += b; }
875 else out += c;
876 }
877 }
878 return out;
879}

Referenced by saveLog().

◆ jsonFieldBool()

bool ConnectionsPage::jsonFieldBool ( const std::string & json,
const std::string & key )
staticprivate

Definition at line 900 of file ConnectionsPage.cpp.

901{
902 const std::string needle = "\"" + key + "\":";
903 auto pos = json.find(needle);
904 if (pos == std::string::npos) return false;
905 pos += needle.size();
906 return json.substr(pos, 4) == "true";
907}
nlohmann::json json
Definition Backend.cpp:2908

Referenced by loadLog().

◆ jsonFieldStr()

std::string ConnectionsPage::jsonFieldStr ( const std::string & json,
const std::string & key )
staticprivate

Definition at line 881 of file ConnectionsPage.cpp.

882{
883 const std::string needle = "\"" + key + "\":\"";
884 auto pos = json.find(needle);
885 if (pos == std::string::npos) return {};
886 pos += needle.size();
887 auto end = json.find('"', pos);
888 return end == std::string::npos ? std::string{} : json.substr(pos, end - pos);
889}

Referenced by loadLog().

◆ jsonFieldUint()

uint32_t ConnectionsPage::jsonFieldUint ( const std::string & json,
const std::string & key )
staticprivate

Definition at line 891 of file ConnectionsPage.cpp.

892{
893 const std::string needle = "\"" + key + "\":";
894 auto pos = json.find(needle);
895 if (pos == std::string::npos) return 0;
896 pos += needle.size();
897 return static_cast<uint32_t>(std::strtoul(json.c_str() + pos, nullptr, 10));
898}

Referenced by loadLog().

◆ loadLog()

void ConnectionsPage::loadLog ( )

Definition at line 956 of file ConnectionsPage.cpp.

957{
958 std::ifstream f(logPath());
959 if (!f.is_open()) return;
960
961 const int64_t now_epoch = static_cast<int64_t>(std::time(nullptr));
962 const int64_t cutoff = now_epoch - kRetentionSeconds;
963 int n_apps = 0, n_conns = 0, n_dropped = 0;
964
965 // Helper parser che gestiscono uint64/int64 (jsonFieldUint e' solo u32)
966 auto parse_u64 = [](const std::string& line, const char* key) -> uint64_t {
967 const std::string needle = std::string("\"") + key + "\":";
968 auto pos = line.find(needle);
969 if (pos == std::string::npos) return 0;
970 pos += needle.size();
971 return std::strtoull(line.c_str() + pos, nullptr, 10);
972 };
973 auto parse_i64 = [](const std::string& line, const char* key) -> int64_t {
974 const std::string needle = std::string("\"") + key + "\":";
975 auto pos = line.find(needle);
976 if (pos == std::string::npos) return 0;
977 pos += needle.size();
978 return std::strtoll(line.c_str() + pos, nullptr, 10);
979 };
980
981 std::string line;
982 while (std::getline(f, line)) {
983 if (line.empty() || line[0] != '{') continue;
984
985 const std::string type = jsonFieldStr(line, "type");
986
987 if (type == "app") {
988 // Ripristina aggregato per-app (caso v3)
989 const std::string app = jsonFieldStr(line, "app");
990 if (app.empty()) continue;
991 const int64_t ts = parse_i64(line, "last_ts");
992 // Retention 7 giorni: aggregato obsoleto se l'ultima
993 // attivita' e' oltre il cutoff.
994 if (ts > 0 && ts < cutoff) { ++n_dropped; continue; }
995 AppStats s;
996 s.conn_count = jsonFieldUint(line, "count");
997 s.estimated_bytes = parse_u64(line, "bytes");
998 s.last_seen_epoch = ts;
999 m_appStats[app] = s;
1000 ++n_apps;
1001 continue;
1002 }
1003
1004 if (type == "conn") {
1005 // Evento detail persistito: lo rimettiamo nel ring buffer
1006 // dell'app corrispondente (se fresco).
1007 const std::string app = jsonFieldStr(line, "app");
1008 if (app.empty()) continue;
1009 const int64_t ts = parse_i64(line, "ts");
1010 if (ts > 0 && ts < cutoff) { ++n_dropped; continue; }
1011
1012 DetailEvent e;
1013 e.ts_epoch = ts;
1014 e.time = jsonFieldStr(line, "time");
1015 e.pid = jsonFieldUint(line, "pid");
1016 e.protocol = jsonFieldStr(line, "proto");
1017 e.src_ip = jsonFieldStr(line, "src_ip");
1018 e.host = jsonFieldStr(line, "host");
1019 e.ip = jsonFieldStr(line, "ip");
1020 e.port = jsonFieldUint(line, "port");
1021 e.args = jsonFieldStr(line, "args");
1022 e.verdict = jsonFieldStr(line, "verdict");
1023 e.pending = jsonFieldBool(line, "pending");
1024 e.count = jsonFieldUint(line, "count");
1025 if (e.count == 0) e.count = 1;
1026 // Append in coda: preserviamo l'ordine cronologico del file
1027 // (e' scritto cronologico crescente). Se il ring e' pieno
1028 // sconfiniamo a kDetailRingMax — via pushDetailEvent.
1029 pushDetailEvent(app, e);
1030 ++n_conns;
1031 continue;
1032 }
1033
1034 // Log legacy v2 senza "type": trattalo come conn per ricostruire
1035 // almeno il totale aggregato.
1036 const std::string app = jsonFieldStr(line, "app");
1037 if (app.empty()) continue;
1038 AppStats& s = m_appStats[app];
1039 s.conn_count += 1;
1040 s.estimated_bytes += kEstimatedBytesPerConn;
1041 if (s.last_seen_epoch == 0) s.last_seen_epoch = now_epoch;
1042 }
1043
1044 // Popola la ListStore apps a partire dagli aggregati accumulati.
1045 for (const auto& kv : m_appStats) {
1046 auto it = m_appStore->append();
1047 auto row = *it;
1049 row[m_appColumns.col_app] = kv.first;
1050 row[m_appColumns.col_conn_count] = kv.second.conn_count;
1051 row[m_appColumns.col_bytes_raw] = kv.second.estimated_bytes;
1052 row[m_appColumns.col_bytes_str] = formatBytes(kv.second.estimated_bytes);
1053 row[m_appColumns.col_last_seen] = formatEpochTime(kv.second.last_seen_epoch);
1054 row[m_appColumns.col_last_ts] = kv.second.last_seen_epoch;
1055 m_appIndex[kv.first] = it;
1056 }
1057
1058 std::cerr << "[ConnectionsPage] Caricato log: "
1059 << n_apps << " app + " << n_conns << " eventi detail, "
1060 << n_dropped << " scartati per retention 7gg ("
1061 << m_appStats.size() << " app uniche totali)\n";
1062}
static constexpr uint64_t kEstimatedBytesPerConn
void pushDetailEvent(const std::string &app_display, const DetailEvent &ev)
static std::string formatEpochTime(int64_t epoch)
static std::string formatBytes(uint64_t n)
static bool jsonFieldBool(const std::string &json, const std::string &key)
static std::string jsonFieldStr(const std::string &json, const std::string &key)
static constexpr int64_t kRetentionSeconds
std::string resolveIconName(const std::string &app_display)
static uint32_t jsonFieldUint(const std::string &json, const std::string &key)

References ConnectionsPage::DetailEvent::args, ConnectionsPage::AppColumns::col_app, ConnectionsPage::AppColumns::col_bytes_raw, ConnectionsPage::AppColumns::col_bytes_str, ConnectionsPage::AppColumns::col_conn_count, ConnectionsPage::AppColumns::col_icon_name, ConnectionsPage::AppColumns::col_last_seen, ConnectionsPage::AppColumns::col_last_ts, ConnectionsPage::AppStats::conn_count, ConnectionsPage::DetailEvent::count, ConnectionsPage::AppStats::estimated_bytes, formatBytes(), formatEpochTime(), ConnectionsPage::DetailEvent::host, ConnectionsPage::DetailEvent::ip, jsonFieldBool(), jsonFieldStr(), jsonFieldUint(), kEstimatedBytesPerConn, kRetentionSeconds, ConnectionsPage::AppStats::last_seen_epoch, logPath(), m_appColumns, m_appIndex, m_appStats, m_appStore, ConnectionsPage::DetailEvent::pending, ConnectionsPage::DetailEvent::pid, ConnectionsPage::DetailEvent::port, ConnectionsPage::DetailEvent::protocol, pushDetailEvent(), resolveIconName(), ConnectionsPage::DetailEvent::src_ip, ConnectionsPage::DetailEvent::time, ConnectionsPage::DetailEvent::ts_epoch, and ConnectionsPage::DetailEvent::verdict.

◆ logPath()

std::string ConnectionsPage::logPath ( )
static

Definition at line 857 of file ConnectionsPage.cpp.

858{
859 return "/tmp/bastionguard-sc-connections-" +
860 std::to_string(::getuid()) + ".jsonl";
861}

Referenced by clearLog(), loadLog(), and saveLog().

◆ onAppActivated()

void ConnectionsPage::onAppActivated ( const Gtk::TreeModel::Path & path,
Gtk::TreeViewColumn * column )
private

Definition at line 340 of file ConnectionsPage.cpp.

342{
343 auto it = m_appStore->get_iter(path);
344 if (!it) return;
345 const Glib::ustring app = (*it)[m_appColumns.col_app];
346 showDetailView(app.raw());
347}
void showDetailView(const std::string &app)

References ConnectionsPage::AppColumns::col_app, m_appColumns, m_appStore, and showDetailView().

Referenced by buildAppTreeView().

◆ onBackClicked()

void ConnectionsPage::onBackClicked ( )
private

Definition at line 335 of file ConnectionsPage.cpp.

336{
337 showAppsView();
338}

References showAppsView().

Referenced by buildToolbar().

◆ onClear()

void ConnectionsPage::onClear ( )
private

Definition at line 831 of file ConnectionsPage.cpp.

832{
833 clearLog();
834 // Dopo Pulisci vogliamo sempre ripartire dalla vista aggregata:
835 // 1) il filtro corrente ("Connessioni di firefox") diventa stale se
836 // siamo in detail — nuove connessioni di altre app non apparirebbero
837 // nel detail (per design), e il risultato sarebbe una schermata che
838 // sembra "rotta" con zero contenuti. L'utente si aspetta una
839 // panoramica fresca.
840 // 2) Se l'utente vuole rientrare nel detail di un'app, ora dopo il
841 // reset basta un doppio click sulla riga app appena ricreata.
842 if (m_stack.get_visible_child_name() == "detail") {
843 showAppsView();
844 }
845}

References clearLog(), m_stack, and showAppsView().

Referenced by buildToolbar().

◆ onNewConnection()

void ConnectionsPage::onNewConnection ( const ConnectionInfo & info,
bool pending = true )

Aggiunge una riga con tutti i dati eBPF. Se esiste gia' una riga con stesso (app, host/ip, port, proto) incrementa il contatore invece di creare un duplicato. Aggiorna anche i contatori aggregati per-app e lo sparkline.

Definition at line 471 of file ConnectionsPage.cpp.

472{
473 // Nome applicazione risolto da .desktop quando possibile
474 const std::string app_display = bsc::ui_util::best_connection_display_name(
475 info.app_comm, info.app_path, info.app_args, info.dest_ip, info.src_ip);
476
477 // Argomenti come stringa
478 std::string argsStr;
479 for (size_t i = 0; i < info.app_args.size(); ++i) {
480 if (i > 0) argsStr += ' ';
481 argsStr += info.app_args[i];
482 }
483
484 // 1) Sparkline: aggiungi al bucket corrente (gli eventi pending contano
485 // comunque come traffico intenzionale — l'utente potrebbe anche
486 // negarli, ma nel frattempo il socket e' stato aperto).
488
489 // 2) Aggregati per-app (lista principale)
490 if (!app_display.empty()) {
491 updateAppAggregate(app_display);
492 }
493
494 // 3) Ring buffer per-app: persistiamo ogni evento detail (non solo
495 // se siamo in vista detail). Cosi' entrando in un'app al prossimo
496 // avvio (o semplicemente nella stessa sessione) vediamo lo storico
497 // anche se non era mai stata aperta la sua detail view.
498 if (!app_display.empty()) {
499 DetailEvent e;
500 e.ts_epoch = static_cast<int64_t>(std::time(nullptr));
501 e.time = currentTime();
502 e.pid = info.pid;
503 e.protocol = info.protocol;
504 e.src_ip = info.src_ip;
505 e.host = info.dest_host;
506 e.ip = info.dest_ip;
507 e.port = info.dest_port;
508 e.args = argsStr;
509 // verdict resta "" — lo popoliamo alla risoluzione (vedi
510 // setLastVerdict sotto). pending=true se era un popup in volo.
511 e.verdict = pending ? "" : "";
512 e.pending = pending;
513 e.count = 1;
514 pushDetailEvent(app_display, e);
515 }
516
517 // 4) Vista detail: solo se siamo in detail e l'app matcha il filtro.
518 // Altrimenti la riga non viene aggiunta al m_store (il filtro live
519 // previene di sporcare la vista con app non pertinenti).
520 const bool in_detail_view = (m_stack.get_visible_child_name() == "detail");
521 if (in_detail_view && !m_currentAppFilter.empty()
522 && app_display == m_currentAppFilter) {
523 addOrUpdateDetailRow(info, app_display, argsStr, pending);
524 }
525}
std::string m_currentAppFilter
void updateAppAggregate(const std::string &app_display)
void addOrUpdateDetailRow(const ConnectionInfo &info, const std::string &app_display, const std::string &argsStr, bool pending)
std::string best_connection_display_name(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args, const std::string &dst_ip, const std::string &src_ip)
std::string app_path
Definition UIServer.hpp:27
std::vector< std::string > app_args
Definition UIServer.hpp:30
std::string app_comm
Definition UIServer.hpp:28

References addOrUpdateDetailRow(), ConnectionInfo::app_args, ConnectionInfo::app_comm, ConnectionInfo::app_path, ConnectionsPage::DetailEvent::args, bsc::ui_util::best_connection_display_name(), ConnectionsPage::DetailEvent::count, currentTime(), ConnectionInfo::dest_host, ConnectionInfo::dest_ip, ConnectionInfo::dest_port, ConnectionsPage::DetailEvent::host, ConnectionsPage::DetailEvent::ip, kEstimatedBytesPerConn, m_currentAppFilter, m_currentBucket, m_stack, ConnectionsPage::DetailEvent::pending, ConnectionInfo::pid, ConnectionsPage::DetailEvent::pid, ConnectionsPage::DetailEvent::port, ConnectionInfo::protocol, ConnectionsPage::DetailEvent::protocol, pushDetailEvent(), ConnectionInfo::src_ip, ConnectionsPage::DetailEvent::src_ip, ConnectionsPage::DetailEvent::time, ConnectionsPage::DetailEvent::ts_epoch, updateAppAggregate(), and ConnectionsPage::DetailEvent::verdict.

Referenced by MainWindow::onDaemonAlert(), and MainWindow::onNewConnection().

◆ onSparkDraw()

void ConnectionsPage::onSparkDraw ( const Cairo::RefPtr< Cairo::Context > & cr,
int width,
int height )
private

Definition at line 742 of file ConnectionsPage.cpp.

744{
745 if (m_sparkBuckets.empty() || width <= 0 || height <= 0) return;
746
747 // Trova il massimo per normalizzare
748 uint64_t peak = 0;
749 for (auto b : m_sparkBuckets) if (b > peak) peak = b;
750 if (peak == 0) peak = 1; // evita div/0 quando tutto vuoto
751
752 const int n = static_cast<int>(m_sparkBuckets.size());
753 const double bar_w = static_cast<double>(width) / n;
754 const double pad = 4.0;
755
756 // Sfondo sottile (griglia a 25/50/75% dell'altezza)
757 cr->set_source_rgba(1, 1, 1, 0.06);
758 cr->set_line_width(1);
759 for (int i = 1; i < 4; ++i) {
760 double y = pad + (height - 2 * pad) * i / 4.0;
761 cr->move_to(0, y);
762 cr->line_to(width, y);
763 cr->stroke();
764 }
765
766 // Area riempita (verde BastionGuard morbido)
767 cr->set_source_rgba(0.30, 0.78, 0.55, 0.85);
768 cr->move_to(0, height - pad);
769 for (int i = 0; i < n; ++i) {
770 const double frac = static_cast<double>(m_sparkBuckets[i]) / peak;
771 const double x = i * bar_w;
772 const double y = (height - pad) - frac * (height - 2 * pad);
773 cr->line_to(x, y);
774 }
775 cr->line_to(width, height - pad);
776 cr->close_path();
777 cr->fill_preserve();
778
779 // Contorno superiore piu' marcato
780 cr->set_source_rgba(0.30, 0.78, 0.55, 1.0);
781 cr->set_line_width(1.5);
782 cr->stroke();
783}

References m_sparkBuckets.

Referenced by buildSparkline().

◆ onSparkTick()

bool ConnectionsPage::onSparkTick ( )
private

Definition at line 712 of file ConnectionsPage.cpp.

713{
714 // Sposta il bucket corrente nella storia. Mantieni al massimo
715 // kSparkBucketsMax bucket; scarta il piu' vecchio in testa.
717 while (static_cast<int>(m_sparkBuckets.size()) > kSparkBucketsMax) {
718 m_sparkBuckets.pop_front();
719 }
720 m_currentBucket = 0;
721
722 // Aggiorna l'etichetta con il picco + media degli ultimi 60 s
723 uint64_t peak = 0, sum = 0;
724 for (auto b : m_sparkBuckets) {
725 if (b > peak) peak = b;
726 sum += b;
727 }
728 // Per avere "byte/s" moltiplichiamo per 1000/kSparkBucketMs = 2
729 const uint64_t avg_bps = (sum * 1000ULL) /
731 const uint64_t peak_bps = (peak * 1000ULL) / kSparkBucketMs;
732 m_sparkLabel.set_markup(
733 Glib::ustring::compose(
734 _("<b>Throughput stimato</b> <small>ultimi 60 s — media %1/s · picco %2/s (stima basata sul numero di connessioni)</small>"),
735 formatBytes(avg_bps),
736 formatBytes(peak_bps)));
737
738 m_sparkArea.queue_draw();
739 return true; // mantieni il timer attivo
740}

References _, formatBytes(), kSparkBucketMs, kSparkBucketsMax, m_currentBucket, m_sparkArea, m_sparkBuckets, and m_sparkLabel.

Referenced by ConnectionsPage().

◆ onVerdictEdited()

void ConnectionsPage::onVerdictEdited ( const Glib::ustring & pathStr,
const Glib::ustring & newText )
private

Definition at line 808 of file ConnectionsPage.cpp.

810{
811 auto it = m_store->get_iter(pathStr);
812 if (!it) return;
813
814 const bool wasPending = (*it)[m_columns.col_pending];
815 (*it)[m_columns.col_verdict] = newText;
816
817 if (wasPending) {
819 (*it)[m_columns.col_pending] = false;
820 }
821}
ReplyAction parseReplyAction(const Glib::ustring &text) const
void sendReply(ReplyAction action)
Definition UIServer.cpp:422

References ConnectionsPage::DetailColumns::col_pending, ConnectionsPage::DetailColumns::col_verdict, m_columns, m_store, m_uiServer, parseReplyAction(), and UIServer::sendReply().

Referenced by buildDetailTreeView().

◆ parseReplyAction()

ReplyAction ConnectionsPage::parseReplyAction ( const Glib::ustring & text) const
private

Definition at line 823 of file ConnectionsPage.cpp.

824{
825 if (text == "allow once") return ReplyAction::ALLOW_ONCE;
826 if (text == "allow always") return ReplyAction::ALLOW_ALWAYS;
827 if (text == "deny always") return ReplyAction::DENY_ALWAYS;
829}

References ALLOW_ALWAYS, ALLOW_ONCE, DENY_ALWAYS, and DENY_ONCE.

Referenced by onVerdictEdited().

◆ populateDetailFromRing()

void ConnectionsPage::populateDetailFromRing ( const std::string & app_display)
private

Definition at line 582 of file ConnectionsPage.cpp.

583{
584 auto it = m_detailRings.find(app_display);
585 if (it == m_detailRings.end()) return;
586
587 const auto& ring = it->second;
588 if (ring.empty()) return;
589
590 // PERF: stacchiamo il modello dalla TreeView prima dell'inserimento
591 // massivo. Con il modello ancora attaccato, GTK emette row-inserted
592 // e ridisegna per OGNI append — con fino a kDetailRingMax (500)
593 // eventi nel ring buffer, il click su un'app per aprirne il detail
594 // risultava percepibilmente lento (decine/centinaia di ms di lag
595 // prima che la vista comparisse). Staccando il modello facciamo un
596 // unico redraw alla fine, riportando il tempo a sostanzialmente
597 // istantaneo anche con la history piena. Stesso pattern usato da
598 // file manager / log viewer GTK per liste lunghe.
599 m_treeView.unset_model();
600
601 // Iteriamo dal piu' recente al piu' vecchio e prependiamo, cosi' il
602 // display ha lo stesso ordine live (piu' recente in cima).
603 for (auto r_it = ring.rbegin(); r_it != ring.rend(); ++r_it) {
604 const auto& e = *r_it;
605 auto row_it = m_store->append(); // append, non prepend: iteriamo reverse
606 auto row = *row_it;
607 row[m_columns.col_time] = e.time;
608 row[m_columns.col_app] = app_display;
609 row[m_columns.col_verdict] = e.verdict;
610 row[m_columns.col_count] = e.count;
611 row[m_columns.col_pid] = e.pid;
612 row[m_columns.col_protocol] = e.protocol;
613 row[m_columns.col_src_ip] = e.src_ip;
614 row[m_columns.col_host] = e.host;
615 row[m_columns.col_ip] = e.ip;
616 row[m_columns.col_port] = e.port;
617 row[m_columns.col_args] = e.args;
618 row[m_columns.col_pending] = e.pending;
619 // group_key vuoto: non popoliamo m_groupIndex con righe storiche
620 // per evitare che il dedup online colleghi pin fresco a riga
621 // ricostruita da log.
622 row[m_columns.col_group_key] = "";
623 }
624
625 // Ricolleghiamo il modello: un unico redraw per tutte le righe.
626 m_treeView.set_model(m_store);
627}

References ConnectionsPage::DetailColumns::col_app, ConnectionsPage::DetailColumns::col_args, ConnectionsPage::DetailColumns::col_count, ConnectionsPage::DetailColumns::col_group_key, ConnectionsPage::DetailColumns::col_host, ConnectionsPage::DetailColumns::col_ip, ConnectionsPage::DetailColumns::col_pending, ConnectionsPage::DetailColumns::col_pid, ConnectionsPage::DetailColumns::col_port, ConnectionsPage::DetailColumns::col_protocol, ConnectionsPage::DetailColumns::col_src_ip, ConnectionsPage::DetailColumns::col_time, ConnectionsPage::DetailColumns::col_verdict, m_columns, m_detailRings, m_store, and m_treeView.

Referenced by showDetailView().

◆ pushDetailEvent()

void ConnectionsPage::pushDetailEvent ( const std::string & app_display,
const DetailEvent & ev )
private

Definition at line 568 of file ConnectionsPage.cpp.

570{
571 auto& ring = m_detailRings[app_display];
572 ring.push_back(ev);
573 // Enforce max size con eviction FIFO: se superiamo, scartiamo il
574 // piu' vecchio (front). Alternativa sarebbe ignorare il nuovo, ma
575 // gli eventi piu' recenti sono in genere quelli che interessano di
576 // piu' all'utente.
577 while (ring.size() > kDetailRingMax) {
578 ring.pop_front();
579 }
580}
static constexpr size_t kDetailRingMax

References kDetailRingMax, and m_detailRings.

Referenced by loadLog(), and onNewConnection().

◆ resolveIconName()

std::string ConnectionsPage::resolveIconName ( const std::string & app_display)
private

Definition at line 413 of file ConnectionsPage.cpp.

414{
415 if (app_display == bsc::ui_util::safe_local_process_label()) {
417 }
418 return bsc::ui_util::best_app_icon_name(app_display, app_display);
419}
std::string safe_local_process_icon_name()
std::string safe_local_process_label()
std::string best_app_icon_name(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)

References bsc::ui_util::best_app_icon_name(), bsc::ui_util::safe_local_process_icon_name(), and bsc::ui_util::safe_local_process_label().

Referenced by loadLog(), and updateAppAggregate().

◆ saveLog()

void ConnectionsPage::saveLog ( ) const

Definition at line 909 of file ConnectionsPage.cpp.

910{
911 std::ofstream f(logPath(), std::ios::trunc);
912 if (!f.is_open()) return;
913
914 // Persistiamo:
915 // - aggregati per-app (type="app") — per la vista "Applicazioni"
916 // - ultimi 500 eventi detail per app (type="conn") — per la vista
917 // dettagli quando l'utente rientra in un'app
918 //
919 // Retention 7 giorni enforced al loadLog(): il file cresce comunque
920 // proporzionalmente al kDetailRingMax (500 eventi × numero app),
921 // tipicamente <5 MB anche con uso intenso.
922 for (const auto& kv : m_appStats) {
923 const std::string& app = kv.first;
924 const AppStats& s = kv.second;
925 f << "{\"type\":\"app\""
926 << ",\"app\":\"" << jsonEscapeStr(app) << "\""
927 << ",\"count\":" << s.conn_count
928 << ",\"bytes\":" << s.estimated_bytes
929 << ",\"last_ts\":" << s.last_seen_epoch
930 << "}\n";
931 }
932
933 // Eventi detail: per ogni app scriviamo il suo ring buffer in ordine
934 // cronologico crescente (front = piu' vecchio, back = piu' recente).
935 for (const auto& [app, ring] : m_detailRings) {
936 for (const auto& e : ring) {
937 f << "{\"type\":\"conn\""
938 << ",\"app\":\"" << jsonEscapeStr(app) << "\""
939 << ",\"ts\":" << e.ts_epoch
940 << ",\"time\":\"" << jsonEscapeStr(e.time) << "\""
941 << ",\"pid\":" << e.pid
942 << ",\"proto\":\"" << jsonEscapeStr(e.protocol) << "\""
943 << ",\"src_ip\":\"" << jsonEscapeStr(e.src_ip) << "\""
944 << ",\"host\":\"" << jsonEscapeStr(e.host) << "\""
945 << ",\"ip\":\"" << jsonEscapeStr(e.ip) << "\""
946 << ",\"port\":" << e.port
947 << ",\"args\":\"" << jsonEscapeStr(e.args) << "\""
948 << ",\"verdict\":\"" << jsonEscapeStr(e.verdict) << "\""
949 << ",\"pending\":" << (e.pending ? "true" : "false")
950 << ",\"count\":" << e.count
951 << "}\n";
952 }
953 }
954}
static std::string jsonEscapeStr(const std::string &s)

References ConnectionsPage::AppStats::conn_count, ConnectionsPage::AppStats::estimated_bytes, jsonEscapeStr(), ConnectionsPage::AppStats::last_seen_epoch, logPath(), m_appStats, and m_detailRings.

Referenced by MainWindow::onDaemonAlert(), MainWindow::onNewConnection(), MainWindow::onVerdict(), and MainWindow::~MainWindow().

◆ setLastVerdict()

void ConnectionsPage::setLastVerdict ( const std::string & verdict)

Aggiorna il verdetto dell'ultima riga pending.

Definition at line 789 of file ConnectionsPage.cpp.

790{
791 auto children = m_store->children();
792 for (auto it = children.begin(); it != children.end(); ++it) {
793 auto row = *it;
794 if (row[m_columns.col_pending]) {
795 row[m_columns.col_verdict] = verdict;
796 row[m_columns.col_pending] = false;
797 const Glib::ustring k = row[m_columns.col_group_key];
798 if (!k.empty()) m_groupIndex[k.raw()] = it;
799 return;
800 }
801 }
802 if (!children.empty()) {
803 auto first = *children.begin();
804 first[m_columns.col_verdict] = verdict;
805 }
806}

References ConnectionsPage::DetailColumns::col_group_key, ConnectionsPage::DetailColumns::col_pending, ConnectionsPage::DetailColumns::col_verdict, m_columns, m_groupIndex, and m_store.

Referenced by MainWindow::onDaemonAlert(), MainWindow::onNewConnection(), and MainWindow::onVerdict().

◆ showAppsView()

void ConnectionsPage::showAppsView ( )
private

Definition at line 287 of file ConnectionsPage.cpp.

288{
289 m_stack.set_visible_child("apps");
290 m_backBtn.set_visible(false);
291 m_detailTitle.set_visible(false);
292 m_currentAppFilter.clear();
293 // Svuota il modello detail per liberare memoria: verra' ripopolato
294 // quando l'utente rientra in una specifica app (filtrando le connessioni
295 // arrivate nel frattempo dal log / dagli aggregati).
296 //
297 // NOTA: NON tocchiamo m_appStats / m_appIndex (servono agli aggregati).
298 m_store->clear();
299 m_groupIndex.clear();
300}

References m_backBtn, m_currentAppFilter, m_detailTitle, m_groupIndex, m_stack, and m_store.

Referenced by ConnectionsPage(), onBackClicked(), and onClear().

◆ showDetailView()

void ConnectionsPage::showDetailView ( const std::string & app)
private

Definition at line 302 of file ConnectionsPage.cpp.

303{
304 m_currentAppFilter = app;
305 m_detailTitle.set_text(Glib::ustring::compose(
306 _("Connessioni di %1"), app));
307 m_detailTitle.set_visible(true);
308 m_backBtn.set_visible(true);
309 m_stack.set_visible_child("detail");
310
311 // Popola dalla history persistita per questa app (ring buffer di
312 // 500 eventi max). Cosi' rientrando in un'app che era gia' stata
313 // attiva vedi lo storico invece di una schermata vuota —
314 // continuando ad aggiungere in cima i nuovi eventi live.
315 //
316 // PERF: stacchiamo il modello PRIMA di clear() + append, cosi'
317 // evitiamo sia il burst di row-deleted (se passiamo da detail-X
318 // a detail-Y senza tornare indietro: m_store puo' avere fino a
319 // 500 righe) sia il burst di row-inserted (ring buffer della
320 // nuova app). populateDetailFromRing ha gia' il suo
321 // unset/set_model, ma se lo chiamiamo qui con il modello gia'
322 // staccato, il suo set_model finale si occupa di riattaccare.
323 m_treeView.unset_model();
324 m_store->clear();
325 m_groupIndex.clear();
327 // Safety net: populateDetailFromRing esce subito se il ring e'
328 // vuoto, senza riattaccare il modello. Garantiamo qui che la
329 // view torni sempre associata a m_store.
330 if (m_treeView.get_model() != m_store) {
331 m_treeView.set_model(m_store);
332 }
333}
void populateDetailFromRing(const std::string &app_display)

References _, m_backBtn, m_currentAppFilter, m_detailTitle, m_groupIndex, m_stack, m_store, m_treeView, and populateDetailFromRing().

Referenced by onAppActivated().

◆ updateAppAggregate()

void ConnectionsPage::updateAppAggregate ( const std::string & app_display)
private

Definition at line 531 of file ConnectionsPage.cpp.

532{
533 const int64_t now_epoch = static_cast<int64_t>(std::time(nullptr));
534 auto& stats = m_appStats[app_display];
535 stats.conn_count += 1;
536 stats.estimated_bytes += kEstimatedBytesPerConn;
537 stats.last_seen_epoch = now_epoch;
538
539 auto idx_it = m_appIndex.find(app_display);
540 if (idx_it != m_appIndex.end()
541 && idx_it->second
542 && m_appStore->iter_is_valid(idx_it->second))
543 {
544 auto row = *(idx_it->second);
545 row[m_appColumns.col_conn_count] = stats.conn_count;
546 row[m_appColumns.col_bytes_raw] = stats.estimated_bytes;
547 row[m_appColumns.col_bytes_str] = formatBytes(stats.estimated_bytes);
549 row[m_appColumns.col_last_ts] = now_epoch;
550 } else {
551 auto it = m_appStore->append();
552 auto row = *it;
553 row[m_appColumns.col_icon_name] = resolveIconName(app_display);
554 row[m_appColumns.col_app] = app_display;
555 row[m_appColumns.col_conn_count] = stats.conn_count;
556 row[m_appColumns.col_bytes_raw] = stats.estimated_bytes;
557 row[m_appColumns.col_bytes_str] = formatBytes(stats.estimated_bytes);
559 row[m_appColumns.col_last_ts] = now_epoch;
560 m_appIndex[app_display] = it;
561 }
562}

References ConnectionsPage::AppColumns::col_app, ConnectionsPage::AppColumns::col_bytes_raw, ConnectionsPage::AppColumns::col_bytes_str, ConnectionsPage::AppColumns::col_conn_count, ConnectionsPage::AppColumns::col_icon_name, ConnectionsPage::AppColumns::col_last_seen, ConnectionsPage::AppColumns::col_last_ts, formatBytes(), formatEpochTime(), kEstimatedBytesPerConn, m_appColumns, m_appIndex, m_appStats, m_appStore, and resolveIconName().

Referenced by onNewConnection().

Member Data Documentation

◆ kDetailRingMax

size_t ConnectionsPage::kDetailRingMax = 500
staticconstexprprivate

Definition at line 153 of file ConnectionsPage.hpp.

Referenced by pushDetailEvent().

◆ kEstimatedBytesPerConn

uint64_t ConnectionsPage::kEstimatedBytesPerConn = 4096
staticconstexpr

Definition at line 66 of file ConnectionsPage.hpp.

Referenced by loadLog(), onNewConnection(), and updateAppAggregate().

◆ kRetentionSeconds

int64_t ConnectionsPage::kRetentionSeconds = 7 * 24 * 3600
staticconstexprprivate

Definition at line 162 of file ConnectionsPage.hpp.

Referenced by loadLog().

◆ kSparkBucketMs

int ConnectionsPage::kSparkBucketMs = 500
staticconstexprprivate

Definition at line 167 of file ConnectionsPage.hpp.

Referenced by ConnectionsPage(), and onSparkTick().

◆ kSparkBucketsMax

int ConnectionsPage::kSparkBucketsMax = 120
staticconstexprprivate

Definition at line 168 of file ConnectionsPage.hpp.

Referenced by buildSparkline(), and onSparkTick().

◆ m_appColumns

AppColumns ConnectionsPage::m_appColumns
private

Definition at line 175 of file ConnectionsPage.hpp.

Referenced by buildAppTreeView(), loadLog(), onAppActivated(), and updateAppAggregate().

◆ m_appIndex

std::unordered_map<std::string, Gtk::TreeModel::iterator> ConnectionsPage::m_appIndex
private

Definition at line 206 of file ConnectionsPage.hpp.

Referenced by clearLog(), loadLog(), and updateAppAggregate().

◆ m_appScroll

Gtk::ScrolledWindow ConnectionsPage::m_appScroll
private

Definition at line 183 of file ConnectionsPage.hpp.

Referenced by ConnectionsPage().

◆ m_appStats

std::unordered_map<std::string, AppStats> ConnectionsPage::m_appStats
private

Definition at line 205 of file ConnectionsPage.hpp.

Referenced by clearLog(), loadLog(), saveLog(), and updateAppAggregate().

◆ m_appStore

Glib::RefPtr<Gtk::ListStore> ConnectionsPage::m_appStore
private

◆ m_appTreeView

Gtk::TreeView ConnectionsPage::m_appTreeView
private

Definition at line 181 of file ConnectionsPage.hpp.

Referenced by buildAppTreeView(), and ConnectionsPage().

◆ m_backBtn

Gtk::Button ConnectionsPage::m_backBtn
private

Definition at line 191 of file ConnectionsPage.hpp.

Referenced by buildToolbar(), showAppsView(), and showDetailView().

◆ m_clearBtn

Gtk::Button ConnectionsPage::m_clearBtn
private

Definition at line 190 of file ConnectionsPage.hpp.

Referenced by buildToolbar().

◆ m_columns

DetailColumns ConnectionsPage::m_columns
private

◆ m_comboCol

ComboCol ConnectionsPage::m_comboCol
private

Definition at line 176 of file ConnectionsPage.hpp.

Referenced by buildVerdictModel().

◆ m_currentAppFilter

std::string ConnectionsPage::m_currentAppFilter
private

Definition at line 211 of file ConnectionsPage.hpp.

Referenced by onNewConnection(), showAppsView(), and showDetailView().

◆ m_currentBucket

uint64_t ConnectionsPage::m_currentBucket = 0
private

Definition at line 170 of file ConnectionsPage.hpp.

Referenced by onNewConnection(), and onSparkTick().

◆ m_desktopIndex

std::unordered_map<std::string, std::string> ConnectionsPage::m_desktopIndex
private

Definition at line 220 of file ConnectionsPage.hpp.

Referenced by buildDesktopIndex().

◆ m_desktopIndexBuilt

bool ConnectionsPage::m_desktopIndexBuilt = false
private

Definition at line 221 of file ConnectionsPage.hpp.

Referenced by buildDesktopIndex().

◆ m_detailRings

std::unordered_map<std::string, std::deque<DetailEvent> > ConnectionsPage::m_detailRings
private

Definition at line 156 of file ConnectionsPage.hpp.

Referenced by clearLog(), populateDetailFromRing(), pushDetailEvent(), and saveLog().

◆ m_detailTitle

Gtk::Label ConnectionsPage::m_detailTitle
private

Definition at line 192 of file ConnectionsPage.hpp.

Referenced by buildToolbar(), showAppsView(), and showDetailView().

◆ m_groupIndex

std::unordered_map<std::string, Gtk::TreeModel::iterator> ConnectionsPage::m_groupIndex
private

◆ m_iconCache

std::unordered_map<std::string, std::string> ConnectionsPage::m_iconCache
private

Definition at line 215 of file ConnectionsPage.hpp.

◆ m_scroll

Gtk::ScrolledWindow ConnectionsPage::m_scroll
private

Definition at line 182 of file ConnectionsPage.hpp.

Referenced by ConnectionsPage().

◆ m_search

Gtk::SearchEntry ConnectionsPage::m_search
private

Definition at line 193 of file ConnectionsPage.hpp.

Referenced by buildToolbar().

◆ m_sparkArea

Gtk::DrawingArea ConnectionsPage::m_sparkArea
private

Definition at line 199 of file ConnectionsPage.hpp.

Referenced by buildSparkline(), and onSparkTick().

◆ m_sparkBox

Gtk::Box ConnectionsPage::m_sparkBox { Gtk::Orientation::VERTICAL, 4 }
private

Definition at line 197 of file ConnectionsPage.hpp.

Referenced by buildSparkline().

◆ m_sparkBuckets

std::deque<uint64_t> ConnectionsPage::m_sparkBuckets
private

Definition at line 169 of file ConnectionsPage.hpp.

Referenced by buildSparkline(), onSparkDraw(), and onSparkTick().

◆ m_sparkFrame

Gtk::Frame ConnectionsPage::m_sparkFrame
private

Definition at line 196 of file ConnectionsPage.hpp.

Referenced by buildSparkline(), and ConnectionsPage().

◆ m_sparkLabel

Gtk::Label ConnectionsPage::m_sparkLabel
private

Definition at line 198 of file ConnectionsPage.hpp.

Referenced by buildSparkline(), and onSparkTick().

◆ m_sparkTick

sigc::connection ConnectionsPage::m_sparkTick
private

Definition at line 171 of file ConnectionsPage.hpp.

Referenced by ConnectionsPage(), and ~ConnectionsPage().

◆ m_stack

Gtk::Stack ConnectionsPage::m_stack
private

◆ m_store

Glib::RefPtr<Gtk::ListStore> ConnectionsPage::m_store
private

◆ m_toolbar

Gtk::Box ConnectionsPage::m_toolbar { Gtk::Orientation::HORIZONTAL, 6 }
private

Definition at line 189 of file ConnectionsPage.hpp.

Referenced by buildToolbar(), and ConnectionsPage().

◆ m_treeView

Gtk::TreeView ConnectionsPage::m_treeView
private

◆ m_uiServer

UIServer& ConnectionsPage::m_uiServer
private

Definition at line 223 of file ConnectionsPage.hpp.

Referenced by ConnectionsPage(), and onVerdictEdited().

◆ m_verdictModel

Glib::RefPtr<Gtk::ListStore> ConnectionsPage::m_verdictModel
private

Definition at line 177 of file ConnectionsPage.hpp.

Referenced by buildDetailTreeView(), and buildVerdictModel().


The documentation for this class was generated from the following files: