@localstack/appinspector-ui 1.0.174 → 1.0.176
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 +86 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +81 -81
- package/dist/index.js.map +1 -1
- package/dist/spans-worker.js +242 -0
- package/package.json +3 -2
package/dist/index.d.cts
CHANGED
|
@@ -40,6 +40,12 @@ interface AppInspectorContextProviderProps {
|
|
|
40
40
|
* The prefix for the event studio routes.
|
|
41
41
|
*/
|
|
42
42
|
routePrefix: string;
|
|
43
|
+
/**
|
|
44
|
+
* URL of the standalone worker shipped as `@localstack/appinspector-ui/spans-worker.js`,
|
|
45
|
+
* served by the host from its own origin. When omitted, the worker is started from an
|
|
46
|
+
* inlined `blob:` URL (needs `worker-src blob:` in the CSP).
|
|
47
|
+
*/
|
|
48
|
+
spansWorkerUrl?: string;
|
|
43
49
|
}
|
|
44
50
|
declare const AppInspectorContextProvider: (props: PropsWithChildren<AppInspectorContextProviderProps>) => react_jsx_runtime.JSX.Element;
|
|
45
51
|
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ interface AppInspectorContextProviderProps {
|
|
|
40
40
|
* The prefix for the event studio routes.
|
|
41
41
|
*/
|
|
42
42
|
routePrefix: string;
|
|
43
|
+
/**
|
|
44
|
+
* URL of the standalone worker shipped as `@localstack/appinspector-ui/spans-worker.js`,
|
|
45
|
+
* served by the host from its own origin. When omitted, the worker is started from an
|
|
46
|
+
* inlined `blob:` URL (needs `worker-src blob:` in the CSP).
|
|
47
|
+
*/
|
|
48
|
+
spansWorkerUrl?: string;
|
|
43
49
|
}
|
|
44
50
|
declare const AppInspectorContextProvider: (props: PropsWithChildren<AppInspectorContextProviderProps>) => react_jsx_runtime.JSX.Element;
|
|
45
51
|
|
package/dist/index.js
CHANGED
|
@@ -16984,76 +16984,14 @@ var toSpanFilterProjection = (span) => ({
|
|
|
16984
16984
|
});
|
|
16985
16985
|
|
|
16986
16986
|
// src/hooks/use-spans-compute.ts
|
|
16987
|
-
import { useCallback as
|
|
16987
|
+
import { useCallback as useCallback6, useEffect as useEffect15, useRef as useRef9, useState as useState10 } from "react";
|
|
16988
16988
|
|
|
16989
|
-
//
|
|
16990
|
-
|
|
16991
|
-
|
|
16992
|
-
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
16993
|
-
try {
|
|
16994
|
-
return new Worker(url);
|
|
16995
|
-
} finally {
|
|
16996
|
-
URL.revokeObjectURL(url);
|
|
16997
|
-
}
|
|
16998
|
-
}
|
|
16999
|
-
|
|
17000
|
-
// src/hooks/use-spans-compute.ts
|
|
17001
|
-
var EMPTY_RESULT = {
|
|
17002
|
-
consumerValues: [],
|
|
17003
|
-
errorCount: 0,
|
|
17004
|
-
hasExternalProducer: false,
|
|
17005
|
-
orderedFilteredIds: [],
|
|
17006
|
-
producerValues: [],
|
|
17007
|
-
statusValues: [],
|
|
17008
|
-
warningCount: 0
|
|
17009
|
-
};
|
|
17010
|
-
var useSpansCompute = () => {
|
|
17011
|
-
const [result, setResult] = useState10(EMPTY_RESULT);
|
|
17012
|
-
const [workerError, setWorkerError] = useState10();
|
|
17013
|
-
const [retryCount, setRetryCount] = useState10(0);
|
|
17014
|
-
const workerRef = useRef8();
|
|
17015
|
-
const seqRef = useRef8(0);
|
|
17016
|
-
const lastAppliedRef = useRef8(0);
|
|
17017
|
-
useEffect14(() => {
|
|
17018
|
-
try {
|
|
17019
|
-
const worker = createInlineWorker();
|
|
17020
|
-
worker.addEventListener("message", (event) => {
|
|
17021
|
-
const { result: nextResult, seq } = event.data;
|
|
17022
|
-
if (seq < lastAppliedRef.current) return;
|
|
17023
|
-
lastAppliedRef.current = seq;
|
|
17024
|
-
setResult(nextResult);
|
|
17025
|
-
});
|
|
17026
|
-
worker.addEventListener("error", (event) => {
|
|
17027
|
-
setWorkerError(new Error(event.message || "Spans filter worker crashed."));
|
|
17028
|
-
});
|
|
17029
|
-
workerRef.current = worker;
|
|
17030
|
-
return () => {
|
|
17031
|
-
worker.terminate();
|
|
17032
|
-
workerRef.current = void 0;
|
|
17033
|
-
};
|
|
17034
|
-
} catch (error) {
|
|
17035
|
-
setWorkerError(error instanceof Error ? error : new Error("Failed to start the spans filter worker."));
|
|
17036
|
-
return;
|
|
17037
|
-
}
|
|
17038
|
-
}, [retryCount]);
|
|
17039
|
-
const retryWorker = useCallback5(() => {
|
|
17040
|
-
setWorkerError(void 0);
|
|
17041
|
-
setRetryCount((count2) => count2 + 1);
|
|
17042
|
-
}, []);
|
|
17043
|
-
const addPage = useCallback5((direction, projections) => {
|
|
17044
|
-
workerRef.current?.postMessage({ direction, op: "addPage", projections, seq: ++seqRef.current });
|
|
17045
|
-
}, []);
|
|
17046
|
-
const setFilters = useCallback5((filters) => {
|
|
17047
|
-
workerRef.current?.postMessage({ filters, op: "setFilters", seq: ++seqRef.current });
|
|
17048
|
-
}, []);
|
|
17049
|
-
const clear = useCallback5(() => {
|
|
17050
|
-
workerRef.current?.postMessage({ op: "clear", seq: ++seqRef.current });
|
|
17051
|
-
}, []);
|
|
17052
|
-
return { addPage, clear, result, retryWorker, setFilters, workerError };
|
|
17053
|
-
};
|
|
16989
|
+
// src/context.tsx
|
|
16990
|
+
import { createContext as createContext9, useContext as useContext15 } from "react";
|
|
16991
|
+
import { useCallback as useCallback5, useMemo as useMemo5 } from "react";
|
|
17054
16992
|
|
|
17055
|
-
// src/
|
|
17056
|
-
|
|
16993
|
+
// src/api/client.ts
|
|
16994
|
+
var import_semver2 = __toESM(require_semver2(), 1);
|
|
17057
16995
|
|
|
17058
16996
|
// src/config.ts
|
|
17059
16997
|
var APPINSPECTOR_API_PREFIX = "/_localstack/appinspector";
|
|
@@ -17081,13 +17019,6 @@ var getAppInspectorApiUrl = (localstackEndpoint, endpoint) => {
|
|
|
17081
17019
|
};
|
|
17082
17020
|
var LOCALSTACK_INFO_PATH = "/_localstack/info";
|
|
17083
17021
|
|
|
17084
|
-
// src/context.tsx
|
|
17085
|
-
import { createContext as createContext9, useContext as useContext15 } from "react";
|
|
17086
|
-
import { useCallback as useCallback6, useMemo as useMemo5 } from "react";
|
|
17087
|
-
|
|
17088
|
-
// src/api/client.ts
|
|
17089
|
-
var import_semver2 = __toESM(require_semver2(), 1);
|
|
17090
|
-
|
|
17091
17022
|
// src/constants/error-messages.ts
|
|
17092
17023
|
var ERROR_MESSAGES = {
|
|
17093
17024
|
APP_INSPECTOR_DISABLED: "App Inspector is currently disabled.",
|
|
@@ -17352,7 +17283,7 @@ var createApiClient = ({ localstackEndpoint }) => {
|
|
|
17352
17283
|
};
|
|
17353
17284
|
|
|
17354
17285
|
// src/hooks/use-appinspector-open-analytics.ts
|
|
17355
|
-
import { useEffect as
|
|
17286
|
+
import { useEffect as useEffect14, useRef as useRef8 } from "react";
|
|
17356
17287
|
|
|
17357
17288
|
// node_modules/.pnpm/ua-parser-js@2.0.9/node_modules/ua-parser-js/src/main/ua-parser.mjs
|
|
17358
17289
|
var LIBVERSION = "2.0.9";
|
|
@@ -19234,8 +19165,8 @@ var useAppInspectorOpenAnalytics = () => {
|
|
|
19234
19165
|
const api = useAppInspectorApi();
|
|
19235
19166
|
const { deploymentContainer } = useAppInspector();
|
|
19236
19167
|
const { status, statusError } = useAppInspectorStatus();
|
|
19237
|
-
const firedRef =
|
|
19238
|
-
|
|
19168
|
+
const firedRef = useRef8(false);
|
|
19169
|
+
useEffect14(() => {
|
|
19239
19170
|
if (firedRef.current) {
|
|
19240
19171
|
return;
|
|
19241
19172
|
}
|
|
@@ -19273,7 +19204,7 @@ var AppInspectorContextProvider = (props) => {
|
|
|
19273
19204
|
() => createApiClient({ localstackEndpoint: props.localstackEndpoint }),
|
|
19274
19205
|
[props.localstackEndpoint]
|
|
19275
19206
|
);
|
|
19276
|
-
const resolveLink =
|
|
19207
|
+
const resolveLink = useCallback5(
|
|
19277
19208
|
(options) => {
|
|
19278
19209
|
if (options.to === "settings") {
|
|
19279
19210
|
return `${props.routePrefix}/settings`;
|
|
@@ -19295,14 +19226,83 @@ var AppInspectorContextProvider = (props) => {
|
|
|
19295
19226
|
headerHeight: props.headerHeight ?? 0,
|
|
19296
19227
|
linkComponent: props.linkComponent,
|
|
19297
19228
|
localstackEndpoint: props.localstackEndpoint,
|
|
19298
|
-
resolveLink
|
|
19229
|
+
resolveLink,
|
|
19230
|
+
spansWorkerUrl: props.spansWorkerUrl
|
|
19299
19231
|
}),
|
|
19300
|
-
[deploymentContainer, props.headerHeight, props.linkComponent, props.localstackEndpoint, resolveLink]
|
|
19232
|
+
[deploymentContainer, props.headerHeight, props.linkComponent, props.localstackEndpoint, props.spansWorkerUrl, resolveLink]
|
|
19301
19233
|
);
|
|
19302
19234
|
return /* @__PURE__ */ jsx132(ApiProvider, { api, children: /* @__PURE__ */ jsx132(StatusProvider, { children: /* @__PURE__ */ jsx132(AppInspectorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx132(AppInspectorAnalyticsBoundary, { children: props.children }) }) }) });
|
|
19303
19235
|
};
|
|
19304
19236
|
|
|
19237
|
+
// inline-worker:/home/runner/work/localstack-appinspector-ui/localstack-appinspector-ui/src/workers/spans-worker.worker.ts
|
|
19238
|
+
var source = '(() => {\n // src/types/error-levels.ts\n var ErrorLevel = {\n ERROR: 3,\n // General error\n IAM_PERMISSION: 5,\n // IAM permission issues\n OK: 2,\n // OK\n // Standard OTEL status codes\n UNSET: 1,\n // Unset\n // Extended custom levels\n WARNING: 4\n // Warning\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/error-level.ts\n var calculateHighestErrorLevel = (span) => {\n if (span.operation_status) {\n switch (span.operation_status) {\n case "error": {\n return ErrorLevel.ERROR;\n }\n case "iam_explicit_deny":\n case "iam_explicit_warn":\n case "iam_implicit_deny":\n case "iam_implicit_warn": {\n return ErrorLevel.IAM_PERMISSION;\n }\n default: {\n return ErrorLevel.OK;\n }\n }\n }\n const hasIamDenial = span.events.some((event) => {\n if (!event.event_type?.toLowerCase().includes("iam")) return false;\n if (typeof event.attributes?.payload !== "string") return false;\n const parsed = parseIAMEvent(event.attributes.payload);\n return parsed?.permission === "explicitly_denied" || parsed?.permission === "implicitly_denied";\n });\n if (hasIamDenial) return ErrorLevel.IAM_PERMISSION;\n if (span.status_code === 2) return ErrorLevel.ERROR;\n const hasErrorInErrors = span.errors?.some((error) => error.level === "error");\n if (hasErrorInErrors) return ErrorLevel.ERROR;\n const hasWarning = span.errors?.some((error) => error.level === "warning");\n if (hasWarning) return ErrorLevel.WARNING;\n return span.status_code || ErrorLevel.UNSET;\n };\n\n // src/utils/spans-filters.ts\n var EMPTY_FILTERS = {\n consumerService: [],\n producerService: [],\n status: []\n };\n var EXTERNAL_PRODUCER_VALUE = "__external__";\n function isFiltersEmpty(filters) {\n return filters.consumerService.length === 0 && filters.producerService.length === 0 && filters.status.length === 0;\n }\n var collapseStatusValue = (status) => {\n if (status === "iam_explicit_deny" || status === "iam_implicit_deny") return "iam_deny";\n if (status === "iam_explicit_warn" || status === "iam_implicit_warn") return "iam_warn";\n return status;\n };\n function applyFilters(spans, filters) {\n if (isFiltersEmpty(filters)) return spans;\n return spans.filter((span) => {\n if (filters.consumerService.length > 0 && !filters.consumerService.includes(span.service_name)) {\n return false;\n }\n if (filters.producerService.length > 0) {\n const includesExternal = filters.producerService.includes(EXTERNAL_PRODUCER_VALUE);\n const namedServices = filters.producerService.filter((value) => value !== EXTERNAL_PRODUCER_VALUE);\n const matchesExternal = includesExternal && !span.parent_service_name;\n const matchesNamed = namedServices.length > 0 && !!span.parent_service_name && namedServices.includes(span.parent_service_name);\n if (!matchesExternal && !matchesNamed) return false;\n }\n if (filters.status.length > 0) {\n if (!span.operation_status) return false;\n if (!filters.status.includes(collapseStatusValue(span.operation_status))) return false;\n }\n return true;\n });\n }\n\n // src/workers/spans-reducer.ts\n var compareByStartTime = (a, b) => a.start_time_unix_nano < b.start_time_unix_nano ? -1 : 1;\n var insertSorted = (array, item, compareFunction, hint) => {\n let left;\n let right;\n if (hint === "back") {\n left = array.length;\n right = array.length;\n let step = 1;\n while (left > 0 && compareFunction(array[left - 1], item) > 0) {\n right = left;\n left = Math.max(0, left - step);\n step *= 2;\n }\n } else {\n left = 0;\n right = 0;\n let step = 1;\n while (right < array.length && compareFunction(array[right], item) <= 0) {\n left = right + 1;\n right = Math.min(array.length, right + step);\n step *= 2;\n }\n }\n while (left < right) {\n const mid = left + right >>> 1;\n if (compareFunction(array[mid], item) <= 0) {\n left = mid + 1;\n } else {\n right = mid;\n }\n }\n array.splice(left, 0, item);\n };\n var createSpansReducer = () => {\n let corpus = [];\n let filters = EMPTY_FILTERS;\n const computeResult = () => {\n const filtered = applyFilters(corpus, filters);\n let errorCount = 0;\n let warningCount = 0;\n const orderedFilteredIds = [];\n for (const entry of filtered) {\n orderedFilteredIds.push(entry.span_id);\n if (entry.errorLevel === ErrorLevel.ERROR || entry.errorLevel === ErrorLevel.IAM_PERMISSION) {\n errorCount++;\n } else if (entry.errorLevel === ErrorLevel.WARNING) {\n warningCount++;\n }\n }\n const producers = /* @__PURE__ */ new Set();\n const consumers = /* @__PURE__ */ new Set();\n const statuses = /* @__PURE__ */ new Set();\n let hasExternalProducer = false;\n for (const entry of corpus) {\n if (entry.parent_service_name) producers.add(entry.parent_service_name);\n else hasExternalProducer = true;\n consumers.add(entry.service_name);\n if (entry.operation_status) statuses.add(collapseStatusValue(entry.operation_status));\n }\n return {\n consumerValues: [...consumers].toSorted(),\n errorCount,\n hasExternalProducer,\n orderedFilteredIds,\n producerValues: [...producers].toSorted(),\n statusValues: [...statuses].toSorted(),\n warningCount\n };\n };\n return {\n addPage: (direction, projections) => {\n const hint = direction === "forward" ? "back" : "front";\n const sorted = projections.toSorted(compareByStartTime);\n for (const projection of sorted) {\n insertSorted(corpus, { ...projection, errorLevel: calculateHighestErrorLevel(projection) }, compareByStartTime, hint);\n }\n return computeResult();\n },\n clear: () => {\n corpus = [];\n filters = EMPTY_FILTERS;\n return computeResult();\n },\n setFilters: (next) => {\n filters = next;\n return computeResult();\n }\n };\n };\n\n // src/workers/spans-worker.worker.ts\n var context = globalThis;\n var reducer = createSpansReducer();\n context.addEventListener("message", (event) => {\n const request = event.data;\n const result = request.op === "addPage" ? reducer.addPage(request.direction, request.projections) : request.op === "setFilters" ? reducer.setFilters(request.filters) : reducer.clear();\n context.postMessage({ result, seq: request.seq });\n });\n})();\n';
|
|
19239
|
+
function createInlineWorker() {
|
|
19240
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
19241
|
+
try {
|
|
19242
|
+
return new Worker(url);
|
|
19243
|
+
} finally {
|
|
19244
|
+
URL.revokeObjectURL(url);
|
|
19245
|
+
}
|
|
19246
|
+
}
|
|
19247
|
+
|
|
19248
|
+
// src/hooks/use-spans-compute.ts
|
|
19249
|
+
var EMPTY_RESULT = {
|
|
19250
|
+
consumerValues: [],
|
|
19251
|
+
errorCount: 0,
|
|
19252
|
+
hasExternalProducer: false,
|
|
19253
|
+
orderedFilteredIds: [],
|
|
19254
|
+
producerValues: [],
|
|
19255
|
+
statusValues: [],
|
|
19256
|
+
warningCount: 0
|
|
19257
|
+
};
|
|
19258
|
+
var useSpansCompute = () => {
|
|
19259
|
+
const { spansWorkerUrl } = useAppInspector();
|
|
19260
|
+
const [result, setResult] = useState10(EMPTY_RESULT);
|
|
19261
|
+
const [workerError, setWorkerError] = useState10();
|
|
19262
|
+
const [retryCount, setRetryCount] = useState10(0);
|
|
19263
|
+
const workerRef = useRef9();
|
|
19264
|
+
const seqRef = useRef9(0);
|
|
19265
|
+
const lastAppliedRef = useRef9(0);
|
|
19266
|
+
useEffect15(() => {
|
|
19267
|
+
try {
|
|
19268
|
+
const worker = spansWorkerUrl ? new Worker(spansWorkerUrl) : createInlineWorker();
|
|
19269
|
+
worker.addEventListener("message", (event) => {
|
|
19270
|
+
const { result: nextResult, seq } = event.data;
|
|
19271
|
+
if (seq < lastAppliedRef.current) return;
|
|
19272
|
+
lastAppliedRef.current = seq;
|
|
19273
|
+
setResult(nextResult);
|
|
19274
|
+
});
|
|
19275
|
+
worker.addEventListener("error", (event) => {
|
|
19276
|
+
setWorkerError(new Error(event.message || "Spans filter worker crashed."));
|
|
19277
|
+
});
|
|
19278
|
+
workerRef.current = worker;
|
|
19279
|
+
return () => {
|
|
19280
|
+
worker.terminate();
|
|
19281
|
+
workerRef.current = void 0;
|
|
19282
|
+
};
|
|
19283
|
+
} catch (error) {
|
|
19284
|
+
setWorkerError(error instanceof Error ? error : new Error("Failed to start the spans filter worker."));
|
|
19285
|
+
return;
|
|
19286
|
+
}
|
|
19287
|
+
}, [retryCount, spansWorkerUrl]);
|
|
19288
|
+
const retryWorker = useCallback6(() => {
|
|
19289
|
+
setWorkerError(void 0);
|
|
19290
|
+
setRetryCount((count2) => count2 + 1);
|
|
19291
|
+
}, []);
|
|
19292
|
+
const addPage = useCallback6((direction, projections) => {
|
|
19293
|
+
workerRef.current?.postMessage({ direction, op: "addPage", projections, seq: ++seqRef.current });
|
|
19294
|
+
}, []);
|
|
19295
|
+
const setFilters = useCallback6((filters) => {
|
|
19296
|
+
workerRef.current?.postMessage({ filters, op: "setFilters", seq: ++seqRef.current });
|
|
19297
|
+
}, []);
|
|
19298
|
+
const clear = useCallback6(() => {
|
|
19299
|
+
workerRef.current?.postMessage({ op: "clear", seq: ++seqRef.current });
|
|
19300
|
+
}, []);
|
|
19301
|
+
return { addPage, clear, result, retryWorker, setFilters, workerError };
|
|
19302
|
+
};
|
|
19303
|
+
|
|
19305
19304
|
// src/hooks/use-spans-ws.tsx
|
|
19305
|
+
import { useCallback as useCallback7, useEffect as useEffect16, useRef as useRef10, useState as useState11 } from "react";
|
|
19306
19306
|
var wsLogger = createScopedLogger("WS");
|
|
19307
19307
|
var isLegacySpanFields = (value) => {
|
|
19308
19308
|
return typeof value.span_id === "string" && typeof value.trace_id === "string" && typeof value.timestamp === "string";
|