diff --git a/plugins/SplunkCloudStatus/v1/configValidation.json b/plugins/SplunkCloudStatus/v1/configValidation.json new file mode 100644 index 00000000..b514224b --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/configValidation.json @@ -0,0 +1,13 @@ +{ + "steps": [ + { + "displayName": "Read the Splunk Cloud status page", + "dataStream": { + "name": "statusValidation" + }, + "required": true, + "error": "Could not read the Splunk Cloud status page. Check status.splunkcloud.com is reachable from the SquaredUp agent or cloud.", + "success": "Status page read successfully." + } + ] +} diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/componentStatus.json b/plugins/SplunkCloudStatus/v1/dataStreams/componentStatus.json new file mode 100644 index 00000000..005f1286 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/componentStatus.json @@ -0,0 +1,66 @@ +{ + "name": "componentStatus", + "displayName": "Component Status", + "description": "Current status of each Splunk Cloud Platform component, one row per component", + "tags": ["Status"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "components.json", + "paging": { + "mode": "none" + }, + "postRequestScript": "componentStatus.js" + }, + "matches": "none", + "metadata": [ + { + "name": "id", + "displayName": "ID", + "shape": "string", + "role": "id", + "visible": false + }, + { + "name": "name", + "displayName": "Component", + "shape": "string", + "role": "label" + }, + { + "name": "group", + "displayName": "Group", + "shape": "string" + }, + { + "name": "status", + "displayName": "Status", + "shape": "string" + }, + { + "name": "state", + "displayName": "State", + "shape": "state" + }, + { + "name": "severity", + "displayName": "Severity", + "shape": "number", + "visible": false + }, + { + "name": "updatedAt", + "displayName": "Status changed", + "shape": "date" + } + ], + "defaultShaping": { + "sort": { + "by": [ + ["severity", "desc"], + ["name", "asc"] + ] + } + }, + "timeframes": false +} diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/incidents.json b/plugins/SplunkCloudStatus/v1/dataStreams/incidents.json new file mode 100644 index 00000000..915de8e7 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/incidents.json @@ -0,0 +1,115 @@ +{ + "name": "incidents", + "displayName": "Incidents", + "description": "Splunk Cloud Platform incidents, one row per incident (or per affected component), with impact, timing and the latest update", + "tags": ["Status", "Incidents"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "incidents.json", + "paging": { + "mode": "none" + }, + "postRequestScript": "incidents.js" + }, + "matches": "none", + "ui": [ + { + "type": "toggle", + "name": "oneRowPerComponent", + "label": "One row per affected component", + "defaultValue": false, + "help": "Splits an incident that affects several components into one row per component, so tiles can group by component. When off, each incident is one row." + } + ], + "metadata": [ + { + "name": "id", + "displayName": "ID", + "shape": "string", + "role": "id", + "visible": false + }, + { + "name": "name", + "displayName": "Incident", + "shape": "string", + "role": "label" + }, + { + "name": "impact", + "displayName": "Impact", + "shape": "string" + }, + { + "name": "worstComponentStatus", + "displayName": "Worst component status", + "shape": "string" + }, + { + "name": "status", + "displayName": "Status", + "shape": "string" + }, + { + "name": "phase", + "displayName": "Phase", + "shape": "string" + }, + { + "name": "state", + "displayName": "State", + "shape": "state" + }, + { + "name": "components", + "displayName": "Components", + "shape": ["array", { "itemShape": "string" }] + }, + { + "name": "startedAt", + "displayName": "Started", + "shape": "date", + "role": "timestamp" + }, + { + "name": "resolvedAt", + "displayName": "Resolved", + "shape": "date" + }, + { + "name": "updatedAt", + "displayName": "Last updated", + "shape": "date" + }, + { + "name": "timeToResolveMinutes", + "displayName": "Time to resolve", + "shape": "minutes" + }, + { + "name": "updateCount", + "displayName": "Updates", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "latestUpdate", + "displayName": "Latest update", + "shape": "string" + }, + { + "name": "link", + "displayName": "Link", + "shape": ["url", { "label": "View incident" }], + "role": "link" + } + ], + "defaultShaping": { + "sort": { + "by": [["startedAt", "desc"]] + } + }, + "timeframes": true, + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/maintenance.json b/plugins/SplunkCloudStatus/v1/dataStreams/maintenance.json new file mode 100644 index 00000000..4047242b --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/maintenance.json @@ -0,0 +1,86 @@ +{ + "name": "maintenance", + "displayName": "Scheduled Maintenance", + "description": "Splunk Cloud Platform maintenance windows, upcoming, in progress and completed", + "tags": ["Status", "Maintenance"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "scheduled-maintenances.json", + "paging": { + "mode": "none" + }, + "postRequestScript": "maintenance.js" + }, + "matches": "none", + "metadata": [ + { + "name": "id", + "displayName": "ID", + "shape": "string", + "role": "id", + "visible": false + }, + { + "name": "name", + "displayName": "Maintenance", + "shape": "string", + "role": "label" + }, + { + "name": "status", + "displayName": "Status", + "shape": "string" + }, + { + "name": "phase", + "displayName": "Phase", + "shape": "string" + }, + { + "name": "state", + "displayName": "State", + "shape": "state" + }, + { + "name": "components", + "displayName": "Components", + "shape": ["array", { "itemShape": "string" }] + }, + { + "name": "scheduledFor", + "displayName": "Scheduled start", + "shape": "date", + "role": "timestamp" + }, + { + "name": "scheduledUntil", + "displayName": "Scheduled end", + "shape": "date" + }, + { + "name": "durationMinutes", + "displayName": "Planned duration", + "shape": "minutes" + }, + { + "name": "description", + "displayName": "Description", + "shape": "string" + }, + { + "name": "link", + "displayName": "Link", + "shape": ["url", { "label": "View maintenance" }], + "role": "link" + } + ], + "defaultShaping": { + "sort": { + "by": [["scheduledFor", "desc"]] + } + }, + "timeframes": true, + "supportsNoneTimeframe": true, + "defaultTimeframe": "none" +} diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/overallStatus.json b/plugins/SplunkCloudStatus/v1/dataStreams/overallStatus.json new file mode 100644 index 00000000..b28ab514 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/overallStatus.json @@ -0,0 +1,66 @@ +{ + "name": "overallStatus", + "displayName": "Overall Status", + "description": "Current overall status of Splunk Cloud Platform with counts of affected components, active incidents and maintenance", + "tags": ["Status", "Incidents"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "summary.json", + "paging": { + "mode": "none" + }, + "postRequestScript": "overallStatus.js" + }, + "matches": "none", + "metadata": [ + { + "name": "status", + "displayName": "Status", + "shape": "string", + "role": "label" + }, + { + "name": "state", + "displayName": "State", + "shape": "state" + }, + { + "name": "indicator", + "displayName": "Indicator", + "shape": "string" + }, + { + "name": "componentsAffected", + "displayName": "Components affected", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "activeIncidents", + "displayName": "Active incidents", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "activeMaintenance", + "displayName": "Active maintenance", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "upcomingMaintenance", + "displayName": "Upcoming maintenance", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "updatedAt", + "displayName": "Updated", + "shape": "date" + }, + { + "name": "link", + "displayName": "Status page", + "shape": ["url", { "label": "Open status page" }], + "role": "link" + } + ], + "timeframes": false +} diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/scripts/componentStatus.js b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/componentStatus.js new file mode 100644 index 00000000..4c1df076 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/componentStatus.js @@ -0,0 +1,29 @@ +if (!data || !Array.isArray(data.components)) { + api.report.error('The Splunk Cloud status page did not return a component list.'); +} + +const LEVELS = { + operational: { label: 'Operational', state: 'success', severity: 0 }, + under_maintenance: { label: 'Under maintenance', state: 'warning', severity: 1 }, + degraded_performance: { label: 'Degraded performance', state: 'warning', severity: 2 }, + partial_outage: { label: 'Partial outage', state: 'error', severity: 3 }, + major_outage: { label: 'Major outage', state: 'error', severity: 4 } +}; + +// Groups are containers that only roll up their children; the children carry a group_id. +const groups = new Map(data.components.filter((c) => c.group).map((c) => [c.id, c.name])); + +result = data.components + .filter((c) => !c.group) + .map((c) => { + const level = LEVELS[c.status] || { label: c.status, state: 'unknown', severity: 0 }; + return { + id: c.id, + name: c.name, + group: (c.group_id && groups.get(c.group_id)) || 'Ungrouped', + status: level.label, + state: level.state, + severity: level.severity, + updatedAt: c.updated_at + }; + }); diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/scripts/incidents.js b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/incidents.js new file mode 100644 index 00000000..197aba84 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/incidents.js @@ -0,0 +1,88 @@ +if (!data || !Array.isArray(data.incidents)) { + api.report.error('The Splunk Cloud status page did not return an incident list.'); +} + +const toDate = (s) => { + const d = s ? new Date(s) : undefined; + return d && !isNaN(d.getTime()) ? d : undefined; +}; +const capitalise = (s) => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s); + +const config = context.config || {}; +const oneRowPerComponent = config.oneRowPerComponent === true || config.oneRowPerComponent === 'true'; + +// A tile set to None still receives a (24 hour) start/end - the enum is the only reliable signal. +const tf = context.timeframe; +const windowStart = tf && tf.enum !== 'none' ? toDate(tf.start) : undefined; +const windowEnd = tf && tf.enum !== 'none' ? toDate(tf.end) : undefined; +const now = new Date(); + +// The incident's own `impact` is set by hand and often understates it (a "Major Outage" can be filed as +// minor), so also report the worst status any component reached during the incident. +const COMPONENT_LEVELS = [ + ['operational', 'Operational'], + ['under_maintenance', 'Under maintenance'], + ['degraded_performance', 'Degraded performance'], + ['partial_outage', 'Partial outage'], + ['major_outage', 'Major outage'] +]; +const worstComponentStatus = (updates) => { + let worst = -1; + for (const update of updates) { + for (const affected of update.affected_components || []) { + worst = Math.max(worst, COMPONENT_LEVELS.findIndex(([key]) => key === affected.new_status)); + } + } + return worst >= 0 ? COMPONENT_LEVELS[worst][1] : undefined; +}; + +const rows = []; +for (const incident of data.incidents) { + const started = toDate(incident.started_at || incident.created_at); + const resolved = toDate(incident.resolved_at); + const closed = incident.status === 'resolved' || incident.status === 'postmortem'; + + // Keep an incident if it was open at any point in the window, so a long one that began before the + // window but is still running is not dropped. + if (windowStart && windowEnd && started) { + const end = resolved || now; + if (started > windowEnd || end < windowStart) continue; + } + + let state = 'success'; + if (!closed) { + state = incident.impact === 'critical' || incident.impact === 'major' ? 'error' : 'warning'; + } + + // Updates are listed newest first. + const updates = incident.incident_updates || []; + const names = (incident.components || []).map((c) => c.name); + + const row = { + id: incident.id, + name: incident.name, + impact: capitalise(incident.impact), + worstComponentStatus: worstComponentStatus(updates), + status: capitalise(incident.status), + phase: closed ? 'Resolved' : 'Active', + state, + startedAt: started && started.toISOString(), + resolvedAt: resolved && closed ? resolved.toISOString() : undefined, + updatedAt: incident.updated_at, + // Splunk occasionally records a resolve time earlier than the start time; that isn't a real duration. + timeToResolveMinutes: started && resolved && closed && resolved >= started ? Math.round((resolved - started) / 60000) : undefined, + updateCount: updates.length, + latestUpdate: updates.length > 0 ? updates[0].body : undefined, + link: incident.shortlink + }; + + if (oneRowPerComponent && names.length > 1) { + for (const name of names) { + rows.push({ ...row, components: [name] }); + } + } else { + rows.push({ ...row, components: names }); + } +} + +result = rows; diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/scripts/maintenance.js b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/maintenance.js new file mode 100644 index 00000000..41ffb062 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/maintenance.js @@ -0,0 +1,48 @@ +if (!data || !Array.isArray(data.scheduled_maintenances)) { + api.report.error('The Splunk Cloud status page did not return a maintenance list.'); +} + +const toDate = (s) => { + const d = s ? new Date(s) : undefined; + return d && !isNaN(d.getTime()) ? d : undefined; +}; + +const STATUSES = { scheduled: 'Scheduled', in_progress: 'In progress', verifying: 'Verifying', completed: 'Completed' }; +const PHASES = { scheduled: 'Upcoming', in_progress: 'Active', verifying: 'Active', completed: 'Completed' }; +const STATES = { Upcoming: 'unknown', Active: 'warning', Completed: 'success' }; + +// A tile set to None still receives a (24 hour) start/end - the enum is the only reliable signal. +const tf = context.timeframe; +const windowStart = tf && tf.enum !== 'none' ? toDate(tf.start) : undefined; +const windowEnd = tf && tf.enum !== 'none' ? toDate(tf.end) : undefined; + +const rows = []; +for (const m of data.scheduled_maintenances) { + const start = toDate(m.scheduled_for); + const end = toDate(m.scheduled_until); + + // Keep a window if it overlaps the timeframe at any point. + if (windowStart && windowEnd && start && end) { + if (start > windowEnd || end < windowStart) continue; + } + + const phase = PHASES[m.status] || 'Completed'; + // Updates are listed newest first, so the first update posted - the one that describes the work - is last. + const updates = m.incident_updates || []; + + rows.push({ + id: m.id, + name: m.name, + status: STATUSES[m.status] || m.status, + phase, + state: STATES[phase], + components: (m.components || []).map((c) => c.name), + scheduledFor: start && start.toISOString(), + scheduledUntil: end && end.toISOString(), + durationMinutes: start && end ? Math.round((end - start) / 60000) : undefined, + description: updates.length > 0 ? updates[updates.length - 1].body : undefined, + link: m.shortlink + }); +} + +result = rows; diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/scripts/overallStatus.js b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/overallStatus.js new file mode 100644 index 00000000..25d8a288 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/overallStatus.js @@ -0,0 +1,25 @@ +if (!data || !data.status || !data.components) { + api.report.error('The Splunk Cloud status page did not return a summary document.'); +} + +// Statuspage's overall indicator is none | minor | major | critical | maintenance. +const STATES = { none: 'success', minor: 'warning', maintenance: 'warning', major: 'error', critical: 'error' }; +const indicator = data.status.indicator; + +// Group entries are containers whose status just rolls up their children, so count leaf components only. +const components = data.components.filter((c) => !c.group); +const maintenance = data.scheduled_maintenances || []; + +result = [ + { + status: data.status.description, + state: STATES[indicator] || 'unknown', + indicator, + componentsAffected: components.filter((c) => c.status !== 'operational').length, + activeIncidents: (data.incidents || []).length, + activeMaintenance: maintenance.filter((m) => m.status === 'in_progress' || m.status === 'verifying').length, + upcomingMaintenance: maintenance.filter((m) => m.status === 'scheduled').length, + updatedAt: data.page && data.page.updated_at, + link: data.page && data.page.url + } +]; diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/scripts/statusValidation.js b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/statusValidation.js new file mode 100644 index 00000000..e4a97188 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/scripts/statusValidation.js @@ -0,0 +1,13 @@ +// A 2xx alone doesn't prove the page is usable - an HTML error or maintenance page would also +// arrive as 200 and reach this script as `data === undefined`. Check it parsed as Statuspage JSON. +if (!data || !data.page || !data.status) { + api.report.error('The Splunk Cloud status page did not return a status document.'); +} + +result = [ + { + page: data.page.name, + indicator: data.status.indicator, + description: data.status.description + } +]; diff --git a/plugins/SplunkCloudStatus/v1/dataStreams/statusValidation.json b/plugins/SplunkCloudStatus/v1/dataStreams/statusValidation.json new file mode 100644 index 00000000..800fa621 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/dataStreams/statusValidation.json @@ -0,0 +1,25 @@ +{ + "name": "statusValidation", + "displayName": "Status Validation", + "description": "Confirms the Splunk Cloud status page returns a valid status document", + "tags": ["Status"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "status.json", + "paging": { + "mode": "none" + }, + "postRequestScript": "statusValidation.js" + }, + "matches": "none", + "visibility": { + "type": "hidden" + }, + "metadata": [ + { + "pattern": ".*" + } + ], + "timeframes": false +} diff --git a/plugins/SplunkCloudStatus/v1/defaultContent/incidents.dash.json b/plugins/SplunkCloudStatus/v1/defaultContent/incidents.dash.json new file mode 100644 index 00000000..4dc546fe --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/defaultContent/incidents.dash.json @@ -0,0 +1,532 @@ +{ + "name": "Incidents", + "schemaVersion": "1.5", + "timeframe": "thisYear", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "afb62720-9ec6-4aab-a515-9a4d068a6e24", + "x": 0, + "y": 0, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incidents", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Incidents" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "b11816d2-72a6-4381-a60d-b30281aef13b", + "x": 2, + "y": 0, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Median Time to Resolve", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "timeToResolveMinutes", + "operation": "greaterthan", + "value": "-1" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "median", + "names": [ + "timeToResolveMinutes" + ] + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "timeToResolveMinutes_median", + "type": "quantitative", + "title": "Median Time to Resolve" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "a9e20d3e-254c-43f8-b17e-6176eb91b116", + "x": 0, + "y": 2, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incidents by Worst Component Status", + "description": "Worst status any component reached during the incident", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "worstComponentStatus", + "operation": "notempty" + } + ] + }, + "group": { + "by": [ + [ + "worstComponentStatus", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "pie", + "encoding": { + "label": { + "field": "worstComponentStatus_uniqueValues", + "type": "nominal", + "title": "Incidents by Worst Component Status" + }, + "value": { + "field": "count", + "type": "quantitative", + "title": "Incidents" + } + }, + "style": { + "pieInnerRadius": 60, + "pieDonutCenterValue": "total", + "labelFontSize": 16, + "pieLegendStyle": "table", + "pieLabels": "nameAndValue", + "mappedColors": { + "Major outage": "#ef4444", + "Partial outage": "#f97316", + "Degraded performance": "#f59e0b", + "Operational": "#22c55e" + } + } + } + } + } + }, + { + "i": "69611d97-9d8d-414a-8b68-cf692e96409a", + "x": 2, + "y": 2, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incidents by Impact", + "description": "Impact as set by the status page, which can understate severity", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "impact", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "pie", + "encoding": { + "label": { + "field": "impact_uniqueValues", + "type": "nominal", + "title": "Incidents by Impact" + }, + "value": { + "field": "count", + "type": "quantitative", + "title": "Incidents" + } + }, + "style": { + "pieInnerRadius": 60, + "pieDonutCenterValue": "total", + "labelFontSize": 16, + "pieLegendStyle": "table", + "pieLabels": "nameAndValue", + "mappedColors": { + "Critical": "#ef4444", + "Major": "#f97316", + "Minor": "#f59e0b", + "None": "#9ca3af" + } + } + } + } + } + }, + { + "i": "7b0cbd72-82b4-4478-afc2-815da4e33f46", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incidents by Component", + "description": "Incidents that affected each component", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "oneRowPerComponent": true + }, + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "components", + "operation": "notempty" + } + ] + }, + "group": { + "by": [ + [ + "components", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "components_uniqueValues", + "type": "nominal", + "title": "Component" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Incidents" + } + }, + "style": { + "cartesianBarBorderRadius": 4 + } + } + } + } + }, + { + "i": "b08a418b-e610-4045-91e8-b8e49b16ef8c", + "x": 2, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incidents Over Time", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "startedAt", + "byMonth" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "startedAt_byMonth", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "startedAt_byMonth", + "type": "temporal", + "title": "Month" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Incidents" + } + }, + "style": { + "cartesianAreaGradient": true + } + } + } + } + }, + { + "i": "0261a3df-86c3-480e-b988-8e1f3315c911", + "x": 0, + "y": 8, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incident History", + "description": "Every incident on record", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "startedAt", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Incident", + "renderer": { + "type": "text" + } + }, + { + "field": "impact", + "title": "Impact", + "renderer": { + "type": "badge", + "colors": { + "Critical": "#ef4444", + "Major": "#f97316", + "Minor": "#f59e0b", + "None": "#9ca3af" + } + } + }, + { + "field": "worstComponentStatus", + "title": "Worst Component Status", + "renderer": { + "type": "badge", + "colors": { + "Major outage": "#ef4444", + "Partial outage": "#f97316", + "Degraded performance": "#f59e0b", + "Operational": "#22c55e" + } + } + }, + { + "field": "components", + "title": "Components", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "startedAt", + "title": "Started", + "renderer": { + "type": "date" + } + }, + { + "field": "timeToResolveMinutes", + "title": "Time to Resolve", + "renderer": { + "type": "number", + "decimals": 0, + "suffix": " min" + }, + "align": "right" + }, + { + "field": "link", + "title": "Link", + "renderer": { + "type": "link" + } + } + ] + }, + "style": {} + } + } + } + } + ] + } +} diff --git a/plugins/SplunkCloudStatus/v1/defaultContent/maintenance.dash.json b/plugins/SplunkCloudStatus/v1/defaultContent/maintenance.dash.json new file mode 100644 index 00000000..43852e26 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/defaultContent/maintenance.dash.json @@ -0,0 +1,234 @@ +{ + "name": "Maintenance", + "schemaVersion": "1.5", + "timeframe": "none", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "8232c571-5f9b-4a8b-9d28-948ade88f1d5", + "x": 0, + "y": 0, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Upcoming Windows", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[maintenance]}}", + "name": "maintenance", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "phase", + "operation": "equals", + "value": "Upcoming" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Upcoming Windows" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "e9a58300-3a46-48aa-98e4-6a983a250451", + "x": 2, + "y": 0, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Windows in Progress", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[maintenance]}}", + "name": "maintenance", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "phase", + "operation": "equals", + "value": "Active" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative", + "title": "Windows in Progress" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "35736294-799d-40c1-bb2d-df112cb8f6bb", + "x": 0, + "y": 2, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Maintenance Windows", + "description": "Every maintenance window on record", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[maintenance]}}", + "name": "maintenance", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "scheduledFor", + "desc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Maintenance", + "renderer": { + "type": "text" + } + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "badge", + "colors": { + "Scheduled": "#3b82f6", + "In progress": "#f59e0b", + "Verifying": "#f59e0b", + "Completed": "#22c55e" + } + } + }, + { + "field": "scheduledFor", + "title": "Scheduled Start", + "renderer": { + "type": "date" + } + }, + { + "field": "scheduledUntil", + "title": "Scheduled End", + "renderer": { + "type": "date" + } + }, + { + "field": "durationMinutes", + "title": "Planned Duration", + "renderer": { + "type": "number", + "decimals": 0, + "suffix": " min" + }, + "align": "right" + }, + { + "field": "components", + "title": "Components", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "link", + "title": "Link", + "renderer": { + "type": "link" + } + } + ] + }, + "style": {} + } + } + } + } + ] + } +} diff --git a/plugins/SplunkCloudStatus/v1/defaultContent/manifest.json b/plugins/SplunkCloudStatus/v1/defaultContent/manifest.json new file mode 100644 index 00000000..0c78dbcf --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/defaultContent/manifest.json @@ -0,0 +1,16 @@ +{ + "items": [ + { + "name": "overview", + "type": "dashboard" + }, + { + "name": "incidents", + "type": "dashboard" + }, + { + "name": "maintenance", + "type": "dashboard" + } + ] +} diff --git a/plugins/SplunkCloudStatus/v1/defaultContent/overview.dash.json b/plugins/SplunkCloudStatus/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..0d580e4a --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/defaultContent/overview.dash.json @@ -0,0 +1,477 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "thisYear", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "9627297a-9b18-4746-906d-6a38b0115250", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Overall Status", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[overallStatus]}}", + "name": "overallStatus", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "status", + "type": "nominal", + "title": "Overall Status" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "39b16c7c-d844-4131-af7f-0f14ad076644", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Components Affected", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[overallStatus]}}", + "name": "overallStatus", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "componentsAffected", + "type": "quantitative", + "title": "Components Affected" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "cfd58279-ec37-48d9-a6c4-7b3e4c5ebeb2", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Incidents", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[overallStatus]}}", + "name": "overallStatus", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "activeIncidents", + "type": "quantitative", + "title": "Active Incidents" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "f58a66ba-a51b-4dd6-b49e-43f93c3120fa", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Upcoming Maintenance", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[overallStatus]}}", + "name": "overallStatus", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "upcomingMaintenance", + "type": "quantitative", + "title": "Upcoming Maintenance" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "98627a60-240a-437b-97da-5430ad275de4", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Component Status", + "description": "Current status of each Splunk Cloud Platform component", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[componentStatus]}}", + "name": "componentStatus", + "pluginConfigId": "{{configId}}" + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "name", + "type": "nominal" + }, + "status": { + "field": "state", + "type": "nominal" + }, + "group": { + "field": "group", + "type": "nominal" + } + }, + "style": { + "statusGridColumnsPerRow": 2, + "statusGridSecondaryTemplate": "{{{{raw}}}}{{status}}{{{{/raw}}}}" + } + } + } + } + }, + { + "i": "64fba396-b701-4163-abc5-8204bca36566", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Incidents Per Month", + "description": "Incidents started in each month of the dashboard timeframe", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "startedAt", + "byMonth" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "startedAt_byMonth", + "asc" + ] + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "startedAt_byMonth", + "type": "temporal", + "title": "Month" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Incidents" + } + }, + "style": { + "cartesianBarBorderRadius": 4 + } + } + } + } + }, + { + "i": "242fc251-5592-421e-b9e5-706b589c2134", + "x": 0, + "y": 6, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Incidents", + "description": "Incidents that are still open", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[incidents]}}", + "name": "incidents", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "phase", + "operation": "equals", + "value": "Active" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Incident", + "renderer": { + "type": "text" + } + }, + { + "field": "impact", + "title": "Impact", + "renderer": { + "type": "badge", + "colors": { + "Critical": "#ef4444", + "Major": "#f97316", + "Minor": "#f59e0b", + "None": "#9ca3af" + } + } + }, + { + "field": "worstComponentStatus", + "title": "Worst Component Status", + "renderer": { + "type": "badge", + "colors": { + "Major outage": "#ef4444", + "Partial outage": "#f97316", + "Degraded performance": "#f59e0b", + "Operational": "#22c55e" + } + } + }, + { + "field": "components", + "title": "Components", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "startedAt", + "title": "Started", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "link", + "title": "Link", + "renderer": { + "type": "link" + } + } + ] + }, + "style": {} + } + } + } + }, + { + "i": "3639c21c-23a9-42f1-8984-16801531b2f0", + "x": 2, + "y": 6, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Upcoming Maintenance", + "description": "Maintenance windows that have not finished yet", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[maintenance]}}", + "name": "maintenance", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "phase", + "operation": "notequals", + "value": "Completed" + } + ] + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Maintenance", + "renderer": { + "type": "text" + } + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "text" + } + }, + { + "field": "scheduledFor", + "title": "Scheduled Start", + "renderer": { + "type": "date" + } + }, + { + "field": "durationMinutes", + "title": "Planned Duration", + "renderer": { + "type": "number", + "decimals": 0, + "suffix": " min" + }, + "align": "right" + }, + { + "field": "link", + "title": "Link", + "renderer": { + "type": "link" + } + } + ] + }, + "style": {} + } + } + } + } + ] + } +} diff --git a/plugins/SplunkCloudStatus/v1/docs/README.md b/plugins/SplunkCloudStatus/v1/docs/README.md new file mode 100644 index 00000000..c406b89d --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/docs/README.md @@ -0,0 +1,57 @@ +Track the health of [Splunk Cloud Platform](https://www.splunk.com/en_us/products/splunk-cloud-platform.html) in SquaredUp, using the public [Splunk Cloud Platform status page](https://status.splunkcloud.com): component status, incidents and scheduled maintenance. + +> ⚠️ This data source shows Splunk's platform-wide status page. It does not show the health of an individual Splunk Cloud stack, and it does not read from your own Splunk deployment. + +## Setup + +You don't need credentials, because the status page is public. + +1. Add the data source. +2. Save. The data source reads the status page once to check that it is reachable. + +## Configuration fields + +This data source has no configuration fields. + +When you save, the data source requests the status page's current status and checks that it returns a valid status document. A failure means the page could not be reached from the SquaredUp agent or cloud, or it returned something other than status data (for example a maintenance page). + +## What this plugin monitors + +- **Overall status**: the status banner shown at the top of the status page, with counts of affected components, active incidents, and active and upcoming maintenance. +- **Component health**: the current status of each Splunk Cloud Platform component, such as Search, Index, Ingest and Login. +- **Incidents**: every incident Splunk has published, with its impact, status, affected components, when it started and was resolved, the time to resolve, and the latest update. +- **Scheduled maintenance**: maintenance windows that are upcoming, in progress or completed, with their scheduled times and affected components. + +The plugin includes three dashboards: **Overview**, **Incidents** and **Maintenance**. + +## Data streams + +- **Overall Status**: one row with the status banner text, and counts of affected components, active incidents, and active and upcoming maintenance. It always shows the current state, so it doesn't support timeframes. +- **Component Status**: one row per Splunk Cloud Platform component, with its group and current status. It always shows the current state, so it doesn't support timeframes. +- **Incidents**: one row per incident. It has one parameter: + - **One row per affected component**: splits an incident that affects several components into one row per component, so that breakdowns by component count the incident against each component it affects. + + The timeframe returns every incident that was open at any point in the range, including one that started earlier and was still running. Set the timeframe to **None** to return every incident the status page lists, which is its 50 most recent. +- **Scheduled Maintenance**: one row per maintenance window. The timeframe returns every window that overlaps the range. Set the timeframe to **None** to return every window the status page lists, including upcoming ones, which is its 50 most recent. + +The **State** column uses these rules: + +| Stream | Success | Warning | Error | Unknown | +| ------ | ------- | ------- | ----- | ------- | +| Overall Status | No incidents or maintenance | Minor incidents, or maintenance in progress | Major or critical incidents | | +| Component Status | Operational | Degraded performance, or under maintenance | Partial or major outage | | +| Incidents | Resolved | Open incident that is minor or has no impact | Open incident that is major or critical | | +| Scheduled Maintenance | Completed | In progress or verifying | | Upcoming | + +## What gets indexed + +Nothing. The status page contains status updates, not inventory, so this data source doesn't import any objects into the SquaredUp graph. Components appear as row values, not as objects. + +## Known limitations + +- **Status is platform-wide.** The status page covers Splunk Cloud Platform as a whole. It can't tell you whether your own stack is affected. +- **An incident's impact can understate it.** Splunk sets the **Impact** value by hand, and it is sometimes lower than what happened (an incident titled "Major Outage" can be filed as minor). **Worst component status** shows the worst status any affected component reached during the incident, so use it when you need severity. +- **History is limited to the 50 most recent records.** The status page returns only its 50 most recent incidents and its 50 most recent maintenance windows, and has no paging. Once either list grows past 50, older records drop out of the data source, even with the timeframe set to **None**. At the time of writing there are 35 incidents, going back to June 2023, so nothing is missing yet. +- **Every tile downloads the whole list.** The status page has no server-side filtering, so each incident request fetches the full list (about 200 KB at the time of writing) and filters it after download. +- **Some resolve times are wrong at the source.** A few older incidents were recorded as resolved before they started. **Time to resolve** is left empty for those rather than showing a negative duration. +- **Some incidents have no components.** Splunk doesn't always tag an incident with the components it affected, so those incidents have an empty **Components** value. diff --git a/plugins/SplunkCloudStatus/v1/icon.svg b/plugins/SplunkCloudStatus/v1/icon.svg new file mode 100644 index 00000000..1594d2c7 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/icon.svg @@ -0,0 +1 @@ +Splunk diff --git a/plugins/SplunkCloudStatus/v1/metadata.json b/plugins/SplunkCloudStatus/v1/metadata.json new file mode 100644 index 00000000..dbe4d0ff --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/metadata.json @@ -0,0 +1,42 @@ +{ + "name": "splunk-cloud-status", + "displayName": "Splunk Cloud Status", + "version": "1.0.0", + "author": { "name": "@shawn149", "type": "community" }, + "description": "Track Splunk Cloud Platform component health, incidents and scheduled maintenance from the public Splunk Cloud status page.", + "category": "Monitoring", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": true, + "restrictedToPlatforms": [], + "keywords": ["splunk", "splunk cloud", "status", "statuspage", "incidents", "maintenance", "outage"], + "objectTypes": [], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/SplunkCloudStatus/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/SplunkCloudStatus/v1", + "label": "Repository" + } + ], + "screenshots": [ + "https://github.com/squaredup/plugins/blob/main/plugins/SplunkCloudStatus/v1/screenshots/setup.png", + "https://github.com/squaredup/plugins/blob/main/plugins/SplunkCloudStatus/v1/screenshots/overview.png", + "https://github.com/squaredup/plugins/blob/main/plugins/SplunkCloudStatus/v1/screenshots/incidents.png", + "https://github.com/squaredup/plugins/blob/main/plugins/SplunkCloudStatus/v1/screenshots/maintenance.png" + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://status.splunkcloud.com/api/v2", + "authMode": "none", + "headers": [], + "queryArgs": [] + } + } +} diff --git a/plugins/SplunkCloudStatus/v1/screenshots/incidents.png b/plugins/SplunkCloudStatus/v1/screenshots/incidents.png new file mode 100644 index 00000000..12457e2b Binary files /dev/null and b/plugins/SplunkCloudStatus/v1/screenshots/incidents.png differ diff --git a/plugins/SplunkCloudStatus/v1/screenshots/maintenance.png b/plugins/SplunkCloudStatus/v1/screenshots/maintenance.png new file mode 100644 index 00000000..78ff3e02 Binary files /dev/null and b/plugins/SplunkCloudStatus/v1/screenshots/maintenance.png differ diff --git a/plugins/SplunkCloudStatus/v1/screenshots/overview.png b/plugins/SplunkCloudStatus/v1/screenshots/overview.png new file mode 100644 index 00000000..0c55d33f Binary files /dev/null and b/plugins/SplunkCloudStatus/v1/screenshots/overview.png differ diff --git a/plugins/SplunkCloudStatus/v1/screenshots/setup.png b/plugins/SplunkCloudStatus/v1/screenshots/setup.png new file mode 100644 index 00000000..88026c31 Binary files /dev/null and b/plugins/SplunkCloudStatus/v1/screenshots/setup.png differ diff --git a/plugins/SplunkCloudStatus/v1/ui.json b/plugins/SplunkCloudStatus/v1/ui.json new file mode 100644 index 00000000..4d9f70e8 --- /dev/null +++ b/plugins/SplunkCloudStatus/v1/ui.json @@ -0,0 +1,7 @@ +[ + { + "type": "markdown", + "name": "info", + "content": "Reads the public [Splunk Cloud Platform status page](https://status.splunkcloud.com). No credentials are needed." + } +]