aws-inventory-manager 0.13.2__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 aws-inventory-manager might be problematic. Click here for more details.

Files changed (145) hide show
  1. aws_inventory_manager-0.13.2.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.13.2.dist-info/METADATA +1226 -0
  3. aws_inventory_manager-0.13.2.dist-info/RECORD +145 -0
  4. aws_inventory_manager-0.13.2.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.13.2.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.13.2.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 +3626 -0
  15. src/config_service/__init__.py +21 -0
  16. src/config_service/collector.py +346 -0
  17. src/config_service/detector.py +256 -0
  18. src/config_service/resource_type_mapping.py +328 -0
  19. src/cost/__init__.py +5 -0
  20. src/cost/analyzer.py +226 -0
  21. src/cost/explorer.py +209 -0
  22. src/cost/reporter.py +237 -0
  23. src/delta/__init__.py +5 -0
  24. src/delta/calculator.py +206 -0
  25. src/delta/differ.py +185 -0
  26. src/delta/formatters.py +272 -0
  27. src/delta/models.py +154 -0
  28. src/delta/reporter.py +234 -0
  29. src/models/__init__.py +21 -0
  30. src/models/config_diff.py +135 -0
  31. src/models/cost_report.py +87 -0
  32. src/models/deletion_operation.py +104 -0
  33. src/models/deletion_record.py +97 -0
  34. src/models/delta_report.py +122 -0
  35. src/models/efs_resource.py +80 -0
  36. src/models/elasticache_resource.py +90 -0
  37. src/models/group.py +318 -0
  38. src/models/inventory.py +133 -0
  39. src/models/protection_rule.py +123 -0
  40. src/models/report.py +288 -0
  41. src/models/resource.py +111 -0
  42. src/models/security_finding.py +102 -0
  43. src/models/snapshot.py +122 -0
  44. src/restore/__init__.py +20 -0
  45. src/restore/audit.py +175 -0
  46. src/restore/cleaner.py +461 -0
  47. src/restore/config.py +209 -0
  48. src/restore/deleter.py +976 -0
  49. src/restore/dependency.py +254 -0
  50. src/restore/safety.py +115 -0
  51. src/security/__init__.py +0 -0
  52. src/security/checks/__init__.py +0 -0
  53. src/security/checks/base.py +56 -0
  54. src/security/checks/ec2_checks.py +88 -0
  55. src/security/checks/elasticache_checks.py +149 -0
  56. src/security/checks/iam_checks.py +102 -0
  57. src/security/checks/rds_checks.py +140 -0
  58. src/security/checks/s3_checks.py +95 -0
  59. src/security/checks/secrets_checks.py +96 -0
  60. src/security/checks/sg_checks.py +142 -0
  61. src/security/cis_mapper.py +97 -0
  62. src/security/models.py +53 -0
  63. src/security/reporter.py +174 -0
  64. src/security/scanner.py +87 -0
  65. src/snapshot/__init__.py +6 -0
  66. src/snapshot/capturer.py +451 -0
  67. src/snapshot/filter.py +259 -0
  68. src/snapshot/inventory_storage.py +236 -0
  69. src/snapshot/report_formatter.py +250 -0
  70. src/snapshot/reporter.py +189 -0
  71. src/snapshot/resource_collectors/__init__.py +5 -0
  72. src/snapshot/resource_collectors/apigateway.py +140 -0
  73. src/snapshot/resource_collectors/backup.py +136 -0
  74. src/snapshot/resource_collectors/base.py +81 -0
  75. src/snapshot/resource_collectors/cloudformation.py +55 -0
  76. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  77. src/snapshot/resource_collectors/codebuild.py +69 -0
  78. src/snapshot/resource_collectors/codepipeline.py +82 -0
  79. src/snapshot/resource_collectors/dynamodb.py +65 -0
  80. src/snapshot/resource_collectors/ec2.py +240 -0
  81. src/snapshot/resource_collectors/ecs.py +215 -0
  82. src/snapshot/resource_collectors/efs_collector.py +102 -0
  83. src/snapshot/resource_collectors/eks.py +200 -0
  84. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  85. src/snapshot/resource_collectors/elb.py +126 -0
  86. src/snapshot/resource_collectors/eventbridge.py +156 -0
  87. src/snapshot/resource_collectors/iam.py +188 -0
  88. src/snapshot/resource_collectors/kms.py +111 -0
  89. src/snapshot/resource_collectors/lambda_func.py +139 -0
  90. src/snapshot/resource_collectors/rds.py +109 -0
  91. src/snapshot/resource_collectors/route53.py +86 -0
  92. src/snapshot/resource_collectors/s3.py +105 -0
  93. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  94. src/snapshot/resource_collectors/sns.py +68 -0
  95. src/snapshot/resource_collectors/sqs.py +82 -0
  96. src/snapshot/resource_collectors/ssm.py +160 -0
  97. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  98. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  99. src/snapshot/resource_collectors/waf.py +159 -0
  100. src/snapshot/storage.py +351 -0
  101. src/storage/__init__.py +21 -0
  102. src/storage/audit_store.py +419 -0
  103. src/storage/database.py +294 -0
  104. src/storage/group_store.py +749 -0
  105. src/storage/inventory_store.py +320 -0
  106. src/storage/resource_store.py +413 -0
  107. src/storage/schema.py +288 -0
  108. src/storage/snapshot_store.py +346 -0
  109. src/utils/__init__.py +12 -0
  110. src/utils/export.py +305 -0
  111. src/utils/hash.py +60 -0
  112. src/utils/logging.py +63 -0
  113. src/utils/pagination.py +41 -0
  114. src/utils/paths.py +51 -0
  115. src/utils/progress.py +41 -0
  116. src/utils/unsupported_resources.py +306 -0
  117. src/web/__init__.py +5 -0
  118. src/web/app.py +97 -0
  119. src/web/dependencies.py +69 -0
  120. src/web/routes/__init__.py +1 -0
  121. src/web/routes/api/__init__.py +18 -0
  122. src/web/routes/api/charts.py +156 -0
  123. src/web/routes/api/cleanup.py +186 -0
  124. src/web/routes/api/filters.py +253 -0
  125. src/web/routes/api/groups.py +305 -0
  126. src/web/routes/api/inventories.py +80 -0
  127. src/web/routes/api/queries.py +202 -0
  128. src/web/routes/api/resources.py +379 -0
  129. src/web/routes/api/snapshots.py +314 -0
  130. src/web/routes/api/views.py +260 -0
  131. src/web/routes/pages.py +198 -0
  132. src/web/services/__init__.py +1 -0
  133. src/web/templates/base.html +949 -0
  134. src/web/templates/components/navbar.html +31 -0
  135. src/web/templates/components/sidebar.html +104 -0
  136. src/web/templates/pages/audit_logs.html +86 -0
  137. src/web/templates/pages/cleanup.html +279 -0
  138. src/web/templates/pages/dashboard.html +227 -0
  139. src/web/templates/pages/diff.html +175 -0
  140. src/web/templates/pages/error.html +30 -0
  141. src/web/templates/pages/groups.html +721 -0
  142. src/web/templates/pages/queries.html +246 -0
  143. src/web/templates/pages/resources.html +2251 -0
  144. src/web/templates/pages/snapshot_detail.html +271 -0
  145. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,227 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Dashboard - AWS Inventory Browser{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="space-y-6">
7
+ <!-- Header -->
8
+ <div class="md:flex md:items-center md:justify-between">
9
+ <div class="min-w-0 flex-1">
10
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
11
+ Dashboard
12
+ </h1>
13
+ </div>
14
+ </div>
15
+
16
+ <!-- Stats Cards -->
17
+ <div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
18
+ <!-- Total Snapshots -->
19
+ <div class="bg-white overflow-hidden shadow rounded-lg">
20
+ <div class="p-5">
21
+ <div class="flex items-center">
22
+ <div class="flex-shrink-0">
23
+ <div class="rounded-md bg-blue-500 p-3">
24
+ <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
25
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"/>
26
+ </svg>
27
+ </div>
28
+ </div>
29
+ <div class="ml-5 w-0 flex-1">
30
+ <dl>
31
+ <dt class="text-sm font-medium text-gray-500 truncate">Total Snapshots</dt>
32
+ <dd class="text-2xl font-semibold text-gray-900">{{ total_snapshots | format_number }}</dd>
33
+ </dl>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <!-- Total Resources -->
40
+ <div class="bg-white overflow-hidden shadow rounded-lg">
41
+ <div class="p-5">
42
+ <div class="flex items-center">
43
+ <div class="flex-shrink-0">
44
+ <div class="rounded-md bg-green-500 p-3">
45
+ <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"/>
47
+ </svg>
48
+ </div>
49
+ </div>
50
+ <div class="ml-5 w-0 flex-1">
51
+ <dl>
52
+ <dt class="text-sm font-medium text-gray-500 truncate">Total Resources</dt>
53
+ <dd class="text-2xl font-semibold text-gray-900">{{ total_resources | format_number }}</dd>
54
+ </dl>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Active Baseline -->
61
+ <div class="bg-white overflow-hidden shadow rounded-lg">
62
+ <div class="p-5">
63
+ <div class="flex items-center">
64
+ <div class="flex-shrink-0">
65
+ <div class="rounded-md bg-amber-500 p-3">
66
+ <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
67
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
68
+ </svg>
69
+ </div>
70
+ </div>
71
+ <div class="ml-5 w-0 flex-1">
72
+ <dl>
73
+ <dt class="text-sm font-medium text-gray-500 truncate">Active Baseline</dt>
74
+ <dd class="text-lg font-semibold text-gray-900 truncate">
75
+ {% if active_snapshot %}
76
+ {{ active_snapshot }}
77
+ {% else %}
78
+ <span class="text-gray-400">None set</span>
79
+ {% endif %}
80
+ </dd>
81
+ </dl>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Resource Types -->
88
+ <div class="bg-white overflow-hidden shadow rounded-lg">
89
+ <div class="p-5">
90
+ <div class="flex items-center">
91
+ <div class="flex-shrink-0">
92
+ <div class="rounded-md bg-purple-500 p-3">
93
+ <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
94
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/>
95
+ </svg>
96
+ </div>
97
+ </div>
98
+ <div class="ml-5 w-0 flex-1">
99
+ <dl>
100
+ <dt class="text-sm font-medium text-gray-500 truncate">Resource Types</dt>
101
+ <dd class="text-2xl font-semibold text-gray-900">{{ stats_by_type | length }}</dd>
102
+ </dl>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- Charts Row -->
110
+ <div class="grid grid-cols-1 gap-5 lg:grid-cols-2">
111
+ <!-- Resources by Type -->
112
+ <div class="bg-white shadow rounded-lg p-6">
113
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Resources by Type</h3>
114
+ <div class="h-64">
115
+ <canvas id="chart-by-type"></canvas>
116
+ </div>
117
+ </div>
118
+
119
+ <!-- Resources by Region -->
120
+ <div class="bg-white shadow rounded-lg p-6">
121
+ <h3 class="text-lg font-medium text-gray-900 mb-4">Resources by Region</h3>
122
+ <div class="h-64">
123
+ <canvas id="chart-by-region"></canvas>
124
+ </div>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- Recent Snapshots -->
129
+ <div class="bg-white shadow rounded-lg">
130
+ <div class="px-4 py-5 sm:px-6 border-b border-gray-200">
131
+ <h3 class="text-lg font-medium leading-6 text-gray-900">Recent Snapshots</h3>
132
+ </div>
133
+ <ul role="list" class="divide-y divide-gray-200">
134
+ {% for snapshot in recent_snapshots %}
135
+ <li>
136
+ <a href="/snapshots/{{ snapshot.name }}" class="block hover:bg-gray-50">
137
+ <div class="px-4 py-4 sm:px-6">
138
+ <div class="flex items-center justify-between">
139
+ <p class="text-sm font-medium text-blue-600 truncate">{{ snapshot.name }}</p>
140
+ <div class="ml-2 flex-shrink-0 flex">
141
+ {% if snapshot.is_active %}
142
+ <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
143
+ Active
144
+ </span>
145
+ {% endif %}
146
+ </div>
147
+ </div>
148
+ <div class="mt-2 sm:flex sm:justify-between">
149
+ <div class="sm:flex">
150
+ <p class="flex items-center text-sm text-gray-500">
151
+ {{ snapshot.resource_count | format_number }} resources
152
+ </p>
153
+ </div>
154
+ <div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0">
155
+ <svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
156
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
157
+ </svg>
158
+ <p>{{ snapshot.created_at }}</p>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </a>
163
+ </li>
164
+ {% else %}
165
+ <li class="px-4 py-8 text-center text-gray-500">
166
+ <p>No snapshots yet. Create one with:</p>
167
+ <code class="mt-2 block bg-gray-100 px-4 py-2 rounded text-sm">awsinv snapshot create my-baseline --regions us-east-1</code>
168
+ </li>
169
+ {% endfor %}
170
+ </ul>
171
+ {% if recent_snapshots %}
172
+ <div class="px-4 py-3 bg-gray-50 text-right sm:px-6 rounded-b-lg">
173
+ <a href="/snapshots" class="text-sm font-medium text-blue-600 hover:text-blue-500">
174
+ View all snapshots <span aria-hidden="true">&rarr;</span>
175
+ </a>
176
+ </div>
177
+ {% endif %}
178
+ </div>
179
+ </div>
180
+ {% endblock %}
181
+
182
+ {% block scripts %}
183
+ <script>
184
+ // Load charts
185
+ document.addEventListener('DOMContentLoaded', function() {
186
+ // Resources by Type Chart
187
+ fetch('/api/charts/resources-by-type')
188
+ .then(r => r.json())
189
+ .then(data => {
190
+ new Chart(document.getElementById('chart-by-type'), {
191
+ type: 'doughnut',
192
+ data: data,
193
+ options: {
194
+ responsive: true,
195
+ maintainAspectRatio: false,
196
+ plugins: {
197
+ legend: {
198
+ position: 'right',
199
+ labels: { boxWidth: 12 }
200
+ }
201
+ }
202
+ }
203
+ });
204
+ });
205
+
206
+ // Resources by Region Chart
207
+ fetch('/api/charts/resources-by-region')
208
+ .then(r => r.json())
209
+ .then(data => {
210
+ new Chart(document.getElementById('chart-by-region'), {
211
+ type: 'bar',
212
+ data: data,
213
+ options: {
214
+ responsive: true,
215
+ maintainAspectRatio: false,
216
+ plugins: {
217
+ legend: { display: false }
218
+ },
219
+ scales: {
220
+ y: { beginAtZero: true }
221
+ }
222
+ }
223
+ });
224
+ });
225
+ });
226
+ </script>
227
+ {% endblock %}
@@ -0,0 +1,175 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Compare Snapshots - AWS Inventory Browser{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="space-y-6" x-data="{
7
+ snapshot1: '',
8
+ snapshot2: '',
9
+ loading: false,
10
+ results: null,
11
+ error: null
12
+ }">
13
+ <div class="md:flex md:items-center md:justify-between">
14
+ <div class="min-w-0 flex-1">
15
+ <h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl">Compare Snapshots</h1>
16
+ <p class="mt-1 text-sm text-gray-500">See what changed between two snapshots</p>
17
+ </div>
18
+ </div>
19
+
20
+ <!-- Snapshot Selection -->
21
+ <div class="bg-white shadow rounded-lg p-6">
22
+ <div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
23
+ <div>
24
+ <label for="snapshot1" class="block text-sm font-medium text-gray-700">First Snapshot (Before)</label>
25
+ <select id="snapshot1" x-model="snapshot1"
26
+ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
27
+ <option value="">Select snapshot...</option>
28
+ {% for s in snapshots %}
29
+ <option value="{{ s.name }}">{{ s.name }} ({{ s.resource_count }} resources)</option>
30
+ {% endfor %}
31
+ </select>
32
+ </div>
33
+
34
+ <div class="flex items-end justify-center">
35
+ <svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
36
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
37
+ </svg>
38
+ </div>
39
+
40
+ <div>
41
+ <label for="snapshot2" class="block text-sm font-medium text-gray-700">Second Snapshot (After)</label>
42
+ <select id="snapshot2" x-model="snapshot2"
43
+ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
44
+ <option value="">Select snapshot...</option>
45
+ {% for s in snapshots %}
46
+ <option value="{{ s.name }}">{{ s.name }} ({{ s.resource_count }} resources)</option>
47
+ {% endfor %}
48
+ </select>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="mt-6 text-center">
53
+ <button @click="comparSnapshots()"
54
+ :disabled="!snapshot1 || !snapshot2 || loading"
55
+ class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
56
+ <span x-show="!loading">Compare</span>
57
+ <span x-show="loading" class="flex items-center">
58
+ <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
59
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
60
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
61
+ </svg>
62
+ Comparing...
63
+ </span>
64
+ </button>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Results -->
69
+ <div x-show="results" x-cloak class="space-y-6">
70
+ <!-- Summary -->
71
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
72
+ <div class="bg-green-50 border border-green-200 rounded-lg p-4">
73
+ <div class="flex items-center">
74
+ <div class="flex-shrink-0">
75
+ <svg class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
76
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
77
+ </svg>
78
+ </div>
79
+ <div class="ml-4">
80
+ <p class="text-sm font-medium text-green-800">Added</p>
81
+ <p class="text-2xl font-bold text-green-900" x-text="results?.added?.length || 0"></p>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="bg-red-50 border border-red-200 rounded-lg p-4">
87
+ <div class="flex items-center">
88
+ <div class="flex-shrink-0">
89
+ <svg class="h-8 w-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
91
+ </svg>
92
+ </div>
93
+ <div class="ml-4">
94
+ <p class="text-sm font-medium text-red-800">Removed</p>
95
+ <p class="text-2xl font-bold text-red-900" x-text="results?.removed?.length || 0"></p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <div class="bg-amber-50 border border-amber-200 rounded-lg p-4">
101
+ <div class="flex items-center">
102
+ <div class="flex-shrink-0">
103
+ <svg class="h-8 w-8 text-amber-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
104
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
105
+ </svg>
106
+ </div>
107
+ <div class="ml-4">
108
+ <p class="text-sm font-medium text-amber-800">Modified</p>
109
+ <p class="text-2xl font-bold text-amber-900" x-text="results?.modified?.length || 0"></p>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Details -->
116
+ <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
117
+ <!-- Added -->
118
+ <div class="bg-white shadow rounded-lg" x-show="results?.added?.length > 0">
119
+ <div class="px-4 py-5 border-b border-gray-200 bg-green-50 rounded-t-lg">
120
+ <h3 class="text-lg font-medium text-green-800">Added Resources</h3>
121
+ </div>
122
+ <ul class="divide-y divide-gray-200 max-h-96 overflow-y-auto">
123
+ <template x-for="r in results?.added?.slice(0, 50)" :key="r.arn">
124
+ <li class="px-4 py-3">
125
+ <p class="text-sm font-medium text-gray-900" x-text="r.name"></p>
126
+ <p class="text-xs text-gray-500" x-text="r.resource_type"></p>
127
+ </li>
128
+ </template>
129
+ </ul>
130
+ </div>
131
+
132
+ <!-- Removed -->
133
+ <div class="bg-white shadow rounded-lg" x-show="results?.removed?.length > 0">
134
+ <div class="px-4 py-5 border-b border-gray-200 bg-red-50 rounded-t-lg">
135
+ <h3 class="text-lg font-medium text-red-800">Removed Resources</h3>
136
+ </div>
137
+ <ul class="divide-y divide-gray-200 max-h-96 overflow-y-auto">
138
+ <template x-for="r in results?.removed?.slice(0, 50)" :key="r.arn">
139
+ <li class="px-4 py-3">
140
+ <p class="text-sm font-medium text-gray-900" x-text="r.name"></p>
141
+ <p class="text-xs text-gray-500" x-text="r.resource_type"></p>
142
+ </li>
143
+ </template>
144
+ </ul>
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- Error -->
150
+ <div x-show="error" x-cloak class="bg-red-50 border border-red-200 rounded-lg p-4">
151
+ <p class="text-red-800" x-text="error"></p>
152
+ </div>
153
+ </div>
154
+ {% endblock %}
155
+
156
+ {% block scripts %}
157
+ <script>
158
+ function comparSnapshots() {
159
+ const data = Alpine.$data(document.querySelector('[x-data]'));
160
+ data.loading = true;
161
+ data.error = null;
162
+
163
+ fetch(`/api/resources/diff?snapshot1=${data.snapshot1}&snapshot2=${data.snapshot2}`)
164
+ .then(r => r.json())
165
+ .then(result => {
166
+ data.results = result;
167
+ data.loading = false;
168
+ })
169
+ .catch(err => {
170
+ data.error = err.message;
171
+ data.loading = false;
172
+ });
173
+ }
174
+ </script>
175
+ {% endblock %}
@@ -0,0 +1,30 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Error - AWS Inventory Browser{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="min-h-[60vh] flex items-center justify-center">
7
+ <div class="text-center">
8
+ <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-red-100 mb-6">
9
+ <svg class="h-8 w-8 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
10
+ <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"/>
11
+ </svg>
12
+ </div>
13
+ <h1 class="text-3xl font-bold text-gray-900 mb-2">{{ error_title | default('Something went wrong') }}</h1>
14
+ <p class="text-gray-600 mb-6 max-w-md mx-auto">{{ error_message | default('An unexpected error occurred. Please try again.') }}</p>
15
+ {% if error_details %}
16
+ <div class="bg-gray-100 rounded-lg p-4 mb-6 max-w-xl mx-auto text-left">
17
+ <p class="text-xs font-mono text-gray-600">{{ error_details }}</p>
18
+ </div>
19
+ {% endif %}
20
+ <div class="flex items-center justify-center space-x-4">
21
+ <a href="/" 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">
22
+ Go to Dashboard
23
+ </a>
24
+ <button onclick="window.history.back()" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
25
+ Go Back
26
+ </button>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ {% endblock %}