@localstack/appinspector-ui 1.0.191 → 1.0.193
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.
- package/dist/index.cjs +6 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -14
- package/dist/index.js.map +1 -1
- package/dist/spans-worker.js +1 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -15361,11 +15361,8 @@ function determinePermissionStatus(payload) {
|
|
|
15361
15361
|
if (payload.explicit_deny_count && payload.explicit_deny_count > 0) {
|
|
15362
15362
|
return "explicitly_denied";
|
|
15363
15363
|
}
|
|
15364
|
-
if (payload.is_allowed && payload.explicit_allow_count && payload.explicit_allow_count > 0) {
|
|
15365
|
-
return "explicitly_allowed";
|
|
15366
|
-
}
|
|
15367
15364
|
if (payload.is_allowed) {
|
|
15368
|
-
return "
|
|
15365
|
+
return "explicitly_allowed";
|
|
15369
15366
|
}
|
|
15370
15367
|
return "implicitly_denied";
|
|
15371
15368
|
}
|
|
@@ -15377,9 +15374,6 @@ function formatPermissionStatus(status) {
|
|
|
15377
15374
|
case "explicitly_denied": {
|
|
15378
15375
|
return "Explicitly Denied";
|
|
15379
15376
|
}
|
|
15380
|
-
case "implicitly_allowed": {
|
|
15381
|
-
return "Implicitly Allowed";
|
|
15382
|
-
}
|
|
15383
15377
|
case "implicitly_denied": {
|
|
15384
15378
|
return "Implicitly Denied";
|
|
15385
15379
|
}
|
|
@@ -15387,8 +15381,7 @@ function formatPermissionStatus(status) {
|
|
|
15387
15381
|
}
|
|
15388
15382
|
function getPermissionStatusColor(status) {
|
|
15389
15383
|
switch (status) {
|
|
15390
|
-
case "explicitly_allowed":
|
|
15391
|
-
case "implicitly_allowed": {
|
|
15384
|
+
case "explicitly_allowed": {
|
|
15392
15385
|
return "success.main";
|
|
15393
15386
|
}
|
|
15394
15387
|
case "explicitly_denied": {
|
|
@@ -16887,8 +16880,7 @@ var DetailRow = ({ content, label }) => /* @__PURE__ */ (0, import_jsx_runtime13
|
|
|
16887
16880
|
] });
|
|
16888
16881
|
var PermissionIcon = ({ status }) => {
|
|
16889
16882
|
switch (status) {
|
|
16890
|
-
case "explicitly_allowed":
|
|
16891
|
-
case "implicitly_allowed": {
|
|
16883
|
+
case "explicitly_allowed": {
|
|
16892
16884
|
return /* @__PURE__ */ (0, import_jsx_runtime133.jsx)(import_icons_material9.CheckCircle, { sx: { color: "success.main", fontSize: "1rem" } });
|
|
16893
16885
|
}
|
|
16894
16886
|
case "explicitly_denied": {
|
|
@@ -16979,7 +16971,7 @@ var IAMPermissionItem = ({ event, spanStatusCode }) => {
|
|
|
16979
16971
|
return null;
|
|
16980
16972
|
}
|
|
16981
16973
|
const { parsedIAM } = parsedData;
|
|
16982
|
-
const isAllowed = parsedIAM.permission === "explicitly_allowed"
|
|
16974
|
+
const isAllowed = parsedIAM.permission === "explicitly_allowed";
|
|
16983
16975
|
const chipColors = { backgroundColor: getChipColor(isAllowed, spanStatusCode), color: "white" };
|
|
16984
16976
|
return /* @__PURE__ */ (0, import_jsx_runtime134.jsxs)(import_material14.Box, { sx: { mb: 0.5 }, children: [
|
|
16985
16977
|
/* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_material14.Box, { sx: { alignItems: "center", display: "flex", gap: 1, mb: 0.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(import_material14.Chip, { label: formatPermissionStatus(parsedIAM.permission), size: "small", sx: { ...chipColors, fontWeight: 500 } }) }),
|
|
@@ -31494,7 +31486,7 @@ var EventDetails = ({ onClose, selectedEvent }) => {
|
|
|
31494
31486
|
variant: "scrollable",
|
|
31495
31487
|
children: [
|
|
31496
31488
|
/* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_material19.Tab, { "data-testid": DETAIL_TAB_DETAILS_TEST_ID, label: "Details", value: "details" }),
|
|
31497
|
-
/* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_material19.Tab, { "data-testid": DETAIL_TAB_REQUEST_TEST_ID, label: "
|
|
31489
|
+
/* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_material19.Tab, { "data-testid": DETAIL_TAB_REQUEST_TEST_ID, label: "Payload", value: "request" }),
|
|
31498
31490
|
/* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_material19.Tab, { "data-testid": DETAIL_TAB_RESPONSE_TEST_ID, label: "Response", value: "response" }),
|
|
31499
31491
|
showError && /* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_material19.Tab, { "data-testid": DETAIL_TAB_ERROR_TEST_ID, icon: /* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_icons_material11.Cancel, { color: "error", fontSize: "small" }), iconPosition: "end", label: "Error", value: "error" }),
|
|
31500
31492
|
showPermissions && /* @__PURE__ */ (0, import_jsx_runtime142.jsx)(import_material19.Tab, { "data-testid": DETAIL_TAB_PERMISSIONS_TEST_ID, icon: permissionsIcon, iconPosition: "end", label: "Permissions", value: "permissions" }),
|
|
@@ -33088,7 +33080,7 @@ var toSpanFilterProjection = (span) => ({
|
|
|
33088
33080
|
var import_react62 = require("react");
|
|
33089
33081
|
|
|
33090
33082
|
// inline-worker:/home/runner/work/localstack-appinspector-ui/localstack-appinspector-ui/src/workers/spans-worker.worker.ts
|
|
33091
|
-
var source = '(() => {\n // src/utils/query.ts\n var EXTERNAL_PRODUCER = "external";\n var QUERY_KEYS = ["service", "producer", "status", "op", "resource", "client", "region", "account", "dur", "trace"];\n var isQueryKey = (value) => QUERY_KEYS.includes(value);\n var TOKEN_PATTERN = /(?:[^\\s"]+|"[^"]*"?)+/g;\n var unquote = (value) => value.replaceAll(\'"\', "");\n var isExclusion = (token) => token.length > 1 && token.startsWith("-");\n var splitTerm = (token) => {\n const unquoted = unquote(isExclusion(token) ? token.slice(1) : token);\n const separator = unquoted.indexOf(":");\n if (separator <= 0) {\n return void 0;\n }\n const key = unquoted.slice(0, separator).toLowerCase();\n const value = unquoted.slice(separator + 1);\n return isQueryKey(key) && value !== "" ? [key, value] : void 0;\n };\n var parseQuery = (query) => {\n const parsed = { excludedTerms: {}, excludedWords: [], terms: {}, words: [] };\n for (const token of query.match(TOKEN_PATTERN) ?? []) {\n const excluded = isExclusion(token);\n const term = splitTerm(token);\n if (term) {\n const [key, value] = term;\n ((excluded ? parsed.excludedTerms : parsed.terms)[key] ??= []).push(value);\n } else {\n const word = unquote(excluded ? token.slice(1) : token).toLowerCase();\n if (word !== "") {\n (excluded ? parsed.excludedWords : parsed.words).push(word);\n }\n }\n }\n return parsed;\n };\n var hasExclusions = (parsed) => parsed.excludedWords.length > 0 || Object.keys(parsed.excludedTerms).length > 0;\n var isQueryEmpty = (parsed) => parsed.words.length === 0 && Object.keys(parsed.terms).length === 0 && !hasExclusions(parsed);\n var UNIT_TO_MS = { m: 6e4, ms: 1, s: 1e3, us: 1e-3 };\n var parseDuration = (value) => {\n const match = /^(>=|<=|>|<|=)?\\s*(\\d+(?:\\.\\d+)?)\\s*(us|ms|s|m)?$/i.exec(value.trim());\n if (!match) {\n return void 0;\n }\n const [, operator = ">=", amount = "0", unit = "ms"] = match;\n const threshold = Number(amount) * (UNIT_TO_MS[unit.toLowerCase()] ?? 1);\n switch (operator) {\n case "<": {\n return (durationMs) => durationMs < threshold;\n }\n case "<=": {\n return (durationMs) => durationMs <= threshold;\n }\n case "=": {\n return (durationMs) => Math.round(durationMs) === Math.round(threshold);\n }\n case ">": {\n return (durationMs) => durationMs > threshold;\n }\n default: {\n return (durationMs) => durationMs >= threshold;\n }\n }\n };\n var includesIgnoreCase = (haystack, needle) => haystack.toLowerCase().includes(needle.toLowerCase());\n var matchesTerm = (span, key, value) => {\n switch (key) {\n case "account": {\n return span.accountId === value;\n }\n case "client": {\n return span.triggerKind === value.toLowerCase();\n }\n case "dur": {\n const predicate = parseDuration(value);\n return predicate !== void 0 && span.durationMs !== void 0 && predicate(span.durationMs);\n }\n case "op": {\n return includesIgnoreCase(span.operationName, value);\n }\n case "producer": {\n return span.producer.toLowerCase() === value.toLowerCase();\n }\n case "region": {\n return span.region.toLowerCase() === value.toLowerCase();\n }\n case "resource": {\n return includesIgnoreCase(span.resourceName, value) || includesIgnoreCase(span.arn, value);\n }\n case "service": {\n return span.serviceName.toLowerCase() === value.toLowerCase();\n }\n case "status": {\n const status = value.toLowerCase();\n if (status === "error") return span.error;\n if (status === "warning" || status === "warn") return span.warning;\n if (status === "ok") return !span.error && !span.warning;\n return span.statusMessage?.includes(value) ?? false;\n }\n case "trace": {\n return span.traceId.startsWith(value.toLowerCase());\n }\n }\n };\n var matchesExclusion = (span, parsed) => Object.entries(parsed.excludedTerms).some(([key, values]) => values.some((value) => matchesTerm(span, key, value))) || parsed.excludedWords.some((word) => span.haystack.includes(word));\n var matchesQuery = (span, parsed, textMatches) => {\n if (matchesExclusion(span, parsed)) {\n return false;\n }\n for (const [key, values] of Object.entries(parsed.terms)) {\n if (!values.some((value) => matchesTerm(span, key, value))) {\n return false;\n }\n }\n if (parsed.words.length > 0 && !textMatches?.has(span.spanId)) {\n return parsed.words.every((word) => span.haystack.includes(word));\n }\n return true;\n };\n\n // src/utils/iam-utils.ts\n function determinePermissionStatus(payload) {\n if (payload.explicit_deny_count && payload.explicit_deny_count > 0) {\n return "explicitly_denied";\n }\n if (payload.is_allowed && payload.explicit_allow_count && payload.explicit_allow_count > 0) {\n return "explicitly_allowed";\n }\n if (payload.is_allowed) {\n return "implicitly_allowed";\n }\n return "implicitly_denied";\n }\n function parseIAMEvent(payloadString) {\n try {\n const payload = JSON.parse(payloadString);\n const permission = determinePermissionStatus(payload);\n const actions = [];\n const resources = [];\n if (payload.explicit_allows) {\n for (const allow of payload.explicit_allows) {\n if (!actions.includes(allow.action)) actions.push(allow.action);\n if (!resources.includes(allow.resource)) resources.push(allow.resource);\n }\n }\n if (payload.explicit_denies) {\n for (const deny of payload.explicit_denies) {\n if (!actions.includes(deny.action)) actions.push(deny.action);\n if (!resources.includes(deny.resource)) resources.push(deny.resource);\n }\n }\n if (payload.implicit_denies) {\n for (const deny of payload.implicit_denies) {\n if (!actions.includes(deny.action)) actions.push(deny.action);\n if (!resources.includes(deny.resource)) resources.push(deny.resource);\n }\n }\n return {\n details: {\n actions,\n explicitAllows: payload.explicit_allow_count,\n explicitDenies: payload.explicit_deny_count,\n implicitDenies: payload.implicit_deny_count,\n resources\n },\n operation: payload.operation,\n permission,\n principal: payload.principal_arn,\n service: payload.service\n };\n } catch (error) {\n console.error("Error parsing IAM event payload:", error);\n return void 0;\n }\n }\n\n // src/utils/span-issues.ts\n var firstText = (...values) => values.find((value) => !!value) ?? void 0;\n var OTEL_STATUS_ERROR = 2;\n var getIamDenials = (span) => span.events.flatMap((event) => {\n if (!event.event_type?.toLowerCase().includes("iam") || typeof event.attributes?.payload !== "string") {\n return [];\n }\n const permission = parseIAMEvent(event.attributes.payload)?.permission;\n if (permission === "explicitly_denied") return ["explicit"];\n if (permission === "implicitly_denied") return ["implicit"];\n return [];\n });\n var getSpanIssues = (span) => {\n const status = span.operation_status;\n const iamDenied = status === "iam_explicit_deny" || status === "iam_implicit_deny";\n const iamWarned = status === "iam_explicit_warn" || status === "iam_implicit_warn";\n const errorMessages = (span.errors ?? []).filter((item) => item.level === "error");\n const warningMessages = (span.errors ?? []).filter((item) => item.level === "warning");\n const denials = getIamDenials(span);\n const unenforcedDenial = !iamDenied && (denials.length > 0 || span.attributes?.["localstack.has_iam_deny_event"] === true);\n const error = status === "error" || iamDenied || span.status_code === OTEL_STATUS_ERROR || errorMessages.length > 0;\n const warning = iamWarned || warningMessages.length > 0 || unenforcedDenial;\n const denialCount = unenforcedDenial ? Math.max(1, denials.length) : 0;\n return {\n error,\n errorType: error ? firstText(span.status_message, errorMessages[0]?.message) ?? (iamDenied ? "AccessDenied" : "Error") : void 0,\n warning,\n warningCount: warning ? Math.max(1, warningMessages.length + denialCount) : 0,\n warningType: warning ? firstText(warningMessages[0]?.message) ?? (unenforcedDenial ? `IAM ${denials[0] ?? "implicit"} deny` : iamWarned ? "IAM permission warning" : "Warning") : void 0\n };\n };\n\n // src/utils/span-parents.ts\n var resolveParents = (spans) => {\n const byId = new Map(spans.map((span) => [span.span_id, span]));\n const parentOf = /* @__PURE__ */ new Map();\n for (const span of spans) {\n const parent = span.parent_span_id === null ? void 0 : byId.get(span.parent_span_id);\n if (parent && parent.span_id !== span.span_id && parent.trace_id === span.trace_id) {\n parentOf.set(span.span_id, parent.span_id);\n }\n }\n const causes = /* @__PURE__ */ new Map();\n const addCause = (spanId, causeId) => {\n const list = causes.get(spanId);\n if (list) list.push(causeId);\n else causes.set(spanId, [causeId]);\n };\n for (const span of spans) {\n for (const causeId of span.outgoing_link_span_ids ?? []) addCause(span.span_id, causeId);\n for (const effectId of span.incoming_link_span_ids ?? []) addCause(effectId, span.span_id);\n }\n const isAncestorOrSelf = (candidateId, spanId) => {\n let current = spanId;\n for (let hops = 0; current !== void 0 && hops <= spans.length; hops++) {\n if (current === candidateId) return true;\n current = parentOf.get(current);\n }\n return false;\n };\n for (const span of spans) {\n if (parentOf.has(span.span_id)) continue;\n const cause = causes.get(span.span_id)?.find((causeId) => byId.has(causeId) && !isAncestorOrSelf(span.span_id, causeId));\n if (cause !== void 0) parentOf.set(span.span_id, cause);\n }\n return parentOf;\n };\n\n // src/utils/trigger.ts\n var getTriggerKind = (span) => span.isInternal ? "internal" : "client";\n\n // src/workers/spans-reducer.ts\n var nanosToMilliseconds = (start, end) => {\n if (!end) return void 0;\n return Number(BigInt(end) - BigInt(start)) / 1e6;\n };\n var toCorpusEntry = (projection, arrivedAt) => {\n const issues = getSpanIssues(projection);\n return {\n accountId: projection.account_id,\n arn: projection.arn,\n arrivedAt,\n durationMs: nanosToMilliseconds(projection.start_time_unix_nano, projection.end_time_unix_nano),\n error: issues.error,\n haystack: [\n projection.service_name,\n projection.operation_name,\n projection.resource_name,\n projection.arn,\n projection.status_message,\n projection.region,\n projection.account_id,\n projection.trace_id,\n projection.span_id,\n ...(projection.errors ?? []).map((error) => error.message)\n ].filter(Boolean).join("\\n").toLowerCase(),\n issues,\n operationName: projection.operation_name,\n parents: projection,\n producer: projection.parent_service_name ?? EXTERNAL_PRODUCER,\n region: projection.region,\n resourceName: projection.resource_name,\n serviceName: projection.service_name,\n spanId: projection.span_id,\n startTime: projection.start_time_unix_nano,\n statusMessage: projection.status_message,\n traceId: projection.trace_id,\n triggerKind: getTriggerKind({ isInternal: projection.is_internal }),\n warning: issues.warning\n };\n };\n var compareByStartTime = (left, right) => {\n if (left.startTime.length !== right.startTime.length) {\n return left.startTime.length - right.startTime.length;\n }\n return left.startTime.localeCompare(right.startTime, "en");\n };\n var countFacet = (counts, value) => {\n counts.set(value, (counts.get(value) ?? 0) + 1);\n };\n var toFacetValues = (counts) => [...counts].map(([value, count]) => ({ count, value })).toSorted((left, right) => left.value.localeCompare(right.value));\n var createSpansReducer = ({ now = Date.now, orphanGraceMs = 0 } = {}) => {\n const corpus = /* @__PURE__ */ new Map();\n let parsedQuery = parseQuery("");\n let textMatches;\n let nextReveal;\n const computeResult = () => {\n const sorted = [...corpus.values()].toSorted(compareByStartTime);\n const queryActive = !isQueryEmpty(parsedQuery);\n const parentOf = resolveParents(sorted.map((entry) => entry.parents));\n const time = now();\n nextReveal = void 0;\n const waiting = /* @__PURE__ */ new Map();\n const isOrphanWaiting = (root) => {\n if (root.parents.parent_span_id === null && (root.parents.outgoing_link_span_ids?.length ?? 0) === 0) return false;\n const revealAt = root.arrivedAt + orphanGraceMs;\n if (time >= revealAt) return false;\n nextReveal = Math.min(nextReveal ?? revealAt, revealAt);\n return true;\n };\n const isWaiting = (entry) => {\n const chain = [];\n let current = entry;\n let result = false;\n while (current) {\n const known = waiting.get(current.spanId);\n if (known !== void 0) {\n result = known;\n break;\n }\n chain.push(current);\n const parent = corpus.get(parentOf.get(current.spanId) ?? "");\n if (!parent) result = isOrphanWaiting(current);\n current = parent;\n }\n for (const item of chain) waiting.set(item.spanId, result);\n return result;\n };\n const entries = orphanGraceMs > 0 ? sorted.filter((entry) => !isWaiting(entry)) : sorted;\n const children = /* @__PURE__ */ new Map();\n const topLevel = [];\n for (const entry of entries) {\n const parentId = parentOf.get(entry.spanId);\n if (parentId === void 0) {\n topLevel.push(entry);\n continue;\n }\n const siblings = children.get(parentId);\n if (siblings) siblings.push(entry);\n else children.set(parentId, [entry]);\n }\n const rows = {};\n const visit = (root) => {\n const stack = [{ entry: root, expanded: false }];\n while (stack.length > 0) {\n const frame = stack.at(-1);\n const kids = children.get(frame.entry.spanId) ?? [];\n if (!frame.expanded) {\n frame.expanded = true;\n for (const kid of kids) {\n if (!(kid.spanId in rows)) stack.push({ entry: kid, expanded: false });\n }\n continue;\n }\n stack.pop();\n const matches = !queryActive || matchesQuery(frame.entry, parsedQuery, textMatches);\n const row = {\n ...frame.entry.issues,\n downstream: { count: 0, errors: 0, warnings: 0 },\n matches,\n parentServiceName: corpus.get(parentOf.get(frame.entry.spanId) ?? "")?.serviceName,\n rootSpanId: root.spanId,\n treeMatches: matches ? 1 : 0,\n treeSize: 1\n };\n for (const kid of kids) {\n const kidRow = rows[kid.spanId];\n if (!kidRow) continue;\n row.downstream.count += kidRow.downstream.count + 1;\n row.downstream.errors += kidRow.downstream.errors + (kidRow.error ? 1 : 0);\n row.downstream.warnings += kidRow.downstream.warnings + (kidRow.warning ? 1 : 0);\n row.treeMatches += kidRow.treeMatches;\n row.treeSize += kidRow.treeSize;\n }\n rows[frame.entry.spanId] = row;\n }\n return rows[root.spanId]?.treeMatches ?? 0;\n };\n const topLevelIds = [];\n for (const entry of topLevel) {\n if (visit(entry) > 0) {\n topLevelIds.push(entry.spanId);\n }\n }\n const allIds = [];\n let excludedCount = 0;\n const excluding = hasExclusions(parsedQuery);\n const producers = /* @__PURE__ */ new Map();\n const services = /* @__PURE__ */ new Map();\n const regions = /* @__PURE__ */ new Map();\n const accounts = /* @__PURE__ */ new Map();\n const clients = /* @__PURE__ */ new Map();\n const issues = /* @__PURE__ */ new Map();\n for (const entry of entries) {\n if (rows[entry.spanId]?.matches) allIds.push(entry.spanId);\n if (excluding && matchesExclusion(entry, parsedQuery)) excludedCount++;\n countFacet(producers, entry.producer);\n countFacet(services, entry.serviceName);\n countFacet(regions, entry.region);\n countFacet(accounts, entry.accountId);\n countFacet(clients, entry.triggerKind);\n if (entry.error) countFacet(issues, "error");\n if (entry.warning) countFacet(issues, "warning");\n if (!entry.error && !entry.warning) countFacet(issues, "ok");\n }\n return {\n allIds: allIds.toReversed(),\n excludedCount,\n facets: {\n account: toFacetValues(accounts),\n client: toFacetValues(clients),\n issues: ["error", "warning", "ok"].map((value) => ({ count: issues.get(value) ?? 0, value })),\n producer: toFacetValues(producers),\n region: toFacetValues(regions),\n service: toFacetValues(services)\n },\n queryActive,\n rows,\n topLevelIds: topLevelIds.toReversed(),\n totalCount: entries.length\n };\n };\n return {\n addPage: (_direction, projections) => {\n for (const projection of projections) {\n corpus.set(projection.span_id, toCorpusEntry(projection, corpus.get(projection.span_id)?.arrivedAt ?? now()));\n }\n return computeResult();\n },\n clear: () => {\n corpus.clear();\n return computeResult();\n },\n nextReveal: () => nextReveal,\n refresh: computeResult,\n setQuery: (query, matches) => {\n parsedQuery = parseQuery(query);\n textMatches = matches ? new Set(matches) : void 0;\n return computeResult();\n }\n };\n };\n\n // src/workers/spans-worker.worker.ts\n var context = globalThis;\n var reducer = createSpansReducer({ orphanGraceMs: 3e3 });\n var revealTimer;\n var post = (result, seq) => {\n context.postMessage({ result, seq });\n clearTimeout(revealTimer);\n const revealAt = reducer.nextReveal();\n if (revealAt !== void 0) {\n revealTimer = setTimeout(() => {\n post(reducer.refresh(), seq);\n }, Math.max(0, revealAt - Date.now()));\n }\n };\n context.addEventListener("message", (event) => {\n const request = event.data;\n let result;\n switch (request.op) {\n case "addPage": {\n result = reducer.addPage(request.direction, request.projections);\n break;\n }\n case "clear": {\n result = reducer.clear();\n break;\n }\n case "setQuery": {\n result = reducer.setQuery(request.query, request.textMatches);\n break;\n }\n }\n post(result, request.seq);\n });\n})();\n';
|
|
33083
|
+
var source = '(() => {\n // src/utils/query.ts\n var EXTERNAL_PRODUCER = "external";\n var QUERY_KEYS = ["service", "producer", "status", "op", "resource", "client", "region", "account", "dur", "trace"];\n var isQueryKey = (value) => QUERY_KEYS.includes(value);\n var TOKEN_PATTERN = /(?:[^\\s"]+|"[^"]*"?)+/g;\n var unquote = (value) => value.replaceAll(\'"\', "");\n var isExclusion = (token) => token.length > 1 && token.startsWith("-");\n var splitTerm = (token) => {\n const unquoted = unquote(isExclusion(token) ? token.slice(1) : token);\n const separator = unquoted.indexOf(":");\n if (separator <= 0) {\n return void 0;\n }\n const key = unquoted.slice(0, separator).toLowerCase();\n const value = unquoted.slice(separator + 1);\n return isQueryKey(key) && value !== "" ? [key, value] : void 0;\n };\n var parseQuery = (query) => {\n const parsed = { excludedTerms: {}, excludedWords: [], terms: {}, words: [] };\n for (const token of query.match(TOKEN_PATTERN) ?? []) {\n const excluded = isExclusion(token);\n const term = splitTerm(token);\n if (term) {\n const [key, value] = term;\n ((excluded ? parsed.excludedTerms : parsed.terms)[key] ??= []).push(value);\n } else {\n const word = unquote(excluded ? token.slice(1) : token).toLowerCase();\n if (word !== "") {\n (excluded ? parsed.excludedWords : parsed.words).push(word);\n }\n }\n }\n return parsed;\n };\n var hasExclusions = (parsed) => parsed.excludedWords.length > 0 || Object.keys(parsed.excludedTerms).length > 0;\n var isQueryEmpty = (parsed) => parsed.words.length === 0 && Object.keys(parsed.terms).length === 0 && !hasExclusions(parsed);\n var UNIT_TO_MS = { m: 6e4, ms: 1, s: 1e3, us: 1e-3 };\n var parseDuration = (value) => {\n const match = /^(>=|<=|>|<|=)?\\s*(\\d+(?:\\.\\d+)?)\\s*(us|ms|s|m)?$/i.exec(value.trim());\n if (!match) {\n return void 0;\n }\n const [, operator = ">=", amount = "0", unit = "ms"] = match;\n const threshold = Number(amount) * (UNIT_TO_MS[unit.toLowerCase()] ?? 1);\n switch (operator) {\n case "<": {\n return (durationMs) => durationMs < threshold;\n }\n case "<=": {\n return (durationMs) => durationMs <= threshold;\n }\n case "=": {\n return (durationMs) => Math.round(durationMs) === Math.round(threshold);\n }\n case ">": {\n return (durationMs) => durationMs > threshold;\n }\n default: {\n return (durationMs) => durationMs >= threshold;\n }\n }\n };\n var includesIgnoreCase = (haystack, needle) => haystack.toLowerCase().includes(needle.toLowerCase());\n var matchesTerm = (span, key, value) => {\n switch (key) {\n case "account": {\n return span.accountId === value;\n }\n case "client": {\n return span.triggerKind === value.toLowerCase();\n }\n case "dur": {\n const predicate = parseDuration(value);\n return predicate !== void 0 && span.durationMs !== void 0 && predicate(span.durationMs);\n }\n case "op": {\n return includesIgnoreCase(span.operationName, value);\n }\n case "producer": {\n return span.producer.toLowerCase() === value.toLowerCase();\n }\n case "region": {\n return span.region.toLowerCase() === value.toLowerCase();\n }\n case "resource": {\n return includesIgnoreCase(span.resourceName, value) || includesIgnoreCase(span.arn, value);\n }\n case "service": {\n return span.serviceName.toLowerCase() === value.toLowerCase();\n }\n case "status": {\n const status = value.toLowerCase();\n if (status === "error") return span.error;\n if (status === "warning" || status === "warn") return span.warning;\n if (status === "ok") return !span.error && !span.warning;\n return span.statusMessage?.includes(value) ?? false;\n }\n case "trace": {\n return span.traceId.startsWith(value.toLowerCase());\n }\n }\n };\n var matchesExclusion = (span, parsed) => Object.entries(parsed.excludedTerms).some(([key, values]) => values.some((value) => matchesTerm(span, key, value))) || parsed.excludedWords.some((word) => span.haystack.includes(word));\n var matchesQuery = (span, parsed, textMatches) => {\n if (matchesExclusion(span, parsed)) {\n return false;\n }\n for (const [key, values] of Object.entries(parsed.terms)) {\n if (!values.some((value) => matchesTerm(span, key, value))) {\n return false;\n }\n }\n if (parsed.words.length > 0 && !textMatches?.has(span.spanId)) {\n return parsed.words.every((word) => span.haystack.includes(word));\n }\n return true;\n };\n\n // src/utils/iam-utils.ts\n function determinePermissionStatus(payload) {\n if (payload.explicit_deny_count && payload.explicit_deny_count > 0) {\n return "explicitly_denied";\n }\n if (payload.is_allowed) {\n return "explicitly_allowed";\n }\n return "implicitly_denied";\n }\n function parseIAMEvent(payloadString) {\n try {\n const payload = JSON.parse(payloadString);\n const permission = determinePermissionStatus(payload);\n const actions = [];\n const resources = [];\n if (payload.explicit_allows) {\n for (const allow of payload.explicit_allows) {\n if (!actions.includes(allow.action)) actions.push(allow.action);\n if (!resources.includes(allow.resource)) resources.push(allow.resource);\n }\n }\n if (payload.explicit_denies) {\n for (const deny of payload.explicit_denies) {\n if (!actions.includes(deny.action)) actions.push(deny.action);\n if (!resources.includes(deny.resource)) resources.push(deny.resource);\n }\n }\n if (payload.implicit_denies) {\n for (const deny of payload.implicit_denies) {\n if (!actions.includes(deny.action)) actions.push(deny.action);\n if (!resources.includes(deny.resource)) resources.push(deny.resource);\n }\n }\n return {\n details: {\n actions,\n explicitAllows: payload.explicit_allow_count,\n explicitDenies: payload.explicit_deny_count,\n implicitDenies: payload.implicit_deny_count,\n resources\n },\n operation: payload.operation,\n permission,\n principal: payload.principal_arn,\n service: payload.service\n };\n } catch (error) {\n console.error("Error parsing IAM event payload:", error);\n return void 0;\n }\n }\n\n // src/utils/span-issues.ts\n var firstText = (...values) => values.find((value) => !!value) ?? void 0;\n var OTEL_STATUS_ERROR = 2;\n var getIamDenials = (span) => span.events.flatMap((event) => {\n if (!event.event_type?.toLowerCase().includes("iam") || typeof event.attributes?.payload !== "string") {\n return [];\n }\n const permission = parseIAMEvent(event.attributes.payload)?.permission;\n if (permission === "explicitly_denied") return ["explicit"];\n if (permission === "implicitly_denied") return ["implicit"];\n return [];\n });\n var getSpanIssues = (span) => {\n const status = span.operation_status;\n const iamDenied = status === "iam_explicit_deny" || status === "iam_implicit_deny";\n const iamWarned = status === "iam_explicit_warn" || status === "iam_implicit_warn";\n const errorMessages = (span.errors ?? []).filter((item) => item.level === "error");\n const warningMessages = (span.errors ?? []).filter((item) => item.level === "warning");\n const denials = getIamDenials(span);\n const unenforcedDenial = !iamDenied && (denials.length > 0 || span.attributes?.["localstack.has_iam_deny_event"] === true);\n const error = status === "error" || iamDenied || span.status_code === OTEL_STATUS_ERROR || errorMessages.length > 0;\n const warning = iamWarned || warningMessages.length > 0 || unenforcedDenial;\n const denialCount = unenforcedDenial ? Math.max(1, denials.length) : 0;\n return {\n error,\n errorType: error ? firstText(span.status_message, errorMessages[0]?.message) ?? (iamDenied ? "AccessDenied" : "Error") : void 0,\n warning,\n warningCount: warning ? Math.max(1, warningMessages.length + denialCount) : 0,\n warningType: warning ? firstText(warningMessages[0]?.message) ?? (unenforcedDenial ? `IAM ${denials[0] ?? "implicit"} deny` : iamWarned ? "IAM permission warning" : "Warning") : void 0\n };\n };\n\n // src/utils/span-parents.ts\n var resolveParents = (spans) => {\n const byId = new Map(spans.map((span) => [span.span_id, span]));\n const parentOf = /* @__PURE__ */ new Map();\n for (const span of spans) {\n const parent = span.parent_span_id === null ? void 0 : byId.get(span.parent_span_id);\n if (parent && parent.span_id !== span.span_id && parent.trace_id === span.trace_id) {\n parentOf.set(span.span_id, parent.span_id);\n }\n }\n const causes = /* @__PURE__ */ new Map();\n const addCause = (spanId, causeId) => {\n const list = causes.get(spanId);\n if (list) list.push(causeId);\n else causes.set(spanId, [causeId]);\n };\n for (const span of spans) {\n for (const causeId of span.outgoing_link_span_ids ?? []) addCause(span.span_id, causeId);\n for (const effectId of span.incoming_link_span_ids ?? []) addCause(effectId, span.span_id);\n }\n const isAncestorOrSelf = (candidateId, spanId) => {\n let current = spanId;\n for (let hops = 0; current !== void 0 && hops <= spans.length; hops++) {\n if (current === candidateId) return true;\n current = parentOf.get(current);\n }\n return false;\n };\n for (const span of spans) {\n if (parentOf.has(span.span_id)) continue;\n const cause = causes.get(span.span_id)?.find((causeId) => byId.has(causeId) && !isAncestorOrSelf(span.span_id, causeId));\n if (cause !== void 0) parentOf.set(span.span_id, cause);\n }\n return parentOf;\n };\n\n // src/utils/trigger.ts\n var getTriggerKind = (span) => span.isInternal ? "internal" : "client";\n\n // src/workers/spans-reducer.ts\n var nanosToMilliseconds = (start, end) => {\n if (!end) return void 0;\n return Number(BigInt(end) - BigInt(start)) / 1e6;\n };\n var toCorpusEntry = (projection, arrivedAt) => {\n const issues = getSpanIssues(projection);\n return {\n accountId: projection.account_id,\n arn: projection.arn,\n arrivedAt,\n durationMs: nanosToMilliseconds(projection.start_time_unix_nano, projection.end_time_unix_nano),\n error: issues.error,\n haystack: [\n projection.service_name,\n projection.operation_name,\n projection.resource_name,\n projection.arn,\n projection.status_message,\n projection.region,\n projection.account_id,\n projection.trace_id,\n projection.span_id,\n ...(projection.errors ?? []).map((error) => error.message)\n ].filter(Boolean).join("\\n").toLowerCase(),\n issues,\n operationName: projection.operation_name,\n parents: projection,\n producer: projection.parent_service_name ?? EXTERNAL_PRODUCER,\n region: projection.region,\n resourceName: projection.resource_name,\n serviceName: projection.service_name,\n spanId: projection.span_id,\n startTime: projection.start_time_unix_nano,\n statusMessage: projection.status_message,\n traceId: projection.trace_id,\n triggerKind: getTriggerKind({ isInternal: projection.is_internal }),\n warning: issues.warning\n };\n };\n var compareByStartTime = (left, right) => {\n if (left.startTime.length !== right.startTime.length) {\n return left.startTime.length - right.startTime.length;\n }\n return left.startTime.localeCompare(right.startTime, "en");\n };\n var countFacet = (counts, value) => {\n counts.set(value, (counts.get(value) ?? 0) + 1);\n };\n var toFacetValues = (counts) => [...counts].map(([value, count]) => ({ count, value })).toSorted((left, right) => left.value.localeCompare(right.value));\n var createSpansReducer = ({ now = Date.now, orphanGraceMs = 0 } = {}) => {\n const corpus = /* @__PURE__ */ new Map();\n let parsedQuery = parseQuery("");\n let textMatches;\n let nextReveal;\n const computeResult = () => {\n const sorted = [...corpus.values()].toSorted(compareByStartTime);\n const queryActive = !isQueryEmpty(parsedQuery);\n const parentOf = resolveParents(sorted.map((entry) => entry.parents));\n const time = now();\n nextReveal = void 0;\n const waiting = /* @__PURE__ */ new Map();\n const isOrphanWaiting = (root) => {\n if (root.parents.parent_span_id === null && (root.parents.outgoing_link_span_ids?.length ?? 0) === 0) return false;\n const revealAt = root.arrivedAt + orphanGraceMs;\n if (time >= revealAt) return false;\n nextReveal = Math.min(nextReveal ?? revealAt, revealAt);\n return true;\n };\n const isWaiting = (entry) => {\n const chain = [];\n let current = entry;\n let result = false;\n while (current) {\n const known = waiting.get(current.spanId);\n if (known !== void 0) {\n result = known;\n break;\n }\n chain.push(current);\n const parent = corpus.get(parentOf.get(current.spanId) ?? "");\n if (!parent) result = isOrphanWaiting(current);\n current = parent;\n }\n for (const item of chain) waiting.set(item.spanId, result);\n return result;\n };\n const entries = orphanGraceMs > 0 ? sorted.filter((entry) => !isWaiting(entry)) : sorted;\n const children = /* @__PURE__ */ new Map();\n const topLevel = [];\n for (const entry of entries) {\n const parentId = parentOf.get(entry.spanId);\n if (parentId === void 0) {\n topLevel.push(entry);\n continue;\n }\n const siblings = children.get(parentId);\n if (siblings) siblings.push(entry);\n else children.set(parentId, [entry]);\n }\n const rows = {};\n const visit = (root) => {\n const stack = [{ entry: root, expanded: false }];\n while (stack.length > 0) {\n const frame = stack.at(-1);\n const kids = children.get(frame.entry.spanId) ?? [];\n if (!frame.expanded) {\n frame.expanded = true;\n for (const kid of kids) {\n if (!(kid.spanId in rows)) stack.push({ entry: kid, expanded: false });\n }\n continue;\n }\n stack.pop();\n const matches = !queryActive || matchesQuery(frame.entry, parsedQuery, textMatches);\n const row = {\n ...frame.entry.issues,\n downstream: { count: 0, errors: 0, warnings: 0 },\n matches,\n parentServiceName: corpus.get(parentOf.get(frame.entry.spanId) ?? "")?.serviceName,\n rootSpanId: root.spanId,\n treeMatches: matches ? 1 : 0,\n treeSize: 1\n };\n for (const kid of kids) {\n const kidRow = rows[kid.spanId];\n if (!kidRow) continue;\n row.downstream.count += kidRow.downstream.count + 1;\n row.downstream.errors += kidRow.downstream.errors + (kidRow.error ? 1 : 0);\n row.downstream.warnings += kidRow.downstream.warnings + (kidRow.warning ? 1 : 0);\n row.treeMatches += kidRow.treeMatches;\n row.treeSize += kidRow.treeSize;\n }\n rows[frame.entry.spanId] = row;\n }\n return rows[root.spanId]?.treeMatches ?? 0;\n };\n const topLevelIds = [];\n for (const entry of topLevel) {\n if (visit(entry) > 0) {\n topLevelIds.push(entry.spanId);\n }\n }\n const allIds = [];\n let excludedCount = 0;\n const excluding = hasExclusions(parsedQuery);\n const producers = /* @__PURE__ */ new Map();\n const services = /* @__PURE__ */ new Map();\n const regions = /* @__PURE__ */ new Map();\n const accounts = /* @__PURE__ */ new Map();\n const clients = /* @__PURE__ */ new Map();\n const issues = /* @__PURE__ */ new Map();\n for (const entry of entries) {\n if (rows[entry.spanId]?.matches) allIds.push(entry.spanId);\n if (excluding && matchesExclusion(entry, parsedQuery)) excludedCount++;\n countFacet(producers, entry.producer);\n countFacet(services, entry.serviceName);\n countFacet(regions, entry.region);\n countFacet(accounts, entry.accountId);\n countFacet(clients, entry.triggerKind);\n if (entry.error) countFacet(issues, "error");\n if (entry.warning) countFacet(issues, "warning");\n if (!entry.error && !entry.warning) countFacet(issues, "ok");\n }\n return {\n allIds: allIds.toReversed(),\n excludedCount,\n facets: {\n account: toFacetValues(accounts),\n client: toFacetValues(clients),\n issues: ["error", "warning", "ok"].map((value) => ({ count: issues.get(value) ?? 0, value })),\n producer: toFacetValues(producers),\n region: toFacetValues(regions),\n service: toFacetValues(services)\n },\n queryActive,\n rows,\n topLevelIds: topLevelIds.toReversed(),\n totalCount: entries.length\n };\n };\n return {\n addPage: (_direction, projections) => {\n for (const projection of projections) {\n corpus.set(projection.span_id, toCorpusEntry(projection, corpus.get(projection.span_id)?.arrivedAt ?? now()));\n }\n return computeResult();\n },\n clear: () => {\n corpus.clear();\n return computeResult();\n },\n nextReveal: () => nextReveal,\n refresh: computeResult,\n setQuery: (query, matches) => {\n parsedQuery = parseQuery(query);\n textMatches = matches ? new Set(matches) : void 0;\n return computeResult();\n }\n };\n };\n\n // src/workers/spans-worker.worker.ts\n var context = globalThis;\n var reducer = createSpansReducer({ orphanGraceMs: 3e3 });\n var revealTimer;\n var post = (result, seq) => {\n context.postMessage({ result, seq });\n clearTimeout(revealTimer);\n const revealAt = reducer.nextReveal();\n if (revealAt !== void 0) {\n revealTimer = setTimeout(() => {\n post(reducer.refresh(), seq);\n }, Math.max(0, revealAt - Date.now()));\n }\n };\n context.addEventListener("message", (event) => {\n const request = event.data;\n let result;\n switch (request.op) {\n case "addPage": {\n result = reducer.addPage(request.direction, request.projections);\n break;\n }\n case "clear": {\n result = reducer.clear();\n break;\n }\n case "setQuery": {\n result = reducer.setQuery(request.query, request.textMatches);\n break;\n }\n }\n post(result, request.seq);\n });\n})();\n';
|
|
33092
33084
|
function createInlineWorker() {
|
|
33093
33085
|
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
33094
33086
|
try {
|