aws-inventory-manager 0.17.12__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.
Files changed (152) hide show
  1. aws_inventory_manager-0.17.12.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.17.12.dist-info/METADATA +1292 -0
  3. aws_inventory_manager-0.17.12.dist-info/RECORD +152 -0
  4. aws_inventory_manager-0.17.12.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.17.12.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.17.12.dist-info/top_level.txt +1 -0
  7. src/__init__.py +3 -0
  8. src/aws/__init__.py +11 -0
  9. src/aws/client.py +128 -0
  10. src/aws/credentials.py +191 -0
  11. src/aws/rate_limiter.py +177 -0
  12. src/cli/__init__.py +12 -0
  13. src/cli/config.py +130 -0
  14. src/cli/main.py +4046 -0
  15. src/cloudtrail/__init__.py +5 -0
  16. src/cloudtrail/query.py +642 -0
  17. src/config_service/__init__.py +21 -0
  18. src/config_service/collector.py +346 -0
  19. src/config_service/detector.py +256 -0
  20. src/config_service/resource_type_mapping.py +328 -0
  21. src/cost/__init__.py +5 -0
  22. src/cost/analyzer.py +226 -0
  23. src/cost/explorer.py +209 -0
  24. src/cost/reporter.py +237 -0
  25. src/delta/__init__.py +5 -0
  26. src/delta/calculator.py +206 -0
  27. src/delta/differ.py +185 -0
  28. src/delta/formatters.py +272 -0
  29. src/delta/models.py +154 -0
  30. src/delta/reporter.py +234 -0
  31. src/matching/__init__.py +6 -0
  32. src/matching/config.py +52 -0
  33. src/matching/normalizer.py +450 -0
  34. src/matching/prompts.py +33 -0
  35. src/models/__init__.py +21 -0
  36. src/models/config_diff.py +135 -0
  37. src/models/cost_report.py +87 -0
  38. src/models/deletion_operation.py +104 -0
  39. src/models/deletion_record.py +97 -0
  40. src/models/delta_report.py +122 -0
  41. src/models/efs_resource.py +80 -0
  42. src/models/elasticache_resource.py +90 -0
  43. src/models/group.py +318 -0
  44. src/models/inventory.py +133 -0
  45. src/models/protection_rule.py +123 -0
  46. src/models/report.py +288 -0
  47. src/models/resource.py +111 -0
  48. src/models/security_finding.py +102 -0
  49. src/models/snapshot.py +122 -0
  50. src/restore/__init__.py +20 -0
  51. src/restore/audit.py +175 -0
  52. src/restore/cleaner.py +461 -0
  53. src/restore/config.py +209 -0
  54. src/restore/deleter.py +976 -0
  55. src/restore/dependency.py +254 -0
  56. src/restore/safety.py +115 -0
  57. src/security/__init__.py +0 -0
  58. src/security/checks/__init__.py +0 -0
  59. src/security/checks/base.py +56 -0
  60. src/security/checks/ec2_checks.py +88 -0
  61. src/security/checks/elasticache_checks.py +149 -0
  62. src/security/checks/iam_checks.py +102 -0
  63. src/security/checks/rds_checks.py +140 -0
  64. src/security/checks/s3_checks.py +95 -0
  65. src/security/checks/secrets_checks.py +96 -0
  66. src/security/checks/sg_checks.py +142 -0
  67. src/security/cis_mapper.py +97 -0
  68. src/security/models.py +53 -0
  69. src/security/reporter.py +174 -0
  70. src/security/scanner.py +87 -0
  71. src/snapshot/__init__.py +6 -0
  72. src/snapshot/capturer.py +453 -0
  73. src/snapshot/filter.py +259 -0
  74. src/snapshot/inventory_storage.py +236 -0
  75. src/snapshot/report_formatter.py +250 -0
  76. src/snapshot/reporter.py +189 -0
  77. src/snapshot/resource_collectors/__init__.py +5 -0
  78. src/snapshot/resource_collectors/apigateway.py +140 -0
  79. src/snapshot/resource_collectors/backup.py +136 -0
  80. src/snapshot/resource_collectors/base.py +81 -0
  81. src/snapshot/resource_collectors/cloudformation.py +55 -0
  82. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  83. src/snapshot/resource_collectors/codebuild.py +69 -0
  84. src/snapshot/resource_collectors/codepipeline.py +82 -0
  85. src/snapshot/resource_collectors/dynamodb.py +65 -0
  86. src/snapshot/resource_collectors/ec2.py +240 -0
  87. src/snapshot/resource_collectors/ecs.py +215 -0
  88. src/snapshot/resource_collectors/efs_collector.py +102 -0
  89. src/snapshot/resource_collectors/eks.py +200 -0
  90. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  91. src/snapshot/resource_collectors/elb.py +126 -0
  92. src/snapshot/resource_collectors/eventbridge.py +156 -0
  93. src/snapshot/resource_collectors/glue.py +199 -0
  94. src/snapshot/resource_collectors/iam.py +188 -0
  95. src/snapshot/resource_collectors/kms.py +111 -0
  96. src/snapshot/resource_collectors/lambda_func.py +139 -0
  97. src/snapshot/resource_collectors/rds.py +109 -0
  98. src/snapshot/resource_collectors/route53.py +86 -0
  99. src/snapshot/resource_collectors/s3.py +105 -0
  100. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  101. src/snapshot/resource_collectors/sns.py +68 -0
  102. src/snapshot/resource_collectors/sqs.py +82 -0
  103. src/snapshot/resource_collectors/ssm.py +160 -0
  104. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  105. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  106. src/snapshot/resource_collectors/waf.py +159 -0
  107. src/snapshot/storage.py +351 -0
  108. src/storage/__init__.py +21 -0
  109. src/storage/audit_store.py +419 -0
  110. src/storage/database.py +294 -0
  111. src/storage/group_store.py +763 -0
  112. src/storage/inventory_store.py +320 -0
  113. src/storage/resource_store.py +416 -0
  114. src/storage/schema.py +339 -0
  115. src/storage/snapshot_store.py +363 -0
  116. src/utils/__init__.py +12 -0
  117. src/utils/export.py +305 -0
  118. src/utils/hash.py +60 -0
  119. src/utils/logging.py +63 -0
  120. src/utils/pagination.py +41 -0
  121. src/utils/paths.py +51 -0
  122. src/utils/progress.py +41 -0
  123. src/utils/unsupported_resources.py +306 -0
  124. src/web/__init__.py +5 -0
  125. src/web/app.py +97 -0
  126. src/web/dependencies.py +69 -0
  127. src/web/routes/__init__.py +1 -0
  128. src/web/routes/api/__init__.py +18 -0
  129. src/web/routes/api/charts.py +156 -0
  130. src/web/routes/api/cleanup.py +186 -0
  131. src/web/routes/api/filters.py +253 -0
  132. src/web/routes/api/groups.py +305 -0
  133. src/web/routes/api/inventories.py +80 -0
  134. src/web/routes/api/queries.py +202 -0
  135. src/web/routes/api/resources.py +393 -0
  136. src/web/routes/api/snapshots.py +314 -0
  137. src/web/routes/api/views.py +260 -0
  138. src/web/routes/pages.py +198 -0
  139. src/web/services/__init__.py +1 -0
  140. src/web/templates/base.html +955 -0
  141. src/web/templates/components/navbar.html +31 -0
  142. src/web/templates/components/sidebar.html +104 -0
  143. src/web/templates/pages/audit_logs.html +86 -0
  144. src/web/templates/pages/cleanup.html +279 -0
  145. src/web/templates/pages/dashboard.html +227 -0
  146. src/web/templates/pages/diff.html +175 -0
  147. src/web/templates/pages/error.html +30 -0
  148. src/web/templates/pages/groups.html +721 -0
  149. src/web/templates/pages/queries.html +246 -0
  150. src/web/templates/pages/resources.html +2429 -0
  151. src/web/templates/pages/snapshot_detail.html +271 -0
  152. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,31 @@
