Repository: evilsocket/opensnitch Branch: master Commit: f2ce07409651 Files: 441 Total size: 6.9 MB Directory structure: gitextract_fp8zrzif/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature-request.md │ └── workflows/ │ ├── build_ebpf_modules.yml │ ├── generic_validations.yml │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── daemon/ │ ├── .gitignore │ ├── Gopkg.toml │ ├── Makefile │ ├── conman/ │ │ ├── connection.go │ │ └── connection_test.go │ ├── core/ │ │ ├── core.go │ │ ├── ebpf.go │ │ ├── gzip.go │ │ ├── system.go │ │ └── version.go │ ├── data/ │ │ ├── default-config.json │ │ ├── init/ │ │ │ ├── opensnitchd-dinit │ │ │ ├── opensnitchd-openrc │ │ │ └── opensnitchd.service │ │ ├── network_aliases.json │ │ ├── rules/ │ │ │ ├── 000-allow-localhost.json │ │ │ └── 000-allow-localhost6.json │ │ ├── system-fw.json │ │ └── tasks/ │ │ └── tasks.json │ ├── dns/ │ │ ├── ebpfhook.go │ │ ├── parse.go │ │ ├── systemd/ │ │ │ └── monitor.go │ │ └── track.go │ ├── firewall/ │ │ ├── common/ │ │ │ └── common.go │ │ ├── config/ │ │ │ ├── config.go │ │ │ └── config_test.go │ │ ├── iptables/ │ │ │ ├── iptables.go │ │ │ ├── monitor.go │ │ │ ├── rules.go │ │ │ └── system.go │ │ ├── nftables/ │ │ │ ├── chains.go │ │ │ ├── chains_test.go │ │ │ ├── exprs/ │ │ │ │ ├── counter.go │ │ │ │ ├── counter_test.go │ │ │ │ ├── ct.go │ │ │ │ ├── ct_test.go │ │ │ │ ├── enums.go │ │ │ │ ├── ether.go │ │ │ │ ├── ether_test.go │ │ │ │ ├── iface.go │ │ │ │ ├── iface_test.go │ │ │ │ ├── ip.go │ │ │ │ ├── ip_test.go │ │ │ │ ├── limit.go │ │ │ │ ├── log.go │ │ │ │ ├── log_test.go │ │ │ │ ├── meta.go │ │ │ │ ├── meta_test.go │ │ │ │ ├── nat.go │ │ │ │ ├── nat_test.go │ │ │ │ ├── notrack.go │ │ │ │ ├── operator.go │ │ │ │ ├── port.go │ │ │ │ ├── port_test.go │ │ │ │ ├── protocol.go │ │ │ │ ├── protocol_test.go │ │ │ │ ├── quota.go │ │ │ │ ├── quota_test.go │ │ │ │ ├── utils.go │ │ │ │ ├── verdict.go │ │ │ │ └── verdict_test.go │ │ │ ├── monitor.go │ │ │ ├── monitor_test.go │ │ │ ├── nftables.go │ │ │ ├── nftest/ │ │ │ │ ├── nftest.go │ │ │ │ ├── test_utils.go │ │ │ │ └── utils.go │ │ │ ├── parser.go │ │ │ ├── rule_helpers.go │ │ │ ├── rules.go │ │ │ ├── rules_test.go │ │ │ ├── system.go │ │ │ ├── system_test.go │ │ │ ├── tables.go │ │ │ ├── tables_test.go │ │ │ ├── testdata/ │ │ │ │ └── test-sysfw-conf.json │ │ │ ├── utils.go │ │ │ └── utils_test.go │ │ └── rules.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ └── testutil/ │ │ └── network.go │ ├── log/ │ │ ├── formats/ │ │ │ ├── csv.go │ │ │ ├── formats.go │ │ │ ├── json.go │ │ │ ├── rfc3164.go │ │ │ └── rfc5424.go │ │ ├── log.go │ │ └── loggers/ │ │ ├── logger.go │ │ ├── remote.go │ │ ├── remote_syslog.go │ │ └── syslog.go │ ├── main.go │ ├── netfilter/ │ │ ├── netfilter_test.go │ │ ├── packet.go │ │ ├── queue.c │ │ ├── queue.go │ │ └── queue.h │ ├── netlink/ │ │ ├── ifaces.go │ │ ├── procmon/ │ │ │ └── procmon.go │ │ ├── socket.go │ │ ├── socket_linux.go │ │ ├── socket_packet.go │ │ ├── socket_test.go │ │ └── socket_xdp.go │ ├── netstat/ │ │ ├── entry.go │ │ ├── find.go │ │ ├── parse.go │ │ └── parse_packet.go │ ├── procmon/ │ │ ├── activepids.go │ │ ├── audit/ │ │ │ ├── client.go │ │ │ ├── config.go │ │ │ └── parse.go │ │ ├── cache.go │ │ ├── cache_events.go │ │ ├── cache_events_test.go │ │ ├── cache_test.go │ │ ├── details.go │ │ ├── ebpf/ │ │ │ ├── cache.go │ │ │ ├── config.go │ │ │ ├── debug.go │ │ │ ├── ebpf.go │ │ │ ├── ebpf_test.go │ │ │ ├── events.go │ │ │ ├── find.go │ │ │ ├── monitor.go │ │ │ └── utils.go │ │ ├── find.go │ │ ├── find_test.go │ │ ├── monitor/ │ │ │ └── init.go │ │ ├── parse.go │ │ ├── process.go │ │ ├── process_test.go │ │ └── testdata/ │ │ └── proc-environ │ ├── rule/ │ │ ├── loader.go │ │ ├── loader_test.go │ │ ├── operator.go │ │ ├── operator_aliases.go │ │ ├── operator_lists.go │ │ ├── operator_test.go │ │ ├── rule.go │ │ ├── rule_test.go │ │ └── testdata/ │ │ ├── 000-allow-chrome.json │ │ ├── 001-deny-chrome.json │ │ ├── invalid-regexp-list.json │ │ ├── invalid-regexp.json │ │ ├── lists/ │ │ │ ├── domains/ │ │ │ │ └── domainlists.txt │ │ │ ├── ips/ │ │ │ │ └── ips.txt │ │ │ ├── nets/ │ │ │ │ └── nets.txt │ │ │ └── regexp/ │ │ │ └── domainsregexp.txt │ │ ├── live_reload/ │ │ │ ├── test-live-reload-delete.json │ │ │ └── test-live-reload-remove.json │ │ ├── rule-disabled-operator-list-expanded.json │ │ ├── rule-disabled-operator-list.json │ │ ├── rule-operator-list-data-empty.json │ │ └── rule-operator-list.json │ ├── statistics/ │ │ ├── event.go │ │ └── stats.go │ ├── tasks/ │ │ ├── base/ │ │ │ └── main.go │ │ ├── config/ │ │ │ ├── main.go │ │ │ ├── monitor.go │ │ │ └── utils.go │ │ ├── doc.go │ │ ├── downloader/ │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── downloader.go │ │ │ ├── main.go │ │ │ └── utils.go │ │ ├── iocscanner/ │ │ │ ├── README.md │ │ │ ├── config/ │ │ │ │ └── config.go │ │ │ ├── main.go │ │ │ ├── run_tools.go │ │ │ └── tools/ │ │ │ ├── base/ │ │ │ │ └── base.go │ │ │ ├── dpkg/ │ │ │ │ └── dpkg.go │ │ │ ├── executer/ │ │ │ │ └── executer.go │ │ │ ├── generic/ │ │ │ │ └── generic.go │ │ │ └── yara/ │ │ │ └── yara.go │ │ ├── load.go │ │ ├── looptask/ │ │ │ └── main.go │ │ ├── main.go │ │ ├── main_test.go │ │ ├── nodemonitor/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── pidmonitor/ │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── scheduler/ │ │ │ ├── daily.go │ │ │ └── scheduler.go │ │ └── socketsmonitor/ │ │ ├── dump.go │ │ ├── main.go │ │ └── options.go │ └── ui/ │ ├── alerts.go │ ├── auth/ │ │ └── auth.go │ ├── client.go │ ├── client_test.go │ ├── config/ │ │ └── config.go │ ├── config_utils.go │ ├── notifications.go │ ├── notifications_tasks.go │ ├── protocol/ │ │ └── .gitkeep │ └── testdata/ │ ├── config-invalid-procmon.json │ ├── default-config.json │ └── default-config.json.orig ├── ebpf_prog/ │ ├── Makefile │ ├── README │ ├── arm-clang-asm-fix.patch │ ├── bpf_headers/ │ │ ├── bpf_core_read.h │ │ ├── bpf_helper_defs.h │ │ ├── bpf_helpers.h │ │ └── bpf_tracing.h │ ├── common.h │ ├── common_defs.h │ ├── opensnitch-dns.c │ ├── opensnitch-procs.c │ └── opensnitch.c ├── proto/ │ ├── .gitignore │ ├── Makefile │ └── ui.proto ├── release.sh ├── ui/ │ ├── .gitignore │ ├── LICENSE │ ├── MANIFEST.in │ ├── Makefile │ ├── bin/ │ │ └── opensnitch-ui │ ├── i18n/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── generate_i18n.sh │ │ ├── locales/ │ │ │ ├── ar/ │ │ │ │ └── opensnitch-ar.ts │ │ │ ├── cs_CZ/ │ │ │ │ └── opensnitch-cs_CZ.ts │ │ │ ├── de_DE/ │ │ │ │ └── opensnitch-de_DE.ts │ │ │ ├── es_ES/ │ │ │ │ └── opensnitch-es_ES.ts │ │ │ ├── eu_ES/ │ │ │ │ └── opensnitch-eu_ES.ts │ │ │ ├── fi_FI/ │ │ │ │ └── opensnitch-fi_FI.ts │ │ │ ├── fr_FR/ │ │ │ │ └── opensnitch-fr_FR.ts │ │ │ ├── he_IL/ │ │ │ │ └── opensnitch-he_IL.ts │ │ │ ├── hi_IN/ │ │ │ │ └── opensnitch-hi_IN.ts │ │ │ ├── hu_HU/ │ │ │ │ └── opensnitch-hu_HU.ts │ │ │ ├── id_ID/ │ │ │ │ └── opensnitch-id_ID.ts │ │ │ ├── it_IT/ │ │ │ │ └── opensnitch-it_IT.ts │ │ │ ├── ja_JP/ │ │ │ │ └── opensnitch-ja_JP.ts │ │ │ ├── lt_LT/ │ │ │ │ └── opensnitch-lt_LT.ts │ │ │ ├── nb_NO/ │ │ │ │ └── opensnitch-nb_NO.ts │ │ │ ├── nl_NL/ │ │ │ │ └── opensnitch-nl_NL.ts │ │ │ ├── pt_BR/ │ │ │ │ └── opensnitch-pt_BR.ts │ │ │ ├── ro_RO/ │ │ │ │ └── opensnitch-ro_RO.ts │ │ │ ├── ru_RU/ │ │ │ │ └── opensnitch-ru_RU.ts │ │ │ ├── sq_AL/ │ │ │ │ └── opensnitch-sq_AL.ts │ │ │ ├── sv_SE/ │ │ │ │ └── opensnitch-sv_SE.ts │ │ │ ├── tr_TR/ │ │ │ │ └── opensnitch-tr_TR.ts │ │ │ ├── uk_UA/ │ │ │ │ └── opensnitch-uk_UA.ts │ │ │ ├── zh_Hans/ │ │ │ │ └── opensnitch-zh_Hans.ts │ │ │ └── zh_TW/ │ │ │ └── opensnitch-zh_TW.ts │ │ └── opensnitch_i18n.pro │ ├── opensnitch/ │ │ ├── __init__.py │ │ ├── actions/ │ │ │ ├── __init__.py │ │ │ ├── default_configs.py │ │ │ ├── enums.py │ │ │ └── utils.py │ │ ├── auth/ │ │ │ └── __init__.py │ │ ├── config.py │ │ ├── customwidgets/ │ │ │ ├── __init__.py │ │ │ ├── addresstablemodel.py │ │ │ ├── colorizeddelegate.py │ │ │ ├── completer.py │ │ │ ├── firewalltableview.py │ │ │ ├── generictableview.py │ │ │ ├── itemwidgetcentered.py │ │ │ ├── main.py │ │ │ ├── netstattablemodel.py │ │ │ └── updownbtndelegate.py │ │ ├── database/ │ │ │ ├── __init__.py │ │ │ ├── enums.py │ │ │ └── migrations/ │ │ │ ├── upgrade_1.sql │ │ │ ├── upgrade_2.sql │ │ │ └── upgrade_3.sql │ │ ├── desktop_parser.py │ │ ├── dialogs/ │ │ │ ├── __init__.py │ │ │ ├── conndetails.py │ │ │ ├── events/ │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ ├── config.py │ │ │ │ ├── constants.py │ │ │ │ ├── dialog.py │ │ │ │ ├── menu_actions.py │ │ │ │ ├── menus.py │ │ │ │ ├── nodes.py │ │ │ │ ├── queries.py │ │ │ │ ├── tasks/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── netstat.py │ │ │ │ │ └── nodemon.py │ │ │ │ └── views.py │ │ │ ├── firewall.py │ │ │ ├── firewall_rule/ │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ ├── dialog.py │ │ │ │ ├── notifications.py │ │ │ │ ├── rules.py │ │ │ │ ├── statements.py │ │ │ │ └── utils.py │ │ │ ├── preferences/ │ │ │ │ ├── __init__.py │ │ │ │ ├── dialog.py │ │ │ │ ├── sections/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ ├── nodes.py │ │ │ │ │ └── ui.py │ │ │ │ ├── settings.py │ │ │ │ ├── signals.py │ │ │ │ └── utils.py │ │ │ ├── processdetails.py │ │ │ ├── prompt/ │ │ │ │ ├── __init__.py │ │ │ │ ├── checksums.py │ │ │ │ ├── constants.py │ │ │ │ ├── details.py │ │ │ │ ├── dialog.py │ │ │ │ └── utils.py │ │ │ └── ruleseditor/ │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── dialog.py │ │ │ ├── nodes.py │ │ │ ├── rules.py │ │ │ ├── signals.py │ │ │ └── utils.py │ │ ├── firewall/ │ │ │ ├── __init__.py │ │ │ ├── chains.py │ │ │ ├── enums.py │ │ │ ├── exprs.py │ │ │ ├── profiles.py │ │ │ ├── rules.py │ │ │ └── utils.py │ │ ├── nodes.py │ │ ├── notifications.py │ │ ├── plugins/ │ │ │ ├── __init__.py │ │ │ ├── downloader/ │ │ │ │ ├── __init__.py │ │ │ │ ├── _gui.py │ │ │ │ ├── downloader.py │ │ │ │ └── example/ │ │ │ │ └── downloaders.json │ │ │ ├── highlight/ │ │ │ │ ├── __init__.py │ │ │ │ ├── example/ │ │ │ │ │ ├── commonActionsDelegate.json │ │ │ │ │ └── rulesActionsDelegate.json │ │ │ │ └── highlight.py │ │ │ ├── sample/ │ │ │ │ ├── __init__.py │ │ │ │ └── sample.py │ │ │ ├── versionchecker/ │ │ │ │ ├── __init__.py │ │ │ │ ├── versionchecker.json │ │ │ │ └── versionchecker.py │ │ │ └── virustotal/ │ │ │ ├── __init__.py │ │ │ ├── _models.py │ │ │ ├── _popups.py │ │ │ ├── _procdialog.py │ │ │ ├── _utils.py │ │ │ ├── example/ │ │ │ │ └── virustotal.json │ │ │ └── virustotal.py │ │ ├── proto/ │ │ │ ├── __init__.py │ │ │ ├── enums.py │ │ │ ├── pre3200/ │ │ │ │ ├── ui_pb2.py │ │ │ │ └── ui_pb2_grpc.py │ │ │ ├── ui_pb2.py │ │ │ └── ui_pb2_grpc.py │ │ ├── res/ │ │ │ ├── __init__.py │ │ │ ├── firewall.ui │ │ │ ├── firewall_rule.ui │ │ │ ├── preferences.ui │ │ │ ├── process_details.ui │ │ │ ├── prompt.ui │ │ │ ├── resources.qrc │ │ │ ├── ruleseditor.ui │ │ │ ├── stats.ui │ │ │ └── themes/ │ │ │ └── dark/ │ │ │ └── icons/ │ │ │ └── LICENSE │ │ ├── rules.py │ │ ├── service.py │ │ ├── themes/ │ │ │ ├── README.md │ │ │ └── dark_white.xml │ │ ├── utils/ │ │ │ ├── __init__.py │ │ │ ├── duration/ │ │ │ │ ├── __init__.py │ │ │ │ └── duration.py │ │ │ ├── infowindow.py │ │ │ ├── languages.py │ │ │ ├── logger/ │ │ │ │ ├── __init__.py │ │ │ │ └── logger.py │ │ │ ├── network_aliases/ │ │ │ │ ├── __init__.py │ │ │ │ ├── network_aliases.json │ │ │ │ └── network_aliases.py │ │ │ ├── qvalidator.py │ │ │ ├── sockets.py │ │ │ ├── themes/ │ │ │ │ ├── __init__.py │ │ │ │ └── themes.py │ │ │ └── xdg.py │ │ └── version.py │ ├── requirements.txt │ ├── resources/ │ │ ├── io.github.evilsocket.opensnitch.appdata.xml │ │ ├── kcm_opensnitch.desktop │ │ └── opensnitch_ui.desktop │ ├── setup.py │ └── tests/ │ ├── README.md │ ├── __init__.py │ ├── conftest.py │ ├── dialogs/ │ │ ├── __init__.py │ │ ├── test_preferences.py │ │ └── test_ruleseditor.py │ └── test_nodes.py └── utils/ ├── legacy/ │ └── make_ads_rules.py ├── packaging/ │ ├── build_modules.sh │ ├── daemon/ │ │ ├── deb/ │ │ │ └── debian/ │ │ │ ├── NEWS │ │ │ ├── changelog │ │ │ ├── control │ │ │ ├── copyright │ │ │ ├── gbp.conf │ │ │ ├── gitlab-ci.yml │ │ │ ├── opensnitch.init │ │ │ ├── opensnitch.install │ │ │ ├── opensnitch.logrotate │ │ │ ├── opensnitch.service │ │ │ ├── rules │ │ │ ├── source/ │ │ │ │ └── format │ │ │ └── watch │ │ └── rpm/ │ │ └── opensnitch.spec │ └── ui/ │ ├── deb/ │ │ └── debian/ │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ ├── copyright │ │ ├── postinst │ │ ├── postrm │ │ ├── rules │ │ └── source/ │ │ ├── format │ │ └── options │ └── rpm/ │ └── opensnitch-ui.spec └── scripts/ ├── ads/ │ └── update_adlists.sh ├── debug-ebpf-maps.sh ├── ipasn_db_sync.sh ├── ipasn_db_update.sh └── restart-opensnitch-onsleep.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: gustavo-iniguez-goya patreon: # Replace with a single patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: 🐞 Bug report about: Create a report to help us improve title: '[Bug Report]
Join the project community on our server!
OpenSnitch is a GNU/Linux application firewall.
•• Key Features • Download • Installation • Usage examples • In the press ••
Quick help
" \ "- Use CTRL+c to copy selected rows.
" \ "- Use Home,End,PgUp,PgDown,PgUp,Up or Down keys to navigate rows.
" \ "- Use right click on a row to stop refreshing the view.
" \ "- Selecting more than one row also stops refreshing the view.
" "- On the Events view, clicking on columns Node, Process or Rule
" \
"jumps to the view of the selected item.
- On the rest of the views, double click on a row to get detailed
" \
" information.
For more information visit the wiki
" \ "daemon uptime: {0}
".format(col_uptime) + \ "Version: {0}
".format(col_version) + \ "Kernel: {0}
".format(col_kernel) ) ) def unmonitor_deselected_node(self, last_addr): if not self.win._nodes.is_connected(last_addr): self.reset_node_info(QC.translate("stats", "node not connected")) else: noti = ui_pb2.Notification( clientName="", serverName="", type=ui_pb2.TASK_STOP, data='{"name": "%s", "data": {"node": "%s", "interval": "5s"}}' % (TASK_NAME, last_addr), rules=[]) nid = self.win.send_notification( last_addr, noti, self.win._notification_callback ) if nid is not None: self.win.save_ntf(nid, noti) self.win.labelNodeDetails.setText("") # XXX: would be useful to leave latest data? #self.reset_node_info() # create plugins and actions before dialogs def update_node_info(self, data): try: # TODO: move to .utils def formatUptime(uptime): hours = uptime / 3600 minutes = uptime % 3600 / 60 #seconds = uptime % 60 days = (uptime / 1440) / 60 months = 0 years = 0 if days > 0: hours = hours % 24 minutes = (uptime % 3600) / 60 if days > 0: uptime = "{0:.0f} days {1:.0f}h {2:.0f}m".format(days, hours, minutes) else: uptime = "{0:.0f}h {1:.0f}m".format(hours, minutes) return QC.translate( "stats", "System uptime: %s" % uptime ) # TODO: move to .utils def bytes2units(value): units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] idx = 0 while value / 1024 > 0: value = value / 1024 idx+=1 if value < 1024: break return "{0:.0f} {1}".format(value, units [idx]) node_data = json.loads(data) load1 = node_data['Loads'][0] / 100000 totalRam = node_data['Totalram'] totalSwap = node_data['Totalswap'] freeRam = totalRam - node_data['Freeram'] freeSwap = totalSwap - node_data['Freeswap'] self.win.nodeRAMProgress.setMaximum(int(totalRam/1000)) self.win.nodeRAMProgress.setValue(int(freeRam/1000)) self.win.nodeRAMProgress.setFormat("%p%") self.win.nodeSwapProgress.setMaximum(int(totalSwap/1000)) self.win.nodeSwapProgress.setFormat("%p%") self.win.nodeSwapProgress.setValue(int(freeSwap/1000)) # if any of these values is 0, set max progressbar value to 1, to # avoid the "busy" effect: # https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QProgressBar.html#detailed-description if self.win.nodeRAMProgress.value() == 0: self.win.nodeRAMProgress.setMaximum(1) if self.win.nodeSwapProgress.value() == 0: self.win.nodeSwapProgress.setMaximum(1) ram = bytes2units(totalRam) free = bytes2units(node_data['Freeram']) swap = bytes2units(totalSwap) freeSwap = bytes2units(node_data['Freeswap']) self.win.labelNodeRAM.setText("RAM: {0} Free: {1}".format(ram, free)) self.win.labelNodeSwap.setText("Swap: {0} Free: {1}".format(swap, freeSwap)) self.win.labelNodeProcs.setText( QC.translate("stats", "Processes: {0}".format(node_data['Procs'])) ) self.win.nodeRAMProgress.setFormat("%p%") self.win.nodeSwapProgress.setFormat("%p%") self.win.labelNodeLoadAvg.setText( QC.translate( "stats", "Load avg: {0:.2f}, {1:.2f}, {2:.2f}".format( node_data['Loads'][0] / 100000, node_data['Loads'][1] / 100000, node_data['Loads'][2] / 100000 ) ) ) self.win.labelNodeUptime.setText(formatUptime(node_data['Uptime'])) except Exception as e: print("exception parsing taskStart data:", e, data) # TODO: update nodes tab ================================================ FILE: ui/opensnitch/dialogs/events/views.py ================================================ import csv import io import os import threading import datetime from PyQt6 import QtCore, QtWidgets from PyQt6.QtCore import QCoreApplication as QC from opensnitch.customwidgets.colorizeddelegate import ColorizedDelegate from opensnitch.utils import ( Message ) from opensnitch.config import Config from .tasks import ( nodemon ) from . import ( base, constants, config, nodes, queries ) class ViewsManager(config.ConfigManager, nodes.NodesManager, base.EventsBase): def __init__(self, parent): super(ViewsManager, self).__init__(parent) self._lock = threading.RLock() self.cfg = Config.get() self.node_mon = nodemon.Nodemon(self) self.queries = queries.Queries(self) self._last_update = datetime.datetime.now() self.TABLES = self.default_views_config() # restore scrollbar position when going back from a detail view self.LAST_SCROLL_VALUE = None # try to restore last selections self.LAST_SELECTED_ITEM = "" self.LAST_TAB = 0 self.LAST_NETSTAT_NODE = None # if the user clicks on an item of a table, it'll enter into the detail # view. From there, deny further clicks on the items. self.IN_DETAIL_VIEW = { constants.TAB_MAIN: False, constants.TAB_NODES: False, constants.TAB_RULES: False, constants.TAB_HOSTS: False, constants.TAB_PROCS: False, constants.TAB_ADDRS: False, constants.TAB_PORTS: False, constants.TAB_USERS: False, constants.TAB_NETSTAT: False, constants.TAB_FIREWALL: False, constants.TAB_ALERTS: False } # used to skip updates while the user is moving the scrollbar self.scrollbar_active = False # skip table updates if a contextual menu is active self._context_menu_active = False def view_setup( self, tableWidget, table_name, fields="*", group_by="", order_by="2", sort_direction=constants.SORT_ORDER[constants.SORT_DESC], limit="", resize_cols=(), model=None, delegate=None, verticalScrollBar=None, tracking_column=constants.COL_TIME, widget=QtWidgets.QTableView ): tableWidget.setSortingEnabled(True) if model is None: model = self._db.get_new_qsql_model() if verticalScrollBar is not None: tableWidget.setVerticalScrollBar(verticalScrollBar) tableWidget.verticalScrollBar().sliderPressed.connect(self.cb_scrollbar_pressed) tableWidget.verticalScrollBar().sliderReleased.connect(self.cb_scrollbar_released) tableWidget.setTrackingColumn(tracking_column) # "SELECT " + fields + " FROM " + table_name + group_by + " ORDER BY " + order_by + " " + sort_direction + limit) self.queries.setQuery( model, f"SELECT {fields} FROM {table_name}{group_by} ORDER BY {order_by} {sort_direction}{limit}", limit=self.get_query_limit() ) tableWidget.setModel(model) if delegate is not None: # configure the personalized delegate from actions, if any action = self._actions.get(delegate) if action is not None: tableWidget.setItemDelegate(ColorizedDelegate(tableWidget, actions=action)) header = tableWidget.horizontalHeader() if header is not None: header.sortIndicatorChanged.connect(self._cb_table_header_clicked) header.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu) header.customContextMenuRequested.connect(self.configure_header_contextual_menu) for _, col in enumerate(resize_cols): header.setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) header.setSectionsMovable(True) cur_idx = self.get_current_view_idx() self.cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx), header.saveState()) return tableWidget # ignore updates while the user is using the scrollbar. def cb_scrollbar_pressed(self): self.set_scrollbar_active(True) def cb_scrollbar_released(self): self.set_scrollbar_active(False) def reset_statusbar(self): self.daemonVerLabel.setText("") self.uptimeLabel.setText("") self.rulesLabel.setText("") self.consLabel.setText("") self.droppedLabel.setText("") def needs_refresh(self): diff = datetime.datetime.now() - self._last_update if diff.seconds < self._ui_refresh_interval: return False return True def in_detail_view(self, idx): return self.IN_DETAIL_VIEW[idx] def set_in_detail_view(self, idx, state): self.IN_DETAIL_VIEW[idx] = state def set_last_selected_item(self, what): self.LAST_SELECTED_ITEM = what def get_view_context_menu(self, idx): return self.TABLES[idx]['context_menu'] def set_view_context_menu(self, idx, menu): self.TABLES[idx]['context_menu'] = menu def set_scrollbar_active(self, state): self.scrollbar_active = state def is_scrollbar_active(self): return self.scrollbar_active def set_context_menu_active(self, state): self._context_menu_active = state def is_context_menu_active(self): return self._context_menu_active def get_view_limit(self): limit = constants.LIMITS[self.limitCombo.currentIndex()] if limit == "": return "" return " " + limit def get_query_limit(self): limit = 0 if self.limitCombo.currentText() != "": limit = int(self.limitCombo.currentText()) return limit def get_view_order(self, field=None): cur_idx = self.get_current_view_idx() order_field = self.TABLES[cur_idx]['last_order_by'] if field is not None: order_field = field return " ORDER BY %s %s" % (order_field, constants.SORT_ORDER[self.TABLES[cur_idx]['last_order_to']]) def get_view_config(self, idx): return self.TABLES[idx] def set_view_config(self, idx, config): self.TABLES[idx] = config def get_view_name(self, idx): return self.TABLES[idx]['name'] def get_view(self, idx): return self.TABLES[idx]['view'] def set_view(self, idx, view): self.TABLES[idx]['view'] = view def update_interception_status(self, enabled): self.startButton.setDown(enabled) self.startButton.setChecked(enabled) if enabled: self._update_status_label(running=True, text=self.FIREWALL_RUNNING) else: self._update_status_label(running=False, text=self.FIREWALL_DISABLED) def clear_rows_selection(self): cur_idx = self.get_current_view_idx() self.TABLES[cur_idx]['view'].clearSelection() def are_rows_selected(self): cur_idx = self.get_current_view_idx() view = self.TABLES[cur_idx]['view'] ret = False if view is not None: ret = len(view.selectionModel().selectedRows(0)) > 0 return ret def set_filter_line_color(self, text): if text == "": self.filterLine.setStyleSheet('') else: self.filterLine.setStyleSheet('background-color: #55ff7f') # https://stackoverflow.com/questions/40225270/copy-paste-multiple-items-from-qtableview-in-pyqt4 def copy_selected_rows(self): cur_idx = self.get_current_view_idx() if self.get_current_view_idx() == constants.TAB_RULES and self.fwTable.isVisible(): cur_idx = constants.TAB_FIREWALL elif self.get_current_view_idx() == constants.TAB_RULES and not self.fwTable.isVisible(): cur_idx = constants.TAB_RULES selection = self.TABLES[cur_idx]['view'].selectedRows() if selection: stream = io.StringIO() csv.writer(stream, delimiter=',').writerows(selection) QtWidgets.QApplication.clipboard().setText(stream.getvalue()) stream.close() stream = None selection = None # must be called after setModel() or setQuery() def show_columns(self): hideNodeCol = self.nodes_count() < 2 self.eventsTable.setColumnHidden(constants.COL_NODE, hideNodeCol) self.rulesTable.setColumnHidden(constants.COL_R_NODE, hideNodeCol) for idx in range(len(self.TABLES)): self.show_view_columns(idx) def show_view_columns(self, idx): tbl_name = self.TABLES[idx]['name'] view = self.TABLES[idx]['view'] cols_num = len(view.model().headers()) cols = self.cfg.getSettings(Config.STATS_SHOW_COLUMNS + f"_{tbl_name}") if cols is not None: for c in range(cols_num): view.setColumnHidden(c, str(c) not in cols) def on_filter_line_changed(self, text): cur_idx = self.get_current_view_idx() model = self.TABLES[cur_idx]['view'].model() self.set_filter_line_color(text) if text == "" and not self.in_detail_view(cur_idx): qstr = self.queries.get_view_query(model, cur_idx) self.queries.setQuery(model, qstr, limit=self.get_query_limit()) return adv_filter = self.queries.advanced_search(text) qstr = None if cur_idx == constants.TAB_MAIN: self.cfg.setSettings(Config.STATS_FILTER_TEXT, text) self.queries.set_events_query(adv_filter) return elif cur_idx == constants.TAB_NODES: qstr = self.queries.get_nodes_filter( self.in_detail_view(constants.TAB_NODES), model.query().lastQuery(), text, adv_filter ) elif cur_idx == constants.TAB_RULES and self.fwTable.isVisible(): self.TABLES[constants.TAB_FIREWALL]['view'].filterByQuery(text) return elif self.in_detail_view(cur_idx): qstr = self.queries.get_indetail_filter( self.in_detail_view(cur_idx), model.query().lastQuery(), text, adv_filter) else: where_clause = self.queries.get_filter_line(cur_idx, text, adv_filter) qstr = self.queries.get_view_query(model, cur_idx, where_clause) if qstr is not None: self.queries.setQuery(model, qstr, limit=self.get_query_limit()) def on_splitter_moved(self, tab, pos, index): if tab == constants.TAB_RULES: self.comboRulesFilter.setVisible(pos == 0) self.cfg.setSettings(Config.STATS_RULES_SPLITTER_POS, self.rulesSplitter.saveState()) elif tab == constants.TAB_NODES: #w = self.nodesSplitter.width() #if pos >= w-2: # self._unmonitor_deselected_node() self.cfg.setSettings(Config.STATS_NODES_SPLITTER_POS, self.nodesSplitter.saveState()) def on_table_clicked(self, idx): cur_idx = self.get_current_view_idx() if cur_idx != constants.TAB_NODES: return try: row = idx.row() model = idx.model() addr = model.index(row, constants.COL_NODE).data() uptime = model.index(row, constants.COL_N_UPTIME).data() host = model.index(row, constants.COL_N_HOSTNAME).data() node_version = model.index(row, constants.COL_N_VERSION).data() kernel = model.index(row, constants.COL_N_KERNEL).data() unmonitor = self.LAST_SELECTED_ITEM == addr or (self.LAST_SELECTED_ITEM != addr and self.LAST_SELECTED_ITEM != "") monitor = self.LAST_SELECTED_ITEM == "" or self.LAST_SELECTED_ITEM != addr if unmonitor: self.node_mon.unmonitor_deselected_node(self.LAST_SELECTED_ITEM) if monitor: self.node_mon.monitor_selected_node( addr, uptime, host, node_version, kernel ) if monitor: self.LAST_SELECTED_ITEM = addr else: self.LAST_SELECTED_ITEM = "" except Exception as e: print("[stats] exception monitoring node:", e) def on_table_header_clicked(self, pos, sortOrder): cur_idx = self.get_current_view_idx() # TODO: allow ordering by Network column if cur_idx == constants.TAB_ADDRS and pos == 2: return model = self.get_active_table().model() qstr = model.query().lastQuery().split("ORDER BY")[0] q = qstr.strip(" ") + " ORDER BY %d %s" % (pos+1, constants.SORT_ORDER[sortOrder.value]) if cur_idx > 0 and self.TABLES[cur_idx]['cmd'].isVisible() is False: self.TABLES[cur_idx]['last_order_by'] = pos+1 self.TABLES[cur_idx]['last_order_to'] = sortOrder.value q = qstr.strip(" ") + self.get_view_order() q += self.get_view_limit() self.queries.setQuery(model, q, limit=self.get_query_limit()) header = self.get_active_table().horizontalHeader() sort_order = QtCore.Qt.SortOrder.DescendingOrder if sortOrder.value == constants.SORT_DESC else QtCore.Qt.SortOrder.AscendingOrder header.setSortIndicator(pos, sort_order) def on_menu_export_csv_clicked(self, tab_idx): tbl_name = self.get_view_name(tab_idx) filename = QtWidgets.QFileDialog.getSaveFileName( self, QC.translate("stats", 'Save as CSV'), tbl_name + ".csv", 'All Files (*);;CSV Files (*.csv)')[0].strip() if not filename: return file_dir = os.path.dirname(filename) if not os.path.exists(file_dir): Message.ok( QC.translate("preferences", "Warning"), QC.translate("preferences", "Invalid file selected: