9#include <glibmm/i18n.h>
10#include <glibmm/datetime.h>
11#include <gtkmm/cellrenderercombo.h>
12#include <gtkmm/cellrendererpixbuf.h>
13#include <gtkmm/cellrenderertext.h>
14#include <giomm/appinfo.h>
15#include <giomm/desktopappinfo.h>
16#include <giomm/themedicon.h>
29 "allow once",
"allow always",
"deny once",
"deny always",
nullptr
37: Gtk::Box(Gtk::Orientation::VERTICAL, 0)
54 m_stack.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
55 m_stack.set_transition_duration(220);
57 m_stack.set_visible_child(
"apps");
102 m_backBtn.set_tooltip_text(
_(
"Torna all'elenco applicazioni"));
113 m_search.set_placeholder_text(
_(
"Filtra per app o host..."));
132 m_appTreeView.get_style_context()->add_class(
"bsc-treeview");
133 m_appTreeView.set_grid_lines(Gtk::TreeView::GridLines::HORIZONTAL);
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>();
145 iconCell->property_icon_size() = Gtk::IconSize::NORMAL;
146 iconCell->set_padding(2, 0);
147 textCell->set_padding(4, 0);
149 col->pack_start(*iconCell,
false);
150 col->pack_start(*textCell,
true);
162 const int minW[] = { 260, 110, 130, 130 };
163 for (
int i = 0; i < 4; ++i) {
165 c->set_min_width(minW[i]);
166 c->set_resizable(
true);
186 Gtk::SortType::DESCENDING);
203 m_treeView.get_style_context()->add_class(
"bsc-treeview");
204 m_treeView.set_grid_lines(Gtk::TreeView::GridLines::HORIZONTAL);
211 auto* col = Gtk::make_managed<Gtk::TreeViewColumn>(
_(
"Esito"));
212 auto* cell = Gtk::make_managed<Gtk::CellRendererCombo>();
214 cell->property_text_column() = 0;
215 cell->property_editable() =
true;
216 cell->property_has_entry() =
false;
217 cell->signal_edited().connect(
219 col->pack_start(*cell,
true);
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);
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);
244 const int minW[] = { 75, 180, 140, 40, 55, 75, 110, 160, 110, 55, 180 };
245 for (
int i = 0; i < 11; ++i) {
247 c->set_min_width(minW[i]);
248 c->set_resizable(
true);
265 _(
"<b>Throughput stimato</b> <small>(ultimi 60 s — stima basata sul numero di connessioni)</small>"));
289 m_stack.set_visible_child(
"apps");
306 _(
"Connessioni di %1"), app));
309 m_stack.set_visible_child(
"detail");
341 Gtk::TreeViewColumn* )
366 auto apps = Gio::AppInfo::get_all();
367 for (
const auto& ai : apps) {
368 auto dai = std::dynamic_pointer_cast<Gio::DesktopAppInfo>(ai);
372 Glib::ustring icon_name;
373 auto icon = dai->get_icon();
375 auto themed = std::dynamic_pointer_cast<Gio::ThemedIcon>(icon);
377 const auto names = themed->get_names();
378 if (!names.empty()) icon_name = names[0];
381 if (icon_name.empty())
continue;
384 const std::string exec = dai->get_commandline();
385 if (exec.empty())
continue;
387 auto sp = exec.find(
' ');
388 std::string first = (sp == std::string::npos) ? exec : exec.substr(0, sp);
390 auto sl = first.rfind(
'/');
391 std::string base = (sl == std::string::npos) ? first : first.substr(sl + 1);
393 for (
auto& c : base) c = std::tolower(
static_cast<unsigned char>(c));
394 if (base.empty())
continue;
404 const std::string wm = dai->get_startup_wm_class();
406 std::string wmlc = wm;
407 for (
auto& c : wmlc) c = std::tolower(
static_cast<unsigned char>(c));
426 const std::string& protocol,
427 const std::string& host,
428 const std::string& ip,
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);
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));
450 std::snprintf(buf,
sizeof(buf),
"%.2f GB",
451 n / (1024.0 * 1024.0 * 1024.0));
453 return std::string(buf);
458 if (epoch <= 0)
return "";
459 std::time_t t =
static_cast<std::time_t
>(epoch);
461 localtime_r(&t, &tmv);
463 std::strftime(buf,
sizeof(buf),
"%H:%M:%S", &tmv);
464 return std::string(buf);
479 for (
size_t i = 0; i < info.
app_args.size(); ++i) {
480 if (i > 0) argsStr +=
' ';
490 if (!app_display.empty()) {
498 if (!app_display.empty()) {
500 e.
ts_epoch =
static_cast<int64_t
>(std::time(
nullptr));
520 const bool in_detail_view = (
m_stack.get_visible_child_name() ==
"detail");
533 const int64_t now_epoch =
static_cast<int64_t
>(std::time(
nullptr));
535 stats.conn_count += 1;
537 stats.last_seen_epoch = now_epoch;
544 auto row = *(idx_it->second);
587 const auto& ring = it->second;
588 if (ring.empty())
return;
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();
634 const std::string& app_display,
635 const std::string& argsStr,
643 if (!key.empty() && !pending) {
646 auto it = it_idx->second;
647 if (it &&
m_store->iter_is_valid(it)) {
663 auto newIt =
m_store->prepend();
664 auto newRow = *newIt;
723 uint64_t peak = 0, sum = 0;
725 if (b > peak) peak = b;
729 const uint64_t avg_bps = (sum * 1000ULL) /
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>"),
743 int width,
int height)
750 if (peak == 0) peak = 1;
753 const double bar_w =
static_cast<double>(width) / n;
754 const double pad = 4.0;
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;
762 cr->line_to(width, y);
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);
775 cr->line_to(width, height - pad);
780 cr->set_source_rgba(0.30, 0.78, 0.55, 1.0);
781 cr->set_line_width(1.5);
791 auto children =
m_store->children();
792 for (
auto it = children.begin(); it != children.end(); ++it) {
802 if (!children.empty()) {
803 auto first = *children.begin();
809 const Glib::ustring& newText)
811 auto it =
m_store->get_iter(pathStr);
842 if (
m_stack.get_visible_child_name() ==
"detail") {
849 auto now = Glib::DateTime::create_now_local();
850 return now.format(
"%H:%M:%S");
859 return "/tmp/bastionguard-sc-connections-" +
860 std::to_string(::getuid()) +
".jsonl";
866 for (
unsigned char c : s) {
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;
874 if (c < 0x20) {
char b[8]; std::snprintf(b,
sizeof(b),
"\\u%04x", c); out += b; }
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);
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));
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";
911 std::ofstream f(
logPath(), std::ios::trunc);
912 if (!f.is_open())
return;
923 const std::string& app = kv.first;
925 f <<
"{\"type\":\"app\""
936 for (
const auto& e : ring) {
937 f <<
"{\"type\":\"conn\""
939 <<
",\"ts\":" << e.ts_epoch
941 <<
",\"pid\":" << e.pid
946 <<
",\"port\":" << e.port
949 <<
",\"pending\":" << (e.pending ?
"true" :
"false")
950 <<
",\"count\":" << e.count
959 if (!f.is_open())
return;
961 const int64_t now_epoch =
static_cast<int64_t
>(std::time(
nullptr));
963 int n_apps = 0, n_conns = 0, n_dropped = 0;
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);
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);
982 while (std::getline(f, line)) {
983 if (line.empty() || line[0] !=
'{')
continue;
990 if (app.empty())
continue;
991 const int64_t ts = parse_i64(line,
"last_ts");
994 if (ts > 0 && ts < cutoff) { ++n_dropped;
continue; }
1004 if (type ==
"conn") {
1008 if (app.empty())
continue;
1009 const int64_t ts = parse_i64(line,
"ts");
1010 if (ts > 0 && ts < cutoff) { ++n_dropped;
continue; }
1037 if (app.empty())
continue;
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";
1073 std::filesystem::remove(
logPath(), ec);
static const char * kVerdictOptions[]
void onAppActivated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column)
std::deque< uint64_t > m_sparkBuckets
Gtk::TreeView m_appTreeView
Gtk::ScrolledWindow m_scroll
std::unordered_map< std::string, std::string > m_desktopIndex
Gtk::ScrolledWindow m_appScroll
ConnectionsPage(UIServer &uiServer)
Glib::RefPtr< Gtk::ListStore > m_appStore
void onSparkDraw(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
static constexpr uint64_t kEstimatedBytesPerConn
void setLastVerdict(const std::string &verdict)
void buildDetailTreeView()
void pushDetailEvent(const std::string &app_display, const DetailEvent &ev)
static std::string groupKeyFor(const std::string &app, const std::string &protocol, const std::string &host, const std::string &ip, uint32_t port)
Gtk::DrawingArea m_sparkArea
std::string currentTime()
Glib::RefPtr< Gtk::ListStore > m_store
void onVerdictEdited(const Glib::ustring &pathStr, const Glib::ustring &newText)
static std::string formatEpochTime(int64_t epoch)
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_appIndex
Glib::RefPtr< Gtk::ListStore > m_verdictModel
std::unordered_map< std::string, std::deque< DetailEvent > > m_detailRings
static std::string formatBytes(uint64_t n)
std::unordered_map< std::string, AppStats > m_appStats
static std::string jsonEscapeStr(const std::string &s)
static bool jsonFieldBool(const std::string &json, const std::string &key)
sigc::connection m_sparkTick
void onNewConnection(const ConnectionInfo &info, bool pending=true)
static std::string jsonFieldStr(const std::string &json, const std::string &key)
~ConnectionsPage() override
std::string m_currentAppFilter
static constexpr int kSparkBucketMs
void populateDetailFromRing(const std::string &app_display)
void updateAppAggregate(const std::string &app_display)
void addOrUpdateDetailRow(const ConnectionInfo &info, const std::string &app_display, const std::string &argsStr, bool pending)
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_groupIndex
void showDetailView(const std::string &app)
static constexpr int kSparkBucketsMax
ReplyAction parseReplyAction(const Glib::ustring &text) const
Gtk::SearchEntry m_search
static constexpr int64_t kRetentionSeconds
static std::string logPath()
static constexpr size_t kDetailRingMax
std::string resolveIconName(const std::string &app_display)
static uint32_t jsonFieldUint(const std::string &json, const std::string &key)
void sendReply(ReplyAction action)
std::string safe_local_process_icon_name()
std::string safe_local_process_label()
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 best_app_icon_name(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)
std::vector< std::string > app_args
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
Gtk::TreeModelColumn< Glib::ustring > val
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