diff --git a/frontend/webapp/app/globals.css b/frontend/webapp/app/globals.css
index 302fd6694..37f548102 100644
--- a/frontend/webapp/app/globals.css
+++ b/frontend/webapp/app/globals.css
@@ -2,6 +2,7 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Kode+Mono:wght@100;200;300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
* {
scrollbar-color: black transparent;
diff --git a/frontend/webapp/app/layout.tsx b/frontend/webapp/app/layout.tsx
index 62d808b5d..f0254a8b5 100644
--- a/frontend/webapp/app/layout.tsx
+++ b/frontend/webapp/app/layout.tsx
@@ -20,7 +20,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
- {METADATA.title}
@@ -28,6 +27,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
+ {METADATA.title}
diff --git a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx
index 6f9381e93..b1feeeb42 100644
--- a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx
+++ b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx
@@ -1,14 +1,14 @@
import React, { useEffect, useMemo, useState } from 'react';
import buildCard from './build-card';
import styled from 'styled-components';
-import { useSourceCRUD } from '@/hooks';
import { useDrawerStore } from '@/store';
import buildDrawerItem from './build-drawer-item';
import { UpdateSourceBody } from '../update-source-body';
+import { useDescribeSource, useSourceCRUD } from '@/hooks';
import OverviewDrawer from '../../overview/overview-drawer';
import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type K8sActualSource } from '@/types';
-import { ACTION, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon } from '@/utils';
import { ConditionDetails, DataCard, DataCardRow, DataCardFieldTypes } from '@/reuseable-components';
+import { ACTION, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon, safeJsonStringify } from '@/utils';
interface Props {}
@@ -93,6 +93,7 @@ export const SourceDrawer: React.FC = () => {
if (!selectedItem?.item) return null;
const { id, item } = selectedItem as { id: WorkloadId; item: K8sActualSource };
+ const { data: describe } = useDescribeSource(id);
const handleEdit = (bool?: boolean) => {
setIsEditing(typeof bool === 'boolean' ? bool : true);
@@ -141,6 +142,15 @@ export const SourceDrawer: React.FC = () => {
+
)}
diff --git a/frontend/webapp/graphql/queries/describe.ts b/frontend/webapp/graphql/queries/describe.ts
new file mode 100644
index 000000000..b2207ef9c
--- /dev/null
+++ b/frontend/webapp/graphql/queries/describe.ts
@@ -0,0 +1,224 @@
+import { gql } from '@apollo/client';
+
+export const DESCRIBE_SOURCE = gql`
+ query DescribeSource($namespace: String!, $kind: String!, $name: String!) {
+ describeSource(namespace: $namespace, kind: $kind, name: $name) {
+ name {
+ name
+ value
+ status
+ explain
+ }
+ kind {
+ name
+ value
+ status
+ explain
+ }
+ namespace {
+ name
+ value
+ status
+ explain
+ }
+ labels {
+ instrumented {
+ name
+ value
+ status
+ explain
+ }
+ workload {
+ name
+ value
+ status
+ explain
+ }
+ namespace {
+ name
+ value
+ status
+ explain
+ }
+ instrumentedText {
+ name
+ value
+ status
+ explain
+ }
+ }
+ instrumentationConfig {
+ created {
+ name
+ value
+ status
+ explain
+ }
+ createTime {
+ name
+ value
+ status
+ explain
+ }
+ }
+ runtimeInfo {
+ generation {
+ name
+ value
+ status
+ explain
+ }
+ containers {
+ containerName {
+ name
+ value
+ status
+ explain
+ }
+ language {
+ name
+ value
+ status
+ explain
+ }
+ runtimeVersion {
+ name
+ value
+ status
+ explain
+ }
+ envVars {
+ name
+ value
+ status
+ explain
+ }
+ }
+ }
+ instrumentedApplication {
+ created {
+ name
+ value
+ status
+ explain
+ }
+ createTime {
+ name
+ value
+ status
+ explain
+ }
+ containers {
+ containerName {
+ name
+ value
+ status
+ explain
+ }
+ language {
+ name
+ value
+ status
+ explain
+ }
+ runtimeVersion {
+ name
+ value
+ status
+ explain
+ }
+ envVars {
+ name
+ value
+ status
+ explain
+ }
+ }
+ }
+ instrumentationDevice {
+ statusText {
+ name
+ value
+ status
+ explain
+ }
+ containers {
+ containerName {
+ name
+ value
+ status
+ explain
+ }
+ devices {
+ name
+ value
+ status
+ explain
+ }
+ originalEnv {
+ name
+ value
+ status
+ explain
+ }
+ }
+ }
+ totalPods
+ podsPhasesCount
+ pods {
+ podName {
+ name
+ value
+ status
+ explain
+ }
+ nodeName {
+ name
+ value
+ status
+ explain
+ }
+ phase {
+ name
+ value
+ status
+ explain
+ }
+ containers {
+ containerName {
+ name
+ value
+ status
+ explain
+ }
+ actualDevices {
+ name
+ value
+ status
+ explain
+ }
+ instrumentationInstances {
+ healthy {
+ name
+ value
+ status
+ explain
+ }
+ message {
+ name
+ value
+ status
+ explain
+ }
+ identifyingAttributes {
+ name
+ value
+ status
+ explain
+ }
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/frontend/webapp/graphql/queries/index.ts b/frontend/webapp/graphql/queries/index.ts
index 498bd90f5..a7c69d689 100644
--- a/frontend/webapp/graphql/queries/index.ts
+++ b/frontend/webapp/graphql/queries/index.ts
@@ -1,3 +1,4 @@
export * from './config';
export * from './compute-platform';
+export * from './describe';
export * from './destination';
diff --git a/frontend/webapp/hooks/describe/index.ts b/frontend/webapp/hooks/describe/index.ts
new file mode 100644
index 000000000..0d183ae60
--- /dev/null
+++ b/frontend/webapp/hooks/describe/index.ts
@@ -0,0 +1 @@
+export * from './useDescribeSource';
diff --git a/frontend/webapp/hooks/describe/useDescribeSource.ts b/frontend/webapp/hooks/describe/useDescribeSource.ts
new file mode 100644
index 000000000..209e92794
--- /dev/null
+++ b/frontend/webapp/hooks/describe/useDescribeSource.ts
@@ -0,0 +1,16 @@
+import { useQuery } from '@apollo/client';
+import { DESCRIBE_SOURCE } from '@/graphql';
+import type { DescribeSource, WorkloadId } from '@/types';
+
+export const useDescribeSource = ({ namespace, name, kind }: WorkloadId) => {
+ const { data, loading, error } = useQuery(DESCRIBE_SOURCE, {
+ variables: { namespace, name, kind },
+ pollInterval: 5000,
+ });
+
+ return {
+ data: data?.describeSource,
+ loading,
+ error,
+ };
+};
diff --git a/frontend/webapp/hooks/index.tsx b/frontend/webapp/hooks/index.ts
similarity index 90%
rename from frontend/webapp/hooks/index.tsx
rename to frontend/webapp/hooks/index.ts
index 6198e2ae9..0996541ea 100644
--- a/frontend/webapp/hooks/index.tsx
+++ b/frontend/webapp/hooks/index.ts
@@ -1,9 +1,10 @@
+export * from './actions';
export * from './common';
+export * from './compute-platform';
export * from './config';
-export * from './sources';
-export * from './actions';
-export * from './overview';
-export * from './notification';
+export * from './describe';
export * from './destinations';
-export * from './compute-platform';
export * from './instrumentation-rules';
+export * from './notification';
+export * from './overview';
+export * from './sources';
diff --git a/frontend/webapp/package.json b/frontend/webapp/package.json
index 9fdd896f0..39b8b08dd 100644
--- a/frontend/webapp/package.json
+++ b/frontend/webapp/package.json
@@ -18,6 +18,7 @@
"graphql": "^16.9.0",
"javascript-time-ago": "^2.5.11",
"next": "15.0.3",
+ "prism-react-renderer": "^2.4.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-flow-renderer": "^10.3.17",
diff --git a/frontend/webapp/reuseable-components/code/index.tsx b/frontend/webapp/reuseable-components/code/index.tsx
new file mode 100644
index 000000000..21d281f22
--- /dev/null
+++ b/frontend/webapp/reuseable-components/code/index.tsx
@@ -0,0 +1,37 @@
+import styled from 'styled-components';
+import { Highlight, themes as prismThemes } from 'prism-react-renderer';
+import { flattenObjectKeys, safeJsonParse, safeJsonStringify } from '@/utils';
+
+interface Props {
+ language: string;
+ code: string;
+ flatten?: boolean;
+}
+
+const Token = styled.span`
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ opacity: 0.75;
+ font-size: 12px;
+ font-family: ${({ theme }) => theme.font_family.code};
+`;
+
+export const Code: React.FC = ({ language, code, flatten }) => {
+ const str = flatten && language === 'json' ? safeJsonStringify(flattenObjectKeys(safeJsonParse(code, {}))) : code;
+
+ return (
+
+ {({ getLineProps, getTokenProps, tokens }) => (
+
+ {tokens.map((line, i) => (
+
+ {line.map((token, ii) => (
+
+ ))}
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx
index dc449ea79..c09ce002e 100644
--- a/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx
+++ b/frontend/webapp/reuseable-components/data-card/data-card-fields/index.tsx
@@ -1,6 +1,6 @@
import React, { useId } from 'react';
import styled from 'styled-components';
-import { ActiveStatus, DataTab, Divider, InstrumentStatus, MonitorsIcons, Text, Tooltip } from '@/reuseable-components';
+import { ActiveStatus, Code, DataTab, Divider, InstrumentStatus, MonitorsIcons, Text, Tooltip } from '@/reuseable-components';
import { capitalizeFirstLetter, getProgrammingLanguageIcon, parseJsonStringToPrettyString, safeJsonParse, WORKLOAD_PROGRAMMING_LANGUAGES } from '@/utils';
export enum DataCardFieldTypes {
@@ -8,6 +8,7 @@ export enum DataCardFieldTypes {
MONITORS = 'monitors',
ACTIVE_STATUS = 'active-status',
SOURCE_CONTAINER = 'source-container',
+ CODE = 'code',
}
export interface DataCardRow {
@@ -45,7 +46,7 @@ const ItemTitle = styled(Text)`
export const DataCardFields: React.FC = ({ data }) => {
return (
- {data.map(({ type, title, tooltip, value, width = 'unset' }) => {
+ {data.map(({ type, title, tooltip, value, width = 'inherit' }) => {
const id = useId();
return (
@@ -97,6 +98,12 @@ const renderValue = (type: DataCardRow['type'], value: DataCardRow['value']) =>
);
}
+ case DataCardFieldTypes.CODE: {
+ const params = safeJsonParse(value, { language: '', code: '' });
+
+ return
;
+ }
+
default: {
return {parseJsonStringToPrettyString(value || '-')};
}
diff --git a/frontend/webapp/reuseable-components/index.ts b/frontend/webapp/reuseable-components/index.ts
index 6735fe776..1e2240a25 100644
--- a/frontend/webapp/reuseable-components/index.ts
+++ b/frontend/webapp/reuseable-components/index.ts
@@ -36,3 +36,4 @@ export * from './extend-icon';
export * from './condition-details';
export * from './data-card';
export * from './data-tab';
+export * from './code';
diff --git a/frontend/webapp/styles/theme.ts b/frontend/webapp/styles/theme.ts
index 48fa07254..6e1e71cee 100644
--- a/frontend/webapp/styles/theme.ts
+++ b/frontend/webapp/styles/theme.ts
@@ -245,6 +245,7 @@ const text = {
const font_family = {
primary: 'Inter, sans-serif',
secondary: 'Kode Mono, sans-serif',
+ code: 'IBM Plex Mono, monospace',
};
// Define the theme interface
diff --git a/frontend/webapp/types/describe.ts b/frontend/webapp/types/describe.ts
new file mode 100644
index 000000000..5aa3a8551
--- /dev/null
+++ b/frontend/webapp/types/describe.ts
@@ -0,0 +1,86 @@
+interface EntityProperty {
+ name: string;
+ value: string;
+ status?: string;
+ explain?: string;
+}
+
+interface InstrumentationLabelsAnalyze {
+ instrumented: EntityProperty;
+ workload?: EntityProperty;
+ namespace?: EntityProperty;
+ instrumentedText?: EntityProperty;
+}
+
+interface InstrumentationConfigAnalyze {
+ created: EntityProperty;
+ createTime?: EntityProperty;
+}
+
+interface ContainerRuntimeInfoAnalyze {
+ containerName: EntityProperty;
+ language: EntityProperty;
+ runtimeVersion: EntityProperty;
+ envVars: EntityProperty[];
+}
+
+interface RuntimeInfoAnalyze {
+ generation: EntityProperty;
+ containers: ContainerRuntimeInfoAnalyze[];
+}
+
+interface InstrumentedApplicationAnalyze {
+ created: EntityProperty;
+ createTime?: EntityProperty;
+ containers: ContainerRuntimeInfoAnalyze[];
+}
+
+interface ContainerWorkloadManifestAnalyze {
+ containerName: EntityProperty;
+ devices: EntityProperty;
+ originalEnv: EntityProperty[];
+}
+
+interface InstrumentationDeviceAnalyze {
+ statusText: EntityProperty;
+ containers: ContainerWorkloadManifestAnalyze[];
+}
+
+interface InstrumentationInstanceAnalyze {
+ healthy: EntityProperty;
+ message?: EntityProperty;
+ identifyingAttributes: EntityProperty[];
+}
+
+interface PodContainerAnalyze {
+ containerName: EntityProperty;
+ actualDevices: EntityProperty;
+ instrumentationInstances: InstrumentationInstanceAnalyze[];
+}
+
+interface PodAnalyze {
+ podName: EntityProperty;
+ nodeName: EntityProperty;
+ phase: EntityProperty;
+ containers: PodContainerAnalyze[];
+}
+
+interface SourceAnalyze {
+ name: EntityProperty;
+ kind: EntityProperty;
+ namespace: EntityProperty;
+ labels: InstrumentationLabelsAnalyze;
+
+ instrumentationConfig: InstrumentationConfigAnalyze;
+ runtimeInfo?: RuntimeInfoAnalyze;
+ instrumentedApplication: InstrumentedApplicationAnalyze;
+ instrumentationDevice: InstrumentationDeviceAnalyze;
+
+ totalPods: number;
+ podsPhasesCount: string;
+ pods: PodAnalyze[];
+}
+
+export interface DescribeSource {
+ describeSource: SourceAnalyze;
+}
diff --git a/frontend/webapp/types/index.ts b/frontend/webapp/types/index.ts
index 1a5009a4f..4dee70aa1 100644
--- a/frontend/webapp/types/index.ts
+++ b/frontend/webapp/types/index.ts
@@ -2,6 +2,7 @@ export * from './actions';
export * from './common';
export * from './compute-platform';
export * from './data-flow';
+export * from './describe';
export * from './destinations';
export * from './instrumentation-rules';
export * from './metrics';
diff --git a/frontend/webapp/utils/constants/string.tsx b/frontend/webapp/utils/constants/string.tsx
index 56a4ab275..1eb13a917 100644
--- a/frontend/webapp/utils/constants/string.tsx
+++ b/frontend/webapp/utils/constants/string.tsx
@@ -64,7 +64,7 @@ export const DATA_CARDS = {
RULE_DETAILS: 'Instrumentation Rule Details',
DESTINATION_DETAILS: 'Destination Details',
SOURCE_DETAILS: 'Source Details',
-
+ DESCRIBE_SOURCE: 'Describe Source',
DETECTED_CONTAINERS: 'Detected Containers',
DETECTED_CONTAINERS_DESCRIPTION: 'The system automatically instruments the containers it detects with a supported programming language.',
};
diff --git a/frontend/webapp/utils/functions/formatters/flatten-object-keys/index.ts b/frontend/webapp/utils/functions/formatters/flatten-object-keys/index.ts
new file mode 100644
index 000000000..b89355214
--- /dev/null
+++ b/frontend/webapp/utils/functions/formatters/flatten-object-keys/index.ts
@@ -0,0 +1,62 @@
+/**
+ * Recursively flattens a nested object into a single-level object where each key
+ * represents the path to its corresponding value in the original object. Keys for nested
+ * properties are concatenated using a dot (`.`) as a separator, while array elements
+ * include their index in square brackets (`[]`).
+ *
+ * @param {Record} obj - The input object to be flattened.
+ * @param {string} [prefix=''] - The current prefix for the keys, used for recursion.
+ * @param {Record} [result={}] - The accumulator object that stores the flattened result.
+ * @returns {Record} A new object where all nested properties are flattened into
+ * a single level with their paths as keys.
+ *
+ * @example
+ * const input = {
+ * name: {
+ * name: 'Name',
+ * value: 'load-generator',
+ * status: null,
+ * explain: '...',
+ * },
+ * };
+ *
+ * const output = flattenObjectKeys(input);
+ * Output:
+ * {
+ * 'name.name': 'Name',
+ * 'name.value': 'load-generator',
+ * 'name.status': null,
+ * 'name.explain': '...',
+ * }
+ */
+
+export const flattenObjectKeys = (obj: Record, prefix: string = '', result: Record = {}) => {
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ const value = obj[key];
+ const newKey = prefix ? `${prefix}.${key}` : key;
+
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
+ // Recurse for nested objects
+ flattenObjectKeys(value, newKey, result);
+ } else if (Array.isArray(value)) {
+ value.forEach((item, index) => {
+ const arrayKey = `${newKey}[${index}]`;
+
+ if (item !== null && typeof item === 'object') {
+ // Recurse for objects in arrays
+ flattenObjectKeys(item, arrayKey, result);
+ } else {
+ // Assign primitive array values
+ result[arrayKey] = item;
+ }
+ });
+ } else {
+ // Assign non-object, non-array values
+ result[newKey] = value;
+ }
+ }
+ }
+
+ return result;
+};
diff --git a/frontend/webapp/utils/functions/formatters/index.ts b/frontend/webapp/utils/functions/formatters/index.ts
index 60092f157..64249733c 100644
--- a/frontend/webapp/utils/functions/formatters/index.ts
+++ b/frontend/webapp/utils/functions/formatters/index.ts
@@ -1,8 +1,10 @@
export * from './clean-object-empty-strings-values';
export * from './extract-monitors';
+export * from './flatten-object-keys';
export * from './format-bytes';
export * from './get-id-from-sse-target';
export * from './get-sse-target-from-id';
export * from './parse-json-string-to-pretty-string';
export * from './safe-json-parse';
+export * from './safe-json-stringify';
export * from './stringify-non-string-values';
diff --git a/frontend/webapp/utils/functions/formatters/safe-json-stringify/index.ts b/frontend/webapp/utils/functions/formatters/safe-json-stringify/index.ts
new file mode 100644
index 000000000..0351a873d
--- /dev/null
+++ b/frontend/webapp/utils/functions/formatters/safe-json-stringify/index.ts
@@ -0,0 +1,3 @@
+export const safeJsonStringify = (obj?: Record, indent = 2) => {
+ return JSON.stringify(obj || {}, null, indent);
+};
diff --git a/frontend/webapp/yarn.lock b/frontend/webapp/yarn.lock
index 200d61fe5..40efdcef6 100644
--- a/frontend/webapp/yarn.lock
+++ b/frontend/webapp/yarn.lock
@@ -2265,6 +2265,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
+"@types/prismjs@^1.26.0":
+ version "1.26.5"
+ resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6"
+ integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==
+
"@types/prop-types@*":
version "15.7.13"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
@@ -2953,6 +2958,11 @@ client-only@0.0.1:
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
+clsx@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
+ integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -5142,6 +5152,14 @@ pretty-bytes@^5.6.0:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
+prism-react-renderer@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz#ac63b7f78e56c8f2b5e76e823a976d5ede77e35f"
+ integrity sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==
+ dependencies:
+ "@types/prismjs" "^1.26.0"
+ clsx "^2.0.0"
+
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"