souleyez 2.43.29__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of souleyez might be problematic. Click here for more details.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9564 -2881
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +564 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +409 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +417 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +913 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +219 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +237 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23034 -10679
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Team collaboration features for deliverables.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from typing import Dict, List, Optional
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from .database import get_db
|
|
@@ -9,66 +10,69 @@ import os
|
|
|
9
10
|
|
|
10
11
|
class TeamCollaboration:
|
|
11
12
|
"""Manage team collaboration on deliverables."""
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
def __init__(self):
|
|
14
15
|
self.db = get_db()
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
def _get_current_user(self) -> str:
|
|
17
18
|
"""Get current username from environment or system."""
|
|
18
19
|
# Try environment variable first
|
|
19
|
-
user = os.environ.get(
|
|
20
|
+
user = os.environ.get("USER") or os.environ.get("USERNAME") or "unknown"
|
|
20
21
|
return user
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
def log_activity(
|
|
23
24
|
self,
|
|
24
25
|
deliverable_id: int,
|
|
25
26
|
engagement_id: int,
|
|
26
27
|
action: str,
|
|
27
28
|
details: str = None,
|
|
28
|
-
user: str = None
|
|
29
|
+
user: str = None,
|
|
29
30
|
) -> int:
|
|
30
31
|
"""
|
|
31
32
|
Log activity on a deliverable.
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
Args:
|
|
34
35
|
deliverable_id: Deliverable ID
|
|
35
36
|
engagement_id: Engagement ID
|
|
36
37
|
action: Action type (started, completed, updated, assigned, etc.)
|
|
37
38
|
details: Additional details
|
|
38
39
|
user: Username (defaults to current user)
|
|
39
|
-
|
|
40
|
+
|
|
40
41
|
Returns:
|
|
41
42
|
Activity log ID
|
|
42
43
|
"""
|
|
43
44
|
if user is None:
|
|
44
45
|
user = self._get_current_user()
|
|
45
|
-
|
|
46
|
-
activity_id = self.db.insert(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
|
|
47
|
+
activity_id = self.db.insert(
|
|
48
|
+
"deliverable_activity",
|
|
49
|
+
{
|
|
50
|
+
"deliverable_id": deliverable_id,
|
|
51
|
+
"engagement_id": engagement_id,
|
|
52
|
+
"user": user,
|
|
53
|
+
"action": action,
|
|
54
|
+
"details": details,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
54
58
|
return activity_id
|
|
55
|
-
|
|
59
|
+
|
|
56
60
|
def get_activity(
|
|
57
61
|
self,
|
|
58
62
|
deliverable_id: int = None,
|
|
59
63
|
engagement_id: int = None,
|
|
60
64
|
user: str = None,
|
|
61
|
-
limit: int = 50
|
|
65
|
+
limit: int = 50,
|
|
62
66
|
) -> List[Dict]:
|
|
63
67
|
"""
|
|
64
68
|
Get activity log.
|
|
65
|
-
|
|
69
|
+
|
|
66
70
|
Args:
|
|
67
71
|
deliverable_id: Filter by deliverable
|
|
68
72
|
engagement_id: Filter by engagement
|
|
69
73
|
user: Filter by user
|
|
70
74
|
limit: Max results
|
|
71
|
-
|
|
75
|
+
|
|
72
76
|
Returns:
|
|
73
77
|
List of activity records
|
|
74
78
|
"""
|
|
@@ -82,59 +86,53 @@ class TeamCollaboration:
|
|
|
82
86
|
WHERE 1=1
|
|
83
87
|
"""
|
|
84
88
|
params = []
|
|
85
|
-
|
|
89
|
+
|
|
86
90
|
if deliverable_id:
|
|
87
91
|
query += " AND da.deliverable_id = ?"
|
|
88
92
|
params.append(deliverable_id)
|
|
89
|
-
|
|
93
|
+
|
|
90
94
|
if engagement_id:
|
|
91
95
|
query += " AND da.engagement_id = ?"
|
|
92
96
|
params.append(engagement_id)
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
if user:
|
|
95
99
|
query += " AND da.user = ?"
|
|
96
100
|
params.append(user)
|
|
97
|
-
|
|
101
|
+
|
|
98
102
|
query += " ORDER BY da.created_at DESC LIMIT ?"
|
|
99
103
|
params.append(limit)
|
|
100
|
-
|
|
104
|
+
|
|
101
105
|
return self.db.execute(query, tuple(params))
|
|
102
|
-
|
|
103
|
-
def add_comment(
|
|
104
|
-
self,
|
|
105
|
-
deliverable_id: int,
|
|
106
|
-
comment: str,
|
|
107
|
-
user: str = None
|
|
108
|
-
) -> int:
|
|
106
|
+
|
|
107
|
+
def add_comment(self, deliverable_id: int, comment: str, user: str = None) -> int:
|
|
109
108
|
"""
|
|
110
109
|
Add comment to deliverable.
|
|
111
|
-
|
|
110
|
+
|
|
112
111
|
Args:
|
|
113
112
|
deliverable_id: Deliverable ID
|
|
114
113
|
comment: Comment text
|
|
115
114
|
user: Username (defaults to current user)
|
|
116
|
-
|
|
115
|
+
|
|
117
116
|
Returns:
|
|
118
117
|
Comment ID
|
|
119
118
|
"""
|
|
120
119
|
if user is None:
|
|
121
120
|
user = self._get_current_user()
|
|
122
|
-
|
|
123
|
-
comment_id = self.db.insert(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
|
|
122
|
+
comment_id = self.db.insert(
|
|
123
|
+
"deliverable_comments",
|
|
124
|
+
{"deliverable_id": deliverable_id, "user": user, "comment": comment},
|
|
125
|
+
)
|
|
126
|
+
|
|
129
127
|
return comment_id
|
|
130
|
-
|
|
128
|
+
|
|
131
129
|
def get_comments(self, deliverable_id: int) -> List[Dict]:
|
|
132
130
|
"""
|
|
133
131
|
Get comments for deliverable.
|
|
134
|
-
|
|
132
|
+
|
|
135
133
|
Args:
|
|
136
134
|
deliverable_id: Deliverable ID
|
|
137
|
-
|
|
135
|
+
|
|
138
136
|
Returns:
|
|
139
137
|
List of comments
|
|
140
138
|
"""
|
|
@@ -144,120 +142,116 @@ class TeamCollaboration:
|
|
|
144
142
|
WHERE deliverable_id = ?
|
|
145
143
|
ORDER BY created_at ASC
|
|
146
144
|
""",
|
|
147
|
-
(deliverable_id,)
|
|
145
|
+
(deliverable_id,),
|
|
148
146
|
)
|
|
149
|
-
|
|
147
|
+
|
|
150
148
|
def delete_comment(self, comment_id: int, user: str = None) -> bool:
|
|
151
149
|
"""
|
|
152
150
|
Delete comment (only by original author).
|
|
153
|
-
|
|
151
|
+
|
|
154
152
|
Args:
|
|
155
153
|
comment_id: Comment ID
|
|
156
154
|
user: Username (defaults to current user)
|
|
157
|
-
|
|
155
|
+
|
|
158
156
|
Returns:
|
|
159
157
|
True if deleted
|
|
160
158
|
"""
|
|
161
159
|
if user is None:
|
|
162
160
|
user = self._get_current_user()
|
|
163
|
-
|
|
161
|
+
|
|
164
162
|
# Verify ownership
|
|
165
163
|
comment = self.db.execute_one(
|
|
166
|
-
"SELECT user FROM deliverable_comments WHERE id = ?",
|
|
167
|
-
(comment_id,)
|
|
164
|
+
"SELECT user FROM deliverable_comments WHERE id = ?", (comment_id,)
|
|
168
165
|
)
|
|
169
|
-
|
|
170
|
-
if not comment or comment[
|
|
166
|
+
|
|
167
|
+
if not comment or comment["user"] != user:
|
|
171
168
|
return False
|
|
172
|
-
|
|
173
|
-
self.db.execute(
|
|
174
|
-
|
|
175
|
-
(comment_id,)
|
|
176
|
-
)
|
|
177
|
-
|
|
169
|
+
|
|
170
|
+
self.db.execute("DELETE FROM deliverable_comments WHERE id = ?", (comment_id,))
|
|
171
|
+
|
|
178
172
|
return True
|
|
179
|
-
|
|
173
|
+
|
|
180
174
|
def assign_deliverable(
|
|
181
175
|
self,
|
|
182
176
|
deliverable_id: int,
|
|
183
177
|
engagement_id: int,
|
|
184
178
|
assigned_to: str,
|
|
185
|
-
assigned_by: str = None
|
|
179
|
+
assigned_by: str = None,
|
|
186
180
|
) -> bool:
|
|
187
181
|
"""
|
|
188
182
|
Assign deliverable to user.
|
|
189
|
-
|
|
183
|
+
|
|
190
184
|
Args:
|
|
191
185
|
deliverable_id: Deliverable ID
|
|
192
186
|
engagement_id: Engagement ID
|
|
193
187
|
assigned_to: Username to assign to
|
|
194
188
|
assigned_by: Username doing assignment
|
|
195
|
-
|
|
189
|
+
|
|
196
190
|
Returns:
|
|
197
191
|
True if successful
|
|
198
192
|
"""
|
|
199
193
|
if assigned_by is None:
|
|
200
194
|
assigned_by = self._get_current_user()
|
|
201
|
-
|
|
195
|
+
|
|
202
196
|
# Update deliverable
|
|
203
197
|
self.db.execute(
|
|
204
198
|
"UPDATE deliverables SET assigned_to = ? WHERE id = ?",
|
|
205
|
-
(assigned_to, deliverable_id)
|
|
199
|
+
(assigned_to, deliverable_id),
|
|
206
200
|
)
|
|
207
|
-
|
|
201
|
+
|
|
208
202
|
# Log activity
|
|
209
|
-
details =
|
|
203
|
+
details = (
|
|
204
|
+
f"Assigned to {assigned_to}"
|
|
205
|
+
if assigned_by != assigned_to
|
|
206
|
+
else "Self-assigned"
|
|
207
|
+
)
|
|
210
208
|
self.log_activity(
|
|
211
209
|
deliverable_id=deliverable_id,
|
|
212
210
|
engagement_id=engagement_id,
|
|
213
|
-
action=
|
|
211
|
+
action="assigned",
|
|
214
212
|
details=details,
|
|
215
|
-
user=assigned_by
|
|
213
|
+
user=assigned_by,
|
|
216
214
|
)
|
|
217
|
-
|
|
215
|
+
|
|
218
216
|
return True
|
|
219
|
-
|
|
217
|
+
|
|
220
218
|
def unassign_deliverable(
|
|
221
|
-
self,
|
|
222
|
-
deliverable_id: int,
|
|
223
|
-
engagement_id: int,
|
|
224
|
-
user: str = None
|
|
219
|
+
self, deliverable_id: int, engagement_id: int, user: str = None
|
|
225
220
|
) -> bool:
|
|
226
221
|
"""
|
|
227
222
|
Unassign deliverable.
|
|
228
|
-
|
|
223
|
+
|
|
229
224
|
Args:
|
|
230
225
|
deliverable_id: Deliverable ID
|
|
231
226
|
engagement_id: Engagement ID
|
|
232
227
|
user: Username doing unassignment
|
|
233
|
-
|
|
228
|
+
|
|
234
229
|
Returns:
|
|
235
230
|
True if successful
|
|
236
231
|
"""
|
|
237
232
|
if user is None:
|
|
238
233
|
user = self._get_current_user()
|
|
239
|
-
|
|
234
|
+
|
|
240
235
|
# Update deliverable
|
|
241
236
|
self.db.execute(
|
|
242
|
-
"UPDATE deliverables SET assigned_to = NULL WHERE id = ?",
|
|
243
|
-
(deliverable_id,)
|
|
237
|
+
"UPDATE deliverables SET assigned_to = NULL WHERE id = ?", (deliverable_id,)
|
|
244
238
|
)
|
|
245
|
-
|
|
239
|
+
|
|
246
240
|
# Log activity
|
|
247
241
|
self.log_activity(
|
|
248
242
|
deliverable_id=deliverable_id,
|
|
249
243
|
engagement_id=engagement_id,
|
|
250
|
-
action=
|
|
244
|
+
action="unassigned",
|
|
251
245
|
details="Unassigned",
|
|
252
|
-
user=user
|
|
246
|
+
user=user,
|
|
253
247
|
)
|
|
254
|
-
|
|
248
|
+
|
|
255
249
|
return True
|
|
256
|
-
|
|
250
|
+
|
|
257
251
|
def get_team_summary(self, engagement_id: int) -> Dict:
|
|
258
252
|
"""
|
|
259
253
|
Get team activity summary for engagement.
|
|
260
|
-
|
|
254
|
+
|
|
261
255
|
Returns:
|
|
262
256
|
Dict with team statistics
|
|
263
257
|
"""
|
|
@@ -268,16 +262,16 @@ class TeamCollaboration:
|
|
|
268
262
|
FROM deliverable_activity
|
|
269
263
|
WHERE engagement_id = ?
|
|
270
264
|
""",
|
|
271
|
-
(engagement_id,)
|
|
265
|
+
(engagement_id,),
|
|
272
266
|
)
|
|
273
|
-
|
|
274
|
-
user_list = [u[
|
|
275
|
-
|
|
267
|
+
|
|
268
|
+
user_list = [u["user"] for u in users]
|
|
269
|
+
|
|
276
270
|
# Get activity count per user
|
|
277
271
|
user_activity = {}
|
|
278
272
|
for user_row in users:
|
|
279
|
-
user = user_row[
|
|
280
|
-
|
|
273
|
+
user = user_row["user"]
|
|
274
|
+
|
|
281
275
|
# Count activities
|
|
282
276
|
activity_count = self.db.execute_one(
|
|
283
277
|
"""
|
|
@@ -285,9 +279,9 @@ class TeamCollaboration:
|
|
|
285
279
|
FROM deliverable_activity
|
|
286
280
|
WHERE engagement_id = ? AND user = ?
|
|
287
281
|
""",
|
|
288
|
-
(engagement_id, user)
|
|
282
|
+
(engagement_id, user),
|
|
289
283
|
)
|
|
290
|
-
|
|
284
|
+
|
|
291
285
|
# Count assignments
|
|
292
286
|
assigned_count = self.db.execute_one(
|
|
293
287
|
"""
|
|
@@ -295,9 +289,9 @@ class TeamCollaboration:
|
|
|
295
289
|
FROM deliverables
|
|
296
290
|
WHERE engagement_id = ? AND assigned_to = ?
|
|
297
291
|
""",
|
|
298
|
-
(engagement_id, user)
|
|
292
|
+
(engagement_id, user),
|
|
299
293
|
)
|
|
300
|
-
|
|
294
|
+
|
|
301
295
|
# Count completed
|
|
302
296
|
completed_count = self.db.execute_one(
|
|
303
297
|
"""
|
|
@@ -305,25 +299,25 @@ class TeamCollaboration:
|
|
|
305
299
|
FROM deliverables
|
|
306
300
|
WHERE engagement_id = ? AND assigned_to = ? AND status = 'completed'
|
|
307
301
|
""",
|
|
308
|
-
(engagement_id, user)
|
|
302
|
+
(engagement_id, user),
|
|
309
303
|
)
|
|
310
|
-
|
|
304
|
+
|
|
311
305
|
user_activity[user] = {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
306
|
+
"activity_count": activity_count["count"] if activity_count else 0,
|
|
307
|
+
"assigned_count": assigned_count["count"] if assigned_count else 0,
|
|
308
|
+
"completed_count": completed_count["count"] if completed_count else 0,
|
|
315
309
|
}
|
|
316
|
-
|
|
310
|
+
|
|
317
311
|
return {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
312
|
+
"users": user_list,
|
|
313
|
+
"user_activity": user_activity,
|
|
314
|
+
"total_users": len(user_list),
|
|
321
315
|
}
|
|
322
|
-
|
|
316
|
+
|
|
323
317
|
def get_user_workload(self, engagement_id: int) -> List[Dict]:
|
|
324
318
|
"""
|
|
325
319
|
Get workload per user.
|
|
326
|
-
|
|
320
|
+
|
|
327
321
|
Returns:
|
|
328
322
|
List of users with workload stats
|
|
329
323
|
"""
|
|
@@ -341,66 +335,66 @@ class TeamCollaboration:
|
|
|
341
335
|
GROUP BY assigned_to
|
|
342
336
|
ORDER BY total_assigned DESC
|
|
343
337
|
""",
|
|
344
|
-
(engagement_id,)
|
|
338
|
+
(engagement_id,),
|
|
345
339
|
)
|
|
346
|
-
|
|
340
|
+
|
|
347
341
|
return workload
|
|
348
|
-
|
|
342
|
+
|
|
349
343
|
def get_recent_activity_feed(
|
|
350
|
-
self,
|
|
351
|
-
engagement_id: int,
|
|
352
|
-
limit: int = 20
|
|
344
|
+
self, engagement_id: int, limit: int = 20
|
|
353
345
|
) -> List[Dict]:
|
|
354
346
|
"""
|
|
355
347
|
Get recent activity feed with formatted messages.
|
|
356
|
-
|
|
348
|
+
|
|
357
349
|
Returns:
|
|
358
350
|
List of activity items with human-readable messages
|
|
359
351
|
"""
|
|
360
352
|
activities = self.get_activity(engagement_id=engagement_id, limit=limit)
|
|
361
|
-
|
|
353
|
+
|
|
362
354
|
feed = []
|
|
363
355
|
for activity in activities:
|
|
364
356
|
# Format message based on action
|
|
365
|
-
action = activity[
|
|
366
|
-
user = activity[
|
|
367
|
-
title = activity.get(
|
|
368
|
-
details = activity.get(
|
|
369
|
-
created_at = activity[
|
|
370
|
-
|
|
371
|
-
if action ==
|
|
357
|
+
action = activity["action"]
|
|
358
|
+
user = activity["user"]
|
|
359
|
+
title = activity.get("deliverable_title", "Unknown")
|
|
360
|
+
details = activity.get("details", "")
|
|
361
|
+
created_at = activity["created_at"]
|
|
362
|
+
|
|
363
|
+
if action == "started":
|
|
372
364
|
message = f"{user} started '{title}'"
|
|
373
|
-
elif action ==
|
|
365
|
+
elif action == "completed":
|
|
374
366
|
message = f"{user} completed '{title}'"
|
|
375
|
-
elif action ==
|
|
367
|
+
elif action == "updated":
|
|
376
368
|
message = f"{user} updated '{title}'"
|
|
377
369
|
if details:
|
|
378
370
|
message += f" ({details})"
|
|
379
|
-
elif action ==
|
|
371
|
+
elif action == "assigned":
|
|
380
372
|
message = f"{user} {details} for '{title}'"
|
|
381
|
-
elif action ==
|
|
373
|
+
elif action == "unassigned":
|
|
382
374
|
message = f"{user} unassigned '{title}'"
|
|
383
|
-
elif action ==
|
|
375
|
+
elif action == "blocker_set":
|
|
384
376
|
message = f"{user} set blocker on '{title}': {details}"
|
|
385
|
-
elif action ==
|
|
377
|
+
elif action == "blocker_cleared":
|
|
386
378
|
message = f"{user} cleared blocker on '{title}'"
|
|
387
|
-
elif action ==
|
|
379
|
+
elif action == "evidence_linked":
|
|
388
380
|
message = f"{user} linked evidence to '{title}'"
|
|
389
381
|
if details:
|
|
390
382
|
message += f" ({details})"
|
|
391
|
-
elif action ==
|
|
383
|
+
elif action == "commented":
|
|
392
384
|
message = f"{user} commented on '{title}'"
|
|
393
385
|
else:
|
|
394
386
|
message = f"{user} {action} '{title}'"
|
|
395
|
-
|
|
396
|
-
feed.append(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
387
|
+
|
|
388
|
+
feed.append(
|
|
389
|
+
{
|
|
390
|
+
"id": activity["id"],
|
|
391
|
+
"message": message,
|
|
392
|
+
"action": action,
|
|
393
|
+
"user": user,
|
|
394
|
+
"deliverable_id": activity["deliverable_id"],
|
|
395
|
+
"created_at": created_at,
|
|
396
|
+
"details": details,
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
|
|
406
400
|
return feed
|