diff --git a/plugins/Zscaler/v1/dataStreams/cloudComponentHistory.json b/plugins/Zscaler/v1/dataStreams/cloudComponentHistory.json new file mode 100644 index 00000000..afa72507 --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/cloudComponentHistory.json @@ -0,0 +1,128 @@ +{ + "name": "cloudComponentHistory", + "displayName": "Cloud Component History", + "description": "Hour-by-hour state of each data center or core service in one Zscaler cloud over the last 7 days, as shown on the Trust portal's Cloud Status page", + "tags": [ + "Status", + "Incidents" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/cloud-status", + "getArgs": [ + { + "key": "cloud", + "value": "{{ (function (v) { v = Array.isArray(v) ? v[0] : v; v = v && typeof v === 'object' ? v.value : v; return v ? String(v).split('|')[0] : null; })(typeof cloud !== 'undefined' ? cloud : undefined) }}" + }, + { + "key": "requestType", + "value": "{{ (function (v) { v = Array.isArray(v) ? v[0] : v; v = v && typeof v === 'object' ? v.value : v; return v || 'datacenter'; })(typeof componentType !== 'undefined' ? componentType : undefined) }}" + }, + { + "key": "dateOffset", + "value": "0" + } + ], + "paging": { + "mode": "none" + }, + "errorHandling": { + "type": "script", + "script": "errorHandling/cloudStatusApi.js" + }, + "postRequestScript": "cloudComponentHistory.js" + }, + "matches": "none", + "ui": [ + { + "type": "autocomplete", + "name": "cloud", + "label": "Cloud", + "isMulti": false, + "allowCustomValues": false, + "data": { + "source": "dataStream", + "dataStreamName": "clouds", + "dataSourceConfig": {} + }, + "validation": { + "required": true + } + }, + { + "type": "autocomplete", + "name": "componentType", + "label": "Component type", + "isMulti": false, + "isClearable": true, + "allowCustomValues": false, + "help": "Lists only the types the selected cloud publishes on the Trust portal. Leave it empty for data centers.", + "data": { + "source": "dataStream", + "dataStreamName": "componentTypes", + "dataSourceConfig": { + "cloud": { + "fieldName": "cloud", + "required": true + } + } + } + } + ], + "metadata": [ + { + "name": "name", + "displayName": "Name", + "shape": "string", + "role": "label" + }, + { + "name": "region", + "displayName": "Region", + "shape": "string" + }, + { + "name": "time", + "displayName": "Hour", + "shape": "date", + "role": "timestamp" + }, + { + "name": "state", + "displayName": "State", + "shape": "state" + }, + { + "name": "event", + "displayName": "Event", + "shape": "string" + }, + { + "name": "eventType", + "displayName": "Event Type", + "shape": "string" + }, + { + "name": "link", + "displayName": "Link", + "shape": [ + "url", + { + "label": "View on Trust portal" + } + ], + "role": "link" + }, + { + "name": "cloud", + "displayName": "Cloud Code", + "shape": "string", + "visible": false + } + ], + "timeframes": [ + "last24hours", + "last7days" + ] +} diff --git a/plugins/Zscaler/v1/dataStreams/cloudComponentStatus.json b/plugins/Zscaler/v1/dataStreams/cloudComponentStatus.json new file mode 100644 index 00000000..5934f8d1 --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/cloudComponentStatus.json @@ -0,0 +1,170 @@ +{ + "name": "cloudComponentStatus", + "displayName": "Cloud Component Status", + "description": "Current status of each data center or core service in one Zscaler cloud, as shown on the Trust portal's Cloud Status page", + "tags": [ + "Status", + "Incidents" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/cloud-status", + "getArgs": [ + { + "key": "cloud", + "value": "{{ (function (v) { v = Array.isArray(v) ? v[0] : v; v = v && typeof v === 'object' ? v.value : v; return v ? String(v).split('|')[0] : null; })(typeof cloud !== 'undefined' ? cloud : undefined) }}" + }, + { + "key": "requestType", + "value": "{{ (function (v) { v = Array.isArray(v) ? v[0] : v; v = v && typeof v === 'object' ? v.value : v; return v || 'datacenter'; })(typeof componentType !== 'undefined' ? componentType : undefined) }}" + }, + { + "key": "dateOffset", + "value": "0" + } + ], + "paging": { + "mode": "none" + }, + "errorHandling": { + "type": "script", + "script": "errorHandling/cloudStatusApi.js" + }, + "postRequestScript": "cloudComponentStatus.js" + }, + "matches": "none", + "ui": [ + { + "type": "autocomplete", + "name": "cloud", + "label": "Cloud", + "isMulti": false, + "allowCustomValues": false, + "data": { + "source": "dataStream", + "dataStreamName": "clouds", + "dataSourceConfig": {} + }, + "validation": { + "required": true + } + }, + { + "type": "autocomplete", + "name": "componentType", + "label": "Component type", + "isMulti": false, + "isClearable": true, + "allowCustomValues": false, + "help": "Lists only the types the selected cloud publishes on the Trust portal. Leave it empty for data centers.", + "data": { + "source": "dataStream", + "dataStreamName": "componentTypes", + "dataSourceConfig": { + "cloud": { + "fieldName": "cloud", + "required": true + } + } + } + } + ], + "metadata": [ + { + "name": "name", + "displayName": "Name", + "shape": "string", + "role": "label" + }, + { + "name": "region", + "displayName": "Region", + "shape": "string" + }, + { + "name": "severity", + "displayName": "Severity", + "shape": "number", + "visible": false + }, + { + "name": "state", + "displayName": "State", + "shape": "state" + }, + { + "name": "statusText", + "displayName": "Status", + "shape": "string", + "role": "description" + }, + { + "name": "activeEvents", + "displayName": "Active Events", + "shape": [ + "number", + { + "decimalPlaces": 0 + } + ] + }, + { + "name": "eventTitle", + "displayName": "Event", + "shape": "string" + }, + { + "name": "eventType", + "displayName": "Event Type", + "shape": "string" + }, + { + "name": "eventStatus", + "displayName": "Event Status", + "shape": "string" + }, + { + "name": "eventStart", + "displayName": "Event Start", + "shape": "date" + }, + { + "name": "eventSummary", + "displayName": "Event Summary", + "shape": "string" + }, + { + "name": "link", + "displayName": "Link", + "shape": [ + "url", + { + "label": "View on Trust portal" + } + ], + "role": "link" + }, + { + "name": "cloud", + "displayName": "Cloud Code", + "shape": "string", + "visible": false + } + ], + "defaultShaping": { + "sort": { + "by": [ + [ + "severity", + "desc" + ], + [ + "name", + "asc" + ] + ] + } + }, + "timeframes": false +} diff --git a/plugins/Zscaler/v1/dataStreams/cloudStatus.json b/plugins/Zscaler/v1/dataStreams/cloudStatus.json index 887188b9..6c65ca45 100644 --- a/plugins/Zscaler/v1/dataStreams/cloudStatus.json +++ b/plugins/Zscaler/v1/dataStreams/cloudStatus.json @@ -87,8 +87,8 @@ }, { "name": "currentEventLink", - "displayName": "Current Event", - "shape": ["url", { "label": "View post" }] + "displayName": "Link", + "shape": ["url", { "label": "View on Trust portal" }] } ], "defaultShaping": { diff --git a/plugins/Zscaler/v1/dataStreams/clouds.json b/plugins/Zscaler/v1/dataStreams/clouds.json new file mode 100644 index 00000000..06cb1a5b --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/clouds.json @@ -0,0 +1,54 @@ +{ + "name": "clouds", + "displayName": "Clouds", + "description": "Zscaler clouds listed on the selected Trust portal", + "tags": [ + "Status" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/global", + "pathToData": "data.clouds", + "paging": { + "mode": "none" + } + }, + "matches": "none", + "visibility": { + "type": "hidden" + }, + "metadata": [ + { + "name": "title", + "displayName": "Cloud", + "shape": "string", + "role": "label" + }, + { + "name": "machine_name", + "displayName": "Cloud Code", + "shape": "string" + }, + { + "name": "domain", + "displayName": "Domain", + "shape": "string" + }, + { + "name": "value", + "displayName": "Value", + "computed": true, + "valueExpression": "{{ $['machine_name'] + '|' + $['domain'] }}", + "shape": "string", + "role": "value", + "visible": false + }, + { + "name": "type", + "displayName": "Product", + "shape": "string" + } + ], + "timeframes": false +} diff --git a/plugins/Zscaler/v1/dataStreams/componentTypes.json b/plugins/Zscaler/v1/dataStreams/componentTypes.json new file mode 100644 index 00000000..ac5f3382 --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/componentTypes.json @@ -0,0 +1,44 @@ +{ + "name": "componentTypes", + "displayName": "Component Types", + "description": "The Cloud Status component types that the selected Zscaler cloud publishes", + "tags": [ + "Status" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/global", + "paging": { + "mode": "none" + }, + "postRequestScript": "componentTypes.js" + }, + "matches": "none", + "visibility": { + "type": "hidden" + }, + "ui": [ + { + "type": "text", + "name": "cloud", + "label": "Cloud" + } + ], + "metadata": [ + { + "name": "label", + "displayName": "Component type", + "shape": "string", + "role": "label" + }, + { + "name": "value", + "displayName": "Value", + "shape": "string", + "role": "value", + "visible": false + } + ], + "timeframes": false +} diff --git a/plugins/Zscaler/v1/dataStreams/scripts/cloudComponentHistory.js b/plugins/Zscaler/v1/dataStreams/scripts/cloudComponentHistory.js new file mode 100644 index 00000000..dc673d61 --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/scripts/cloudComponentHistory.js @@ -0,0 +1,95 @@ +const payload = data && data.data; + +if (!payload || !Array.isArray(payload.category)) { + api.report.error('The Trust portal did not return Cloud Status data for the selected cloud.'); +} + +// Autocomplete values arrive as { value } objects in the tile editor, plain strings from the CLI. +const unwrap = (v) => (v && typeof v === 'object' ? v.value : v); +const asList = (v) => (Array.isArray(v) ? v : v ? [v] : []); +// The Cloud dropdown's value is "|": the API takes the code, but the portal's +// Cloud Status page is addressed by domain. +const [cloud, domain] = String(unwrap(asList(context.config.cloud)[0]) || '').split('|'); +const portal = String(context.dataSources[0].baseUrl || '').replace(/\/+$/, ''); +const statusPage = domain ? `${portal}/${domain}/cloud-status` : `${portal}/cloud-status`; + +const severityNames = {}; +for (const s of payload.severity || []) { + severityNames[s.tid] = s.name; +} + +// Same rules as Cloud Component Status: only an outage is red. +const RANK = { 'Service Disruption': 2, 'Service Degradation': 1, 'Under Investigation': 1 }; +const CLOSED = ['Resolved', 'Remediated', 'Completed', 'Cancelled']; +const STATES = ['success', 'warning', 'error']; +const MINUTE = 60 * 1000; +const HOUR = 60 * MINUTE; + +// The API returns one fixed window: midnight UTC six days ago until now. `start_date` is that +// midnight, and each issue's `startTime` and `endTime` are minutes after it - the same numbers the +// portal uses to draw its 7-day bars. The payload's own `current_time` comes from a cached response +// and can run tens of minutes behind, so "now" is taken from the clock instead. +const weekStart = new Date(String(payload.start_date).replace(' ', 'T') + 'Z').getTime(); +if (isNaN(weekStart)) { + api.report.error('The Trust portal returned Cloud Status data without a date range.'); +} +const now = Math.min(Date.now(), weekStart + 7 * 24 * HOUR - 1); + +const tf = context.timeframe || {}; +const tfStart = tf.start ? new Date(tf.start).getTime() : weekStart; +const tfEnd = tf.end ? new Date(tf.end).getTime() : now; + +const severityOf = (e) => { + const type = severityNames[e.severityTid] || 'Unknown'; + return { type, rank: RANK[type] || 0 }; +}; + +const rows = []; +for (const region of payload.category) { + for (const component of region.subCategory || []) { + const issues = (component.issues || []).map((e) => ({ + ...e, + ...severityOf(e), + start: weekStart + Number(e.startTime || 0) * MINUTE, + // An issue the portal hasn't closed yet has no end - it runs up to now. + end: e.endTime === undefined || e.endTime === null ? now : weekStart + Number(e.endTime) * MINUTE + })); + // The portal's bars stop at each issue's last recorded time, but its current-status icon also + // reflects events that are still open, so the slot holding "now" includes those too. + const open = (component.category_status || []) + .filter((e) => !CLOSED.includes(e.status)) + .map((e) => ({ ...e, ...severityOf(e) })); + + for (let slot = weekStart; slot <= now; slot += HOUR) { + const slotEnd = slot + HOUR; + if (slotEnd <= tfStart || slot >= tfEnd) continue; + + const hits = issues.filter((e) => e.start < slotEnd && e.end >= slot); + if (now >= slot && now < slotEnd) hits.push(...open); + const worst = hits.sort((a, b) => b.rank - a.rank)[0]; + + rows.push({ + name: component.name, + // Core cloud services come back under a single "all" group rather than real regions. + region: region.title === 'all' ? undefined : region.title, + time: new Date(slot).toISOString(), + state: STATES[worst ? worst.rank : 0], + event: worst ? worst.title : undefined, + eventType: worst ? worst.type : undefined, + // Never left empty - the Blocks link mapping throws on an empty link cell. + link: worst && worst.alias ? portal + worst.alias : statusPage, + cloud + }); + } + } +} + +if (!payload.category.some((region) => (region.subCategory || []).length > 0)) { + // A Commercial cloud without premium or business continuity data centers answers with empty + // regions rather than an error. + api.report.error( + 'The selected cloud does not publish this component type on the Trust portal. Pick one from the Component type list, which shows only the types this cloud publishes.' + ); +} + +result = rows; diff --git a/plugins/Zscaler/v1/dataStreams/scripts/cloudComponentStatus.js b/plugins/Zscaler/v1/dataStreams/scripts/cloudComponentStatus.js new file mode 100644 index 00000000..2997088b --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/scripts/cloudComponentStatus.js @@ -0,0 +1,82 @@ +const payload = data && data.data; + +if (!payload || !Array.isArray(payload.category)) { + api.report.error('The Trust portal did not return Cloud Status data for the selected cloud.'); +} + +// Autocomplete values arrive as { value } objects in the tile editor, plain strings from the CLI. +const unwrap = (v) => (v && typeof v === 'object' ? v.value : v); +const asList = (v) => (Array.isArray(v) ? v : v ? [v] : []); +// The Cloud dropdown's value is "|": the API takes the code, but the portal's +// Cloud Status page is addressed by domain. +const [cloud, domain] = String(unwrap(asList(context.config.cloud)[0]) || '').split('|'); +const portal = String(context.dataSources[0].baseUrl || '').replace(/\/+$/, ''); +const statusPage = domain ? `${portal}/${domain}/cloud-status` : `${portal}/cloud-status`; + +const severityNames = {}; +for (const s of payload.severity || []) { + severityNames[s.tid] = s.name; +} + +// Same rule as the RSS streams: only an outage turns a component red. +const RANK = { 'Service Disruption': 2, 'Service Degradation': 1, 'Under Investigation': 1 }; +const CLOSED = ['Resolved', 'Remediated', 'Completed', 'Cancelled']; + +const stripHtml = (s) => + s + ? String(s) + .replace(/<[^>]+>/g, ' ') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/\s+/g, ' ') + .trim() || undefined + : undefined; + +// `ri_date` is "YYYY-MM-DD HH:MM:SS" in UTC - the RFC-822 `from` string beside it carries the same time. +const toIso = (s) => { + const d = s ? new Date(String(s).replace(' ', 'T') + 'Z') : undefined; + return d && !isNaN(d.getTime()) ? d.toISOString() : undefined; +}; + +const rows = []; +for (const region of payload.category) { + for (const component of region.subCategory || []) { + const active = (component.category_status || []) + .filter((e) => !CLOSED.includes(e.status)) + .map((e) => { + const type = severityNames[e.severityTid] || 'Unknown'; + return { ...e, type, rank: RANK[type] || 0 }; + }) + .sort((a, b) => b.rank - a.rank || String(b.ri_date).localeCompare(String(a.ri_date))); + const top = active[0]; + const severity = top ? top.rank : 0; + + rows.push({ + name: component.name, + // Core cloud services come back under a single "all" group rather than real regions. + region: region.title === 'all' ? undefined : region.title, + severity, + state: severity === 2 ? 'error' : severity === 1 ? 'warning' : 'success', + statusText: top ? `${top.type}: ${top.title}` : 'Operational', + activeEvents: active.length, + eventTitle: top ? top.title : undefined, + eventType: top ? top.type : undefined, + eventStatus: top ? top.status : undefined, + eventStart: top ? toIso(top.ri_date) : undefined, + eventSummary: top ? stripHtml(top.short_description) : undefined, + // Never left empty - the Blocks link mapping throws on an empty link cell. + link: top && top.alias ? portal + top.alias : statusPage, + cloud + }); + } +} + +// A Commercial cloud without premium or business continuity data centers answers with empty regions +// rather than an error. +if (rows.length === 0) { + api.report.error( + 'The selected cloud does not publish this component type on the Trust portal. Pick one from the Component type list, which shows only the types this cloud publishes.' + ); +} + +result = rows; diff --git a/plugins/Zscaler/v1/dataStreams/scripts/cloudStatus.js b/plugins/Zscaler/v1/dataStreams/scripts/cloudStatus.js index 3e2b9c8f..f4ca063d 100644 --- a/plugins/Zscaler/v1/dataStreams/scripts/cloudStatus.js +++ b/plugins/Zscaler/v1/dataStreams/scripts/cloudStatus.js @@ -108,6 +108,7 @@ for (const post of posts) { } } +const portal = String((context.dataSources && context.dataSources[0] && context.dataSources[0].baseUrl) || '').replace(/\/+$/, ''); const latest = (list) => list.slice().sort((a, b) => (b.when || 0) - (a.when || 0))[0]; const iso = (d) => (d ? d.toISOString() : undefined); @@ -164,6 +165,9 @@ result = Array.from(byCloud.values()).map((row) => { incidentsLast30Days: incidents.filter((p) => p.when && p.when >= thirtyDaysAgo).length, lastIncident: iso(lastIncident && lastIncident.when), nextMaintenance: iso(nextMaintenance), - currentEventLink: driver ? driver.link : undefined + // Never left empty: the Blocks visualization's link mapping throws ("e.trim is not a + // function") on a row whose link cell is empty, so operational clouds link to their + // Cloud Status page instead. + currentEventLink: driver && driver.link ? driver.link : `${portal}/${row.cloud}/cloud-status` }; }); diff --git a/plugins/Zscaler/v1/dataStreams/scripts/componentTypes.js b/plugins/Zscaler/v1/dataStreams/scripts/componentTypes.js new file mode 100644 index 00000000..6071f378 --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/scripts/componentTypes.js @@ -0,0 +1,24 @@ +// Autocomplete values arrive as { value } objects in the tile editor, plain strings from the CLI. +const unwrap = (v) => (v && typeof v === 'object' ? v.value : v); +const asList = (v) => (Array.isArray(v) ? v : v ? [v] : []); +const cloud = String(unwrap(asList(context.config.cloud)[0]) || '').split('|')[0]; + +const clouds = data && data.data && Array.isArray(data.data.clouds) ? data.data.clouds : []; +const match = clouds.find((c) => c.machine_name === cloud); +const flags = (match && match.cloud_status) || {}; + +// The portal's own flags for which Cloud Status tabs a cloud has. They mix "1"/"0" strings with +// 1/0 numbers, and "0" is truthy, so compare as strings. The flag name for premium data centers is +// singular, but the API's requestType is plural. +const TYPES = [ + { flag: 'datacenter', value: 'datacenter', label: 'Data centers' }, + { flag: 'core_cloud_services', value: 'core_cloud_services', label: 'Core cloud services' }, + { flag: 'premium_datacenter', value: 'premium_datacenters', label: 'Premium data centers' }, + { + flag: 'business_continuity_datacenters', + value: 'business_continuity_datacenters', + label: 'Business continuity data centers' + } +]; + +result = TYPES.filter((t) => String(flags[t.flag]) === '1').map(({ value, label }) => ({ label, value })); diff --git a/plugins/Zscaler/v1/dataStreams/scripts/errorHandling/cloudStatusApi.js b/plugins/Zscaler/v1/dataStreams/scripts/errorHandling/cloudStatusApi.js new file mode 100644 index 00000000..5c855078 --- /dev/null +++ b/plugins/Zscaler/v1/dataStreams/scripts/errorHandling/cloudStatusApi.js @@ -0,0 +1,12 @@ +// Shared by the Cloud Component Status and Cloud Component History streams, which call the same +// Cloud Status API. The portal's own messages are terse, so the two a user can cause are reworded. +const apiMsg = data && data.message; + +if (apiMsg === 'Bad Request type!') { + result = + 'The selected cloud does not publish this component type on the Trust portal. Pick one from the Component type list, which shows only the types this cloud publishes.'; +} else if (apiMsg === 'Cloud bad request!') { + result = 'The Trust portal does not recognize the selected cloud. Pick the cloud again from the Cloud list.'; +} else { + result = apiMsg || 'The Trust portal returned HTTP ' + response.status + '.'; +} diff --git a/plugins/Zscaler/v1/defaultContent/overview.dash.json b/plugins/Zscaler/v1/defaultContent/overview.dash.json index f55317d0..52bd27ec 100644 --- a/plugins/Zscaler/v1/defaultContent/overview.dash.json +++ b/plugins/Zscaler/v1/defaultContent/overview.dash.json @@ -341,7 +341,7 @@ "link": { "field": "currentEventLink", "type": "nominal", - "title": "Current Event" + "title": "Link" } }, "style": { diff --git a/plugins/Zscaler/v1/docs/README.md b/plugins/Zscaler/v1/docs/README.md index 5ab2b6d7..6c904b40 100644 --- a/plugins/Zscaler/v1/docs/README.md +++ b/plugins/Zscaler/v1/docs/README.md @@ -38,6 +38,9 @@ than its feed (for example a maintenance page). customer actions. - **Cloud health**: the current state of each Zscaler cloud, rolled up from active incidents and maintenance. +- **Data centers and core services**: the current state of each data center (or core service such + as **Admin UI** or **DNS**) in a cloud, and its hour-by-hour history over the last 7 days, as + shown on the portal's **Cloud Status** page. The plugin includes three dashboards: **Overview**, **Incidents** and **Maintenance & Advisories**. @@ -55,8 +58,25 @@ The plugin includes three dashboards: **Overview**, **Incidents** and **Maintena - **Cloud Status**: one row per cloud, with its current state and counts of active incidents, active and upcoming maintenance, and open advisories. It always shows current state, so it doesn't support timeframes. - -The **State** column on both streams uses these rules: +- **Cloud Component Status**: one row per data center or core service in one cloud, with its region, + current state and any active event (type, status, start time, summary and a link to the post). + It reads the portal's **Cloud Status** page rather than the RSS feed, and always shows current + state. It has these parameters: + - **Cloud**: the cloud to show, picked from the clouds listed on the selected portal. Required. + - **Component type**: **Data centers**, **Core cloud services**, **Premium data centers** or + **Business continuity data centers**. The list shows only the types the selected cloud + publishes on the portal. Leave it empty for data centers. + + Every data center or service in the cloud is returned. To show only some of them, for example + `Chicago`, `San Francisco IV` and `Washington DC`, add a filter on **Name** in the tile. +- **Cloud Component History**: one row per data center or service per hour, with that hour's + state and the worst event during it. It has the same parameters as Cloud Component Status and + supports the **Last 24 hours** and **Last 7 days** timeframes. Show it as a **Health history** + chart, with **Hour** as the time, **State** as the status and **Name** as the label, to get the + portal's 7-day bars. The latest hour also includes any event that is still open, so it matches + the current-status icon at the end of each bar on the portal. + +The **State** column on the Posts and Cloud Status streams uses these rules: | State | Meaning | | ----- | ------- | @@ -65,6 +85,10 @@ The **State** column on both streams uses these rules: | Unknown | Upcoming maintenance, or an open advisory. Neither affects cloud health. | | Success | Resolved, completed or cancelled | +**Cloud Component Status** and **Cloud Component History** use the same idea: **Error** for a +**Service Disruption**, **Warning** for a **Service Degradation** or an issue **Under +Investigation**, and **Success** otherwise. + ## What gets indexed Nothing. The feed contains status posts, not inventory, so this data source doesn't import any @@ -88,4 +112,16 @@ objects into the SquaredUp graph. Clouds appear as row values, not as objects. timestamp. - **Some text contains replacement characters.** A few posts on the portal include characters that are already corrupted at the source (shown as `�`). The plugin passes them through unchanged. -- **Read-only.** The plugin only reads the public feed and never changes anything in Zscaler. +- **Cloud Component Status and Cloud Component History show one cloud per tile.** The cloud is + chosen in the tile rather than taken from **Cloud(s) to include**. To compare several clouds, add + the stream to a tile once per cloud. +- **Component history covers the portal's current week only.** The portal's window starts at + midnight UTC six days ago, so **Last 7 days** shows six full days plus today so far, and nothing + older is returned. History is in whole hours (UTC), so a short event fills the hour it falls in. +- **Not every cloud publishes every component type.** Premium and business continuity data centers + are only published for some Commercial clouds, and some clouds publish only core services. The + **Component type** list hides the types a cloud doesn't publish. A tile that still asks for one, + for example because its cloud was changed afterwards or because it was left empty on a cloud + without data centers, shows an error saying so. +- **Read-only.** The plugin only reads the public Trust portal and never changes anything in + Zscaler. diff --git a/plugins/Zscaler/v1/metadata.json b/plugins/Zscaler/v1/metadata.json index 0bcdd55c..c7816c9f 100644 --- a/plugins/Zscaler/v1/metadata.json +++ b/plugins/Zscaler/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "zscaler-trust-status", "displayName": "Zscaler Trust Status", - "version": "1.0.0", + "version": "1.1.0", "author": { "name": "@shawn149", "type": "community" }, "description": "Track Zscaler cloud incidents, scheduled maintenance and advisories from the Zscaler Trust portal.", "category": "Monitoring",