diff --git a/plugins/OpenSearch/v1/README.md b/plugins/OpenSearch/v1/README.md deleted file mode 100644 index 2a590c5b..00000000 --- a/plugins/OpenSearch/v1/README.md +++ /dev/null @@ -1,6 +0,0 @@ -## Overview - -Query OpenSearch databases using DSL. This plugin is primarily a wrapper around the `_search` API endpoint that allows for a range of queries. - -See the [OpenSearch API docs](https://docs.opensearch.org/latest/api-reference/search-apis/search/) for more information. - diff --git a/plugins/OpenSearch/v1/configValidation.json b/plugins/OpenSearch/v1/configValidation.json new file mode 100644 index 00000000..2ab8bf37 --- /dev/null +++ b/plugins/OpenSearch/v1/configValidation.json @@ -0,0 +1,18 @@ +{ + "steps": [ + { + "displayName": "Connect and authenticate", + "dataStream": { "name": "clusterInfo" }, + "required": true, + "error": "Could not connect to OpenSearch. Check the Instance URL is reachable (from the relay agent, if one is selected), the credentials are valid, and whether Ignore certificate errors is needed for a self-signed certificate.", + "success": "Connected to OpenSearch." + }, + { + "displayName": "Check cluster monitoring access", + "dataStream": { "name": "clusterHealth" }, + "required": false, + "error": "Could not read cluster health. The cluster, node and index data streams need the readall_and_monitor role or equivalent monitoring permissions. Searching needs read permission on the indices queried, which this check does not test.", + "success": "Cluster monitoring permissions confirmed." + } + ] +} diff --git a/plugins/OpenSearch/v1/custom_types.json b/plugins/OpenSearch/v1/custom_types.json new file mode 100644 index 00000000..7b5830ab --- /dev/null +++ b/plugins/OpenSearch/v1/custom_types.json @@ -0,0 +1,23 @@ +[ + { + "name": "Node", + "sourceType": "Node", + "icon": "server", + "singular": "Node", + "plural": "Nodes" + }, + { + "name": "Index", + "sourceType": "Index", + "icon": "table-list", + "singular": "Index", + "plural": "Indices" + }, + { + "name": "Service", + "sourceType": "Service", + "icon": "gears", + "singular": "Service", + "plural": "Services" + } +] diff --git a/plugins/OpenSearch/v1/dataStreams/clusterHealth.json b/plugins/OpenSearch/v1/dataStreams/clusterHealth.json new file mode 100644 index 00000000..f7cc6294 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/clusterHealth.json @@ -0,0 +1,38 @@ +{ + "name": "clusterHealth", + "displayName": "Cluster Health", + "description": "Current cluster status with node and shard counts", + "tags": ["Cluster", "Health"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "_cluster/health", + "getArgs": [], + "headers": [] + }, + "matches": "none", + "timeframes": false, + "metadata": [ + { "name": "cluster_name", "displayName": "Cluster", "shape": "string", "role": "label" }, + { + "name": "status", + "displayName": "Status", + "shape": ["state", { "map": { "success": ["green"], "warning": ["yellow"], "error": ["red"] } }], + "role": "value" + }, + { "name": "number_of_nodes", "displayName": "Nodes", "shape": "number" }, + { "name": "number_of_data_nodes", "displayName": "Data Nodes", "shape": "number" }, + { "name": "active_primary_shards", "displayName": "Active Primary Shards", "shape": "number" }, + { "name": "active_shards", "displayName": "Active Shards", "shape": "number" }, + { "name": "relocating_shards", "displayName": "Relocating Shards", "shape": "number" }, + { "name": "initializing_shards", "displayName": "Initializing Shards", "shape": "number" }, + { "name": "unassigned_shards", "displayName": "Unassigned Shards", "shape": "number" }, + { "name": "delayed_unassigned_shards", "displayName": "Delayed Unassigned Shards", "shape": "number" }, + { "name": "active_shards_percent_as_number", "displayName": "Active Shards (%)", "shape": ["percent", { "decimalPlaces": 1 }] }, + { "name": "number_of_pending_tasks", "displayName": "Pending Tasks", "shape": "number" }, + { "name": "number_of_in_flight_fetch", "displayName": "In-flight Fetches", "shape": "number" }, + { "name": "task_max_waiting_in_queue_millis", "displayName": "Longest Task Wait", "shape": "milliseconds" }, + { "name": "discovered_cluster_manager", "displayName": "Cluster Manager Discovered", "shape": "boolean" }, + { "name": "timed_out", "displayName": "Timed Out", "shape": "boolean", "visible": false } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/clusterInfo.json b/plugins/OpenSearch/v1/dataStreams/clusterInfo.json new file mode 100644 index 00000000..43216b1e --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/clusterInfo.json @@ -0,0 +1,26 @@ +{ + "name": "clusterInfo", + "displayName": "Cluster Info", + "description": "Cluster name, UUID and OpenSearch version", + "tags": ["Cluster"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "expandInnerObjects": true, + "endpointPath": "", + "getArgs": [], + "headers": [] + }, + "matches": "none", + "timeframes": false, + "metadata": [ + { "name": "cluster_name", "displayName": "Cluster", "shape": "string", "role": "label" }, + { "name": "name", "displayName": "Responding Node", "shape": "string" }, + { "name": "version.number", "displayName": "Version", "shape": "string", "role": "value" }, + { "name": "version.distribution", "displayName": "Distribution", "shape": "string" }, + { "name": "version.build_type", "displayName": "Build Type", "shape": "string" }, + { "name": "version.build_date", "displayName": "Build Date", "shape": "date" }, + { "name": "version.lucene_version", "displayName": "Lucene Version", "shape": "string" }, + { "name": "cluster_uuid", "displayName": "Cluster UUID", "shape": "string", "visible": false } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/documentCount.json b/plugins/OpenSearch/v1/dataStreams/documentCount.json new file mode 100644 index 00000000..3cf858c3 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/documentCount.json @@ -0,0 +1,57 @@ +{ + "name": "documentCount", + "displayName": "Document Count", + "description": "Number of documents in one or more indices, optionally matching a query", + "tags": ["Search"], + "baseDataSourceName": "httpRequestUnscoped", + "matches": "none", + "manualConfigApply": true, + "config": { + "httpMethod": "post", + "endpointPath": "{{index}}/_count", + "getArgs": [], + "headers": [], + "postBody": "{{ (() => { let raw = typeof query === 'undefined' || query === null || query === '' ? {} : query; if (typeof raw === 'string') { try { raw = JSON.parse(raw); } catch (e) { raw = {}; } } const clause = raw && raw.query ? raw.query : (raw && Object.keys(raw).length ? raw : { match_all: {} }); const field = typeof timeField === 'string' ? timeField.trim() : ''; const tf = typeof timeframe !== 'undefined' ? timeframe : null; if (!field || !tf || !tf.start || tf.enum === 'none') { return { query: clause }; } return { query: { bool: { must: [ clause ], filter: [ { range: { [field]: { gte: tf.start, lte: tf.end, format: 'strict_date_optional_time' } } } ] } } }; })() }}", + "errorHandling": { "type": "path", "realm": "payload", "path": "error.root_cause.0.reason" } + }, + "ui": [ + { + "name": "index", + "type": "autocomplete", + "label": "Index", + "validation": { + "required": true + }, + "placeholder": "Select an index or pattern", + "isMulti": false, + "allowCustomValues": true, + "data": { + "source": "dataStream", + "dataStreamName": "indexNames", + "dataSourceConfig": {} + } + }, + { + "name": "query", + "type": "json", + "label": "Query", + "defaultValue": { + "match_all": {} + }, + "help": "Accepts a Query DSL clause, or a request body with a query property." + }, + { + "name": "timeField", + "type": "text", + "label": "Time field", + "placeholder": "@timestamp", + "help": "Counts only documents in the tile's timeframe using this date field. Leave empty to ignore the timeframe." + } + ], + "supportsNoneTimeframe": true, + "requiresParameterTimeframe": true, + "defaultTimeframe": "none", + "metadata": [ + { "name": "count", "displayName": "Documents", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }], "role": "value" } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/indexNames.json b/plugins/OpenSearch/v1/dataStreams/indexNames.json new file mode 100644 index 00000000..ec1c2d37 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/indexNames.json @@ -0,0 +1,25 @@ +{ + "name": "indexNames", + "displayName": "Index Names", + "description": "Open, non-system index names, with wildcard patterns for date-suffixed index families", + "tags": ["Indices"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "_cat/indices/*,-.*", + "getArgs": [ + { "key": "format", "value": "json" }, + { "key": "expand_wildcards", "value": "open" }, + { "key": "s", "value": "index" }, + { "key": "h", "value": "index" } + ], + "postRequestScript": "indexNames.js" + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "value", "displayName": "Index", "shape": "string", "role": "value", "visible": false }, + { "name": "label", "displayName": "Index", "shape": "string", "role": "label" } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/indices.json b/plugins/OpenSearch/v1/dataStreams/indices.json index c612d9db..c69331e9 100644 --- a/plugins/OpenSearch/v1/dataStreams/indices.json +++ b/plugins/OpenSearch/v1/dataStreams/indices.json @@ -1,26 +1,50 @@ { "name": "indices", "displayName": "Indices", + "description": "Health, status, shard, document and storage statistics for each index (system indices excluded)", + "tags": ["Cluster", "Indices"], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", - "expandInnerObjects": true, - "endpointPath": "_cat/indices", - "pathToData": "", - "getArgs": [], - "headers": [] + "endpointPath": "_cat/indices/{{ (typeof index !== 'undefined' && Array.isArray(index) && index.length) ? index.map((o) => [].concat(o.rawId)[0]).join(',') : '*,-.*' }}", + "getArgs": [ + { "key": "format", "value": "json" }, + { "key": "bytes", "value": "b" }, + { "key": "expand_wildcards", "value": "open,closed" }, + { "key": "ignore_unavailable", "value": "true" }, + { "key": "s", "value": "index" }, + { + "key": "h", + "value": "index,uuid,health,status,pri,rep,docs.count,docs.deleted,store.size,pri.store.size,creation.date.string" + } + ] }, - "timeframes": false, "matches": "none", - "visibility": { - "type": "hidden" - }, - "metadata": [ + "ui": [ { - "name": "index", - "shape": "string", - "displayName": "Index", - "role": "value" + "type": "objects", + "name": "index", + "label": "Index (optional)", + "matches": { "sourceType": { "type": "equals", "value": "Index" } } } + ], + "timeframes": false, + "metadata": [ + { "name": "index", "displayName": "Index", "shape": "string", "role": "label" }, + { "sourceId": "index", "sourceType": "Index", "name": "index" }, + { + "name": "health", + "displayName": "Health", + "shape": ["state", { "map": { "success": ["green"], "warning": ["yellow"], "error": ["red"] } }] + }, + { "name": "status", "displayName": "Status", "shape": ["state", { "map": { "success": ["open"], "unknown": ["close"] } }] }, + { "name": "docs.count", "displayName": "Documents", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }], "role": "value" }, + { "name": "docs.deleted", "displayName": "Deleted Documents", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }] }, + { "name": "store.size", "displayName": "Size", "shape": "bytes" }, + { "name": "pri.store.size", "displayName": "Primary Size", "shape": "bytes" }, + { "name": "pri", "displayName": "Primary Shards", "shape": ["number", { "decimalPlaces": 0 }] }, + { "name": "rep", "displayName": "Replicas", "shape": ["number", { "decimalPlaces": 0 }] }, + { "name": "creation.date.string", "displayName": "Created", "shape": "date" }, + { "name": "uuid", "displayName": "UUID", "shape": "string", "visible": false } ] -} \ No newline at end of file +} diff --git a/plugins/OpenSearch/v1/dataStreams/nodes.json b/plugins/OpenSearch/v1/dataStreams/nodes.json new file mode 100644 index 00000000..557a8635 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/nodes.json @@ -0,0 +1,56 @@ +{ + "name": "nodes", + "displayName": "Nodes", + "description": "Current heap, memory, CPU, load and disk usage for each node in the cluster", + "tags": ["Cluster", "Nodes"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "_cat/nodes", + "getArgs": [ + { "key": "format", "value": "json" }, + { "key": "full_id", "value": "true" }, + { "key": "bytes", "value": "b" }, + { "key": "time", "value": "ms" }, + { "key": "s", "value": "name" }, + { + "key": "h", + "value": "id,name,ip,version,node.roles,cluster_manager,heap.percent,heap.current,heap.max,ram.percent,ram.current,ram.max,cpu,load_1m,load_5m,load_15m,disk.used,disk.total,disk.used_percent,uptime" + } + ], + "postRequestScript": "nodes.js" + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "node", + "label": "Node (optional)", + "matches": { "sourceType": { "type": "equals", "value": "Node" } } + } + ], + "timeframes": false, + "metadata": [ + { "name": "name", "displayName": "Node", "shape": "string", "role": "label" }, + { "sourceId": "id", "sourceType": "Node", "name": "name" }, + { "name": "id", "displayName": "Node ID", "shape": "string", "visible": false }, + { "name": "isClusterManager", "displayName": "Cluster Manager", "shape": "boolean" }, + { "name": "roles", "displayName": "Roles", "shape": ["array", { "itemShape": "string" }] }, + { "name": "heap.percent", "displayName": "Heap Used (%)", "shape": ["percent", { "decimalPlaces": 0 }], "role": "value" }, + { "name": "heap.current", "displayName": "Heap Used", "shape": "bytes" }, + { "name": "heap.max", "displayName": "Heap Max", "shape": "bytes" }, + { "name": "ram.percent", "displayName": "Memory Used (%)", "shape": ["percent", { "decimalPlaces": 0 }] }, + { "name": "ram.current", "displayName": "Memory Used", "shape": "bytes" }, + { "name": "ram.max", "displayName": "Memory Total", "shape": "bytes" }, + { "name": "cpu", "displayName": "CPU (%)", "shape": ["percent", { "decimalPlaces": 0 }] }, + { "name": "load_1m", "displayName": "Load (1m)", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "load_5m", "displayName": "Load (5m)", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "load_15m", "displayName": "Load (15m)", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "disk.used_percent", "displayName": "Disk Used (%)", "shape": ["percent", { "decimalPlaces": 1 }] }, + { "name": "disk.used", "displayName": "Disk Used", "shape": "bytes" }, + { "name": "disk.total", "displayName": "Disk Total", "shape": "bytes" }, + { "name": "uptime", "displayName": "Uptime", "shape": ["milliseconds", { "formatDuration": true }] }, + { "name": "version", "displayName": "Version", "shape": "string" }, + { "name": "ip", "displayName": "IP Address", "shape": "string" } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/scripts/indexNames.js b/plugins/OpenSearch/v1/dataStreams/scripts/indexNames.js new file mode 100644 index 00000000..8089833b --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/scripts/indexNames.js @@ -0,0 +1,26 @@ +// Time-series data usually lands in one index per day or rollover (otel-logs-2026.10.16, +// logs-000042), so offer a wildcard pattern for each family ahead of the individual names. +const FAMILY = [ + /^(.+?[-_.])[0-9]{4}[-_.][0-9]{2}([-_.][0-9]{2})?([-_.].*)?$/, // date suffix + /^(.+?[-_])[0-9]{6}$/ // rollover counter +]; + +const names = (data || []).map((r) => r.index).filter(Boolean); +const families = new Map(); +for (const name of names) { + for (const pattern of FAMILY) { + const match = name.match(pattern); + if (match) { + const wildcard = `${match[1]}*`; + families.set(wildcard, (families.get(wildcard) || 0) + 1); + break; + } + } +} + +result = [ + ...[...families.entries()] + .filter(([, count]) => count > 1) + .map(([wildcard, count]) => ({ value: wildcard, label: `${wildcard} (${count} indices)` })), + ...names.map((name) => ({ value: name, label: name })) +]; diff --git a/plugins/OpenSearch/v1/dataStreams/scripts/nodes.js b/plugins/OpenSearch/v1/dataStreams/scripts/nodes.js new file mode 100644 index 00000000..0418a76c --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/scripts/nodes.js @@ -0,0 +1,14 @@ +// _cat/nodes has no node filter, so the optional Node picker is applied here. +// Selected objects arrive as an array, each rawId a single-element array. +const unwrap = (v) => (Array.isArray(v) ? v[0] : v); +const selected = (context.config && context.config.node) || []; +const nodeIds = new Set(selected.map((o) => unwrap(o.rawId)).filter(Boolean)); + +result = (data || []) + .filter((n) => nodeIds.size === 0 || nodeIds.has(n.id)) + .map(({ 'node.roles': roles, cluster_manager: clusterManager, ...n }) => ({ + ...n, + roles: roles ? roles.split(',') : [], + // The elected cluster manager is marked "*", other nodes "-" + isClusterManager: clusterManager === '*' + })); diff --git a/plugins/OpenSearch/v1/dataStreams/scripts/search.js b/plugins/OpenSearch/v1/dataStreams/scripts/search.js index cc8c056f..afd4fa3f 100644 --- a/plugins/OpenSearch/v1/dataStreams/scripts/search.js +++ b/plugins/OpenSearch/v1/dataStreams/scripts/search.js @@ -1,29 +1,42 @@ -if (data.aggregations && Object.keys(data.aggregations).length > 0) { - - const agg = data.aggregations[Object.keys(data.aggregations).at(-1)]; // last aggregation to support pipelining - - if(agg.buckets) { - - result = agg.buckets; - - } else if (agg.values) { - - result = [agg.values]; - - } else if (agg.value) { - - result = [{ - value: agg.value - }]; - - } else { - - result = [agg]; - - } - +// Date histogram buckets carry an ISO date in key_as_string; other aggregations may use it for +// formatted numbers or booleans, so only ISO dates become the bucketTime column. +const ISO_DATE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T/; + +const bucketRows = (buckets) => { + // Keyed aggregations (named filters, keyed ranges) return buckets as an object + const list = Array.isArray(buckets) ? buckets : Object.entries(buckets).map(([key, b]) => ({ key, ...b })); + return list.map((b) => (typeof b.key_as_string === 'string' && ISO_DATE.test(b.key_as_string) ? { bucketTime: b.key_as_string, ...b } : b)); +}; + +// Values requested with fields, docvalue_fields or script_fields always arrive as arrays +const fieldValues = (fields) => + Object.fromEntries(Object.entries(fields || {}).map(([name, v]) => [name, Array.isArray(v) && v.length === 1 ? v[0] : v])); + +const aggregations = data && data.aggregations; + +if (aggregations && Object.keys(aggregations).length > 0) { + // OpenSearch places pipeline aggregations after the aggregations they read from, + // so the last one in the response is the final result. + const agg = aggregations[Object.keys(aggregations).at(-1)]; + + if (agg.buckets) { + result = bucketRows(agg.buckets); + } else if (Array.isArray(agg.values)) { + result = agg.values; // percentiles with keyed: false + } else if (agg.values) { + result = [agg.values]; + } else if ('value' in agg) { + // Single-value metrics; a zero or null (no documents) value keeps the same row shape + result = [{ value: agg.value }]; + } else { + result = [agg]; + } } else { - - result = data.hits.hits.map(hit => hit._source); - -} \ No newline at end of file + result = ((data && data.hits && data.hits.hits) || []).map((hit) => ({ + ...hit._source, + ...fieldValues(hit.fields), + _id: hit._id, + _index: hit._index, + _score: hit._score + })); +} diff --git a/plugins/OpenSearch/v1/dataStreams/scripts/serviceLogVolume.js b/plugins/OpenSearch/v1/dataStreams/scripts/serviceLogVolume.js new file mode 100644 index 00000000..ae1eca41 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/scripts/serviceLogVolume.js @@ -0,0 +1,15 @@ +// Flattens time bucket > service > severity range into one row per combination. +// auto_date_histogram is the outer level so every service shares the same intervals. +const timeBuckets = (data && data.aggregations && data.aggregations.overTime && data.aggregations.overTime.buckets) || []; + +result = timeBuckets.flatMap((t) => + ((t.services && t.services.buckets) || []).flatMap((s) => + ((s.severity && s.severity.buckets) || []).map((r) => ({ + // key is always epoch milliseconds; key_as_string follows the field's mapping format + timestamp: typeof t.key === 'number' ? new Date(t.key).toISOString() : t.key_as_string, + service: String(s.key), + severity: r.key, + logs: r.doc_count + })) + ) +); diff --git a/plugins/OpenSearch/v1/dataStreams/scripts/serviceLogs.js b/plugins/OpenSearch/v1/dataStreams/scripts/serviceLogs.js new file mode 100644 index 00000000..450dc506 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/scripts/serviceLogs.js @@ -0,0 +1,41 @@ +// Log documents keep OpenTelemetry attributes either as nested objects (resource.service.name) +// or as literal dotted keys, depending on the exporter, so look fields up both ways. +const get = (obj, path) => { + if (obj === null || typeof obj !== 'object') return undefined; + if (path in obj) return obj[path]; + const parts = path.split('.'); + for (let i = parts.length - 1; i > 0; i--) { + const head = parts.slice(0, i).join('.'); + if (head in obj) return get(obj[head], parts.slice(i).join('.')); + } + return undefined; +}; + +// OpenTelemetry SeverityNumber ranges: 1-4 TRACE, 5-8 DEBUG, 9-12 INFO, 13-16 WARN, 17-20 ERROR, 21-24 FATAL +const severityName = (n) => { + if (typeof n !== 'number' || n < 1) return 'Unspecified'; + return ['Trace', 'Debug', 'Info', 'Warn', 'Error', 'Fatal'][Math.min(Math.floor((n - 1) / 4), 5)]; +}; + +// Date fields can be stored as epoch milliseconds as well as ISO strings +const isoTimestamp = (v) => (typeof v === 'number' ? new Date(v).toISOString() : v); + +const serviceField ='{{ (dataSource.otelServiceField || "resource.service.name.keyword").replace(/[.]keyword$/, "") }}'; +const timestampField = '{{ dataSource.otelTimestampField || "@timestamp" }}'; +const severityField = '{{ dataSource.otelSeverityField || "severity.number" }}'; + +result = ((data && data.hits && data.hits.hits) || []).map((hit) => { + const doc = hit._source || {}; + const body = get(doc, 'body'); + return { + timestamp: isoTimestamp(get(doc, timestampField)), + service: get(doc, serviceField), + severity: severityName(get(doc, severityField)), + message: typeof body === 'string' ? body : JSON.stringify(body), + traceId: get(doc, 'traceId') || null, + spanId: get(doc, 'spanId') || null, + pod: get(doc, 'resource.k8s.pod.name') || null, + _index: hit._index, + _id: hit._id + }; +}); diff --git a/plugins/OpenSearch/v1/dataStreams/scripts/services.js b/plugins/OpenSearch/v1/dataStreams/scripts/services.js new file mode 100644 index 00000000..6237c8f2 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/scripts/services.js @@ -0,0 +1,20 @@ +// The request groups logs by service, with warning (severity 13-16) and error +// (severity 17+, OpenTelemetry ERROR and FATAL) sub-counts nested in each bucket, +// plus the resource attributes a service can report several values for. +const buckets = (data && data.aggregations && data.aggregations.services && data.aggregations.services.buckets) || []; +const keys = (agg) => ((agg && agg.buckets) || []).map((x) => String(x.key)); + +result = buckets.map((b) => { + const errors = b.errors ? b.errors.doc_count : 0; + return { + service: String(b.key), + logs: b.doc_count, + warnings: b.warnings ? b.warnings.doc_count : 0, + errors, + errorRate: b.doc_count ? (100 * errors) / b.doc_count : 0, + // deployment.environment was renamed deployment.environment.name in newer semantic conventions + environments: [...new Set([...keys(b.environmentName), ...keys(b.environment)])], + k8sNamespaces: keys(b.k8sNamespaces), + k8sDeployments: keys(b.k8sDeployments) + }; +}); diff --git a/plugins/OpenSearch/v1/dataStreams/search.json b/plugins/OpenSearch/v1/dataStreams/search.json index b410b5be..ca0092fa 100644 --- a/plugins/OpenSearch/v1/dataStreams/search.json +++ b/plugins/OpenSearch/v1/dataStreams/search.json @@ -1,6 +1,8 @@ { "name": "search", - "displayName": "Search", + "displayName": "Search Results", + "description": "Documents or aggregation results from a Query DSL search of one or more indices", + "tags": ["Search"], "baseDataSourceName": "httpRequestUnscoped", "matches": "none", "manualConfigApply": true, @@ -11,39 +13,54 @@ "postRequestScript": "search.js", "getArgs": [], "headers": [], - "postBody": "{{query}}" + "postBody": "{{ (() => { let body = query; if (typeof body === 'string') { try { body = JSON.parse(body); } catch (e) { return body; } } const field = typeof timeField === 'string' ? timeField.trim() : ''; const tf = typeof timeframe !== 'undefined' ? timeframe : null; if (!field || !tf || !tf.start || tf.enum === 'none') { return body; } const span = { range: { [field]: { gte: tf.start, lte: tf.end, format: 'strict_date_optional_time' } } }; return Object.assign({}, body, { query: { bool: { must: [ (body && body.query) || { match_all: {} } ], filter: [ span ] } } }); })() }}", + "errorHandling": { "type": "path", "realm": "payload", "path": "error.root_cause.0.reason" } }, "ui": [ { - "name": "index", - "type": "autocomplete", - "label": "Index", - "validation": { - "required": true - }, - "placeholder": "Select index", - "isMulti": false, - "allowCustomValues": true, - "data": { - "source": "dataStream", - "dataStreamName": "indices", - "dataSourceConfig": {} - } + "name": "index", + "type": "autocomplete", + "label": "Index", + "validation": { + "required": true + }, + "placeholder": "Select an index or pattern", + "isMulti": false, + "allowCustomValues": true, + "data": { + "source": "dataStream", + "dataStreamName": "indexNames", + "dataSourceConfig": {} + } }, { - "name": "query", - "type": "json", - "defaultValue": { - "size": 100 + "name": "query", + "type": "json", + "defaultValue": { + "size": 100 }, - "label": "Query body", - "help": "The Query DSL JSON. See [OpenSearch API docs](https://docs.opensearch.org/latest/api-reference/search-apis/search/#request-body).", - "validation": { - "required": true - } + "label": "Query body", + "help": "The Query DSL request body. See the [OpenSearch search API](https://docs.opensearch.org/latest/api-reference/search-apis/search/#request-body).", + "validation": { + "required": true + } + }, + { + "name": "timeField", + "type": "text", + "label": "Time field", + "placeholder": "@timestamp", + "help": "Filters results to the tile's timeframe using this date field. Leave empty to ignore the timeframe." } ], "supportsNoneTimeframe": true, "requiresParameterTimeframe": true, - "defaultTimeframe": "none" -} \ No newline at end of file + "defaultTimeframe": "none", + "metadata": [ + { "name": "bucketTime", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "_id", "displayName": "Document ID", "shape": "string", "visible": false }, + { "name": "_index", "displayName": "Index", "shape": "string", "visible": false }, + { "name": "_score", "displayName": "Score", "shape": "number", "visible": false }, + { "pattern": ".*" } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/serviceLogVolume.json b/plugins/OpenSearch/v1/dataStreams/serviceLogVolume.json new file mode 100644 index 00000000..948178b0 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/serviceLogVolume.json @@ -0,0 +1,37 @@ +{ + "name": "serviceLogVolume", + "displayName": "Service Log Volume", + "description": "OpenTelemetry log counts over time by service and severity (requires Import OpenTelemetry services)", + "tags": ["Logs", "Services"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "post", + "endpointPath": "{{ (dataSource.indexOtelServices === true || dataSource.indexOtelServices === 'true') && dataSource.otelLogIndexPattern ? dataSource.otelLogIndexPattern.trim() : 'squaredup-otel-disabled-*' }}/_search", + "getArgs": [ + { "key": "allow_no_indices", "value": "true" }, + { "key": "ignore_unavailable", "value": "true" } + ], + "postBody": "{{ (() => { const svc = dataSource.otelServiceField || 'resource.service.name.keyword'; const ts = dataSource.otelTimestampField || '@timestamp'; const sev = dataSource.otelSeverityField || 'severity.number'; const names = (typeof service !== 'undefined' && Array.isArray(service)) ? service.map((o) => [].concat(o.rawId)[0]) : []; const tf = typeof timeframe !== 'undefined' ? timeframe : null; const span = (tf && tf.start && tf.enum !== 'none') ? { gte: tf.start, lte: tf.end, format: 'strict_date_optional_time' } : { gte: 'now-24h' }; const filter = [ { range: { [ts]: span } } ]; if (names.length) { filter.push({ terms: { [svc]: names } }); } return { size: 0, track_total_hits: false, query: { bool: { filter: filter } }, aggs: { overTime: { auto_date_histogram: { field: ts, buckets: 60 }, aggs: { services: { terms: { field: svc, size: 100 }, aggs: { severity: { range: { field: sev, ranges: [ { key: 'Unspecified', to: 1 }, { key: 'Trace', from: 1, to: 5 }, { key: 'Debug', from: 5, to: 9 }, { key: 'Info', from: 9, to: 13 }, { key: 'Warn', from: 13, to: 17 }, { key: 'Error', from: 17, to: 21 }, { key: 'Fatal', from: 21 } ] } } } } } } } }; })() }}", + "postRequestScript": "serviceLogVolume.js", + "errorHandling": { "type": "path", "realm": "payload", "path": "error.root_cause.0.reason" } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "service", + "label": "Service (optional)", + "matches": { "sourceType": { "type": "equals", "value": "Service" } } + } + ], + "timeframes": true, + "metadata": [ + { "name": "timestamp", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "service", "displayName": "Service", "shape": "string", "role": "label" }, + { "sourceId": "service", "sourceType": "Service", "name": "service" }, + { "name": "serviceEnvironments", "displayName": "Service Environments", "sourceId": "service", "sourceType": "Service", "objectPropertyPath": "environments", "shape": ["array", { "itemShape": "string" }] }, + { "name": "serviceK8sNamespaces", "displayName": "Service Kubernetes Namespaces", "sourceId": "service", "sourceType": "Service", "objectPropertyPath": "k8sNamespaces", "shape": ["array", { "itemShape": "string" }] }, + { "name": "severity", "displayName": "Severity", "shape": "string" }, + { "name": "logs", "displayName": "Logs", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }], "role": "value" } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/serviceLogs.json b/plugins/OpenSearch/v1/dataStreams/serviceLogs.json new file mode 100644 index 00000000..1dbfa797 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/serviceLogs.json @@ -0,0 +1,67 @@ +{ + "name": "serviceLogs", + "displayName": "Service Logs", + "description": "Most recent OpenTelemetry log records by service, newest first (requires Import OpenTelemetry services)", + "tags": ["Logs", "Services"], + "baseDataSourceName": "httpRequestUnscoped", + "manualConfigApply": true, + "config": { + "httpMethod": "post", + "endpointPath": "{{ (dataSource.indexOtelServices === true || dataSource.indexOtelServices === 'true') && dataSource.otelLogIndexPattern ? dataSource.otelLogIndexPattern.trim() : 'squaredup-otel-disabled-*' }}/_search", + "getArgs": [ + { "key": "allow_no_indices", "value": "true" }, + { "key": "ignore_unavailable", "value": "true" } + ], + "postBody": "{{ (() => { const svc = dataSource.otelServiceField || 'resource.service.name.keyword'; const ts = dataSource.otelTimestampField || '@timestamp'; const sev = dataSource.otelSeverityField || 'severity.number'; const names = (typeof service !== 'undefined' && Array.isArray(service)) ? service.map((o) => [].concat(o.rawId)[0]) : []; const tf = typeof timeframe !== 'undefined' ? timeframe : null; const span = (tf && tf.start && tf.enum !== 'none') ? { gte: tf.start, lte: tf.end, format: 'strict_date_optional_time' } : { gte: 'now-24h' }; const filter = [ { range: { [ts]: span } } ]; if (names.length) { filter.push({ terms: { [svc]: names } }); } const picked = typeof minimumSeverity !== 'undefined' && minimumSeverity !== null ? [].concat(minimumSeverity)[0] : null; const floor = Number(picked && typeof picked === 'object' ? picked.value : picked); if (floor > 0) { filter.push({ range: { [sev]: { gte: floor } } }); } const size = Math.min(Math.max(Number(typeof limit !== 'undefined' && limit ? limit : 100), 1), 1000); return { size: size, track_total_hits: false, sort: [ { [ts]: { order: 'desc' } } ], query: { bool: { filter: filter } } }; })() }}", + "postRequestScript": "serviceLogs.js", + "errorHandling": { "type": "path", "realm": "payload", "path": "error.root_cause.0.reason" } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "service", + "label": "Service (optional)", + "matches": { "sourceType": { "type": "equals", "value": "Service" } } + }, + { + "type": "autocomplete", + "name": "minimumSeverity", + "label": "Minimum severity", + "isMulti": false, + "isClearable": true, + "data": { + "source": "fixed", + "values": [ + { "value": "5", "label": "Debug" }, + { "value": "9", "label": "Info" }, + { "value": "13", "label": "Warn" }, + { "value": "17", "label": "Error" }, + { "value": "21", "label": "Fatal" } + ] + } + }, + { + "type": "number", + "name": "limit", + "label": "Maximum records", + "defaultValue": 100, + "validation": { "min": 1, "max": 1000 } + } + ], + "timeframes": true, + "metadata": [ + { "name": "timestamp", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "service", "displayName": "Service", "shape": "string", "role": "label" }, + { "sourceId": "service", "sourceType": "Service", "name": "service" }, + { "name": "serviceEnvironments", "displayName": "Service Environments", "sourceId": "service", "sourceType": "Service", "objectPropertyPath": "environments", "shape": ["array", { "itemShape": "string" }] }, + { "name": "serviceK8sNamespaces", "displayName": "Service Kubernetes Namespaces", "sourceId": "service", "sourceType": "Service", "objectPropertyPath": "k8sNamespaces", "shape": ["array", { "itemShape": "string" }] }, + { "name": "severity", "displayName": "Severity", "shape": ["state", { "map": { "error": ["Error", "Fatal"], "warning": ["Warn"], "success": ["Info", "Debug", "Trace"], "unknown": ["Unspecified"] } }] }, + { "name": "message", "displayName": "Message", "shape": "string", "role": "value" }, + { "name": "traceId", "displayName": "Trace ID", "shape": "string" }, + { "name": "spanId", "displayName": "Span ID", "shape": "string", "visible": false }, + { "name": "pod", "displayName": "Pod", "shape": "string" }, + { "name": "_index", "displayName": "Index", "shape": "string", "visible": false }, + { "name": "_id", "displayName": "Document ID", "shape": "string", "visible": false } + ] +} diff --git a/plugins/OpenSearch/v1/dataStreams/services.json b/plugins/OpenSearch/v1/dataStreams/services.json new file mode 100644 index 00000000..f3959180 --- /dev/null +++ b/plugins/OpenSearch/v1/dataStreams/services.json @@ -0,0 +1,40 @@ +{ + "name": "services", + "displayName": "Services", + "description": "Services found in OpenTelemetry logs, with log, warning and error counts (requires Import OpenTelemetry services)", + "tags": ["Logs", "Services"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "post", + "endpointPath": "{{ (dataSource.indexOtelServices === true || dataSource.indexOtelServices === 'true') && dataSource.otelLogIndexPattern ? dataSource.otelLogIndexPattern.trim() : 'squaredup-otel-disabled-*' }}/_search", + "getArgs": [ + { "key": "allow_no_indices", "value": "true" }, + { "key": "ignore_unavailable", "value": "true" } + ], + "postBody": "{{ (() => { const svc = dataSource.otelServiceField || 'resource.service.name.keyword'; const layout = svc.match(/^(.*)service[.]name([.]keyword)?$/) || ['', '', '']; const attr = (name) => (layout[1] || '') + name + (layout[2] || ''); const ts = dataSource.otelTimestampField || '@timestamp'; const sev = dataSource.otelSeverityField || 'severity.number'; const names = (typeof service !== 'undefined' && Array.isArray(service)) ? service.map((o) => [].concat(o.rawId)[0]) : []; const tf = typeof timeframe !== 'undefined' ? timeframe : null; const span = (tf && tf.start && tf.enum !== 'none') ? { gte: tf.start, lte: tf.end, format: 'strict_date_optional_time' } : { gte: 'now-24h' }; const filter = [ { range: { [ts]: span } } ]; if (names.length) { filter.push({ terms: { [svc]: names } }); } return { size: 0, track_total_hits: false, query: { bool: { filter: filter } }, aggs: { services: { terms: { field: svc, size: 1000 }, aggs: { warnings: { filter: { range: { [sev]: { gte: 13, lt: 17 } } } }, errors: { filter: { range: { [sev]: { gte: 17 } } } }, environment: { terms: { field: attr('deployment.environment'), size: 20 } }, environmentName: { terms: { field: attr('deployment.environment.name'), size: 20 } }, k8sNamespaces: { terms: { field: attr('k8s.namespace.name'), size: 50 } }, k8sDeployments: { terms: { field: attr('k8s.deployment.name'), size: 50 } } } } } }; })() }}", + "postRequestScript": "services.js", + "errorHandling": { "type": "path", "realm": "payload", "path": "error.root_cause.0.reason" } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "service", + "label": "Service (optional)", + "matches": { "sourceType": { "type": "equals", "value": "Service" } } + } + ], + "timeframes": true, + "supportsNoneTimeframe": true, + "metadata": [ + { "name": "service", "displayName": "Service", "shape": "string", "role": "label" }, + { "sourceId": "service", "sourceType": "Service", "name": "service" }, + { "name": "logs", "displayName": "Logs", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }] }, + { "name": "warnings", "displayName": "Warnings", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }] }, + { "name": "errors", "displayName": "Errors", "shape": ["number", { "decimalPlaces": 0, "thousandsSeparator": true }], "role": "value" }, + { "name": "errorRate", "displayName": "Error Rate", "shape": ["percent", { "decimalPlaces": 2 }] }, + { "name": "environments", "displayName": "Environments", "shape": ["array", { "itemShape": "string" }] }, + { "name": "k8sNamespaces", "displayName": "Kubernetes Namespaces", "shape": ["array", { "itemShape": "string" }] }, + { "name": "k8sDeployments", "displayName": "Kubernetes Deployments", "shape": ["array", { "itemShape": "string" }] } + ] +} diff --git a/plugins/OpenSearch/v1/defaultContent/index.dash.json b/plugins/OpenSearch/v1/defaultContent/index.dash.json new file mode 100644 index 00000000..54984698 --- /dev/null +++ b/plugins/OpenSearch/v1/defaultContent/index.dash.json @@ -0,0 +1,469 @@ +{ + "name": "Index", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "f7326f6d-5b9f-491c-88c7-7da018b75345", + "x": 0, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Index Properties", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "uuid", + "created", + "primaryShards", + "replicas" + ], + "hiddenColumns": [ + "id", + "links", + "type", + "label", + "sourceId", + "sourceType", + "sourceName" + ], + "columnDisplayNames": { + "primaryShards": "Primary shards", + "uuid": "UUID" + } + } + } + } + } + }, + { + "i": "b6eabe89-d701-4194-881e-6750ee818946", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Health", + "description": "Index health", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "index", + "stateColumn": "health", + "sublabel": "health[Expanded].rawState", + "columns": 1 + } + } + } + } + }, + { + "i": "93ec73ab-df03-4adb-9b0c-ce1f070cb90b", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Status", + "description": "Open or closed status", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "index", + "stateColumn": "status", + "sublabel": "status[Expanded].rawState", + "columns": 1 + } + } + } + } + }, + { + "i": "e3a45fc2-34dc-422d-9454-3c8e2f5b0f54", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Documents", + "description": "Live documents in the index", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "docs.count", + "comparisonColumn": "none", + "label": "Documents" + } + } + } + } + }, + { + "i": "9306a0dc-ccbd-4367-b4bb-a69b24e69910", + "x": 1, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Size", + "description": "Total store size including replicas", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "store.size", + "comparisonColumn": "none", + "label": "Size" + } + } + } + } + }, + { + "i": "8f096e52-c2bf-4d11-90a4-8f2fd54b86d2", + "x": 2, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Primary Size", + "description": "Store size of primary shards only", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "pri.store.size", + "comparisonColumn": "none", + "label": "Primary size" + } + } + } + } + }, + { + "i": "a1858530-4e1b-4398-b588-9718ab88e467", + "x": 3, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Deleted Documents", + "description": "Documents marked deleted but not yet merged away", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "docs.deleted", + "comparisonColumn": "none", + "label": "Deleted documents" + } + } + } + } + }, + { + "i": "c617abeb-95d8-433c-9442-2d592c64a10b", + "x": 0, + "y": 4, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Primary Shards", + "description": "Number of primary shards", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "pri", + "comparisonColumn": "none", + "label": "Primary shards" + } + } + } + } + }, + { + "i": "ca40e1a2-62bd-466e-9c26-907fc2e7a083", + "x": 2, + "y": 4, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Replicas", + "description": "Replica copies per primary shard", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "index": { + "variable": "{{variables.[OpenSearch Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Indices]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Indices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Index]}}" + }, + "variables": [ + "{{variables.[OpenSearch Index]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "rep", + "comparisonColumn": "none", + "label": "Replicas" + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenSearch/v1/defaultContent/manifest.json b/plugins/OpenSearch/v1/defaultContent/manifest.json new file mode 100644 index 00000000..b65723f5 --- /dev/null +++ b/plugins/OpenSearch/v1/defaultContent/manifest.json @@ -0,0 +1,20 @@ +{ + "items": [ + { + "name": "overview", + "type": "dashboard" + }, + { + "name": "node", + "type": "dashboard" + }, + { + "name": "index", + "type": "dashboard" + }, + { + "name": "service", + "type": "dashboard" + } + ] +} diff --git a/plugins/OpenSearch/v1/defaultContent/node.dash.json b/plugins/OpenSearch/v1/defaultContent/node.dash.json new file mode 100644 index 00000000..84791080 --- /dev/null +++ b/plugins/OpenSearch/v1/defaultContent/node.dash.json @@ -0,0 +1,436 @@ +{ + "name": "Node", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "b83708b0-6f58-4ef1-9858-dd9a5237acd2", + "x": 0, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Node Properties", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "ip", + "version", + "roles" + ], + "hiddenColumns": [ + "id", + "links", + "type", + "label", + "sourceId", + "sourceType", + "sourceName" + ], + "columnDisplayNames": {} + } + } + } + } + }, + { + "i": "99948cd0-ea2f-4cdc-85c2-e406cc8149ec", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Heap Used", + "description": "Percentage of JVM heap in use", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "heap.percent", + "comparisonColumn": "none", + "label": "Heap used" + } + } + } + } + }, + { + "i": "4b2cbfcd-5f8a-44ae-9a95-6938dae7c686", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Memory Used", + "description": "Percentage of system memory in use", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "ram.percent", + "comparisonColumn": "none", + "label": "Memory used" + } + } + } + } + }, + { + "i": "9ccbb722-e38f-4a02-901c-3733082f9882", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "CPU", + "description": "CPU utilisation", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "cpu", + "comparisonColumn": "none", + "label": "CPU" + } + } + } + } + }, + { + "i": "93217d44-6818-4795-8f99-8fbe74ac17ac", + "x": 1, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Disk Used", + "description": "Percentage of disk space in use", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "disk.used_percent", + "comparisonColumn": "none", + "label": "Disk used" + } + } + } + } + }, + { + "i": "ca55c435-5887-4ba8-8444-61cad51d4940", + "x": 2, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Load", + "description": "One-minute load average", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "load_1m", + "comparisonColumn": "none", + "label": "Load (1m)" + } + } + } + } + }, + { + "i": "d3a09a5c-e919-472b-bdba-4aa766530275", + "x": 3, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Uptime", + "description": "Time since the node started", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "uptime", + "comparisonColumn": "none", + "label": "Uptime" + } + } + } + } + }, + { + "i": "a4c5d734-a7e4-4072-87a2-543d4b014b5c", + "x": 0, + "y": 4, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Node Details", + "description": "Memory, heap and disk capacity for this node", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "node": { + "variable": "{{variables.[OpenSearch Node]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Nodes]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Nodes]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Node]}}" + }, + "variables": [ + "{{variables.[OpenSearch Node]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "isClusterManager", + "roles", + "version", + "ip", + "heap.current", + "heap.max", + "ram.current", + "ram.max", + "disk.used", + "disk.total", + "load_1m", + "load_5m", + "load_15m" + ], + "hiddenColumns": [ + "id", + "heap.percent", + "ram.percent", + "cpu", + "disk.used_percent", + "uptime" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenSearch/v1/defaultContent/overview.dash.json b/plugins/OpenSearch/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..901eb8af --- /dev/null +++ b/plugins/OpenSearch/v1/defaultContent/overview.dash.json @@ -0,0 +1,465 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "8fd115b0-0bca-408c-94c1-40ee3b19abf1", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Cluster Status", + "description": "Current cluster health status", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[clusterHealth]}}", + "name": "clusterHealth", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "cluster_name", + "stateColumn": "status", + "sublabel": "status[Expanded].rawState", + "columns": 1 + } + } + } + } + }, + { + "i": "dd45645e-ec7c-4b1f-9fbc-47ef0f038ed5", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Version", + "description": "OpenSearch version running on the cluster", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[clusterInfo]}}", + "name": "clusterInfo", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "version.number", + "comparisonColumn": "none", + "label": "Version" + } + } + } + } + }, + { + "i": "adfa5670-700c-4489-9679-dcbf85d1e501", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Nodes", + "description": "Nodes in the cluster", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[clusterHealth]}}", + "name": "clusterHealth", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "number_of_nodes", + "comparisonColumn": "none", + "label": "Nodes" + } + } + } + } + }, + { + "i": "d716ba27-cdc0-47a0-be89-3be8858d6707", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Unassigned Shards", + "description": "Shards not allocated to any node", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[clusterHealth]}}", + "name": "clusterHealth", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "unassigned_shards", + "comparisonColumn": "none", + "label": "Unassigned shards" + } + } + } + } + }, + { + "i": "92e16ab2-25ee-4723-ab72-639e7b04fb28", + "x": 0, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Shards", + "description": "Percentage of shards that are active", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[clusterHealth]}}", + "name": "clusterHealth", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "active_shards_percent_as_number", + "comparisonColumn": "none", + "label": "Active shards" + } + } + } + } + }, + { + "i": "388ed09a-21a4-46e9-b863-fb604c035d16", + "x": 1, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pending Tasks", + "description": "Cluster-level tasks waiting to run", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[clusterHealth]}}", + "name": "clusterHealth", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "number_of_pending_tasks", + "comparisonColumn": "none", + "label": "Pending tasks" + } + } + } + } + }, + { + "i": "99c6a4df-0bf4-43e4-849b-a2d68d849a72", + "x": 2, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Indices", + "description": "Non-system indices", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Indices" + } + } + } + } + }, + { + "i": "245da0f2-a00b-4a10-a35c-afaa6e34c34f", + "x": 3, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Documents", + "description": "Documents across all non-system indices", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "sum", + "names": [ + "docs.count" + ] + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "docs.count_sum", + "comparisonColumn": "none", + "label": "Documents" + } + } + } + } + }, + { + "i": "f19dd426-d657-4665-ac41-3ae1642f01b9", + "x": 0, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Index Health", + "description": "Health of each non-system index", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "index", + "asc" + ] + ] + } + }, + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "index", + "stateColumn": "health", + "sublabel": "health[Expanded].rawState", + "columns": 4, + "linkColumn": "index" + } + } + } + } + }, + { + "i": "138bd259-81d4-4594-9d86-f088c3148d69", + "x": 2, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Largest Indices", + "description": "Top 10 indices by total store size", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[indices]}}", + "name": "indices", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "store.size", + "desc" + ] + ], + "top": 10 + } + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "index", + "yAxisData": [ + "store.size" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "", + "showXAxisLabel": false, + "showYAxisLabel": false, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "horizontal", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "a3734c4c-4580-4902-89d5-504aedb8b5db", + "x": 0, + "y": 8, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Node Resources", + "description": "Heap, memory, CPU, load and disk usage per node", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[nodes]}}", + "name": "nodes", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "isClusterManager", + "heap.percent", + "ram.percent", + "cpu", + "disk.used_percent", + "load_1m", + "uptime" + ], + "hiddenColumns": [ + "id", + "roles", + "heap.current", + "heap.max", + "ram.current", + "ram.max", + "load_5m", + "load_15m", + "disk.used", + "disk.total", + "version", + "ip" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenSearch/v1/defaultContent/scopes.json b/plugins/OpenSearch/v1/defaultContent/scopes.json new file mode 100644 index 00000000..eb0076ba --- /dev/null +++ b/plugins/OpenSearch/v1/defaultContent/scopes.json @@ -0,0 +1,53 @@ +[ + { + "name": "OpenSearch Nodes", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Node" + ] + } + }, + "variable": { + "name": "OpenSearch Node", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "OpenSearch Indices", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Index" + ] + } + }, + "variable": { + "name": "OpenSearch Index", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "OpenSearch Services", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Service" + ] + } + }, + "variable": { + "name": "OpenSearch Service", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/OpenSearch/v1/defaultContent/service.dash.json b/plugins/OpenSearch/v1/defaultContent/service.dash.json new file mode 100644 index 00000000..7e4dc416 --- /dev/null +++ b/plugins/OpenSearch/v1/defaultContent/service.dash.json @@ -0,0 +1,379 @@ +{ + "name": "Service", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "2011474e-2e63-45d9-9508-6bc28eaa91ee", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Errors", + "description": "Error and fatal log records in the timeframe", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[services]}}", + "name": "services", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "service": { + "variable": "{{variables.[OpenSearch Service]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Services]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "errors", + "comparisonColumn": "none", + "label": "Errors" + } + } + } + } + }, + { + "i": "707e5d77-931d-42a7-aa61-4cc9d52271ba", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Error Rate", + "description": "Share of log records at Error or above", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[services]}}", + "name": "services", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "service": { + "variable": "{{variables.[OpenSearch Service]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Services]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "errorRate", + "comparisonColumn": "none", + "label": "Error rate" + } + } + } + } + }, + { + "i": "3c035275-9171-4fa4-af60-acb9b24f8d1d", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Warnings", + "description": "Warning log records in the timeframe", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[services]}}", + "name": "services", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "service": { + "variable": "{{variables.[OpenSearch Service]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Services]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "warnings", + "comparisonColumn": "none", + "label": "Warnings" + } + } + } + } + }, + { + "i": "9841824d-014e-4524-a614-9ec4e59fc924", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Logs", + "description": "All log records in the timeframe", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[services]}}", + "name": "services", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "service": { + "variable": "{{variables.[OpenSearch Service]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Services]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "logs", + "comparisonColumn": "none", + "label": "Logs" + } + } + } + } + }, + { + "i": "f05d4672-0bc1-4fd3-82c8-ebcdcc44ef74", + "x": 0, + "y": 2, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Properties", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "environments", + "k8sNamespaces", + "k8sDeployments" + ], + "hiddenColumns": [ + "id", + "links", + "type", + "label", + "sourceId", + "sourceType", + "sourceName" + ], + "columnDisplayNames": { + "name": "Service", + "environments": "Environments", + "k8sNamespaces": "Kubernetes namespaces", + "k8sDeployments": "Kubernetes deployments" + } + } + } + } + } + }, + { + "i": "c747cd70-566e-4f8a-97d6-29bbd4fe933c", + "x": 1, + "y": 2, + "w": 3, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Log Volume", + "description": "Log records over time by severity", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[serviceLogVolume]}}", + "name": "serviceLogVolume", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "service": { + "variable": "{{variables.[OpenSearch Service]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Services]}}" + } + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "timestamp", + "yAxisColumn": [ + "logs" + ], + "seriesColumn": "severity", + "showLegend": true, + "legendPosition": "bottom", + "showTrendLine": false + } + } + } + } + }, + { + "i": "cd48c2a9-b4f8-4b4a-b75f-df427a9fcfcd", + "x": 0, + "y": 6, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Warnings and Errors", + "description": "Latest warning, error and fatal log records", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[serviceLogs]}}", + "name": "serviceLogs", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "service": { + "variable": "{{variables.[OpenSearch Service]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[OpenSearch Services]}}" + }, + "minimumSeverity": "13", + "limit": 50 + } + }, + "scope": { + "scope": "{{scopes.[OpenSearch Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[OpenSearch Service]}}" + }, + "variables": [ + "{{variables.[OpenSearch Service]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "timestamp", + "severity", + "message", + "pod", + "traceId" + ], + "hiddenColumns": [ + "service" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/OpenSearch/v1/docs/README.md b/plugins/OpenSearch/v1/docs/README.md new file mode 100644 index 00000000..438bafb5 --- /dev/null +++ b/plugins/OpenSearch/v1/docs/README.md @@ -0,0 +1,102 @@ +Monitor the health of your [OpenSearch](https://opensearch.org) cluster, its nodes and indices, and chart search results and aggregations from your data in SquaredUp, using the [OpenSearch REST API](https://docs.opensearch.org/latest/api-reference/). + +## Setup + +### 1. Choose how SquaredUp reaches the cluster + +This plugin runs either in the SquaredUp cloud or through a relay agent on your own network: + +- **Cloud** — SquaredUp connects to the cluster directly. The **Instance URL** must be reachable from the internet, for example a public Amazon OpenSearch Service domain. If the cluster restricts access by IP address, allow the SquaredUp addresses listed in [Data security](https://docs.squaredup.com/reference/security/data-security). +- **Relay agent** — use this for a cluster on a private network, in a VPC, or on `localhost`. [Install a relay agent](https://docs.squaredup.com/features/connect-and-explore/relay-agents) on a machine that can reach the cluster's REST port (usually `9200`), then select that agent when you add the data source. The **Instance URL** is then resolved from the agent, so an internal hostname works. + +### 2. Create read-only credentials + +The plugin only reads data. Give it a dedicated identity with the built-in **readall_and_monitor** role, which allows searching every index and reading cluster, node and index statistics. If you only need search, the **readall** role is enough, but the cluster, node and index data streams and dashboards will be empty. + +**Username and password** (any OpenSearch version, including Amazon OpenSearch Service with fine-grained access control): + +1. In OpenSearch Dashboards, go to **Management → Security → Internal users** and select **Create internal user**. +2. Enter a username and password, then select **Create**. +3. Go to **Security → Roles**, open **readall_and_monitor**, select the **Mapped users** tab, then **Manage mapping**, and add the new user. + +**API key** (OpenSearch 3.7 and later, with [API keys enabled](https://docs.opensearch.org/latest/security/access-control/api-keys/)): + +1. As a security administrator, go to **Security → API Keys** in OpenSearch Dashboards, or call `POST _plugins/_security/api/apitokens`. +2. Grant the `cluster_monitor` cluster permission, and `read` and `indices_monitor` on the index pattern `*` (or only the indices you want to query). +3. Copy the token, which starts with `os_`. It is shown only once. + +API keys expire, after 90 days at most. Create a new key and update the data source before it expires. + +### 3. Add the data source + +Enter the **Instance URL** and choose an **Authentication** method. If the cluster uses a self-signed certificate, which a default OpenSearch install does, turn on **Ignore certificate errors**. + +### 4. Optional: import OpenTelemetry services + +If the cluster stores logs from the [OpenTelemetry Collector OpenSearch exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/opensearchexporter), turn on **Import OpenTelemetry services**. Each service that writes logs becomes an object with its own dashboard showing log volume, errors and recent log records. + +The defaults match the exporter's standard document layout and index names. If your pipeline writes elsewhere, change **Log index pattern**. For a different document layout, such as Data Prepper's, also change the field settings to that layout's field names. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| ----- | ---------- | ---------------- | -------- | +| **Instance URL** | The base URL of the OpenSearch REST API, including the scheme and port, for example `https://opensearch.example.com:9200`. Use `https://` whenever the plugin sends credentials, as `http://` sends them unencrypted. | The address clients use to connect to the cluster. For Amazon OpenSearch Service, the domain endpoint in the AWS console. | Yes | +| **Authentication** | How the plugin signs in: **Username and password**, **API key**, or **No authentication** for a cluster with the Security plugin disabled. | — | Yes | +| **Username** | The OpenSearch user the plugin signs in as. | OpenSearch Dashboards → **Security → Internal users**. | When using username and password | +| **Password** | The password for that user. | Set when the user is created. | When using username and password | +| **API key** | An API key token, sent as `Authorization: ApiKey `. | OpenSearch Dashboards → **Security → API Keys**. | When using an API key | +| **Ignore certificate errors** | Skips TLS certificate validation. Only use it for a self-signed certificate on a network path you trust, as it removes protection against interception. | — | No | +| **Import OpenTelemetry services** | Imports each service found in OpenTelemetry logs as an object and enables the service data streams. | — | No | +| **Log index pattern** | Comma-separated index names or wildcard patterns holding OpenTelemetry logs. Defaults to `ss4o_logs-*,otel-logs-*`. | The exporter's `logs_index` setting, or its default `ss4o_logs--`. | When importing services | +| **Service field** | An aggregatable field holding `service.name`. Defaults to `resource.service.name.keyword`. Use `resource.service.name` if your index template maps it as a keyword. | OpenSearch Dashboards → **Discover**, or `GET /_mapping`. | When importing services | +| **Timestamp field** | The log record's date field. Defaults to `@timestamp`. | As above. | When importing services | +| **Severity number field** | The numeric OpenTelemetry SeverityNumber field. Defaults to `severity.number`. | As above. | When importing services | + +When you save, the plugin checks it can connect and authenticate by reading the cluster's name and version. A failure means the URL is unreachable (from the relay agent, if you selected one), the credentials are wrong, or the certificate is not trusted. A second check reads cluster health; if it fails, the user is missing the monitoring permissions above. Neither check tests search, which needs read permission on the indices you query. + +## What this plugin monitors + +- **Cluster health**: status, node counts, shard allocation (including unassigned shards), pending tasks and version. +- **Nodes**: heap, memory, CPU, load, disk and uptime for each node, plus roles and which node is the cluster manager. +- **Indices**: health, open or closed status, document counts and storage size for every index. +- **Your data**: any Query DSL search or aggregation, charted directly, plus document counts for KPIs and monitors. Both can follow the tile's timeframe. +- **OpenTelemetry services** (optional): log volume by severity, error counts and rates, and the latest log records for each service. + +The out-of-the-box dashboards are an **Overview** of the cluster, plus a perspective for each **Node**, **Index** and **Service**. + +## Data streams + +- **Cluster Info**: cluster name, UUID and OpenSearch version, one row for the cluster. +- **Cluster Health**: current cluster status with node and shard counts, one row for the cluster. +- **Nodes**: current resource usage for each node, optionally filtered to selected Node objects. +- **Indices**: health, status, shard, document and storage statistics for each index, optionally filtered to selected Index objects. +- **Search Results**: documents or aggregation results from a Query DSL request against an index, alias or pattern. Set **Time field** to filter by the tile's timeframe. +- **Document Count**: the number of documents matching an optional query, with the same **Time field** option. +- **Services**: log, warning and error counts per OpenTelemetry service, with each service's environments, Kubernetes namespaces and deployments. +- **Service Log Volume**: log counts over time per service and severity. +- **Service Logs**: the most recent log records per service, newest first, with an optional minimum severity. +- **Index Names** (hidden): feeds the index picker, suggesting a wildcard pattern for each family of dated or rollover indices. + +## What gets indexed + +| Object type | API source | Represents | +| ----------- | ---------- | ---------- | +| **Node** | `GET _cat/nodes` | A node in the cluster, with its IP address, version and roles. | +| **Index** | `GET _cat/indices` | An open or closed index, with its UUID, creation date, primary shard count and replica count. | +| **Service** | `POST /_search` (terms aggregation over the last 24 hours) | A service writing OpenTelemetry logs, with its environments, Kubernetes namespaces and Kubernetes deployments. Only imported when **Import OpenTelemetry services** is on. | + +Objects are not linked to each other. Each import step is optional, so if one fails, for example because the user lacks monitoring permissions, the others still complete. + +## Known limitations + +- **Current values only.** OpenSearch keeps no history of its own cluster, node or index statistics, so those data streams show the current state. Use monitors or KPIs on the tiles to track them over time. +- **Search result limits.** A search returns at most 10,000 hits (the `index.max_result_window` default), and a response larger than about 6 MB fails. For large data sets, use aggregations or limit `_source` to the fields you need. +- **One aggregation per search.** When a request has several top-level aggregations, only the last one in OpenSearch's response is returned. OpenSearch places pipeline aggregations last, so a pipeline aggregation's result is the one shown. Nested bucket aggregations return one row per outer bucket. +- **The Time field needs a timeframe.** Search Results and Document Count tiles default to no timeframe. After setting **Time field**, choose a timeframe for the tile. +- **System and hidden indices are excluded** from Indices, the index picker and the import, and closed indices from the index picker, since they cannot be searched. Search a hidden index by typing its name. Index objects reflect the cluster at the last import, so an index deleted by a retention policy can remain until the next one. +- **OpenTelemetry services come from recent logs.** Each import (every 12 hours) discovers services that logged in the previous 24 hours, up to 1,000. Warnings are SeverityNumber 13–16; errors are 17 and above (ERROR and FATAL). Pods are not imported, because they change too often to keep current between imports. +- **OpenTelemetry field layout.** The environment, namespace and deployment fields are worked out from **Service field** (for example `resource.k8s.namespace.name.keyword`), so they must follow the same naming. Services without Kubernetes attributes get empty lists. +- **API keys expire** after at most 90 days. Replace the key in the data source before it does. +- **No AWS IAM authentication.** Amazon OpenSearch Service domains need fine-grained access control with an internal user. Domains that only accept IAM (SigV4) signed requests, and OpenSearch Serverless, are not supported. +- **Read-only.** The plugin never creates, changes or deletes anything in OpenSearch. diff --git a/plugins/OpenSearch/v1/docs/setup.md b/plugins/OpenSearch/v1/docs/setup.md deleted file mode 100644 index 73ba1f4a..00000000 --- a/plugins/OpenSearch/v1/docs/setup.md +++ /dev/null @@ -1,21 +0,0 @@ -# Configuration - -## Instance URL - -The base URL of your OpenSearch database. - -For example: -`https://:9200` - -Do *not* include the `/api` path. - -## Authentication - -The data source supports basic (username and password) or anonymous authentication. - -If using basic authentication, ensure the user only has read permissions. - -If using anonymous, ensure the endpoint is secured in some other form. - -If you need to whitelist the IP addresses for SquaredUp these can be found on the [Data Security](https://docs.squaredup.com/reference/security/data-security) KB article. - diff --git a/plugins/OpenSearch/v1/indexDefinitions/default.json b/plugins/OpenSearch/v1/indexDefinitions/default.json new file mode 100644 index 00000000..e01c36e5 --- /dev/null +++ b/plugins/OpenSearch/v1/indexDefinitions/default.json @@ -0,0 +1,45 @@ +{ + "steps": [ + { + "name": "nodes", + "dataStream": { "name": "nodes" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Node" }, + "properties": ["ip", "version", "roles"] + }, + "optional": true + }, + { + "name": "indices", + "dataStream": { "name": "indices" }, + "timeframe": "none", + "objectMapping": { + "id": "index", + "name": "index", + "type": { "value": "Index" }, + "properties": [ + "uuid", + { "created": "creation.date.string" }, + { "primaryShards": "pri" }, + { "replicas": "rep" } + ] + }, + "optional": true + }, + { + "name": "services", + "dataStream": { "name": "services" }, + "timeframe": "none", + "objectMapping": { + "id": "service", + "name": "service", + "type": { "value": "Service" }, + "properties": ["environments", "k8sNamespaces", "k8sDeployments"] + }, + "optional": true + } + ] +} diff --git a/plugins/OpenSearch/v1/metadata.json b/plugins/OpenSearch/v1/metadata.json index bd6419bf..1ed121c6 100644 --- a/plugins/OpenSearch/v1/metadata.json +++ b/plugins/OpenSearch/v1/metadata.json @@ -1,39 +1,53 @@ { "name": "open-search", "displayName": "OpenSearch", - "version": "1.0.4", + "version": "1.1.0", "author": { "name": "SquaredUp Labs", "type": "labs" }, - "description": "Query OpenSearch databases using DSL.", + "description": "Monitor OpenSearch cluster, node and index health, and chart search results and aggregations from your indices.", "category": "Database", - "type": "cloud", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, "restrictedToPlatforms": [], - "importNotSupported": true, - "schemaVersion": "2.0", + "keywords": [ + "opensearch", + "search", + "query dsl", + "aggregations", + "logs", + "cluster health", + "indices", + "amazon opensearch service" + ], + "objectTypes": ["Node", "Index", "Service"], "base": { "plugin": "WebAPI", "majorVersion": "1", "config": { - "authMode": "{{authMode}}", - "basicAuthUsername": "{{basicAuthUsername}}", - "basicAuthPassword": "{{basicAuthPassword}}", + "authMode": "{{authMode === 'basic' ? 'basic' : 'none'}}", + "basicAuthUsername": "{{authMode === 'basic' ? basicAuthUsername : undefined}}", + "basicAuthPassword": "{{authMode === 'basic' ? basicAuthPassword : undefined}}", "queryArgs": [], "headers": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{authMode === 'apiKey' ? 'ApiKey ' + apiKey.trim().replace(/^ApiKey +/i, '') : undefined}}" } ], - "baseUrl": "{{instanceUrl}}", - "ignoreCertificateErrors": "{{ignoreCertificateErrors}}" + "baseUrl": "{{instanceUrl.trim().replace(/[/]+$/, '')}}" } }, "links": [ { "category": "documentation", - "url": "https://github.com/squaredup/plugins/blob/main/plugins/OpenSearch/v1/docs/setup.md", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/OpenSearch/v1/docs/README.md", "label": "Help adding this plugin" }, { @@ -42,4 +56,4 @@ "label": "Repository" } ] -} \ No newline at end of file +} diff --git a/plugins/OpenSearch/v1/ui.json b/plugins/OpenSearch/v1/ui.json index 8ec9c709..6c13feff 100644 --- a/plugins/OpenSearch/v1/ui.json +++ b/plugins/OpenSearch/v1/ui.json @@ -1,30 +1,38 @@ [ { - "type": "text", + "type": "url", "name": "instanceUrl", "label": "Instance URL", - "validation": { "required": true }, - "placeholder": "Enter the OpenSearch URL" + "placeholder": "https://opensearch.example.com:9200", + "validation": { + "required": true, + "pattern": { + "value": "^https?://[^\\s]+$", + "message": "Enter the full URL of the cluster, including http:// or https://" + } + } }, { - "name": "authMode", "type": "radio", + "name": "authMode", "label": "Authentication", - "help": "**Basic:** Authenticate with username and password. \n**Anonymous access:** No authentication required", "defaultValue": "basic", "validation": { "required": true }, "options": [ { - "label": "Basic", + "label": "Username and password", "value": "basic" }, { - "label": "Anonymous access", + "label": "API key (OpenSearch 3.7 and later)", + "value": "apiKey" + }, + { + "label": "No authentication", "value": "none" } - ] }, { @@ -33,26 +41,106 @@ "authMode": "basic" }, "fields": [ + { + "type": "markdown", + "name": "httpsNoteBasic", + "content": "Use an `https://` **Instance URL** with credentials. Over `http://` they are sent unencrypted." + }, { "type": "text", "name": "basicAuthUsername", "label": "Username", - "validation": { "required": true }, - "placeholder": "Enter a username" + "placeholder": "Enter the OpenSearch username", + "validation": { + "required": true + } }, { "type": "password", "name": "basicAuthPassword", "label": "Password", - "validation": { "required": true }, - "placeholder": "Enter a password" + "placeholder": "Enter the OpenSearch password", + "validation": { + "required": true + } + } + ] + }, + { + "type": "fieldGroup", + "visible": { + "authMode": "apiKey" + }, + "fields": [ + { + "type": "markdown", + "name": "httpsNoteApiKey", + "content": "Use an `https://` **Instance URL** with credentials. Over `http://` they are sent unencrypted." + }, + { + "type": "password", + "name": "apiKey", + "label": "API key", + "placeholder": "os_xxxxxxxxxxxxxxxxxxxxxxxx", + "validation": { + "required": true + } } ] }, { - "type": "checkbox", - "name": "ignoreCertificateErrors", - "label": "Ignore certificate errors", - "help": "Ignore certificate errors when connecting to an OpenSearch instance with a self-signed certificate" + "type": "toggle", + "name": "indexOtelServices", + "label": "Import OpenTelemetry services", + "help": "Imports each service found in OpenTelemetry logs as an object, for service-level log dashboards. See the plugin documentation for supported log layouts." + }, + { + "type": "fieldGroup", + "visible": { + "indexOtelServices": "true" + }, + "fields": [ + { + "type": "text", + "name": "otelLogIndexPattern", + "label": "Log index pattern", + "placeholder": "ss4o_logs-*,otel-logs-*", + "defaultValue": "ss4o_logs-*,otel-logs-*", + "help": "Accepts comma-separated index names or wildcard patterns. The default covers the OpenTelemetry Collector OpenSearch exporter's standard index name and the common otel-logs naming.", + "validation": { + "required": true + } + }, + { + "type": "text", + "name": "otelServiceField", + "label": "Service field", + "placeholder": "resource.service.name.keyword", + "defaultValue": "resource.service.name.keyword", + "validation": { + "required": true + } + }, + { + "type": "text", + "name": "otelTimestampField", + "label": "Timestamp field", + "placeholder": "@timestamp", + "defaultValue": "@timestamp", + "validation": { + "required": true + } + }, + { + "type": "text", + "name": "otelSeverityField", + "label": "Severity number field", + "placeholder": "severity.number", + "defaultValue": "severity.number", + "validation": { + "required": true + } + } + ] } ]