diff --git a/src/config/src/config.rs b/src/config/src/config.rs index 6b4e4ce798..6e410c7b23 100644 --- a/src/config/src/config.rs +++ b/src/config/src/config.rs @@ -1334,6 +1334,12 @@ pub struct Common { help = "Enable to show symbol in dashboard" )] pub dashboard_show_symbol_enabled: bool, + #[env_config( + name = "ZO_DASHBOARD_SHOW_FIELD_AS_JSON_ENABLED", + default = false, + help = "Enable to show field as JSON in dashboard table" + )] + pub dashboard_show_field_as_json_enabled: bool, #[env_config(name = "ZO_INGEST_DEFAULT_HEC_STREAM", default = "")] pub default_hec_stream: String, #[env_config( diff --git a/src/handler/http/request/status/mod.rs b/src/handler/http/request/status/mod.rs index 680b62498b..83ff13f2a3 100644 --- a/src/handler/http/request/status/mod.rs +++ b/src/handler/http/request/status/mod.rs @@ -173,6 +173,7 @@ struct ConfigResponse<'a> { ai_enabled: bool, dashboard_placeholder: String, dashboard_show_symbol_enabled: bool, + dashboard_show_field_as_json_enabled: bool, ingest_flatten_level: u32, #[cfg(feature = "enterprise")] license_expiry: i64, @@ -410,6 +411,7 @@ pub async fn zo_config() -> impl IntoResponse { ai_enabled, dashboard_placeholder: cfg.common.dashboard_placeholder.to_string(), dashboard_show_symbol_enabled: cfg.common.dashboard_show_symbol_enabled, + dashboard_show_field_as_json_enabled: cfg.common.dashboard_show_field_as_json_enabled, ingest_flatten_level: cfg.limit.ingest_flatten_level, #[cfg(feature = "enterprise")] license_expiry: expiry_time, diff --git a/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue b/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue index b5af45ce33..ff44fc1672 100644 --- a/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue +++ b/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue @@ -821,7 +821,11 @@ export default defineComponent({ field.showFieldAsJson === undefined || field.showFieldAsJson === null ) { - field.showFieldAsJson = false; + field.showFieldAsJson = + dashboardPanelDataPageKey === "logs" + ? (store?.state?.zoConfig?.dashboard_show_field_as_json_enabled ?? + false) + : false; } }; diff --git a/web/src/components/dashboards/panels/JsonFieldRenderer.vue b/web/src/components/dashboards/panels/JsonFieldRenderer.vue index 744c2716b3..44401aeb6b 100644 --- a/web/src/components/dashboards/panels/JsonFieldRenderer.vue +++ b/web/src/components/dashboards/panels/JsonFieldRenderer.vue @@ -15,7 +15,7 @@ along with this program. If not, see . --> } - +
- {{ formatValue(parsedData) }} + {{ formatValue(parsedData) }}
@@ -85,17 +85,29 @@ const props = defineProps(); const store = useStore(); // Parse the value if it's a string, otherwise use as-is -const parsedData = computed(() => { +// Also tracks whether JSON parsing succeeded so we don't color plain strings as JSON +const parseResult = computed(() => { if (typeof props.value === "string") { try { - return JSON.parse(props.value); + const parsed = JSON.parse(props.value); + // Only treat as JSON if the parsed result is an object or array. + // Bare primitives ("42", "true", "null") are valid JSON but should + // not be colored -> they're just plain field values stored as strings. + const isStructuredJSON = parsed !== null && typeof parsed === "object"; + return { data: parsed, isJSON: isStructuredJSON }; } catch (e) { - return props.value; + return { data: props.value, isJSON: false }; } } - return props.value; + return { data: props.value, isJSON: false }; }); +const parsedData = computed(() => parseResult.value.data); + +// True when the original string was valid JSON (or value was not a string). +// Used to avoid coloring plain non-JSON strings (e.g. timestamps) like JSON values. +const isValidJSON = computed(() => parseResult.value.isJSON); + // Check if theme is dark - memoized const isDarkTheme = computed(() => store.state.theme === "dark"); @@ -202,7 +214,7 @@ const isLastDefinedEntry = (obj: any, currentKey: string): boolean => {