From 0326602b3a314dfe2a7c1c8a2ca01d1a27462cc9 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 16 Jun 2026 14:46:03 +0100 Subject: [PATCH 1/5] Add initial Vercel plugin dataStreams and dashboards --- plugins/Vercel/v1/configValidation.json | 11 + plugins/Vercel/v1/custom_types.json | 16 + plugins/Vercel/v1/dataStreams/activity.json | 36 ++ plugins/Vercel/v1/dataStreams/cost.json | 71 ++++ .../Vercel/v1/dataStreams/currentUser.json | 21 ++ .../Vercel/v1/dataStreams/deployments.json | 95 +++++ .../Vercel/v1/dataStreams/domainConfig.json | 18 + plugins/Vercel/v1/dataStreams/domains.json | 31 ++ .../Vercel/v1/dataStreams/firewallEvents.json | 69 ++++ .../v1/dataStreams/listDeployments.json | 51 +++ .../Vercel/v1/dataStreams/projectInfo.json | 23 ++ plugins/Vercel/v1/dataStreams/projects.json | 43 +++ plugins/Vercel/v1/dataStreams/scripts/cost.js | 25 ++ .../v1/dataStreams/scripts/currentUser.js | 2 + .../v1/dataStreams/scripts/deployments.js | 37 ++ .../Vercel/v1/dataStreams/teamMembers.json | 43 +++ plugins/Vercel/v1/dataStreams/teams.json | 28 ++ .../v1/defaultContent/activity.dash.json | 177 +++++++++ .../Vercel/v1/defaultContent/cost.dash.json | 161 ++++++++ .../v1/defaultContent/deployments.dash.json | 297 +++++++++++++++ .../Vercel/v1/defaultContent/domain.dash.json | 98 +++++ .../Vercel/v1/defaultContent/manifest.json | 10 + .../v1/defaultContent/overview.dash.json | 305 +++++++++++++++ .../v1/defaultContent/project.dash.json | 356 ++++++++++++++++++ plugins/Vercel/v1/defaultContent/scopes.json | 26 ++ plugins/Vercel/v1/docs/README.md | 57 +++ plugins/Vercel/v1/icon.svg | 4 + .../Vercel/v1/indexDefinitions/default.json | 24 ++ plugins/Vercel/v1/metadata.json | 40 ++ plugins/Vercel/v1/ui.json | 21 ++ 30 files changed, 2196 insertions(+) create mode 100644 plugins/Vercel/v1/configValidation.json create mode 100644 plugins/Vercel/v1/custom_types.json create mode 100644 plugins/Vercel/v1/dataStreams/activity.json create mode 100644 plugins/Vercel/v1/dataStreams/cost.json create mode 100644 plugins/Vercel/v1/dataStreams/currentUser.json create mode 100644 plugins/Vercel/v1/dataStreams/deployments.json create mode 100644 plugins/Vercel/v1/dataStreams/domainConfig.json create mode 100644 plugins/Vercel/v1/dataStreams/domains.json create mode 100644 plugins/Vercel/v1/dataStreams/firewallEvents.json create mode 100644 plugins/Vercel/v1/dataStreams/listDeployments.json create mode 100644 plugins/Vercel/v1/dataStreams/projectInfo.json create mode 100644 plugins/Vercel/v1/dataStreams/projects.json create mode 100644 plugins/Vercel/v1/dataStreams/scripts/cost.js create mode 100644 plugins/Vercel/v1/dataStreams/scripts/currentUser.js create mode 100644 plugins/Vercel/v1/dataStreams/scripts/deployments.js create mode 100644 plugins/Vercel/v1/dataStreams/teamMembers.json create mode 100644 plugins/Vercel/v1/dataStreams/teams.json create mode 100644 plugins/Vercel/v1/defaultContent/activity.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/cost.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/deployments.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/domain.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/manifest.json create mode 100644 plugins/Vercel/v1/defaultContent/overview.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/project.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/scopes.json create mode 100644 plugins/Vercel/v1/docs/README.md create mode 100644 plugins/Vercel/v1/icon.svg create mode 100644 plugins/Vercel/v1/indexDefinitions/default.json create mode 100644 plugins/Vercel/v1/metadata.json create mode 100644 plugins/Vercel/v1/ui.json diff --git a/plugins/Vercel/v1/configValidation.json b/plugins/Vercel/v1/configValidation.json new file mode 100644 index 00000000..2ac57f11 --- /dev/null +++ b/plugins/Vercel/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "currentUser" }, + "required": true, + "error": "Could not authenticate with Vercel. Check that your API Token is valid and has not expired.", + "success": "Connected to Vercel successfully." + } + ] +} diff --git a/plugins/Vercel/v1/custom_types.json b/plugins/Vercel/v1/custom_types.json new file mode 100644 index 00000000..e25c0352 --- /dev/null +++ b/plugins/Vercel/v1/custom_types.json @@ -0,0 +1,16 @@ +[ + { + "name": "Vercel Project", + "sourceType": "Vercel Project", + "icon": "rocket", + "singular": "Project", + "plural": "Projects" + }, + { + "name": "Vercel Domain", + "sourceType": "Vercel Domain", + "icon": "globe", + "singular": "Domain", + "plural": "Domains" + } +] diff --git a/plugins/Vercel/v1/dataStreams/activity.json b/plugins/Vercel/v1/dataStreams/activity.json new file mode 100644 index 00000000..8abd5b02 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/activity.json @@ -0,0 +1,36 @@ +{ + "name": "activity", + "displayName": "Activity", + "description": "Vercel account or team activity feed, one row per audit-style event. Backs activity and audit log tiles", + "tags": ["Activity"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v3/events", + "pathToData": "events", + "expandInnerObjects": true, + "getArgs": [ + { "key": "since", "value": "{{timeframe.start}}" }, + { "key": "until", "value": "{{timeframe.end}}" }, + { "key": "limit", "value": "100" } + ], + "paging": { "mode": "none" } + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "role": "id", "visible": false }, + { "name": "type", "displayName": "Type" }, + { "name": "text", "displayName": "Summary", "role": "label" }, + { + "name": "actor", + "displayName": "Actor", + "computed": true, + "valueExpression": "{{ $['user.username'] || $['user.email'] || $['userId'] }}" + }, + { "name": "user.username", "displayName": "User", "visible": false }, + { "name": "user.email", "displayName": "Email", "visible": false }, + { "name": "userId", "displayName": "User ID", "visible": false }, + { "name": "createdAt", "displayName": "Created", "shape": "date", "role": "timestamp" } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/cost.json b/plugins/Vercel/v1/dataStreams/cost.json new file mode 100644 index 00000000..82f237d1 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/cost.json @@ -0,0 +1,71 @@ +{ + "name": "cost", + "displayName": "Cost", + "description": "Vercel usage cost and consumption from the FOCUS billing endpoint, one row per daily charge", + "tags": ["Cost", "Billing"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v1/billing/charges", + "postRequestScript": "cost.js", + "getArgs": [ + { "key": "from", "value": "{{timeframe.start}}" }, + { "key": "to", "value": "{{timeframe.end}}" } + ], + "headers": [ + { + "key": "Accept-Encoding", + "value": "gzip" + } + ] + }, + "matches": "none", + "metadata": [ + { + "name": "service", + "displayName": "Service", + "shape": "string", + "role": "label" + }, + { + "name": "billedCost", + "displayName": "Billed Cost ($)", + "shape": [ + "currency", + { + "code": "usd", + "decimalPlaces": 2, + "thousandsSeparator": true + } + ], + "role": "value" + }, + { + "name": "effectiveCost", + "displayName": "Effective Cost ($)", + "shape": [ + "currency", + { + "code": "usd", + "decimalPlaces": 2, + "thousandsSeparator": true + } + ] + }, + { "name": "quantity", "displayName": "Quantity", "shape": "number" }, + { "name": "unit", "displayName": "Unit", "shape": "string" }, + { + "name": "projectName", + "displayName": "Project", + "shape": "string", + "role": "label" + }, + { + "name": "periodStart", + "displayName": "Period Start", + "shape": "date", + "role": "timestamp" + } + ], + "timeframes": ["last24hours", "last7days", "last30days", "thisMonth"] +} diff --git a/plugins/Vercel/v1/dataStreams/currentUser.json b/plugins/Vercel/v1/dataStreams/currentUser.json new file mode 100644 index 00000000..844ab4ac --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/currentUser.json @@ -0,0 +1,21 @@ +{ + "name": "currentUser", + "displayName": "Current User", + "description": "Returns the authenticated Vercel user. Used to validate the connection.", + "tags": [], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v2/user", + "postRequestScript": "currentUser.js" + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "username", "displayName": "Username", "role": "label" }, + { "name": "name", "displayName": "Name" }, + { "name": "email", "displayName": "Email" } + ], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json new file mode 100644 index 00000000..6066a57c --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -0,0 +1,95 @@ +{ + "name": "deployments", + "displayName": "Deployments", + "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", + "tags": ["Deployments"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v7/deployments", + "postRequestScript": "deployments.js", + "getArgs": [ + { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } + ], + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "project", + "label": "Project (optional)", + "matches": { + "sourceType": { "type": "equals", "value": "Vercel Project" } + } + } + ], + "metadata": [ + { + "name": "uid", + "displayName": "ID", + "role": "value", + "visible": false + }, + { "name": "name", "displayName": "Name", "role": "label" }, + { + "name": "state", + "displayName": "State", + "shape": [ + "state", + { + "map": { + "success": ["READY"], + "error": ["ERROR", "CANCELED"], + "warning": ["BUILDING", "QUEUED", "INITIALIZING"], + "unknown": ["DELETED"] + } + } + ] + }, + { "name": "target", "displayName": "Target" }, + { "name": "projectId", "displayName": "Project ID", "visible": false }, + { + "name": "created", + "displayName": "Created", + "shape": "date", + "role": "timestamp" + }, + { "name": "url", "displayName": "Url", "shape": "url" }, + { + "name": "inspectorUrl", + "displayName": "Inspector Url", + "shape": "url" + }, + { "name": "creator", "displayName": "Creator" }, + { + "name": "ready", + "displayName": "Ready", + "shape": "date", + "role": "timestamp" + }, + { + "name": "buildingAt", + "displayName": "Building At", + "shape": "date", + "role": "timestamp" + }, + { + "name": "createdAt", + "displayName": "Created At", + "shape": "date", + "role": "timestamp" + }, + { "pattern": ".*" } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/domainConfig.json b/plugins/Vercel/v1/dataStreams/domainConfig.json new file mode 100644 index 00000000..8a430814 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/domainConfig.json @@ -0,0 +1,18 @@ +{ + "name": "domainConfig", + "displayName": "Domain Config", + "description": "Configuration health for a single Vercel domain — whether DNS/nameservers are misconfigured, the service type, and who configured it", + "tags": ["Domain"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v6/domains/{{object.name}}/config" + }, + "matches": { "sourceType": { "type": "equals", "value": "Vercel Domain" } }, + "metadata": [ + { "name": "misconfigured", "displayName": "Misconfigured", "shape": "boolean" }, + { "name": "serviceType", "displayName": "Service Type", "shape": "string" }, + { "name": "configuredBy", "displayName": "Configured By", "shape": "string" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/domains.json b/plugins/Vercel/v1/dataStreams/domains.json new file mode 100644 index 00000000..860b80ec --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/domains.json @@ -0,0 +1,31 @@ +{ + "name": "domains", + "displayName": "Domains", + "description": "Lists Vercel custom domains in the configured account or team. Backs the Vercel Domain import and domain inventory tiles.", + "tags": [], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v5/domains", + "pathToData": "domains", + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "sourceType", "computed": true, "valueExpression": "Vercel Domain", "visible": false }, + { "name": "name", "displayName": "Domain", "role": "label" }, + { "name": "verified", "displayName": "Verified" }, + { "name": "serviceType", "displayName": "Service Type" }, + { "name": "expiresAt", "displayName": "Expires", "shape": "date" }, + { "name": "boughtAt", "displayName": "Bought", "shape": "date" }, + { "name": "renew", "displayName": "Auto-renew" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/firewallEvents.json b/plugins/Vercel/v1/dataStreams/firewallEvents.json new file mode 100644 index 00000000..06f5897f --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/firewallEvents.json @@ -0,0 +1,69 @@ +{ + "name": "firewallEvents", + "displayName": "Firewall Events", + "description": "Per-action firewall event counts over the timeframe for a single Vercel project, one row per time-bucket and action type", + "tags": ["Security", "Firewall"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v1/security/firewall/events", + "getArgs": [ + { "key": "projectId", "value": "{{object.rawId}}" }, + { + "key": "startTimestamp", + "value": "{{timeframe.unixStart * 1000}}" + }, + { "key": "endTimestamp", "value": "{{timeframe.unixEnd * 1000}}" } + ], + "pathToData": "actions" + }, + "matches": { + "sourceType": { "type": "equals", "value": "Vercel Project" } + }, + "metadata": [ + { + "name": "startTime", + "displayName": "Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "action", + "displayName": "Action", + "shape": "string", + "role": "label" + }, + { + "name": "count", + "displayName": "Count", + "shape": "number", + "role": "value" + }, + { + "name": "host", + "displayName": "Host", + "shape": "string" + }, + { + "name": "public_ip", + "displayName": "Public IP", + "shape": "string" + }, + { + "name": "action_type", + "displayName": "Action Category", + "shape": "string", + "visible": false + }, + { + "name": "isActive", + "displayName": "Active", + "shape": "boolean", + "visible": false + }, + { + "pattern": ".*" + } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/listDeployments.json b/plugins/Vercel/v1/dataStreams/listDeployments.json new file mode 100644 index 00000000..2da76017 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/listDeployments.json @@ -0,0 +1,51 @@ +{ + "name": "deployments", + "displayName": "Deployments", + "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", + "tags": ["Deployments"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v7/deployments", + "postRequestScript": "deployments.js", + "getArgs": [ + { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } + ], + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "project", + "label": "Project (optional)", + "matches": { + "sourceType": { "type": "equals", "value": "Vercel Project" } + } + } + ], + "metadata": [ + { + "name": "uid", + "displayName": "ID", + "role": "value" + }, + { + "name": "label", + "displayName": "Label", + "role": "label", + "valueExpression": "{{$['status']}}" + }, + { "pattern": ".*" } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/projectInfo.json b/plugins/Vercel/v1/dataStreams/projectInfo.json new file mode 100644 index 00000000..dd7b5c5a --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/projectInfo.json @@ -0,0 +1,23 @@ +{ + "name": "projectInfo", + "displayName": "Project Info", + "description": "Per-project detail (framework, Node version, timestamps, git repo) for a single Vercel project", + "tags": ["Project"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v9/projects/{{object.rawId}}", + "expandInnerObjects": true + }, + "matches": { "sourceType": { "type": "equals", "value": "Vercel Project" } }, + "metadata": [ + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "framework", "displayName": "Framework", "shape": "string" }, + { "name": "nodeVersion", "displayName": "Node Version", "shape": "string" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { "name": "updatedAt", "displayName": "Updated", "shape": "date" }, + { "name": "link.repo", "displayName": "Git Repo", "shape": "string" }, + { "name": "link.type", "displayName": "Git Provider", "shape": "string" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/projects.json b/plugins/Vercel/v1/dataStreams/projects.json new file mode 100644 index 00000000..f15c053a --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/projects.json @@ -0,0 +1,43 @@ +{ + "name": "projects", + "displayName": "Projects", + "description": "Lists Vercel projects in the configured account or team. Backs the Vercel Project import and project inventory tiles.", + "tags": [], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v10/projects", + "pathToData": "projects", + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "from" } + } + }, + "matches": "none", + "metadata": [ + { + "name": "id", + "displayName": "ID", + "role": "value", + "visible": false + }, + { + "name": "sourceType", + "computed": true, + "valueExpression": "Vercel Project", + "visible": false + }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "framework", "displayName": "Framework" }, + { "name": "nodeVersion", "displayName": "Node Version" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { "name": "updatedAt", "displayName": "Updated", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/scripts/cost.js b/plugins/Vercel/v1/dataStreams/scripts/cost.js new file mode 100644 index 00000000..32245cc8 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/cost.js @@ -0,0 +1,25 @@ +// GET /v1/billing/charges returns application/jsonl — newline-delimited JSON +// objects (FOCUS v1.3 cost/usage records), NOT a JSON array. The handler only +// auto-parses JSON/XML, so `data` is null/incomplete here; the raw text is on +// response.body. Split on newlines, drop empties, JSON.parse each line, then map +// each FOCUS record to a flat row with real JS primitives. +const records = (response.body || "") + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => JSON.parse(line)); + +const num = (v) => (v === null || v === undefined || v === "" ? null : Number(v)); + +result = records.map((r) => { + const tags = r.Tags || {}; + return { + service: r.ServiceName, + billedCost: num(r.BilledCost), + effectiveCost: num(r.EffectiveCost), + quantity: num(r.ConsumedQuantity), + unit: r.ConsumedUnit, + projectName: tags.ProjectName || tags.ProjectId || null, + periodStart: r.ChargePeriodStart + }; +}); diff --git a/plugins/Vercel/v1/dataStreams/scripts/currentUser.js b/plugins/Vercel/v1/dataStreams/scripts/currentUser.js new file mode 100644 index 00000000..d294752f --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/currentUser.js @@ -0,0 +1,2 @@ +// /v2/user returns a single { user: {...} } object — wrap it as one row. +result = data && data.user ? [data.user] : []; diff --git a/plugins/Vercel/v1/dataStreams/scripts/deployments.js b/plugins/Vercel/v1/dataStreams/scripts/deployments.js new file mode 100644 index 00000000..18402b4e --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/deployments.js @@ -0,0 +1,37 @@ +// /v7/deployments returns { deployments: [...], pagination: {...} }. +// One row per deployment. Optional `project` objects-picker (stream ui name +// "project") arrives at context.config.project as an array (multi-select), each +// rawId a single-element array. Empty/absent => account-wide, no filter. +const deployments = (data && data.deployments) || []; + +const unwrap = (v) => (Array.isArray(v) ? v[0] : v); +const selected = (context.config && context.config.project) || []; +const projectIds = new Set( + selected.map((o) => unwrap(o.rawId)).filter(Boolean), +); + +// Token paging walks `until` from pagination.next back to the `since` floor, so +// the first page starts at "now". For a historical timeframe (e.g. lastMonth) +// that over-fetches rows newer than the window end — bound the top here. +// (We can't set an `until` getArg: it collides with the paging `out=until`.) +const tf = context.timeframe || {}; +const untilMs = tf.unixEnd ? tf.unixEnd * 1000 : null; + +const scoped = deployments.filter( + (d) => + (!projectIds.size || projectIds.has(d.projectId)) && + (untilMs === null || d.created <= untilMs), +); + +result = scoped.map((d) => ({ + ...d, + uid: d.uid, + name: d.name, + state: d.readyState || d.state, + target: d.target || "preview", + projectId: d.projectId, + created: d.created, + inspectorUrl: d.inspectorUrl, + creator: (d.creator && (d.creator.username || d.creator.uid)) || null, +})); + diff --git a/plugins/Vercel/v1/dataStreams/teamMembers.json b/plugins/Vercel/v1/dataStreams/teamMembers.json new file mode 100644 index 00000000..d7284898 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/teamMembers.json @@ -0,0 +1,43 @@ +{ + "name": "teamMembers", + "displayName": "Team Members", + "description": "Lists members of the configured Vercel team", + "tags": ["Team"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v3/teams/{{dataSource.teamId}}/members", + "pathToData": "members", + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "metadata": [ + { + "name": "uid", + "displayName": "ID", + "shape": "string", + "visible": false + }, + { "name": "name", "displayName": "Name", "shape": "string" }, + { + "name": "username", + "displayName": "Username", + "shape": "string", + "role": "label" + }, + { "name": "email", "displayName": "Email", "shape": "string" }, + { "name": "role", "displayName": "Role", "shape": "string" }, + { "name": "confirmed", "displayName": "Confirmed", "shape": "boolean" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/teams.json b/plugins/Vercel/v1/dataStreams/teams.json new file mode 100644 index 00000000..6acafe5f --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/teams.json @@ -0,0 +1,28 @@ +{ + "name": "teams", + "displayName": "Teams", + "description": "Lists the Vercel teams the configured token can access", + "tags": ["Teams"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v2/teams", + "pathToData": "teams", + "expandInnerObjects": true, + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "slug", "displayName": "Slug" }, + { "name": "membership.role", "displayName": "Your Role" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json new file mode 100644 index 00000000..b672b3c6 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -0,0 +1,177 @@ +{ + "name": "Activity & Team", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "version": 115, + "contents": [ + { + "static": false, + "w": 3, + "moved": false, + "h": 3, + "x": 0, + "y": 0, + "i": "4c0a11bb-6c6b-481b-951c-fe4586313ea4", + "z": 0, + "config": { + "dataStream": { + "name": "activity", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[activity]}}", + "sort": { + "by": [["createdAt", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "Account events", + "activePluginConfigIds": ["{{configId}}"], + "title": "Recent Activity", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "createdAt", + "type", + "text", + "actor" + ], + "hiddenColumns": [ + "id", + "user.username", + "user.email", + "userId" + ], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 3, + "y": 0, + "i": "9838709b-83d2-4542-85a7-6458352f4d93", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "teamMembers", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[teamMembers]}}", + "sort": { + "by": [["name", "asc"]] + } + }, + "_type": "tile/data-stream", + "description": "Members of the configured team", + "activePluginConfigIds": ["{{configId}}"], + "title": "Team Roster", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["username", "role"], + "hiddenColumns": [ + "uid", + "createdAt", + "email", + "name", + "confirmed" + ], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 4, + "moved": false, + "h": 2, + "x": 0, + "y": 3, + "i": "fada223a-5939-4b55-bc61-3ef63deca7d2", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "teams", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[teams]}}", + "sort": { + "by": [["name", "asc"]] + } + }, + "_type": "tile/data-stream", + "description": "Teams accessible to the configured token", + "activePluginConfigIds": ["{{configId}}"], + "title": "Teams", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "slug", + "membership.role", + "createdAt" + ], + "hiddenColumns": ["id"], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 4, + "moved": false, + "h": 3, + "x": 0, + "y": 5, + "i": "34bbffe0-fdcd-479a-8c54-7757030d354a", + "z": 0, + "config": { + "dataStream": { + "name": "activity", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[activity]}}", + "sort": {}, + "group": { + "by": [["createdAt", "byDay"]], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Daily activity", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "seriesColumn": "none", + "xAxisColumn": "createdAt_byDay", + "yAxisColumn": ["count"] + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/cost.dash.json b/plugins/Vercel/v1/defaultContent/cost.dash.json new file mode 100644 index 00000000..d2eebea9 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/cost.dash.json @@ -0,0 +1,161 @@ +{ + "name": "Cost", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "contents": [ + { + "static": false, + "w": 2, + "moved": false, + "h": 4, + "x": 0, + "y": 0, + "i": "0f2bc2a4-cb0b-41a1-a4fb-9b0e29e8a088", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "by": [["billedCost_sum", "desc"]] + }, + "group": { + "by": [["service", "uniqueValues"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Billed cost grouped by service", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by Service", + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "billedCost_sum", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table", + "labelColumn": "service_uniqueValues" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 4, + "x": 2, + "y": 0, + "i": "64bb0cfc-e350-43e6-a047-5fa04377cc10", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "top": 10, + "by": [["billedCost_sum", "desc"]] + }, + "group": { + "by": [["projectName", "uniqueValues"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Billed cost grouped by project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by Project", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisGroup": "none", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "projectName_uniqueValues", + "displayMode": "actual", + "yAxisLabel": "Billed Cost ($)", + "horizontalLayout": "horizontal", + "yAxisData": ["billedCost_sum"], + "showYAxisLabel": true, + "legendPosition": "bottom", + "showXAxisLabel": false + } + } + } + } + }, + { + "static": false, + "w": 4, + "moved": false, + "h": 3, + "x": 0, + "y": 4, + "i": "731acf15-16ed-4e1a-baa9-7f3d992e172f", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": {}, + "group": { + "by": [["periodStart", "byDay"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by day", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "seriesColumn": "none", + "dataPoints": true, + "showTrendLine": true, + "cumulative": false, + "xAxisColumn": "service_uniqueValues", + "yAxisColumn": ["billedCost_sum"] + } + } + } + } + } + ], + "version": 16, + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json new file mode 100644 index 00000000..6eb0ed17 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -0,0 +1,297 @@ +{ + "name": "Deployments", + "schemaVersion": "1.5", + "timeframe": "last30days", + "dashboard": { + "_type": "layout/grid", + "version": 79, + "contents": [ + { + "w": 2, + "h": 3, + "x": 0, + "y": 0, + "z": 0, + "i": "8880ebf2-9570-488b-ab5f-6cfeed0c1727", + "moved": false, + "static": false, + "config": { + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "or", + "filters": [] + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "top": 10, + "by": [["created", "asc"]] + }, + "group": { + "by": [], + "aggregate": [] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Latest Builds (Top 10)", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "state", + "name", + "url", + "created", + "source", + "readyState", + "readySubstate", + "type", + "creator", + "inspectorUrl", + "attribution.commitMeta.name", + "attribution.commitMeta.email", + "attribution.commitMeta.isVerified", + "attribution.gitUser.id", + "attribution.gitUser.login", + "attribution.gitUser.type", + "attribution.gitUser.provider", + "attribution.vercelUser.id", + "attribution.vercelUser.username", + "attribution.vercelUser.teamRoles.0", + "meta.githubCommitAuthorName", + "meta.githubCommitAuthorEmail", + "meta.githubCommitMessage", + "meta.githubCommitOrg", + "meta.githubCommitRef", + "meta.githubCommitRepo", + "meta.githubCommitSha", + "meta.githubDeployment", + "meta.githubOrg", + "meta.githubRepo", + "meta.githubRepoOwnerType", + "meta.githubCommitRepoId", + "meta.githubRepoId", + "meta.githubRepoVisibility", + "meta.githubHost", + "meta.githubCommitAuthorLogin", + "meta.githubCommitVerification", + "meta.repoPushedAt", + "meta.branchAlias", + "meta.lambdaRuntimeStats", + "target", + "aliasError", + "aliasAssigned", + "isRollbackCandidate", + "createdAt", + "buildingAt", + "ready", + "projectSettings.commandForIgnoringBuildStep", + "meta.githubPrId", + "state[Expanded].rawState" + ], + "hiddenColumns": ["uid", "projectId"] + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 0, + "z": 0, + "i": "69617b87-c49a-43a7-a4ec-4b60e2cdd16e", + "moved": false, + "static": false, + "config": { + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "or", + "filters": [ + { + "column": "state", + "operation": "equals", + "value": "error" + } + ] + }, + "id": "{{dataStreams.[deployments]}}", + "group": { + "by": [], + "aggregate": [] + } + }, + "scope": { + "query": "g.V().has('id', within(ids_defaultScopeIds))", + "bindings": { + "ids_defaultScopeIds": [ + "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" + ] + }, + "queryDetail": { + "ids": [ + "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" + ], + "types": [ + { + "value": "data-source" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Latest Failed Builds", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "stateColumn": "state", + "linkColumn": "inspectorUrl", + "sublabel": "url", + "labelColumn": "name" + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 0, + "y": 3, + "z": 0, + "i": "ece1c11a-be71-4a6f-a1f2-ed56c15081d7", + "moved": false, + "static": false, + "config": { + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[deployments]}}", + "sort": { + "by": [["created_byDay", "asc"]] + }, + "group": { + "by": [ + ["created", "byDay"], + ["state", "uniqueValues"] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Deployments per day, split by state", + "activePluginConfigIds": ["{{configId}}"], + "title": "Deployments per day", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "color": { + "type": "default" + }, + "xAxisGroup": "state_uniqueValues", + "showLegend": true, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "created_byDay", + "displayMode": "actual", + "showTotals": true, + "yAxisLabel": "Deployments", + "horizontalLayout": "vertical", + "showValue": false, + "yAxisData": ["count"], + "showYAxisLabel": true, + "xAxisLabel": "", + "legendPosition": "bottom", + "showXAxisLabel": false + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 3, + "z": 0, + "i": "d2eaaf90-d384-478a-b14d-dc955bbdeb3e", + "moved": false, + "static": false, + "config": { + "title": "Average build times", + "description": "", + "_type": "tile/data-stream", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "WITH\r\n \"build_times\" AS (\r\n SELECT\r\n STRFTIME(\r\n DATE_TRUNC('day', \"createdAt\"),\r\n '%Y-%m-%dT%H:%M:%SZ'\r\n ) AS \"date\",\r\n \"name\",\r\n EXTRACT (\r\n EPOCH\r\n FROM\r\n \"buildingAt\" - \"createdAt\"\r\n ) AS \"build_time\"\r\n FROM\r\n \"dataset1\"\r\n )\r\nSELECT\r\n \"date\",\r\n \"name\",\r\n AVG(\"build_time\") AS \"avg_build_time\"\r\nFROM\r\n \"build_times\"\r\nGROUP BY\r\n \"date\",\r\n \"name\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[deployments]}}", + "name": "deployments" + } + } + } + ] + }, + "metadata": [ + { + "shape": [ + "seconds", + { + "thousandsSeparator": true, + "formatDuration": true + } + ], + "name": "avg_build_time" + }, + { + "pattern": ".*" + } + ] + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "date", + "yAxisColumn": ["avg_build_time"], + "seriesColumn": "name", + "showLegend": true, + "legendPosition": "bottom", + "dataPoints": true + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/domain.dash.json b/plugins/Vercel/v1/defaultContent/domain.dash.json new file mode 100644 index 00000000..5e8cb0a8 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/domain.dash.json @@ -0,0 +1,98 @@ +{ + "name": "Domain", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": ["{{variables.[Vercel Domain]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "2d1a775f-5d77-4246-a727-47b4b22c0cc7", + "x": 0, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Configuration Health", + "description": "DNS / nameserver configuration for this domain", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[domainConfig]}}", + "name": "domainConfig", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Vercel Domains]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Vercel Domain]}}" + }, + "variables": ["{{variables.[Vercel Domain]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": ["misconfigured", "serviceType", "configuredBy"] + } + } + }, + "monitorOld": { + "_type": "simple", + "monitorType": "threshold", + "aggregation": "count", + "groupBy": "__group_by_none__", + "frequency": 15, + "tileRollsUp": true, + "condition": { + "columns": ["misconfigured"], + "logic": { "if": [{ "==": [{ "var": "misconfigured" }, true] }, "error"] } + } + } + } + }, + { + "i": "da5c6f6d-e225-4de1-90c8-873e444e2fb2", + "x": 2, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Domain Details", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Vercel Domains]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Vercel Domain]}}" + }, + "variables": ["{{variables.[Vercel Domain]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "hiddenColumns": ["id", "link", "links", "label"] + } + } + } + } + } + ] + } +} diff --git a/plugins/Vercel/v1/defaultContent/manifest.json b/plugins/Vercel/v1/defaultContent/manifest.json new file mode 100644 index 00000000..0fcc169c --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/manifest.json @@ -0,0 +1,10 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "cost", "type": "dashboard" }, + { "name": "deployments", "type": "dashboard" }, + { "name": "activity", "type": "dashboard" }, + { "name": "project", "type": "dashboard" }, + { "name": "domain", "type": "dashboard" } + ] +} diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..dae24374 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -0,0 +1,305 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "version": 95, + "contents": [ + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 0, + "y": 0, + "i": "fa9e69dc-7d70-450b-b973-1a215df3a398", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "projects", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[projects]}}", + "sort": { + "by": [["count", "desc"]] + }, + "group": { + "by": [["framework", "uniqueValues"]], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Projects by Framework", + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table", + "labelColumn": "framework_uniqueValues" + } + } + } + } + }, + { + "static": false, + "w": 3, + "moved": false, + "h": 3, + "x": 1, + "y": 0, + "i": "3850d290-308d-44a1-81de-aeff5200d0ba", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "projects", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[proejcts]}}", + "sort": { + "by": [["updatedAt", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Projects", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "framework", + "nodeVersion", + "updatedAt" + ], + "hiddenColumns": [ + "id", + "sourceType", + "createdAt" + ], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 0, + "y": 3, + "i": "f3ff120e-5ed1-414f-b57e-a2982d25ebbe", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "top": 10, + "by": [] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Overall Cost", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "billedCost_sum", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 3, + "moved": false, + "h": 3, + "x": 1, + "y": 3, + "i": "098760ae-37bf-485c-8b14-605051f34733", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "top": 10, + "by": [["billedCost_sum", "desc"]] + }, + "group": { + "by": [["projectName", "uniqueValues"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Billed cost grouped by project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by Project", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "color": { + "type": "default" + }, + "xAxisGroup": "none", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "projectName_uniqueValues", + "displayMode": "actual", + "showTotals": false, + "yAxisLabel": "Billed Cost ($)", + "horizontalLayout": "horizontal", + "showValue": false, + "yAxisData": ["billedCost_sum"], + "showYAxisLabel": true, + "xAxisLabel": "", + "legendPosition": "bottom", + "showXAxisLabel": false + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 2, + "x": 0, + "y": 6, + "i": "60fe71cb-be1f-4627-9eb7-c4aa7fe10feb", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "domains", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "verified", + "operation": "equals", + "value": "false" + } + ] + }, + "id": "{{dataStreams.[domains]}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Domains that are not verified", + "activePluginConfigIds": ["{{configId}}"], + "title": "Unverified Domains", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Unverified" + } + } + } + } + }, + { + "static": false, + "w": 3, + "moved": false, + "h": 2, + "x": 1, + "y": 6, + "i": "f2843ac7-c436-4ad4-a6c8-5e5cb570420b", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "domains", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[domains]}}", + "sort": { + "by": [["expiresAt", "asc"]] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Domain Inventory", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "verified", + "serviceType", + "renew", + "expiresAt" + ], + "hiddenColumns": [ + "id", + "sourceType", + "boughtAt", + "createdAt" + ], + "transpose": false + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/project.dash.json b/plugins/Vercel/v1/defaultContent/project.dash.json new file mode 100644 index 00000000..95714503 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/project.dash.json @@ -0,0 +1,356 @@ +{ + "name": "Project", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": ["{{variables.[Vercel Project]}}"], + "dashboard": { + "_type": "layout/grid", + "version": 29, + "contents": [ + { + "w": 1, + "h": 2, + "x": 0, + "y": 0, + "z": 0, + "i": "05a07731-9c30-4908-95b4-94f9f345d54c", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "top": 1, + "by": [["created", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "Most recent deployment for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Latest Deployment State", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "stateColumn": "state", + "linkColumn": "inspectorUrl", + "sublabel": "target", + "columns": 1, + "labelColumn": "name" + } + } + } + } + }, + { + "w": 1, + "h": 2, + "x": 1, + "y": 0, + "z": 0, + "i": "d04fd358-ff9f-41f2-a7ea-5398a4cd34b9", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "filter": { + "filters": [ + { + "column": "state", + "operation": "equals", + "value": "error" + } + ], + "multiOperation": "or" + }, + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "group": { + "by": [], + "aggregate": [] + }, + "sort": {} + }, + "_type": "tile/data-stream", + "description": "Deployments in an error state for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Failed Deployments", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none", + "label": "Failed" + } + } + } + } + }, + { + "w": 2, + "h": 2, + "x": 2, + "y": 0, + "z": 0, + "i": "508189d0-5357-4722-8701-09b082e20c35", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "by": [["created_byDay", "asc"]] + }, + "group": { + "by": [ + ["created", "byDay"], + ["state", "uniqueValues"] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Deployments per day for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Deployment Volume", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisGroup": "state_uniqueValues", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "created_byDay", + "displayMode": "actual", + "yAxisLabel": "Deployments", + "horizontalLayout": "vertical", + "yAxisData": ["count"], + "showYAxisLabel": true, + "legendPosition": "bottom", + "showXAxisLabel": false, + "showValue": false, + "xAxisLabel": "", + "color": { + "type": "default" + }, + "showTotals": true + } + } + } + } + }, + { + "w": 4, + "h": 3, + "x": 0, + "y": 2, + "z": 0, + "i": "b2a0e5d2-746d-48e2-b294-0cd82105f86c", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "by": [["created", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "Deployments for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Deployment History", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "created", + "name", + "state", + "target", + "creator", + "inspectorUrl" + ], + "hiddenColumns": ["uid", "projectId"], + "transpose": false + } + } + } + } + }, + { + "w": 1, + "h": 3, + "x": 0, + "y": 5, + "z": 0, + "i": "378b1b08-9b44-4564-a83f-f59f2d02b9f8", + "moved": false, + "static": false, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "projectInfo", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[projectInfo]}}" + }, + "scope": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Project Info", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "framework", + "nodeVersion", + "link.repo", + "link.type", + "createdAt", + "updatedAt" + ], + "transpose": true + } + } + } + } + }, + { + "w": 1, + "h": 3, + "x": 1, + "y": 5, + "z": 0, + "i": "6a4aa9a9-0870-452b-a66b-83917f0641da", + "moved": false, + "static": false, + "config": { + "title": "Firewall Event Actions", + "description": "", + "_type": "tile/data-stream", + "dataStream": { + "id": "{{dataStreams.[firewallEvents]}}", + "name": "firewallEvents" + }, + "scope": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "action" + } + } + }, + "activePluginConfigIds": ["{{configId}}"], + "variables": ["{{variables.[Vercel Project]}}"] + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 5, + "z": 0, + "i": "97508e56-5d5b-4716-9223-8a3903c5d3da", + "moved": false, + "static": false, + "config": { + "title": "Firewall events", + "description": "", + "_type": "tile/data-stream", + "dataStream": { + "id": "{{dataStreams.[firewallEvents]}}", + "name": "firewallEvents" + }, + "scope": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "hiddenColumns": ["isActive", "action_type"], + "columnOrder": [ + "action", + "count", + "host", + "startTime", + "endTime", + "public_ip" + ] + } + } + }, + "activePluginConfigIds": ["{{configId}}"], + "variables": ["{{variables.[Vercel Project]}}"] + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/scopes.json b/plugins/Vercel/v1/defaultContent/scopes.json new file mode 100644 index 00000000..164d0191 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/scopes.json @@ -0,0 +1,26 @@ +[ + { + "name": "Vercel Projects", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Vercel Project"] } + }, + "variable": { + "name": "Vercel Project", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Vercel Domains", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Vercel Domain"] } + }, + "variable": { + "name": "Vercel Domain", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md new file mode 100644 index 00000000..f1081f25 --- /dev/null +++ b/plugins/Vercel/v1/docs/README.md @@ -0,0 +1,57 @@ +# Vercel + +Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in SquaredUp. This plugin connects to the Vercel REST API to import your projects and domains as objects you can scope dashboards to, and provides data streams for deployment health and history, domain verification and expiry, team membership and activity, and a billing-based cost overview. + +## What this plugin monitors + +- **Projects** — your Vercel projects, including framework, git repository, and latest production deployment status. Imported as objects. +- **Domains** — custom domains, including verification status, expiry/renewal dates, and configuration health. Imported as objects. +- **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are *not* indexed as objects, because they change too frequently — they are available as data streams instead.) +- **Team & activity** — team membership roster and recent account activity (events). +- **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). +- **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) posture: whether the firewall is enabled, which managed protections (OWASP-style CRS categories, bot protection, AI bots) are active and their action, plus active attack anomalies over time. Available as data streams on the **Project** perspective. + +The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. + +## Prerequisites — getting a Vercel Access Token + +1. Sign in to Vercel and open **Account Settings → Tokens** (). +2. Click **Create Token**. +3. Give it a name (e.g. `SquaredUp`). +4. **Scope** — choose the scope the token can access: + - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). + - To monitor your **personal account**, scope it to your personal account. +5. **Expiration** — choose an expiration (or no expiration). If the token expires, the plugin's data streams will stop returning data until you supply a new token. +6. Click **Create** and copy the token value immediately — Vercel only shows it once. + +For the **cost overview** and **team members** streams, the token must belong to a Team and carry a role with billing/member visibility (Owner, Member, Developer, Security, or Billing). On personal/Hobby accounts these streams may return no data. + +### Finding your Team ID + +If you are monitoring a Team, open **Team Settings → General** in Vercel; the **Team ID** (format `team_xxxxxxxx`) is shown there. Enter that value in the `Team ID` field. (Leave the field blank to monitor your personal account instead.) + +## Configuration fields + +| Field | Required | Description | Where to find it | +| --- | --- | --- | --- | +| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | +| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | + +## What gets indexed + +The plugin imports two object types into the SquaredUp graph: + +- **Vercel Project** — one object per project in the configured account/team. Carries the project name, framework, git repository link, and identifiers. +- **Vercel Domain** — one object per custom domain. Carries the domain name, verification status, and expiry information. + +Deployments, teams, members, activity events, and cost data are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. + +## Known limitations + +- **Deployments are not indexed.** They are available only as data streams, because deployment volume and churn make them unsuitable as long-lived graph objects. +- **No real-time analytics or metrics via REST.** Vercel does **not** expose Web Analytics (pageviews/visitors), Speed Insights (Core Web Vitals), or real-time Observability metrics (edge requests, function invocations, bandwidth) through its public REST API. Operational usage is available only as **daily billed quantities** via the cost stream — not real-time, per-request telemetry. +- **Cost data is daily granularity** and the billing endpoint returns very large datasets, so the cost stream is restricted to the **Last 24 hours** and **Last 7 days** timeframes. +- **Team members and cost require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. +- **Firewall streams only return data for projects with a configured firewall.** The Vercel Firewall config endpoint returns a 404 (not an empty result) for any project that has never configured a firewall, so the firewall posture and managed-rules tiles render only for projects where the WAF has been set up; for other projects the tile shows no data. The attack-anomalies stream returns an empty result (not an error) when there is no active attack. +- **One connection = one scope.** Each configured plugin instance monitors either your personal account or a single team. To monitor multiple teams, add the plugin once per team. +- **Rate limits.** Vercel enforces per-action rate limits (HTTP 429). Very large accounts may occasionally see throttling during imports. diff --git a/plugins/Vercel/v1/icon.svg b/plugins/Vercel/v1/icon.svg new file mode 100644 index 00000000..73961ba7 --- /dev/null +++ b/plugins/Vercel/v1/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/Vercel/v1/indexDefinitions/default.json b/plugins/Vercel/v1/indexDefinitions/default.json new file mode 100644 index 00000000..7642ba9b --- /dev/null +++ b/plugins/Vercel/v1/indexDefinitions/default.json @@ -0,0 +1,24 @@ +{ + "steps": [ + { + "name": "projects", + "dataStream": { "name": "projects" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": "sourceType" + } + }, + { + "name": "domains", + "dataStream": { "name": "domains" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": "sourceType" + } + } + ] +} diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json new file mode 100644 index 00000000..675c824b --- /dev/null +++ b/plugins/Vercel/v1/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "vercel", + "displayName": "Vercel", + "version": "1.2.0", + "author": { "name": "Andrew Harris", "type": "community" }, + "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) security posture.", + "category": "Cloud Platforms", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": ["vercel", "deployments", "hosting", "frontend", "projects", "domains", "security", "firewall", "waf"], + "objectTypes": ["Vercel Project", "Vercel Domain"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/Vercel/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/Vercel", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://api.vercel.com", + "authMode": "none", + "headers": [ + { "key": "Authorization", "value": "Bearer {{accessToken}}" } + ], + "queryArgs": [ + { "key": "teamId", "value": "{{teamId}}" } + ] + } + } +} diff --git a/plugins/Vercel/v1/ui.json b/plugins/Vercel/v1/ui.json new file mode 100644 index 00000000..86f3d3b9 --- /dev/null +++ b/plugins/Vercel/v1/ui.json @@ -0,0 +1,21 @@ +[ + { + "type": "markdown", + "name": "info", + "content": "Create a Vercel **Access Token** in [Account Settings → Tokens](https://vercel.com/account/tokens) and paste it below. To monitor a Team, also provide its **Team ID**; leave it blank to monitor your personal account." + }, + { + "type": "password", + "name": "accessToken", + "label": "API Token", + "help": "A Vercel Access Token. Sent as a bearer token on every request. Create one at [Account Settings → Tokens](https://vercel.com/account/tokens).", + "validation": { "required": true } + }, + { + "type": "text", + "name": "teamId", + "label": "Team ID", + "placeholder": "team_xxxxxxxxxxxxxxxx", + "help": "Optional. The ID of the Vercel Team to monitor (found at Team Settings → General, format `team_…`). Leave blank to monitor your personal account." + } +] From 138fe02a66a04be5e0a7fa7d71533440fca5efe1 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 16 Jun 2026 14:53:28 +0100 Subject: [PATCH 2/5] Remove unused listDeployments dataStream --- .../v1/dataStreams/listDeployments.json | 51 ------------------- plugins/Vercel/v1/docs/README.md | 4 +- plugins/Vercel/v1/metadata.json | 20 +++++--- 3 files changed, 16 insertions(+), 59 deletions(-) delete mode 100644 plugins/Vercel/v1/dataStreams/listDeployments.json diff --git a/plugins/Vercel/v1/dataStreams/listDeployments.json b/plugins/Vercel/v1/dataStreams/listDeployments.json deleted file mode 100644 index 2da76017..00000000 --- a/plugins/Vercel/v1/dataStreams/listDeployments.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "deployments", - "displayName": "Deployments", - "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", - "tags": ["Deployments"], - "baseDataSourceName": "httpRequestUnscoped", - "config": { - "httpMethod": "get", - "endpointPath": "v7/deployments", - "postRequestScript": "deployments.js", - "getArgs": [ - { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } - ], - "paging": { - "mode": "token", - "pageSize": { - "realm": "queryArg", - "path": "limit", - "value": "100" - }, - "in": { "realm": "payload", "path": "pagination.next" }, - "out": { "realm": "queryArg", "path": "until" } - } - }, - "matches": "none", - "ui": [ - { - "type": "objects", - "name": "project", - "label": "Project (optional)", - "matches": { - "sourceType": { "type": "equals", "value": "Vercel Project" } - } - } - ], - "metadata": [ - { - "name": "uid", - "displayName": "ID", - "role": "value" - }, - { - "name": "label", - "displayName": "Label", - "role": "label", - "valueExpression": "{{$['status']}}" - }, - { "pattern": ".*" } - ], - "timeframes": true -} diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index f1081f25..a1fa5ca1 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -9,7 +9,7 @@ Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in - **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are *not* indexed as objects, because they change too frequently — they are available as data streams instead.) - **Team & activity** — team membership roster and recent account activity (events). - **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). -- **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) posture: whether the firewall is enabled, which managed protections (OWASP-style CRS categories, bot protection, AI bots) are active and their action, plus active attack anomalies over time. Available as data streams on the **Project** perspective. +- **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) event activity: counts of firewall events broken down by action (e.g. allow / deny / challenge) over time. Available as data streams on the **Project** perspective's **Security** section. The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. @@ -52,6 +52,6 @@ Deployments, teams, members, activity events, and cost data are provided as **da - **No real-time analytics or metrics via REST.** Vercel does **not** expose Web Analytics (pageviews/visitors), Speed Insights (Core Web Vitals), or real-time Observability metrics (edge requests, function invocations, bandwidth) through its public REST API. Operational usage is available only as **daily billed quantities** via the cost stream — not real-time, per-request telemetry. - **Cost data is daily granularity** and the billing endpoint returns very large datasets, so the cost stream is restricted to the **Last 24 hours** and **Last 7 days** timeframes. - **Team members and cost require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. -- **Firewall streams only return data for projects with a configured firewall.** The Vercel Firewall config endpoint returns a 404 (not an empty result) for any project that has never configured a firewall, so the firewall posture and managed-rules tiles render only for projects where the WAF has been set up; for other projects the tile shows no data. The attack-anomalies stream returns an empty result (not an error) when there is no active attack. +- **Firewall events only appear for projects with the WAF configured.** The Firewall events stream returns rows only for projects that have the Vercel Firewall set up and that recorded events within the selected timeframe; a project with no firewall activity in that window shows an empty result (not an error). - **One connection = one scope.** Each configured plugin instance monitors either your personal account or a single team. To monitor multiple teams, add the plugin once per team. - **Rate limits.** Vercel enforces per-action rate limits (HTTP 429). Very large accounts may occasionally see throttling during imports. diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index 675c824b..83e2e0a0 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -1,15 +1,25 @@ { "name": "vercel", "displayName": "Vercel", - "version": "1.2.0", + "version": "1.0.0", "author": { "name": "Andrew Harris", "type": "community" }, - "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) security posture.", + "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) event activity.", "category": "Cloud Platforms", "type": "hybrid", "schemaVersion": "2.1", "importNotSupported": false, "restrictedToPlatforms": [], - "keywords": ["vercel", "deployments", "hosting", "frontend", "projects", "domains", "security", "firewall", "waf"], + "keywords": [ + "vercel", + "deployments", + "hosting", + "frontend", + "projects", + "domains", + "security", + "firewall", + "waf" + ], "objectTypes": ["Vercel Project", "Vercel Domain"], "links": [ { @@ -32,9 +42,7 @@ "headers": [ { "key": "Authorization", "value": "Bearer {{accessToken}}" } ], - "queryArgs": [ - { "key": "teamId", "value": "{{teamId}}" } - ] + "queryArgs": [{ "key": "teamId", "value": "{{teamId}}" }] } } } From fc8a29af16b6a6da11074ae89d5cf11191f7a4b3 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 06:50:02 +0100 Subject: [PATCH 3/5] Vercel plugin review comments --- plugins/Vercel/v1/custom_types.json | 8 +++---- .../Vercel/v1/dataStreams/deployments.json | 2 +- .../Vercel/v1/dataStreams/domainConfig.json | 20 ++++++++++++++---- .../Vercel/v1/dataStreams/firewallEvents.json | 2 +- .../Vercel/v1/dataStreams/projectInfo.json | 21 +++++++++++++++---- .../v1/defaultContent/deployments.dash.json | 18 ---------------- .../v1/defaultContent/overview.dash.json | 2 +- .../Vercel/v1/indexDefinitions/default.json | 8 +++++-- 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/plugins/Vercel/v1/custom_types.json b/plugins/Vercel/v1/custom_types.json index e25c0352..5ad86d20 100644 --- a/plugins/Vercel/v1/custom_types.json +++ b/plugins/Vercel/v1/custom_types.json @@ -1,14 +1,14 @@ [ { - "name": "Vercel Project", - "sourceType": "Vercel Project", + "name": "Project", + "sourceType": "Project", "icon": "rocket", "singular": "Project", "plural": "Projects" }, { - "name": "Vercel Domain", - "sourceType": "Vercel Domain", + "name": "Domain", + "sourceType": "Domain", "icon": "globe", "singular": "Domain", "plural": "Domains" diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json index 6066a57c..42eea108 100644 --- a/plugins/Vercel/v1/dataStreams/deployments.json +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -29,7 +29,7 @@ "name": "project", "label": "Project (optional)", "matches": { - "sourceType": { "type": "equals", "value": "Vercel Project" } + "sourceType": { "type": "equals", "value": "Project" } } } ], diff --git a/plugins/Vercel/v1/dataStreams/domainConfig.json b/plugins/Vercel/v1/dataStreams/domainConfig.json index 8a430814..cd87e7a2 100644 --- a/plugins/Vercel/v1/dataStreams/domainConfig.json +++ b/plugins/Vercel/v1/dataStreams/domainConfig.json @@ -8,11 +8,23 @@ "httpMethod": "get", "endpointPath": "v6/domains/{{object.name}}/config" }, - "matches": { "sourceType": { "type": "equals", "value": "Vercel Domain" } }, + "matches": { "sourceType": { "type": "equals", "value": "Domain" } }, "metadata": [ - { "name": "misconfigured", "displayName": "Misconfigured", "shape": "boolean" }, - { "name": "serviceType", "displayName": "Service Type", "shape": "string" }, - { "name": "configuredBy", "displayName": "Configured By", "shape": "string" } + { + "name": "misconfigured", + "displayName": "Misconfigured", + "shape": "boolean" + }, + { + "name": "serviceType", + "displayName": "Service Type", + "shape": "string" + }, + { + "name": "configuredBy", + "displayName": "Configured By", + "shape": "string" + } ], "timeframes": false } diff --git a/plugins/Vercel/v1/dataStreams/firewallEvents.json b/plugins/Vercel/v1/dataStreams/firewallEvents.json index 06f5897f..f77a4298 100644 --- a/plugins/Vercel/v1/dataStreams/firewallEvents.json +++ b/plugins/Vercel/v1/dataStreams/firewallEvents.json @@ -18,7 +18,7 @@ "pathToData": "actions" }, "matches": { - "sourceType": { "type": "equals", "value": "Vercel Project" } + "sourceType": { "type": "equals", "value": "Project" } }, "metadata": [ { diff --git a/plugins/Vercel/v1/dataStreams/projectInfo.json b/plugins/Vercel/v1/dataStreams/projectInfo.json index dd7b5c5a..aa69f7ec 100644 --- a/plugins/Vercel/v1/dataStreams/projectInfo.json +++ b/plugins/Vercel/v1/dataStreams/projectInfo.json @@ -9,15 +9,28 @@ "endpointPath": "v9/projects/{{object.rawId}}", "expandInnerObjects": true }, - "matches": { "sourceType": { "type": "equals", "value": "Vercel Project" } }, + "matches": { "sourceType": { "type": "equals", "value": "Project" } }, "metadata": [ - { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "name", + "displayName": "Name", + "shape": "string", + "role": "label" + }, { "name": "framework", "displayName": "Framework", "shape": "string" }, - { "name": "nodeVersion", "displayName": "Node Version", "shape": "string" }, + { + "name": "nodeVersion", + "displayName": "Node Version", + "shape": "string" + }, { "name": "createdAt", "displayName": "Created", "shape": "date" }, { "name": "updatedAt", "displayName": "Updated", "shape": "date" }, { "name": "link.repo", "displayName": "Git Repo", "shape": "string" }, - { "name": "link.type", "displayName": "Git Provider", "shape": "string" } + { + "name": "link.type", + "displayName": "Git Provider", + "shape": "string" + } ], "timeframes": false } diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index 6eb0ed17..ca646afd 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -128,24 +128,6 @@ "aggregate": [] } }, - "scope": { - "query": "g.V().has('id', within(ids_defaultScopeIds))", - "bindings": { - "ids_defaultScopeIds": [ - "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" - ] - }, - "queryDetail": { - "ids": [ - "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" - ], - "types": [ - { - "value": "data-source" - } - ] - } - }, "_type": "tile/data-stream", "description": "", "activePluginConfigIds": ["{{configId}}"], diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json index dae24374..80f7ada6 100644 --- a/plugins/Vercel/v1/defaultContent/overview.dash.json +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -66,7 +66,7 @@ "dataStream": { "name": "projects", "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[proejcts]}}", + "id": "{{dataStreams.[projects]}}", "sort": { "by": [["updatedAt", "desc"]] } diff --git a/plugins/Vercel/v1/indexDefinitions/default.json b/plugins/Vercel/v1/indexDefinitions/default.json index 7642ba9b..a07b6dc4 100644 --- a/plugins/Vercel/v1/indexDefinitions/default.json +++ b/plugins/Vercel/v1/indexDefinitions/default.json @@ -7,7 +7,9 @@ "objectMapping": { "id": "id", "name": "name", - "type": "sourceType" + "type": { + "value": "Project" + } } }, { @@ -17,7 +19,9 @@ "objectMapping": { "id": "id", "name": "name", - "type": "sourceType" + "type": { + "value": "Domain" + } } } ] From 5b2e47a09e137cf026930649aaab87d2b2e39376 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 06:52:27 +0100 Subject: [PATCH 4/5] update plugin author --- plugins/Vercel/v1/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index 83e2e0a0..36f885db 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -2,7 +2,7 @@ "name": "vercel", "displayName": "Vercel", "version": "1.0.0", - "author": { "name": "Andrew Harris", "type": "community" }, + "author": { "name": "@andrewmumblebee", "type": "community" }, "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) event activity.", "category": "Cloud Platforms", "type": "hybrid", From c70fa6e055d77c8aeac14d9cb05ec13bc6c15ce4 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 12:37:03 +0100 Subject: [PATCH 5/5] Ensure scopes and object types match new sourceTypes --- plugins/Vercel/v1/defaultContent/scopes.json | 4 ++-- plugins/Vercel/v1/docs/README.md | 20 +++++++++----------- plugins/Vercel/v1/metadata.json | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/scopes.json b/plugins/Vercel/v1/defaultContent/scopes.json index 164d0191..da9b2912 100644 --- a/plugins/Vercel/v1/defaultContent/scopes.json +++ b/plugins/Vercel/v1/defaultContent/scopes.json @@ -2,7 +2,7 @@ { "name": "Vercel Projects", "matches": { - "sourceType": { "type": "oneOf", "values": ["Vercel Project"] } + "sourceType": { "type": "oneOf", "values": ["Project"] } }, "variable": { "name": "Vercel Project", @@ -14,7 +14,7 @@ { "name": "Vercel Domains", "matches": { - "sourceType": { "type": "oneOf", "values": ["Vercel Domain"] } + "sourceType": { "type": "oneOf", "values": ["Domain"] } }, "variable": { "name": "Vercel Domain", diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index a1fa5ca1..84047b70 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -1,26 +1,24 @@ -# Vercel +## What this plugin monitors Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in SquaredUp. This plugin connects to the Vercel REST API to import your projects and domains as objects you can scope dashboards to, and provides data streams for deployment health and history, domain verification and expiry, team membership and activity, and a billing-based cost overview. -## What this plugin monitors - - **Projects** — your Vercel projects, including framework, git repository, and latest production deployment status. Imported as objects. - **Domains** — custom domains, including verification status, expiry/renewal dates, and configuration health. Imported as objects. -- **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are *not* indexed as objects, because they change too frequently — they are available as data streams instead.) +- **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are _not_ indexed as objects, because they change too frequently — they are available as data streams instead.) - **Team & activity** — team membership roster and recent account activity (events). - **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). - **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) event activity: counts of firewall events broken down by action (e.g. allow / deny / challenge) over time. Available as data streams on the **Project** perspective's **Security** section. The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. -## Prerequisites — getting a Vercel Access Token +## Prerequisites 1. Sign in to Vercel and open **Account Settings → Tokens** (). 2. Click **Create Token**. 3. Give it a name (e.g. `SquaredUp`). 4. **Scope** — choose the scope the token can access: - - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). - - To monitor your **personal account**, scope it to your personal account. + - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). + - To monitor your **personal account**, scope it to your personal account. 5. **Expiration** — choose an expiration (or no expiration). If the token expires, the plugin's data streams will stop returning data until you supply a new token. 6. Click **Create** and copy the token value immediately — Vercel only shows it once. @@ -32,10 +30,10 @@ If you are monitoring a Team, open **Team Settings → General** in Vercel; the ## Configuration fields -| Field | Required | Description | Where to find it | -| --- | --- | --- | --- | -| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | -| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | +| Field | Required | Description | Where to find it | +| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | +| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | ## What gets indexed diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index 36f885db..a8be3c4f 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -20,7 +20,7 @@ "firewall", "waf" ], - "objectTypes": ["Vercel Project", "Vercel Domain"], + "objectTypes": ["Project", "Domain"], "links": [ { "category": "documentation",