1
+ <!-- Minimal top bar - branding is in sidebar -->
2
+ <nav class="bg-white border-b border-gray-100 shadow-sm sticky top-0 z-40">
3
+ <div class="mx-auto px-4 sm:px-6 lg:px-8">
4
+ <div class="flex h-14 items-center justify-end">
5
+ <div class="flex items-center space-x-4">
6
+ <!-- Quick Actions -->
7
+ <button class="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-colors">
8
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
9
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
10
+ </svg>
11
+ </button>
12
+ <button class="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-colors">
13
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
14
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
15
+ </svg>
16
+ </button>
17
+
18
+ <!-- User/Status -->
19
+ <div class="flex items-center space-x-3 pl-3 border-l border-gray-200">
20
+ <div class="flex items-center space-x-2">
21
+ <span class="flex h-2 w-2 rounded-full bg-emerald-400 animate-pulse"></span>
22
+ <span class="text-sm font-medium text-gray-600">Connected</span>
23
+ </div>
24
+ <div class="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center text-white text-sm font-bold shadow-md">
25
+ U
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </nav>
@@ -0,0 +1,104 @@
1
+ <aside class="w-64 bg-white border-r border-gray-100 min-h-screen shadow-sm">
2
+ <!-- Logo/Brand Section -->
3
+ <div class="px-4 py-5 border-b border-gray-100">
4
+ <div class="flex items-center space-x-3">
5
+ <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center shadow-lg shadow-indigo-200">
6
+ <svg class="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
7
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"/>
8
+ </svg>
9
+ </div>
10
+ <div>
11
+ <h1 class="text-sm font-bold text-gray-900">AWS Inventory</h1>
12
+ <span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">Local</span>
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ <nav class="mt-4 px-3 space-y-1">
18
+ <a href="/"
19
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if request.url.path == '/' %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
20
+ <div class="mr-3 p-1.5 rounded-lg {% if request.url.path == '/' %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
21
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
22
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
23
+ </svg>
24
+ </div>
25
+ Dashboard
26
+ </a>
27
+
28
+ <a href="/snapshots"
29
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/snapshots' in request.url.path %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
30
+ <div class="mr-3 p-1.5 rounded-lg {% if '/snapshots' in request.url.path %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
31
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
32
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
33
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
34
+ </svg>
35
+ </div>
36
+ Snapshots
37
+ </a>
38
+
39
+ <a href="/resources"
40
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/resources' in request.url.path %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
41
+ <div class="mr-3 p-1.5 rounded-lg {% if '/resources' in request.url.path %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
42
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
43
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
44
+ </svg>
45
+ </div>
46
+ Resources
47
+ </a>
48
+
49
+ <a href="/groups"
50
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/groups' in request.url.path %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
51
+ <div class="mr-3 p-1.5 rounded-lg {% if '/groups' in request.url.path %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
52
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
53
+ <path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
54
+ </svg>
55
+ </div>
56
+ Groups
57
+ </a>
58
+
59
+ <a href="/diff"
60
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/diff' in request.url.path %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
61
+ <div class="mr-3 p-1.5 rounded-lg {% if '/diff' in request.url.path %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
62
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
63
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
64
+ </svg>
65
+ </div>
66
+ Compare
67
+ </a>
68
+
69
+ <a href="/queries"
70
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/queries' in request.url.path %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
71
+ <div class="mr-3 p-1.5 rounded-lg {% if '/queries' in request.url.path %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
72
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
73
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
74
+ </svg>
75
+ </div>
76
+ SQL Queries
77
+ </a>
78
+
79
+ <!-- Operations Section -->
80
+ <div class="mt-6 pt-4 border-t border-gray-100">
81
+ <p class="px-3 text-xs font-bold text-gray-400 uppercase tracking-widest mb-2">Operations</p>
82
+ </div>
83
+
84
+ <a href="/cleanup"
85
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/cleanup' in request.url.path %}bg-gradient-to-r from-rose-50 to-orange-50 text-rose-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
86
+ <div class="mr-3 p-1.5 rounded-lg {% if '/cleanup' in request.url.path %}bg-gradient-to-br from-rose-500 to-orange-500 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
87
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
88
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
89
+ </svg>
90
+ </div>
91
+ Cleanup
92
+ </a>
93
+
94
+ <a href="/audit"
95
+ class="group flex items-center px-3 py-2.5 text-sm font-semibold rounded-xl transition-all duration-200 {% if '/audit' in request.url.path %}bg-gradient-to-r from-indigo-50 to-purple-50 text-indigo-700 shadow-sm{% else %}text-gray-600 hover:bg-gray-50 hover:text-gray-900{% endif %}">
96
+ <div class="mr-3 p-1.5 rounded-lg {% if '/audit' in request.url.path %}bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-md{% else %}bg-gray-100 text-gray-500 group-hover:bg-gray-200{% endif %}">
97
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
98
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
99
+ </svg>
100
+ </div>
101
+ Audit Logs
102
+ </a>
103
+ </nav>
104
+ </aside>
@@ -0,0 +1,86 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Audit Logs - AWS Inventory Browser{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="space-y-6">
7
+ <div class="md:flex md:items-center md:justify-between">
8
+ <div class="min-w-0 flex-1">
9
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl">Audit Logs</h1>
10
+ <p class="mt-1 text-sm text-gray-500">History of cleanup operations and their results</p>
11
+ </div>
12
+ </div>
13
+
14
+ <!-- Operations List -->
15
+ <div class="bg-white shadow rounded-lg overflow-hidden">
16
+ {% if operations %}
17
+ <table class="min-w-full divide-y divide-gray-200">
18
+ <thead class="bg-gray-50">
19
+ <tr>
20
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Operation</th>
21
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Baseline</th>
22
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Timestamp</th>
23
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Results</th>
24
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
25
+ </tr>
26
+ </thead>
27
+ <tbody class="bg-white divide-y divide-gray-200">
28
+ {% for op in operations %}
29
+ <tr class="hover:bg-gray-50">
30
+ <td class="px-6 py-4 whitespace-nowrap">
31
+ <div class="text-sm font-medium text-gray-900">{{ op.operation_id[:12] }}...</div>
32
+ <div class="text-xs text-gray-500">{{ op.mode | default('cleanup') }}</div>
33
+ </td>
34
+ <td class="px-6 py-4 whitespace-nowrap">
35
+ <a href="/snapshots/{{ op.baseline_snapshot }}" class="text-sm text-blue-600 hover:text-blue-500">
36
+ {{ op.baseline_snapshot }}
37
+ </a>
38
+ </td>
39
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
40
+ {{ op.timestamp }}
41
+ </td>
42
+ <td class="px-6 py-4 whitespace-nowrap">
43
+ <div class="flex items-center space-x-2">
44
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">
45
+ {{ op.succeeded_count | default(0) }} succeeded
46
+ </span>
47
+ {% if op.failed_count %}
48
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800">
49
+ {{ op.failed_count }} failed
50
+ </span>
51
+ {% endif %}
52
+ {% if op.skipped_count %}
53
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">
54
+ {{ op.skipped_count }} skipped
55
+ </span>
56
+ {% endif %}
57
+ </div>
58
+ </td>
59
+ <td class="px-6 py-4 whitespace-nowrap">
60
+ {% set status_colors = {'completed': 'green', 'failed': 'red', 'partial': 'amber', 'running': 'blue'} %}
61
+ {% set color = status_colors.get(op.status, 'gray') %}
62
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-{{ color }}-100 text-{{ color }}-800">
63
+ {{ op.status }}
64
+ </span>
65
+ </td>
66
+ </tr>
67
+ {% endfor %}
68
+ </tbody>
69
+ </table>
70
+ {% else %}
71
+ <div class="p-12 text-center">
72
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
73
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
74
+ </svg>
75
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No audit logs</h3>
76
+ <p class="mt-1 text-sm text-gray-500">Cleanup operations will appear here after they are executed.</p>
77
+ <div class="mt-6">
78
+ <a href="/cleanup" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700">
79
+ Go to Cleanup
80
+ </a>
81
+ </div>
82
+ </div>
83
+ {% endif %}
84
+ </div>
85
+ </div>
86
+ {% endblock %}
@@ -0,0 +1,279 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Cleanup Operations - AWS Inventory Browser{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="space-y-6" x-data="{
7
+ snapshot: '',
8
+ dryRun: true,
9
+ loading: false,
10
+ previewResults: null,
11
+ error: null,
12
+ showConfirmModal: false,
13
+ confirmText: ''
14
+ }">
15
+ <div class="md:flex md:items-center md:justify-between">
16
+ <div class="min-w-0 flex-1">
17
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl">Cleanup Operations</h1>
18
+ <p class="mt-1 text-sm text-gray-500">Remove resources that were created after a baseline snapshot</p>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
23
+ <!-- Configuration Panel -->
24
+ <div class="lg:col-span-1 space-y-4">
25
+ <div class="bg-white shadow rounded-lg p-6">
26
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Configuration</h3>
27
+
28
+ <div class="space-y-4">
29
+ <div>
30
+ <label for="snapshot" class="block text-sm font-medium text-gray-700">Baseline Snapshot</label>
31
+ <select id="snapshot" x-model="snapshot"
32
+ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
33
+ <option value="">Select a snapshot...</option>
34
+ {% for s in snapshots %}
35
+ <option value="{{ s.name }}">{{ s.name }} ({{ s.resource_count | format_number }} resources)</option>
36
+ {% endfor %}
37
+ </select>
38
+ <p class="mt-1 text-xs text-gray-500">Resources created after this snapshot will be candidates for cleanup</p>
39
+ </div>
40
+
41
+ <div class="pt-4 border-t border-gray-200">
42
+ <button @click="previewCleanup()"
43
+ :disabled="!snapshot || loading"
44
+ class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
45
+ <svg class="-ml-1 mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
47
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
48
+ </svg>
49
+ <span x-text="loading ? 'Loading...' : 'Preview Cleanup'"></span>
50
+ </button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Warning Card -->
56
+ <div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
57
+ <div class="flex">
58
+ <div class="flex-shrink-0">
59
+ <svg class="h-5 w-5 text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
60
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
61
+ </svg>
62
+ </div>
63
+ <div class="ml-3">
64
+ <h3 class="text-sm font-medium text-amber-800">Caution</h3>
65
+ <p class="mt-1 text-sm text-amber-700">
66
+ Cleanup operations delete AWS resources. Always preview first and verify the resources before executing.
67
+ </p>
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Audit History -->
73
+ <div class="bg-white shadow rounded-lg">
74
+ <div class="px-4 py-3 border-b border-gray-200">
75
+ <h3 class="text-sm font-medium text-gray-900">Recent Operations</h3>
76
+ </div>
77
+ <div id="audit-history"
78
+ hx-get="/api/cleanup/operations?limit=5"
79
+ hx-trigger="load"
80
+ hx-swap="innerHTML"
81
+ class="divide-y divide-gray-200 max-h-64 overflow-y-auto">
82
+ <div class="p-4 text-center text-gray-500 text-sm">Loading...</div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Results Panel -->
88
+ <div class="lg:col-span-2">
89
+ <div x-show="!previewResults && !error" class="bg-white shadow rounded-lg p-8 text-center">
90
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
91
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
92
+ </svg>
93
+ <h3 class="mt-2 text-sm font-medium text-gray-900">No Preview</h3>
94
+ <p class="mt-1 text-sm text-gray-500">Select a baseline snapshot and click Preview to see cleanup candidates</p>
95
+ </div>
96
+
97
+ <!-- Preview Results -->
98
+ <div x-show="previewResults" x-cloak class="space-y-4">
99
+ <!-- Summary Stats -->
100
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
101
+ <div class="bg-white shadow rounded-lg p-4">
102
+ <p class="text-sm font-medium text-gray-500">Total Candidates</p>
103
+ <p class="text-2xl font-bold text-gray-900" x-text="previewResults?.total || 0"></p>
104
+ </div>
105
+ <div class="bg-white shadow rounded-lg p-4">
106
+ <p class="text-sm font-medium text-gray-500">Resource Types</p>
107
+ <p class="text-2xl font-bold text-gray-900" x-text="Object.keys(previewResults?.by_type || {}).length"></p>
108
+ </div>
109
+ <div class="bg-white shadow rounded-lg p-4">
110
+ <p class="text-sm font-medium text-gray-500">Regions</p>
111
+ <p class="text-2xl font-bold text-gray-900" x-text="Object.keys(previewResults?.by_region || {}).length"></p>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Resource List -->
116
+ <div class="bg-white shadow rounded-lg overflow-hidden">
117
+ <div class="px-4 py-3 border-b border-gray-200 flex items-center justify-between">
118
+ <h3 class="text-sm font-medium text-gray-900">Resources to Delete</h3>
119
+ <button @click="showConfirmModal = true"
120
+ :disabled="!previewResults?.resources?.length"
121
+ class="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 disabled:opacity-50">
122
+ <svg class="-ml-0.5 mr-1.5 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
123
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
124
+ </svg>
125
+ Execute Cleanup
126
+ </button>
127
+ </div>
128
+ <div class="overflow-x-auto max-h-96">
129
+ <table class="min-w-full divide-y divide-gray-200">
130
+ <thead class="bg-gray-50 sticky top-0">
131
+ <tr>
132
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Resource</th>
133
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
134
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Region</th>
135
+ </tr>
136
+ </thead>
137
+ <tbody class="bg-white divide-y divide-gray-200">
138
+ <template x-for="r in previewResults?.resources?.slice(0, 100)" :key="r.arn">
139
+ <tr class="hover:bg-gray-50">
140
+ <td class="px-6 py-3 whitespace-nowrap">
141
+ <div class="text-sm font-medium text-gray-900" x-text="r.name"></div>
142
+ <div class="text-xs text-gray-500 truncate max-w-xs" x-text="r.arn"></div>
143
+ </td>
144
+ <td class="px-6 py-3 whitespace-nowrap">
145
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800" x-text="r.resource_type"></span>
146
+ </td>
147
+ <td class="px-6 py-3 whitespace-nowrap text-sm text-gray-500" x-text="r.region"></td>
148
+ </tr>
149
+ </template>
150
+ </tbody>
151
+ </table>
152
+ </div>
153
+ <div x-show="previewResults?.resources?.length > 100" class="px-4 py-2 bg-gray-50 text-sm text-gray-500 text-center">
154
+ Showing 100 of <span x-text="previewResults?.total"></span> resources
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <!-- Error -->
160
+ <div x-show="error" x-cloak class="bg-red-50 border border-red-200 rounded-lg p-4">
161
+ <p class="text-red-800" x-text="error"></p>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <!-- Confirm Modal -->
167
+ <div x-show="showConfirmModal" x-cloak class="fixed z-10 inset-0 overflow-y-auto">
168
+ <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
169
+ <div class="fixed inset-0 bg-gray-500 bg-opacity-75" @click="showConfirmModal = false"></div>
170
+ <div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
171
+ <div class="sm:flex sm:items-start">
172
+ <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
173
+ <svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
174
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
175
+ </svg>
176
+ </div>
177
+ <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
178
+ <h3 class="text-lg font-medium text-gray-900">Confirm Cleanup Execution</h3>
179
+ <div class="mt-2">
180
+ <p class="text-sm text-gray-500">
181
+ This will permanently delete <span class="font-bold text-red-600" x-text="previewResults?.total"></span> AWS resources.
182
+ This action cannot be undone.
183
+ </p>
184
+ <p class="mt-3 text-sm text-gray-700">
185
+ Type <span class="font-mono font-bold text-red-600">DELETE</span> to confirm:
186
+ </p>
187
+ <input type="text" x-model="confirmText"
188
+ class="mt-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-red-500 focus:ring-red-500 sm:text-sm"
189
+ placeholder="Type DELETE to confirm">
190
+ </div>
191
+ </div>
192
+ </div>
193
+ <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
194
+ <button @click="executeCleanup()"
195
+ :disabled="confirmText !== 'DELETE'"
196
+ class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 disabled:opacity-50 sm:ml-3 sm:w-auto sm:text-sm">
197
+ Execute Cleanup
198
+ </button>
199
+ <button @click="showConfirmModal = false; confirmText = ''"
200
+ class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 sm:mt-0 sm:w-auto sm:text-sm">
201
+ Cancel
202
+ </button>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ {% endblock %}
209
+
210
+ {% block scripts %}
211
+ <script>
212
+ // Handle audit history response
213
+ document.body.addEventListener('htmx:afterSwap', function(evt) {
214
+ if (evt.detail.target.id === 'audit-history') {
215
+ const data = JSON.parse(evt.detail.xhr.responseText);
216
+ renderAuditHistory(data.operations || [], evt.detail.target);
217
+ }
218
+ });
219
+
220
+ function renderAuditHistory(operations, target) {
221
+ if (operations.length === 0) {
222
+ target.innerHTML = '<div class="p-4 text-center text-gray-500 text-sm">No cleanup operations yet</div>';
223
+ return;
224
+ }
225
+
226
+ let html = '';
227
+ operations.forEach(op => {
228
+ const statusColor = op.status === 'completed' ? 'green' : op.status === 'failed' ? 'red' : 'amber';
229
+ html += `
230
+ <div class="px-4 py-3">
231
+ <div class="flex items-center justify-between">
232
+ <p class="text-sm font-medium text-gray-900">${op.baseline_snapshot}</p>
233
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-${statusColor}-100 text-${statusColor}-800">
234
+ ${op.status}
235
+ </span>
236
+ </div>
237
+ <p class="text-xs text-gray-500">${op.timestamp}</p>
238
+ <p class="text-xs text-gray-600 mt-1">
239
+ ${op.succeeded_count || 0} deleted, ${op.failed_count || 0} failed
240
+ </p>
241
+ </div>
242
+ `;
243
+ });
244
+ target.innerHTML = html;
245
+ }
246
+
247
+ function previewCleanup() {
248
+ const data = Alpine.$data(document.querySelector('[x-data]'));
249
+ data.loading = true;
250
+ data.error = null;
251
+ data.previewResults = null;
252
+
253
+ fetch(`/api/cleanup/preview?snapshot=${data.snapshot}`)
254
+ .then(r => r.json())
255
+ .then(result => {
256
+ if (result.detail) {
257
+ data.error = result.detail;
258
+ } else {
259
+ data.previewResults = result;
260
+ }
261
+ data.loading = false;
262
+ })
263
+ .catch(err => {
264
+ data.error = err.message;
265
+ data.loading = false;
266
+ });
267
+ }
268
+
269
+ function executeCleanup() {
270
+ const data = Alpine.$data(document.querySelector('[x-data]'));
271
+
272
+ // Note: In a real implementation, this would execute the cleanup
273
+ // For now, just show a message and close the modal
274
+ alert('Cleanup execution is not available in the web UI for safety. Please use the CLI: awsinv cleanup execute --snapshot ' + data.snapshot);
275
+ data.showConfirmModal = false;
276
+ data.confirmText = '';
277
+ }
278
+ </script>
279
+ {% endblock %}