@localstack/appinspector-ui 1.0.173 → 1.0.175

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.
@@ -0,0 +1,242 @@
1
+ (() => {
2
+ // src/types/error-levels.ts
3
+ var ErrorLevel = {
4
+ ERROR: 3,
5
+ // General error
6
+ IAM_PERMISSION: 5,
7
+ // IAM permission issues
8
+ OK: 2,
9
+ // OK
10
+ // Standard OTEL status codes
11
+ UNSET: 1,
12
+ // Unset
13
+ // Extended custom levels
14
+ WARNING: 4
15
+ // Warning
16
+ };
17
+
18
+ // src/utils/iam-utils.ts
19
+ function determinePermissionStatus(payload) {
20
+ if (payload.explicit_deny_count && payload.explicit_deny_count > 0) {
21
+ return "explicitly_denied";
22
+ }
23
+ if (payload.is_allowed && payload.explicit_allow_count && payload.explicit_allow_count > 0) {
24
+ return "explicitly_allowed";
25
+ }
26
+ if (payload.is_allowed) {
27
+ return "implicitly_allowed";
28
+ }
29
+ return "implicitly_denied";
30
+ }
31
+ function parseIAMEvent(payloadString) {
32
+ try {
33
+ const payload = JSON.parse(payloadString);
34
+ const permission = determinePermissionStatus(payload);
35
+ const actions = [];
36
+ const resources = [];
37
+ if (payload.explicit_allows) {
38
+ for (const allow of payload.explicit_allows) {
39
+ if (!actions.includes(allow.action)) actions.push(allow.action);
40
+ if (!resources.includes(allow.resource)) resources.push(allow.resource);
41
+ }
42
+ }
43
+ if (payload.explicit_denies) {
44
+ for (const deny of payload.explicit_denies) {
45
+ if (!actions.includes(deny.action)) actions.push(deny.action);
46
+ if (!resources.includes(deny.resource)) resources.push(deny.resource);
47
+ }
48
+ }
49
+ if (payload.implicit_denies) {
50
+ for (const deny of payload.implicit_denies) {
51
+ if (!actions.includes(deny.action)) actions.push(deny.action);
52
+ if (!resources.includes(deny.resource)) resources.push(deny.resource);
53
+ }
54
+ }
55
+ return {
56
+ details: {
57
+ actions,
58
+ explicitAllows: payload.explicit_allow_count,
59
+ explicitDenies: payload.explicit_deny_count,
60
+ implicitDenies: payload.implicit_deny_count,
61
+ resources
62
+ },
63
+ operation: payload.operation,
64
+ permission,
65
+ principal: payload.principal_arn,
66
+ service: payload.service
67
+ };
68
+ } catch (error) {
69
+ console.error("Error parsing IAM event payload:", error);
70
+ return void 0;
71
+ }
72
+ }
73
+
74
+ // src/utils/error-level.ts
75
+ var calculateHighestErrorLevel = (span) => {
76
+ if (span.operation_status) {
77
+ switch (span.operation_status) {
78
+ case "error": {
79
+ return ErrorLevel.ERROR;
80
+ }
81
+ case "iam_explicit_deny":
82
+ case "iam_explicit_warn":
83
+ case "iam_implicit_deny":
84
+ case "iam_implicit_warn": {
85
+ return ErrorLevel.IAM_PERMISSION;
86
+ }
87
+ default: {
88
+ return ErrorLevel.OK;
89
+ }
90
+ }
91
+ }
92
+ const hasIamDenial = span.events.some((event) => {
93
+ if (!event.event_type?.toLowerCase().includes("iam")) return false;
94
+ if (typeof event.attributes?.payload !== "string") return false;
95
+ const parsed = parseIAMEvent(event.attributes.payload);
96
+ return parsed?.permission === "explicitly_denied" || parsed?.permission === "implicitly_denied";
97
+ });
98
+ if (hasIamDenial) return ErrorLevel.IAM_PERMISSION;
99
+ if (span.status_code === 2) return ErrorLevel.ERROR;
100
+ const hasErrorInErrors = span.errors?.some((error) => error.level === "error");
101
+ if (hasErrorInErrors) return ErrorLevel.ERROR;
102
+ const hasWarning = span.errors?.some((error) => error.level === "warning");
103
+ if (hasWarning) return ErrorLevel.WARNING;
104
+ return span.status_code || ErrorLevel.UNSET;
105
+ };
106
+
107
+ // src/utils/spans-filters.ts
108
+ var EMPTY_FILTERS = {
109
+ consumerService: [],
110
+ producerService: [],
111
+ status: []
112
+ };
113
+ var EXTERNAL_PRODUCER_VALUE = "__external__";
114
+ function isFiltersEmpty(filters) {
115
+ return filters.consumerService.length === 0 && filters.producerService.length === 0 && filters.status.length === 0;
116
+ }
117
+ var collapseStatusValue = (status) => {
118
+ if (status === "iam_explicit_deny" || status === "iam_implicit_deny") return "iam_deny";
119
+ if (status === "iam_explicit_warn" || status === "iam_implicit_warn") return "iam_warn";
120
+ return status;
121
+ };
122
+ function applyFilters(spans, filters) {
123
+ if (isFiltersEmpty(filters)) return spans;
124
+ return spans.filter((span) => {
125
+ if (filters.consumerService.length > 0 && !filters.consumerService.includes(span.service_name)) {
126
+ return false;
127
+ }
128
+ if (filters.producerService.length > 0) {
129
+ const includesExternal = filters.producerService.includes(EXTERNAL_PRODUCER_VALUE);
130
+ const namedServices = filters.producerService.filter((value) => value !== EXTERNAL_PRODUCER_VALUE);
131
+ const matchesExternal = includesExternal && !span.parent_service_name;
132
+ const matchesNamed = namedServices.length > 0 && !!span.parent_service_name && namedServices.includes(span.parent_service_name);
133
+ if (!matchesExternal && !matchesNamed) return false;
134
+ }
135
+ if (filters.status.length > 0) {
136
+ if (!span.operation_status) return false;
137
+ if (!filters.status.includes(collapseStatusValue(span.operation_status))) return false;
138
+ }
139
+ return true;
140
+ });
141
+ }
142
+
143
+ // src/workers/spans-reducer.ts
144
+ var compareByStartTime = (a, b) => a.start_time_unix_nano < b.start_time_unix_nano ? -1 : 1;
145
+ var insertSorted = (array, item, compareFunction, hint) => {
146
+ let left;
147
+ let right;
148
+ if (hint === "back") {
149
+ left = array.length;
150
+ right = array.length;
151
+ let step = 1;
152
+ while (left > 0 && compareFunction(array[left - 1], item) > 0) {
153
+ right = left;
154
+ left = Math.max(0, left - step);
155
+ step *= 2;
156
+ }
157
+ } else {
158
+ left = 0;
159
+ right = 0;
160
+ let step = 1;
161
+ while (right < array.length && compareFunction(array[right], item) <= 0) {
162
+ left = right + 1;
163
+ right = Math.min(array.length, right + step);
164
+ step *= 2;
165
+ }
166
+ }
167
+ while (left < right) {
168
+ const mid = left + right >>> 1;
169
+ if (compareFunction(array[mid], item) <= 0) {
170
+ left = mid + 1;
171
+ } else {
172
+ right = mid;
173
+ }
174
+ }
175
+ array.splice(left, 0, item);
176
+ };
177
+ var createSpansReducer = () => {
178
+ let corpus = [];
179
+ let filters = EMPTY_FILTERS;
180
+ const computeResult = () => {
181
+ const filtered = applyFilters(corpus, filters);
182
+ let errorCount = 0;
183
+ let warningCount = 0;
184
+ const orderedFilteredIds = [];
185
+ for (const entry of filtered) {
186
+ orderedFilteredIds.push(entry.span_id);
187
+ if (entry.errorLevel === ErrorLevel.ERROR || entry.errorLevel === ErrorLevel.IAM_PERMISSION) {
188
+ errorCount++;
189
+ } else if (entry.errorLevel === ErrorLevel.WARNING) {
190
+ warningCount++;
191
+ }
192
+ }
193
+ const producers = /* @__PURE__ */ new Set();
194
+ const consumers = /* @__PURE__ */ new Set();
195
+ const statuses = /* @__PURE__ */ new Set();
196
+ let hasExternalProducer = false;
197
+ for (const entry of corpus) {
198
+ if (entry.parent_service_name) producers.add(entry.parent_service_name);
199
+ else hasExternalProducer = true;
200
+ consumers.add(entry.service_name);
201
+ if (entry.operation_status) statuses.add(collapseStatusValue(entry.operation_status));
202
+ }
203
+ return {
204
+ consumerValues: [...consumers].toSorted(),
205
+ errorCount,
206
+ hasExternalProducer,
207
+ orderedFilteredIds,
208
+ producerValues: [...producers].toSorted(),
209
+ statusValues: [...statuses].toSorted(),
210
+ warningCount
211
+ };
212
+ };
213
+ return {
214
+ addPage: (direction, projections) => {
215
+ const hint = direction === "forward" ? "back" : "front";
216
+ const sorted = projections.toSorted(compareByStartTime);
217
+ for (const projection of sorted) {
218
+ insertSorted(corpus, { ...projection, errorLevel: calculateHighestErrorLevel(projection) }, compareByStartTime, hint);
219
+ }
220
+ return computeResult();
221
+ },
222
+ clear: () => {
223
+ corpus = [];
224
+ filters = EMPTY_FILTERS;
225
+ return computeResult();
226
+ },
227
+ setFilters: (next) => {
228
+ filters = next;
229
+ return computeResult();
230
+ }
231
+ };
232
+ };
233
+
234
+ // src/workers/spans-worker.worker.ts
235
+ var context = globalThis;
236
+ var reducer = createSpansReducer();
237
+ context.addEventListener("message", (event) => {
238
+ const request = event.data;
239
+ const result = request.op === "addPage" ? reducer.addPage(request.direction, request.projections) : request.op === "setFilters" ? reducer.setFilters(request.filters) : reducer.clear();
240
+ context.postMessage({ result, seq: request.seq });
241
+ });
242
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localstack/appinspector-ui",
3
- "version": "1.0.173",
3
+ "version": "1.0.175",
4
4
  "description": "LocalStack AppInspector UI",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -14,7 +14,8 @@
14
14
  "types": "./dist/routes.d.ts",
15
15
  "import": "./dist/routes.js",
16
16
  "require": "./dist/routes.cjs"
17
- }
17
+ },
18
+ "./spans-worker.js": "./dist/spans-worker.js"
18
19
  },
19
20
  "files": [
20
21
  "dist"