@ainyc/canonry 4.54.0 → 4.55.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/assets/{BacklinksPage-BXFT4pLI.js → BacklinksPage-DVmaM864.js} +1 -1
- package/assets/assets/{ProjectPage-DAtd9Vay.js → ProjectPage-DtL3LFne.js} +4 -4
- package/assets/assets/{RunRow-38dDceGl.js → RunRow-BRqiLxj2.js} +1 -1
- package/assets/assets/{RunsPage-AJnFLtaE.js → RunsPage-UxZ93-cg.js} +1 -1
- package/assets/assets/{SettingsPage-FT9ZAvFH.js → SettingsPage-Cr5_EGbk.js} +1 -1
- package/assets/assets/TrafficPage-CUC_lfTe.js +1 -0
- package/assets/assets/TrafficSourceDetailPage-DARPL2TU.js +1 -0
- package/assets/assets/extract-error-message-DD5MibWI.js +1 -0
- package/assets/assets/{index-DLPKqyhx.js → index-nnF1LnyK.js} +62 -62
- package/assets/assets/{server-traffic-GqiQYm6x.js → server-traffic-DjRISEZ-.js} +1 -1
- package/assets/assets/{trash-2-BwPzJ8NI.js → trash-2-CJ5M--Le.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-CRO6Q25G.js → chunk-2OI7HFAB.js} +315 -85
- package/dist/{chunk-VZPDBHBW.js → chunk-OFY3Z2F7.js} +8 -4
- package/dist/{chunk-JHAHNKSN.js → chunk-UTM3FPAJ.js} +80 -3
- package/dist/{chunk-J7MX3YOH.js → chunk-ZY3EDW3S.js} +1 -1
- package/dist/cli.js +6 -6
- package/dist/index.d.ts +13 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-OCREQUCQ.js → intelligence-service-NKAEHHJ5.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +14 -13
- package/assets/assets/TrafficPage-B4A3oO8M.js +0 -1
- package/assets/assets/TrafficSourceDetailPage-8NYU1TA6.js +0 -1
- package/assets/assets/arrow-left-DgI0X1Q1.js +0 -1
|
@@ -349,6 +349,9 @@ function authRequired() {
|
|
|
349
349
|
function authInvalid() {
|
|
350
350
|
return new AppError("AUTH_INVALID", "Invalid API key", 401);
|
|
351
351
|
}
|
|
352
|
+
function forbidden(message = "Forbidden") {
|
|
353
|
+
return new AppError("FORBIDDEN", message, 403);
|
|
354
|
+
}
|
|
352
355
|
function providerError(message, details) {
|
|
353
356
|
return new AppError("PROVIDER_ERROR", message, 502, details);
|
|
354
357
|
}
|
|
@@ -2661,7 +2664,7 @@ var serverActivitySectionSchema = z21.object({
|
|
|
2661
2664
|
byOperator: z21.array(z21.object({
|
|
2662
2665
|
operator: z21.string(),
|
|
2663
2666
|
verifiedHits: z21.number(),
|
|
2664
|
-
/** Shown to agency audience only
|
|
2667
|
+
/** Shown to agency audience only: claimed-bot UA, source IP not in a published range. */
|
|
2665
2668
|
unverifiedHits: z21.number(),
|
|
2666
2669
|
/** Per-user fetches from this operator's AI surface (ChatGPT-User, …). */
|
|
2667
2670
|
userFetchHits: z21.number(),
|
|
@@ -3160,7 +3163,7 @@ var VercelTrafficEnvironments = vercelTrafficEnvironmentSchema.enum;
|
|
|
3160
3163
|
var vercelTrafficSourceConfigSchema = z23.object({
|
|
3161
3164
|
/** Vercel project id (e.g. `prj_...`). */
|
|
3162
3165
|
projectId: z23.string().min(1),
|
|
3163
|
-
/** Vercel team
|
|
3166
|
+
/** Vercel team or account id: the org that owns the project. */
|
|
3164
3167
|
teamId: z23.string().min(1),
|
|
3165
3168
|
environment: vercelTrafficEnvironmentSchema
|
|
3166
3169
|
});
|
|
@@ -3196,9 +3199,9 @@ var trafficConnectWordpressRequestSchema = z23.object({
|
|
|
3196
3199
|
var trafficConnectVercelRequestSchema = z23.object({
|
|
3197
3200
|
/** Vercel project id (e.g. `prj_...`) — from the Vercel dashboard or `.vercel/project.json`. */
|
|
3198
3201
|
projectId: z23.string().min(1),
|
|
3199
|
-
/** Vercel team
|
|
3202
|
+
/** Vercel team or account id: the org that owns the project ("orgId" in .vercel/project.json). */
|
|
3200
3203
|
teamId: z23.string().min(1),
|
|
3201
|
-
/** Vercel
|
|
3204
|
+
/** Vercel personal access token. Stored in `~/.canonry/config.yaml`, never the DB. */
|
|
3202
3205
|
token: z23.string().min(1),
|
|
3203
3206
|
/** Which deployment environment's request logs to pull. Default: `production`. */
|
|
3204
3207
|
environment: vercelTrafficEnvironmentSchema.optional(),
|
|
@@ -3606,6 +3609,7 @@ export {
|
|
|
3606
3609
|
validationError,
|
|
3607
3610
|
authRequired,
|
|
3608
3611
|
authInvalid,
|
|
3612
|
+
forbidden,
|
|
3609
3613
|
providerError,
|
|
3610
3614
|
runInProgress,
|
|
3611
3615
|
runNotCancellable,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
categoryLabel,
|
|
11
11
|
determineAnswerMentioned,
|
|
12
12
|
normalizeProjectDomain
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-OFY3Z2F7.js";
|
|
14
14
|
|
|
15
15
|
// src/intelligence-service.ts
|
|
16
16
|
import { eq, desc, asc, and, ne, or, inArray } from "drizzle-orm";
|
|
@@ -243,10 +243,19 @@ var googleConnections = sqliteTable("google_connections", {
|
|
|
243
243
|
propertyId: text("property_id"),
|
|
244
244
|
sitemapUrl: text("sitemap_url"),
|
|
245
245
|
scopes: text("scopes", { mode: "json" }).$type().notNull().default([]),
|
|
246
|
+
// The project that established this connection. Used by the OAuth callback
|
|
247
|
+
// and the DELETE route to refuse cross-project takeover (a malicious caller
|
|
248
|
+
// who points another project at the same `canonicalDomain` cannot overwrite
|
|
249
|
+
// or remove an existing connection owned by the original project). Nullable
|
|
250
|
+
// for legacy rows written before the column existed — those are treated as
|
|
251
|
+
// unowned and the first connect call to claim them succeeds. See root
|
|
252
|
+
// AGENTS.md "Deployment Posture" for the broader multi-tenancy posture.
|
|
253
|
+
createdByProjectId: text("created_by_project_id").references(() => projects.id, { onDelete: "set null" }),
|
|
246
254
|
createdAt: text("created_at").notNull(),
|
|
247
255
|
updatedAt: text("updated_at").notNull()
|
|
248
256
|
}, (table) => [
|
|
249
|
-
uniqueIndex("idx_google_conn_domain_type").on(table.domain, table.connectionType)
|
|
257
|
+
uniqueIndex("idx_google_conn_domain_type").on(table.domain, table.connectionType),
|
|
258
|
+
index("idx_google_conn_project").on(table.createdByProjectId)
|
|
250
259
|
]);
|
|
251
260
|
var gscSearchData = sqliteTable("gsc_search_data", {
|
|
252
261
|
id: text("id").primaryKey(),
|
|
@@ -319,10 +328,16 @@ var bingConnections = sqliteTable("bing_connections", {
|
|
|
319
328
|
id: text("id").primaryKey(),
|
|
320
329
|
domain: text("domain").notNull(),
|
|
321
330
|
siteUrl: text("site_url"),
|
|
331
|
+
// Same takeover-prevention column as `google_connections.createdByProjectId`.
|
|
332
|
+
// The Bing connect / disconnect routes refuse cross-project writes when an
|
|
333
|
+
// existing row's owner doesn't match. Null for legacy rows (treated as
|
|
334
|
+
// unowned).
|
|
335
|
+
createdByProjectId: text("created_by_project_id").references(() => projects.id, { onDelete: "set null" }),
|
|
322
336
|
createdAt: text("created_at").notNull(),
|
|
323
337
|
updatedAt: text("updated_at").notNull()
|
|
324
338
|
}, (table) => [
|
|
325
|
-
uniqueIndex("idx_bing_conn_domain").on(table.domain)
|
|
339
|
+
uniqueIndex("idx_bing_conn_domain").on(table.domain),
|
|
340
|
+
index("idx_bing_conn_project").on(table.createdByProjectId)
|
|
326
341
|
]);
|
|
327
342
|
var bingUrlInspections = sqliteTable("bing_url_inspections", {
|
|
328
343
|
id: text("id").primaryKey(),
|
|
@@ -2157,6 +2172,68 @@ var MIGRATION_VERSIONS = [
|
|
|
2157
2172
|
`DELETE FROM crawler_events_hourly WHERE bot_id = 'mistral-ai' AND sampled_user_agent LIKE '%MistralAI-User%'`,
|
|
2158
2173
|
`UPDATE crawler_events_hourly SET bot_id = 'mistral-bot' WHERE bot_id = 'mistral-ai'`
|
|
2159
2174
|
]
|
|
2175
|
+
},
|
|
2176
|
+
{
|
|
2177
|
+
version: 66,
|
|
2178
|
+
name: "oauth-connections-track-owning-project",
|
|
2179
|
+
// Cross-project OAuth takeover defense. Before this column, the OAuth
|
|
2180
|
+
// callback for Google and the connect route for Bing keyed everything on
|
|
2181
|
+
// `domain` alone — an attacker who created a project pointed at a victim's
|
|
2182
|
+
// canonical domain could complete OAuth from their own Google/Bing account
|
|
2183
|
+
// and silently overwrite the legitimate refresh token under that domain
|
|
2184
|
+
// key. The new `created_by_project_id` column records the project that
|
|
2185
|
+
// first established each connection; the callback and DELETE routes refuse
|
|
2186
|
+
// cross-project writes when it doesn't match.
|
|
2187
|
+
//
|
|
2188
|
+
// Backfill: for each existing connection row, set the owner to the project
|
|
2189
|
+
// whose `canonical_domain` matches AND whose `created_at` is oldest (the
|
|
2190
|
+
// most likely original owner in a 1:N domain-shared install). Rows with no
|
|
2191
|
+
// matching project stay NULL — treated as "unowned" so a future legitimate
|
|
2192
|
+
// connect from any project can claim them.
|
|
2193
|
+
//
|
|
2194
|
+
// Uses the `run` hook so the schema-edit + backfill only fire when the
|
|
2195
|
+
// target tables exist. The legacy-keyword test scenario seeds a DB at v46
|
|
2196
|
+
// without google_connections / bing_connections (they're created in v6 but
|
|
2197
|
+
// the test bypasses the bootstrap) — without the guard, this version's
|
|
2198
|
+
// ALTER fails with "no such table".
|
|
2199
|
+
//
|
|
2200
|
+
// Idempotent: column-existence guard means re-running this version is a
|
|
2201
|
+
// no-op; the backfill UPDATE only writes rows where the column is NULL.
|
|
2202
|
+
statements: [],
|
|
2203
|
+
run: (db) => {
|
|
2204
|
+
if (tableExists(db, "google_connections") && !columnExists(db, "google_connections", "created_by_project_id")) {
|
|
2205
|
+
db.run(sql.raw(
|
|
2206
|
+
`ALTER TABLE google_connections ADD COLUMN created_by_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL`
|
|
2207
|
+
));
|
|
2208
|
+
db.run(sql.raw(`CREATE INDEX IF NOT EXISTS idx_google_conn_project ON google_connections(created_by_project_id)`));
|
|
2209
|
+
db.run(sql.raw(
|
|
2210
|
+
`UPDATE google_connections
|
|
2211
|
+
SET created_by_project_id = (
|
|
2212
|
+
SELECT p.id FROM projects p
|
|
2213
|
+
WHERE LOWER(p.canonical_domain) = LOWER(google_connections.domain)
|
|
2214
|
+
ORDER BY p.created_at ASC
|
|
2215
|
+
LIMIT 1
|
|
2216
|
+
)
|
|
2217
|
+
WHERE created_by_project_id IS NULL`
|
|
2218
|
+
));
|
|
2219
|
+
}
|
|
2220
|
+
if (tableExists(db, "bing_connections") && !columnExists(db, "bing_connections", "created_by_project_id")) {
|
|
2221
|
+
db.run(sql.raw(
|
|
2222
|
+
`ALTER TABLE bing_connections ADD COLUMN created_by_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL`
|
|
2223
|
+
));
|
|
2224
|
+
db.run(sql.raw(`CREATE INDEX IF NOT EXISTS idx_bing_conn_project ON bing_connections(created_by_project_id)`));
|
|
2225
|
+
db.run(sql.raw(
|
|
2226
|
+
`UPDATE bing_connections
|
|
2227
|
+
SET created_by_project_id = (
|
|
2228
|
+
SELECT p.id FROM projects p
|
|
2229
|
+
WHERE LOWER(p.canonical_domain) = LOWER(bing_connections.domain)
|
|
2230
|
+
ORDER BY p.created_at ASC
|
|
2231
|
+
LIMIT 1
|
|
2232
|
+
)
|
|
2233
|
+
WHERE created_by_project_id IS NULL`
|
|
2234
|
+
));
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2160
2237
|
}
|
|
2161
2238
|
];
|
|
2162
2239
|
function isDuplicateColumnError(err) {
|
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
setTelemetrySource,
|
|
24
24
|
showFirstRunNotice,
|
|
25
25
|
trackEvent
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2OI7HFAB.js";
|
|
27
27
|
import {
|
|
28
28
|
CliError,
|
|
29
29
|
EXIT_SYSTEM_ERROR,
|
|
@@ -39,14 +39,14 @@ import {
|
|
|
39
39
|
saveConfig,
|
|
40
40
|
saveConfigPatch,
|
|
41
41
|
usageError
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-ZY3EDW3S.js";
|
|
43
43
|
import {
|
|
44
44
|
apiKeys,
|
|
45
45
|
createClient,
|
|
46
46
|
migrate,
|
|
47
47
|
projects,
|
|
48
48
|
queries
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-UTM3FPAJ.js";
|
|
50
50
|
import {
|
|
51
51
|
CcReleaseSyncStatuses,
|
|
52
52
|
CheckScopes,
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
providerQuotaPolicySchema,
|
|
66
66
|
resolveProviderInput,
|
|
67
67
|
skillsClientSchema
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-OFY3Z2F7.js";
|
|
69
69
|
|
|
70
70
|
// src/cli.ts
|
|
71
71
|
import { pathToFileURL } from "url";
|
|
@@ -3087,7 +3087,7 @@ async function trafficConnectVercel(project, opts) {
|
|
|
3087
3087
|
throw new CliError({
|
|
3088
3088
|
code: "TRAFFIC_VERCEL_TEAM_ID_REQUIRED",
|
|
3089
3089
|
message: "--team-id is required",
|
|
3090
|
-
displayMessage: "Error: --team-id is required (the Vercel team
|
|
3090
|
+
displayMessage: "Error: --team-id is required (the Vercel team or personal account that owns the project)",
|
|
3091
3091
|
details: { project }
|
|
3092
3092
|
});
|
|
3093
3093
|
}
|
|
@@ -3118,7 +3118,7 @@ async function trafficConnectVercel(project, opts) {
|
|
|
3118
3118
|
throw new CliError({
|
|
3119
3119
|
code: "TRAFFIC_VERCEL_TOKEN_REQUIRED",
|
|
3120
3120
|
message: "--token or --token-file is required",
|
|
3121
|
-
displayMessage: "Error: pass --token <token> or --token-file <path>",
|
|
3121
|
+
displayMessage: "Error: pass the Vercel personal access token via --token <token> or --token-file <path>",
|
|
3122
3122
|
details: { project }
|
|
3123
3123
|
});
|
|
3124
3124
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ interface GoogleConnectionConfigEntry {
|
|
|
29
29
|
refreshToken?: string | null;
|
|
30
30
|
tokenExpiresAt?: string | null;
|
|
31
31
|
scopes?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Project ID that first established this connection. Mirrors the
|
|
34
|
+
* `google_connections.created_by_project_id` DB column — written when the
|
|
35
|
+
* OAuth callback completes from a known project. Null/undefined for legacy
|
|
36
|
+
* connections written before the column existed; the API treats those as
|
|
37
|
+
* unowned (claimable by any project's next connect).
|
|
38
|
+
*/
|
|
39
|
+
createdByProjectId?: string | null;
|
|
32
40
|
createdAt: string;
|
|
33
41
|
updatedAt: string;
|
|
34
42
|
}
|
|
@@ -41,6 +49,11 @@ interface BingConnectionConfigEntry {
|
|
|
41
49
|
domain: string;
|
|
42
50
|
apiKey: string;
|
|
43
51
|
siteUrl?: string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Project ID that first established this connection. Mirrors the
|
|
54
|
+
* `bing_connections.created_by_project_id` DB column.
|
|
55
|
+
*/
|
|
56
|
+
createdByProjectId?: string | null;
|
|
44
57
|
createdAt: string;
|
|
45
58
|
updatedAt: string;
|
|
46
59
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2OI7HFAB.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-ZY3EDW3S.js";
|
|
7
|
+
import "./chunk-UTM3FPAJ.js";
|
|
8
|
+
import "./chunk-OFY3Z2F7.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CliError,
|
|
3
3
|
canonryMcpTools,
|
|
4
4
|
createApiClient
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-ZY3EDW3S.js";
|
|
6
|
+
import "./chunk-OFY3Z2F7.js";
|
|
7
7
|
|
|
8
8
|
// src/mcp/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.55.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -46,12 +46,13 @@
|
|
|
46
46
|
"@sinclair/typebox": "^0.34.41",
|
|
47
47
|
"better-sqlite3": "^12.6.2",
|
|
48
48
|
"chrome-remote-interface": "^0.33.2",
|
|
49
|
-
"drizzle-orm": "^0.45.
|
|
50
|
-
"fastify": "^5.
|
|
49
|
+
"drizzle-orm": "^0.45.2",
|
|
50
|
+
"fastify": "^5.8.5",
|
|
51
51
|
"node-cron": "^4.2.1",
|
|
52
52
|
"openai": "^6.0.0",
|
|
53
53
|
"pdf-lib": "^1.17.1",
|
|
54
54
|
"pino-pretty": "^13.1.3",
|
|
55
|
+
"undici": "^7.24.2",
|
|
55
56
|
"yaml": "^2.7.1",
|
|
56
57
|
"zod": "^4.1.12"
|
|
57
58
|
},
|
|
@@ -62,22 +63,22 @@
|
|
|
62
63
|
"tsx": "^4.19.0",
|
|
63
64
|
"@ainyc/canonry-api-client": "0.0.0",
|
|
64
65
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-db": "0.0.0",
|
|
66
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
67
66
|
"@ainyc/canonry-config": "0.0.0",
|
|
68
|
-
"@ainyc/canonry-
|
|
67
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
68
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
69
69
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
70
|
+
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
70
71
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
71
|
-
"@ainyc/canonry-
|
|
72
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
72
73
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
73
|
-
"@ainyc/canonry-integration-wordpress": "
|
|
74
|
-
"@ainyc/canonry-
|
|
75
|
-
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
74
|
+
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
75
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
76
76
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
77
|
-
"@ainyc/canonry-
|
|
78
|
-
"@ainyc/canonry-provider-
|
|
77
|
+
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
78
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
79
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
79
80
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
80
|
-
"@ainyc/canonry-provider-
|
|
81
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
81
82
|
},
|
|
82
83
|
"scripts": {
|
|
83
84
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as a,j as e,u as L,f as U,L as O}from"./vendor-tanstack-Dq7p98wZ.js";import{c as q,br as _,bs as $,bt as D,bu as F,bv as E,bw as B,B as N,i as k,a0 as I,aI as W,l as R,bx as A,g as P,by as G,T as H,bz as J}from"./index-DLPKqyhx.js";import{a as M,b as Q,c as K,d as Z,t as Y,R as X}from"./server-traffic-GqiQYm6x.js";import{A as ee}from"./arrow-left-DgI0X1Q1.js";import"./vendor-radix-B57xfQbP.js";import"./vendor-recharts-DWvKDyBF.js";import"./vendor-markdown-DK7fbRNb.js";const te=[["path",{d:"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z",key:"p7xjir"}]],se=q("cloud",te);const re=[["path",{d:"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z",key:"14u9p9"}]],ne=q("triangle",re);function oe({open:t,onOpenChange:i,projectName:o}){const[r,s]=a.useState("pick");a.useEffect(()=>{t&&s("pick")},[t]);const n=()=>{i(!1),s("pick")};return e.jsx(_,{open:t,onOpenChange:l=>{l?i(!0):n()},children:e.jsx($,{children:r==="pick"?e.jsx(ie,{onPick:s}):r==="wordpress"?e.jsx(ce,{projectName:o,onBack:()=>s("pick"),onClose:n}):r==="vercel"?e.jsx(de,{projectName:o,onBack:()=>s("pick"),onClose:n}):e.jsx(le,{projectName:o,onBack:()=>s("pick"),onClose:n})})})}const ae=[{type:"wordpress",name:"WordPress site",tagline:"Easiest if you run WordPress",description:"Install the Canonry Traffic Logger plugin and connect with an Application Password. No cloud account needed.",icon:B},{type:"cloud-run",name:"Google Cloud Run",tagline:"For apps hosted on Cloud Run",description:"Connect a Google Cloud service account so Canonry can read your Cloud Run request logs.",icon:se},{type:"vercel",name:"Vercel project",tagline:"For sites hosted on Vercel",description:"Connect a Vercel API token so Canonry can pull request logs straight from Vercel — no in-app instrumentation needed.",icon:ne}];function ie({onPick:t}){return e.jsxs(e.Fragment,{children:[e.jsxs(D,{children:[e.jsx(F,{children:"Connect a traffic source"}),e.jsx(E,{children:"Canonry reads your server logs to see when AI crawlers and AI-referred visitors hit your site. Pick where your site is hosted to get started."})]}),e.jsx("div",{className:"mt-6 flex flex-col gap-3",children:ae.map(({type:i,name:o,tagline:r,description:s,icon:n})=>e.jsxs("button",{type:"button",onClick:()=>t(i),className:"group flex items-start gap-4 rounded-lg border border-zinc-800 bg-zinc-900/30 p-4 text-left transition-colors hover:border-zinc-600 hover:bg-zinc-900/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400",children:[e.jsx("span",{className:"mt-0.5 inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-zinc-800 bg-zinc-950 text-zinc-300 group-hover:text-zinc-100",children:e.jsx(n,{className:"size-4"})}),e.jsxs("span",{className:"flex flex-col gap-0.5",children:[e.jsxs("span",{className:"flex flex-wrap items-baseline gap-x-2",children:[e.jsx("span",{className:"text-sm font-medium text-zinc-100",children:o}),e.jsx("span",{className:"text-[11px] text-zinc-500",children:r})]}),e.jsx("span",{className:"text-xs leading-5 text-zinc-500",children:s})]})]},i))})]})}function T({title:t,description:i,onBack:o}){return e.jsxs(D,{children:[e.jsxs("button",{type:"button",onClick:o,className:"mb-1 inline-flex w-fit items-center gap-1 rounded text-xs text-zinc-500 transition-colors hover:text-zinc-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400",children:[e.jsx(ee,{className:"size-3"}),"Choose a different source"]}),e.jsx(F,{children:t}),e.jsx(E,{children:i})]})}function ce({projectName:t,onBack:i,onClose:o}){const[r,s]=a.useState(""),[n,l]=a.useState(""),[m,j]=a.useState(""),[p,w]=a.useState(""),[y,f]=a.useState(null),[z,h]=a.useState(null),v=M(t||null),C=async d=>{if(d.preventDefault(),f(null),h(null),!r.trim()){f("WordPress site URL is required.");return}if(!n.trim()){f("Username is required.");return}if(!m.trim()){f("Application Password is required.");return}try{const g=await v.mutateAsync({baseUrl:r.trim(),username:n.trim(),applicationPassword:m.trim(),displayName:p.trim()||void 0});j(""),h(`Connected ${g.displayName}.`)}catch(g){const x=g instanceof I||g instanceof Error?g.message:String(g);f(x)}};return e.jsxs(e.Fragment,{children:[e.jsx(T,{title:"Connect a WordPress site",description:e.jsxs(e.Fragment,{children:["Pulls request events from the Canonry Traffic Logger plugin. The Application Password is stored in ",e.jsx("code",{children:"~/.canonry/config.yaml"})," on the server and never echoed back to the dashboard."]}),onBack:i}),e.jsxs("form",{onSubmit:k(C),className:"mt-6 flex flex-col gap-5 overflow-y-auto pr-1",children:[e.jsx(u,{label:"Project",description:"Canonry project this source attaches to.",children:e.jsx("input",{type:"text",value:t,disabled:!0,className:"w-full rounded border border-zinc-700 bg-zinc-900/50 px-2 py-1.5 text-sm text-zinc-300"})}),e.jsx(u,{label:"WordPress site URL",description:"Base URL of the site running the Canonry Traffic Logger plugin.",required:!0,children:e.jsx("input",{type:"url",value:r,onChange:d=>s(d.target.value),required:!0,autoComplete:"url",placeholder:"https://example.com",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Username",description:"WordPress user that owns the Application Password.",required:!0,children:e.jsx("input",{type:"text",value:n,onChange:d=>l(d.target.value),required:!0,autoComplete:"username",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Application Password",description:"Create one in wp-admin under Users -> Profile -> Application Passwords.",required:!0,children:e.jsx("input",{type:"password",value:m,onChange:d=>j(d.target.value),required:!0,autoComplete:"new-password",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the WordPress host.",children:e.jsx("input",{type:"text",value:p,onChange:d=>w(d.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),y?e.jsx("p",{className:"rounded-md border border-rose-800/50 bg-rose-950/30 px-3 py-2 text-xs text-rose-200",children:y}):null,z?e.jsx("p",{className:"rounded-md border border-emerald-800/50 bg-emerald-950/30 px-3 py-2 text-xs text-emerald-200",children:z}):null,e.jsxs("div",{className:"mt-2 flex items-center justify-end gap-2 border-t border-zinc-800/60 pt-4",children:[e.jsx(N,{type:"button",variant:"ghost",size:"sm",onClick:o,children:"Close"}),e.jsx(N,{type:"submit",disabled:v.isPending,size:"sm",children:v.isPending?"Connecting...":"Connect"})]})]})]})}function le({projectName:t,onBack:i,onClose:o}){const[r,s]=a.useState(""),[n,l]=a.useState(""),[m,j]=a.useState(""),[p,w]=a.useState(""),[y,f]=a.useState(""),[z,h]=a.useState(null),[v,C]=a.useState(null),d=K(t||null),g=async c=>{if(c.preventDefault(),h(null),C(null),!r.trim()){h("GCP project ID is required.");return}if(!y.trim()){h("Service-account JSON content is required.");return}try{const b=await d.mutateAsync({gcpProjectId:r.trim(),serviceName:n.trim()||void 0,location:m.trim()||void 0,displayName:p.trim()||void 0,keyJson:y.trim()});f(""),C(`Connected ${b.displayName}.`)}catch(b){const V=b instanceof I||b instanceof Error?b.message:String(b);h(V)}},x=async c=>{if(!c)return;const b=await c.text();f(b)};return e.jsxs(e.Fragment,{children:[e.jsx(T,{title:"Connect a Cloud Run service",description:e.jsxs(e.Fragment,{children:["v1 supports service-account JSON only. The private key is stored in"," ",e.jsx("code",{children:"~/.canonry/config.yaml"})," on the server and never echoed back to the dashboard."]}),onBack:i}),e.jsxs("form",{onSubmit:k(g),className:"mt-6 flex flex-col gap-5 overflow-y-auto pr-1",children:[e.jsx(u,{label:"Project",description:"Canonry project this source attaches to.",children:e.jsx("input",{type:"text",value:t,disabled:!0,className:"w-full rounded border border-zinc-700 bg-zinc-900/50 px-2 py-1.5 text-sm text-zinc-300"})}),e.jsx(u,{label:"GCP project ID",description:"The Google Cloud project hosting the Cloud Run service (e.g. my-prod-foo).",required:!0,children:e.jsx("input",{type:"text",value:r,onChange:c=>s(c.target.value),required:!0,autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Service name (optional)",description:"Restrict log pulls to a specific Cloud Run service. Omit to pull all services in the project.",children:e.jsx("input",{type:"text",value:n,onChange:c=>l(c.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Location (optional)",description:"Region of the Cloud Run service (e.g. us-central1). Helpful when multiple regions emit logs.",children:e.jsx("input",{type:"text",value:m,onChange:c=>j(c.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the project + service combo.",children:e.jsx("input",{type:"text",value:p,onChange:c=>w(c.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsxs(u,{label:"Service-account JSON",description:"Paste the contents of the SA key (JSON). The SA needs roles/logging.viewer (or any role granting logging.logEntries.list).",required:!0,children:[e.jsx("textarea",{value:y,onChange:c=>f(c.target.value),rows:6,spellCheck:!1,autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 font-mono text-[11px] text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:'{"type":"service_account","project_id":"…","private_key":"…"}',required:!0}),e.jsxs("label",{className:"mt-2 inline-flex cursor-pointer items-center gap-2 text-xs text-zinc-400 hover:text-zinc-200",children:[e.jsx("input",{type:"file",accept:"application/json,.json",className:"hidden",onChange:c=>{x(c.target.files?.[0]??null)}}),e.jsx("span",{className:"rounded-md border border-zinc-800 px-2 py-1",children:"Or upload a key file"})]})]}),z?e.jsx("p",{className:"rounded-md border border-rose-800/50 bg-rose-950/30 px-3 py-2 text-xs text-rose-200",children:z}):null,v?e.jsx("p",{className:"rounded-md border border-emerald-800/50 bg-emerald-950/30 px-3 py-2 text-xs text-emerald-200",children:v}):null,e.jsxs("div",{className:"mt-2 flex items-center justify-end gap-2 border-t border-zinc-800/60 pt-4",children:[e.jsx(N,{type:"button",variant:"ghost",size:"sm",onClick:o,children:"Close"}),e.jsx(N,{type:"submit",disabled:d.isPending,size:"sm",children:d.isPending?"Connecting…":"Connect"})]})]})]})}function de({projectName:t,onBack:i,onClose:o}){const[r,s]=a.useState(""),[n,l]=a.useState(""),[m,j]=a.useState(""),[p,w]=a.useState("production"),[y,f]=a.useState(""),[z,h]=a.useState(null),[v,C]=a.useState(null),d=Q(t||null),g=async x=>{if(x.preventDefault(),h(null),C(null),!r.trim()){h("Vercel project ID is required.");return}if(!n.trim()){h("Vercel team ID is required.");return}if(!m.trim()){h("Vercel API token is required.");return}try{const c=await d.mutateAsync({projectId:r.trim(),teamId:n.trim(),token:m.trim(),environment:p,displayName:y.trim()||void 0});j(""),C(`Connected ${c.displayName}.`)}catch(c){const b=c instanceof I||c instanceof Error?c.message:String(c);h(b)}};return e.jsxs(e.Fragment,{children:[e.jsx(T,{title:"Connect a Vercel project",description:e.jsxs(e.Fragment,{children:["Pulls request logs straight from Vercel — no in-app instrumentation needed. The API token is stored in ",e.jsx("code",{children:"~/.canonry/config.yaml"})," on the server and never echoed back to the dashboard."]}),onBack:i}),e.jsxs("form",{onSubmit:k(g),className:"mt-6 flex flex-col gap-5 overflow-y-auto pr-1",children:[e.jsx(u,{label:"Project",description:"Canonry project this source attaches to.",children:e.jsx("input",{type:"text",value:t,disabled:!0,className:"w-full rounded border border-zinc-700 bg-zinc-900/50 px-2 py-1.5 text-sm text-zinc-300"})}),e.jsx(u,{label:"Vercel project ID",description:"The prj_… id from the Vercel dashboard or .vercel/project.json.",required:!0,children:e.jsx("input",{type:"text",value:r,onChange:x=>s(x.target.value),required:!0,autoComplete:"off",placeholder:"prj_…",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Vercel team ID",description:"The team_… (or owner) id the project belongs to.",required:!0,children:e.jsx("input",{type:"text",value:n,onChange:x=>l(x.target.value),required:!0,autoComplete:"off",placeholder:"team_…",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"API token",description:"Create one in Vercel under Account Settings → Tokens. Tokens can expire — use a long-lived token.",required:!0,children:e.jsx("input",{type:"password",value:m,onChange:x=>j(x.target.value),required:!0,autoComplete:"new-password",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(u,{label:"Environment",description:"Which deployment environment's request logs to pull.",children:e.jsxs("select",{value:p,onChange:x=>w(x.target.value),className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 focus:border-zinc-500 focus:outline-none",children:[e.jsx("option",{value:"production",children:"production"}),e.jsx("option",{value:"preview",children:"preview"})]})}),e.jsx(u,{label:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the Vercel project ID.",children:e.jsx("input",{type:"text",value:y,onChange:x=>f(x.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),z?e.jsx("p",{className:"rounded-md border border-rose-800/50 bg-rose-950/30 px-3 py-2 text-xs text-rose-200",children:z}):null,v?e.jsx("p",{className:"rounded-md border border-emerald-800/50 bg-emerald-950/30 px-3 py-2 text-xs text-emerald-200",children:v}):null,e.jsxs("div",{className:"mt-2 flex items-center justify-end gap-2 border-t border-zinc-800/60 pt-4",children:[e.jsx(N,{type:"button",variant:"ghost",size:"sm",onClick:o,children:"Close"}),e.jsx(N,{type:"submit",disabled:d.isPending,size:"sm",children:d.isPending?"Connecting…":"Connect"})]})]})]})}function u({label:t,description:i,required:o,children:r}){return e.jsxs("label",{className:"flex flex-col gap-1",children:[e.jsxs("span",{className:"text-xs font-medium text-zinc-200",children:[t,o?e.jsx("span",{className:"ml-1 text-rose-400",children:"*"}):null]}),r,e.jsx("span",{className:"text-[11px] text-zinc-500",children:i})]})}function ue(t){if(!t)return"never";const i=Date.now()-new Date(t).getTime(),o=Math.floor(i/6e4);if(o<1)return"just now";if(o<60)return`${o}m ago`;const r=Math.floor(o/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function S(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:t.toLocaleString()}function ye(){const[t,i]=a.useState(""),[o,r]=a.useState(!1),n=L(W({client:R})).data??[],l=a.useMemo(()=>t||(n[0]?.name??""),[t,n]),m=Z(l||null),j=m.data?.sources??[];return e.jsxs("div",{className:"page-container",children:[e.jsxs("div",{className:"page-header",children:[e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Server traffic"}),e.jsx("p",{className:"page-subtitle",children:"Bulk crawler hits, AI user fetches (ChatGPT-User, Perplexity-User), and AI referral sessions pulled directly from your server logs. Independent of GA — useful when you need server-side evidence that an AI engine actually hit a page."})]}),e.jsx("div",{className:"flex flex-wrap items-center justify-end gap-2",children:e.jsxs(N,{type:"button",variant:"outline",size:"sm",onClick:()=>r(!0),disabled:!l,children:[e.jsx(A,{className:"size-3.5"}),"Connect a source"]})})]}),e.jsxs("section",{children:[e.jsx("div",{className:"filter-row",role:"toolbar","aria-label":"Project picker",children:n.map(p=>e.jsx("button",{type:"button",className:`filter-chip ${l===p.name?"filter-chip-active":""}`,"aria-pressed":l===p.name,onClick:()=>i(p.name),children:p.displayName??p.name},p.id))}),l?m.isLoading?e.jsx(P,{className:"p-6 text-center text-sm text-zinc-500",children:"Loading sources…"}):j.length===0?e.jsxs(P,{className:"p-8 text-center",children:[e.jsxs("p",{className:"text-sm text-zinc-300",children:["No traffic sources connected for ",l,"."]}),e.jsx("p",{className:"mt-1 text-xs text-zinc-500",children:"Connect a traffic source to start ingesting crawler hits and AI-referral sessions from your server logs."}),e.jsx("div",{className:"mt-4 flex flex-wrap items-center justify-center gap-2",children:e.jsxs(N,{type:"button",variant:"outline",size:"sm",onClick:()=>r(!0),children:[e.jsx(A,{className:"size-3.5"}),"Connect a source"]})})]}):e.jsx(pe,{projectName:l,sources:j}):e.jsx(P,{className:"p-6 text-center text-sm text-zinc-500",children:"No projects yet."})]}),e.jsx(oe,{open:o,onOpenChange:r,projectName:l})]})}function pe({projectName:t,sources:i}){const o=U({queries:i.map(s=>({...G({client:R,path:{name:t,id:s.id}}),staleTime:3e4}))}),r=i.map((s,n)=>({source:s,detail:o[n]?.data,isLoading:o[n]?.isLoading??!1}));return e.jsxs("div",{className:"rounded-xl border border-zinc-800/60 bg-zinc-900/30 overflow-hidden",children:[e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{className:"bg-zinc-900/50 text-[10px] font-semibold uppercase tracking-wider text-zinc-500",children:e.jsxs("tr",{children:[e.jsx("th",{className:"px-4 py-2 text-left",children:"Source"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Last sync"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h crawler"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h AI hits"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h AI sessions"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h samples"}),e.jsx("th",{className:"px-4 py-2 text-right"})]})}),e.jsx("tbody",{className:"divide-y divide-zinc-800/60",children:r.map(({source:s,detail:n,isLoading:l})=>e.jsxs("tr",{className:"hover:bg-zinc-900/40 transition-colors",children:[e.jsxs("td",{className:"px-4 py-3",children:[e.jsx("div",{className:"font-medium text-zinc-100",children:s.displayName}),e.jsxs("div",{className:"text-[11px] text-zinc-500 font-mono",children:[s.sourceType," · ",s.id.slice(0,8)]})]}),e.jsxs("td",{className:"px-4 py-3",children:[e.jsx(H,{tone:Y(s.status),children:s.status}),s.lastError?e.jsx("p",{className:"mt-1 max-w-[18rem] truncate text-[11px] text-rose-400/80",title:s.lastError,children:s.lastError}):null]}),e.jsx("td",{className:"px-4 py-3 text-zinc-300",children:ue(s.lastSyncedAt)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:l?"—":S(n?.totals24h.crawlerHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:l?"—":S(n?.totals24h.aiUserFetchHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:l?"—":S(n?.totals24h.aiReferralHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-300",children:l?"—":S(n?.totals24h.sampleCount??0)}),e.jsx("td",{className:"px-4 py-3 text-right",children:e.jsxs(O,{to:"/traffic/$projectName/$sourceId",params:{projectName:t,sourceId:s.id},className:"inline-flex items-center gap-1 text-xs text-zinc-300 hover:text-zinc-100",children:[e.jsx(X,{className:"size-3"}),"View"]})})]},s.id))})]}),e.jsxs("p",{className:"border-t border-zinc-800/60 px-4 py-2 text-[11px] text-zinc-600",children:["Showing ",i.filter(s=>s.status!==J.archived).length," active source",i.length===1?"":"s"," for ",t,". Same shape as ",e.jsxs("code",{className:"text-zinc-400",children:["canonry traffic status ",t," --format json"]}),"."]})]})}export{ye as TrafficPage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e,A as fe,B as ge,r as m,L as be}from"./vendor-tanstack-Dq7p98wZ.js";import{I as je,bA as x,T as ye,B as Ne,g as $,a0 as ve,bB as we,bC as Se}from"./index-DLPKqyhx.js";import{d as K,e as ke,C as Z,a as q,c as ze}from"./ChartPrimitives-9Kx3gzQL.js";import{e as Ce,f as Te,g as Ae,t as Re,R as Ie}from"./server-traffic-GqiQYm6x.js";import{R as $e,B as Le,a as Ue,X as Fe,Y as He,T as Me,b as O,d as B}from"./vendor-recharts-DWvKDyBF.js";import{A as J}from"./arrow-left-DgI0X1Q1.js";import"./vendor-radix-B57xfQbP.js";import"./vendor-markdown-DK7fbRNb.js";function I({value:t,label:a,delta:n,tone:r,description:u,tooltip:i,isNumeric:c=!0,progress:o,providerCoverage:d}){const y=2*Math.PI*48,h=Number.parseInt(t,10),C=typeof o=="number"&&Number.isFinite(o)?Math.min(Math.max(o,0),100)/100:c&&!Number.isNaN(h)?Math.min(h/100,1):.5,N=y*(1-C);return e.jsxs("div",{className:"score-gauge",children:[e.jsxs("div",{className:"gauge-ring-wrapper",children:[e.jsxs("svg",{className:"gauge-ring",viewBox:"0 0 120 120","aria-hidden":"true",children:[e.jsx("circle",{className:"gauge-bg",cx:"60",cy:"60",r:48,strokeWidth:6}),e.jsx("circle",{className:`gauge-fill gauge-fill-${r}`,cx:"60",cy:"60",r:48,strokeWidth:6,strokeDasharray:y,strokeDashoffset:N,transform:"rotate(-90 60 60)"})]}),e.jsx("div",{className:"gauge-center",children:e.jsx("span",{className:c?"gauge-value":"gauge-value-text",children:t.split(" / ")[0]})})]}),e.jsxs("p",{className:"gauge-label",children:[a,i&&e.jsx(je,{text:i})]}),e.jsx("p",{className:"gauge-delta",children:n}),d&&e.jsx("p",{className:"gauge-provider-coverage",children:d}),e.jsx("p",{className:"gauge-description",children:u})]})}const De=[{value:"all",label:"All status"},{value:"2xx",label:"2xx success"},{value:"3xx",label:"3xx redirect"},{value:"4xx",label:"4xx client error"},{value:"5xx",label:"5xx server error"}];function Ee(t){return t==="all"?null:Number.parseInt(t[0],10)}function le(t){switch(t.kind){case x.crawler:case x["ai-user-fetch"]:return t.botId;case x["ai-referral"]:return t.product}}function ie(t){switch(t.kind){case x.crawler:case x["ai-user-fetch"]:return t.pathNormalized;case x["ai-referral"]:return t.landingPathNormalized}}function ce(t,a){if(a==="hour")return t;const n=new Date(t),r=n.getFullYear(),u=String(n.getMonth()+1).padStart(2,"0"),i=String(n.getDate()).padStart(2,"0");return`${r}-${u}-${i}`}function Oe(t,a,n){const r=a.pathQuery.trim().toLowerCase(),u=Ee(a.statusClass);return t.filter(i=>!(a.selectedBucket&&ce(i.tsHour,n)!==a.selectedBucket||a.identity&&le(i)!==a.identity||a.operator&&i.operator!==a.operator||r&&!ie(i).toLowerCase().includes(r)||u!==null&&Math.floor(i.status/100)!==u))}function Be(t,a){if(!t||typeof t!="object")return null;const n=t.activeTooltipIndex;let r;if(typeof n=="number")r=n;else if(typeof n=="string"&&n!=="")r=Number(n);else return null;return!Number.isInteger(r)||r<0||r>=a.length?null:a[r]?.bucket??null}const U=[{value:60,label:"1h",granularity:"hour",fetchLimit:500},{value:360,label:"6h",granularity:"hour",fetchLimit:500},{value:1440,label:"24h",granularity:"hour",fetchLimit:500},{value:10080,label:"7d",granularity:"hour",fetchLimit:1e3},{value:720*60,label:"30d",granularity:"day",fetchLimit:2e3},{value:2160*60,label:"90d",granularity:"day",fetchLimit:5e3}],ee=U.find(t=>t.label==="7d")??U[2],te=K[0],se=K[2],ae=K[1],re=we();function oe(t){const a=new Date(t);return`${a.getMonth()+1}/${a.getDate()} ${String(a.getHours()).padStart(2,"0")}:00`}function Pe(t){const a=new Date(`${t}T00:00:00`);return`${a.getMonth()+1}/${a.getDate()}`}function T(t){if(!t)return"never";const a=Date.now()-new Date(t).getTime(),n=Math.floor(a/6e4);if(n<1)return"just now";if(n<60)return`${n}m ago`;const r=Math.floor(n/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function ne({canGoBack:t,className:a}){const n="inline-flex items-center gap-1";if(t){const r=()=>{window.history.back()};return e.jsxs("button",{type:"button",onClick:r,className:`${n} ${a??""}`,children:[e.jsx(J,{className:"size-3"})," Back"]})}return e.jsxs(be,{to:"/traffic",className:`${n} ${a??""}`,children:[e.jsx(J,{className:"size-3"})," All sources"]})}function st(){const t=fe({strict:!1}),a=t.projectName??"",n=ge(),r=t.sourceId??"",[u,i]=m.useState(ee.value),[c,o]=m.useState(()=>new Set(["crawler","ai-user-fetch","ai-referral"])),[d,w]=m.useState(null),[f,y]=m.useState(""),[h,C]=m.useState(""),[N,F]=m.useState(""),[k,H]=m.useState("all"),[W,G]=m.useState(null),[Y,X]=m.useState(null),b=m.useMemo(()=>U.find(s=>s.value===u)??ee,[u]),M=Ce(a||null,r||null),j=Te(a||null,{kind:"all",sourceId:r||void 0,sinceMinutes:u,limit:b.fetchLimit}),R=Ae(a||null,r||null),l=M.data,v=j.data?.events??[],S=j.data?.totals,D=m.useMemo(()=>v.filter(s=>{switch(s.kind){case x.crawler:return c.has("crawler");case x["ai-user-fetch"]:return c.has("ai-user-fetch");case x["ai-referral"]:return c.has("ai-referral")}}),[v,c]),ue=m.useMemo(()=>{const s=new Set;for(const p of v)s.add(le(p));return f&&s.add(f),[...s].sort((p,g)=>p.localeCompare(g))},[v,f]),de=m.useMemo(()=>{const s=new Set;for(const p of v)s.add(p.operator);return h&&s.add(h),[...s].sort((p,g)=>p.localeCompare(g))},[v,h]),Q=m.useMemo(()=>Oe(D,{selectedBucket:d,identity:f,operator:h,pathQuery:N,statusClass:k},b.granularity),[D,d,f,h,N,k,b.granularity]),V=m.useMemo(()=>d?L(d,b.granularity):null,[d,b.granularity]),z=m.useMemo(()=>_e(v,b.granularity,j.data?.windowStart,j.data?.windowEnd),[v,b.granularity,j.data?.windowStart,j.data?.windowEnd]),E=s=>{o(p=>{const g=new Set(p);return g.has(s)?g.size>1&&g.delete(s):g.add(s),g})},xe=s=>{const p=Be(s,z);p&&w(g=>g===p?null:p)},he=()=>{w(null),y(""),C(""),F(""),H("all")},me=!!(d||f||h||N.trim()||k!=="all"),pe=async()=>{G(null),X(null);try{const s=await R.mutateAsync({sinceMinutes:60});X(`Pulled ${s.pulledEvents} entries · ${s.crawlerHits} crawler · ${s.aiReferralHits} AI referral · ${s.unknownHits} unknown`)}catch(s){const p=s instanceof ve||s instanceof Error?s.message:String(s);G(p)}};return!a||!r?e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-zinc-500",children:"Missing project name or source id in URL."})}):M.isLoading?e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-zinc-500",children:"Loading source…"})}):M.isError||!l?e.jsxs("div",{className:"page-container",children:[e.jsx("p",{className:"text-sm text-rose-300",children:"Could not load this source."}),e.jsx(ne,{canGoBack:n,className:"mt-2 text-xs text-zinc-400 hover:text-zinc-200"})]}):e.jsxs("div",{className:"page-container space-y-8",children:[e.jsxs("div",{className:"page-header",children:[e.jsxs("div",{className:"page-header-left",children:[e.jsx(ne,{canGoBack:n,className:"text-xs text-zinc-500 hover:text-zinc-200"}),e.jsx("h1",{className:"page-title mt-2",children:l.displayName}),e.jsxs("p",{className:"page-subtitle",children:[l.sourceType," · project ",e.jsx("span",{className:"text-zinc-300",children:a})," ·",e.jsx("span",{className:"ml-1 font-mono text-zinc-400",children:l.id})]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(ye,{tone:Re(l.status),children:l.status}),e.jsxs(Ne,{type:"button",variant:"outline",size:"sm",disabled:R.isPending,onClick:()=>{pe()},children:[e.jsx(Ie,{className:`size-3.5 ${R.isPending?"animate-spin":""}`}),R.isPending?"Syncing…":"Sync now"]})]})]}),W?e.jsx("div",{className:"rounded-md border border-rose-800/50 bg-rose-950/30 px-3 py-2 text-xs text-rose-200",children:W}):null,Y?e.jsx("div",{className:"rounded-md border border-emerald-800/50 bg-emerald-950/30 px-3 py-2 text-xs text-emerald-200",children:Y}):null,e.jsxs("section",{className:"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",children:[e.jsx(I,{label:"24h crawler hits",value:String(l.totals24h.crawlerHits),delta:l.lastSyncedAt?`last sync ${T(l.lastSyncedAt)}`:"never synced",tone:l.totals24h.crawlerHits>0?"positive":"neutral",description:"Bulk machine crawl — GPTBot, OAI-SearchBot, PerplexityBot, Googlebot, etc.",isNumeric:!0,progress:Math.min(100,Math.round(l.totals24h.crawlerHits/1e3*100))}),e.jsx(I,{label:"24h AI user fetches",value:String(l.totals24h.aiUserFetchHits),delta:l.lastSyncedAt?`last sync ${T(l.lastSyncedAt)}`:"never synced",tone:l.totals24h.aiUserFetchHits>0?"positive":"neutral",description:"ChatGPT-User, Perplexity-User — fetches initiated by a real user inside an AI surface (citation click, URL read).",isNumeric:!0,progress:Math.min(100,Math.round(l.totals24h.aiUserFetchHits/1e3*100))}),e.jsx(I,{label:"24h AI referral sessions",value:String(l.totals24h.aiReferralHits),delta:l.lastSyncedAt?`last sync ${T(l.lastSyncedAt)}`:"never synced",tone:l.totals24h.aiReferralHits>0?"positive":"neutral",description:"Browser click-throughs from chatgpt.com, perplexity.ai, etc. (Referer / UTM evidence).",isNumeric:!0,progress:Math.min(100,Math.round(l.totals24h.aiReferralHits/1e3*100))}),e.jsx(I,{label:"24h sample rows",value:String(l.totals24h.sampleCount),delta:"bounded per sync",tone:"neutral",description:"Per-request samples retained for evidence (capped to keep storage bounded).",isNumeric:!0,progress:Math.min(100,Math.round(l.totals24h.sampleCount/100*100))})]}),e.jsxs("section",{children:[e.jsx("p",{className:"mb-4 text-[10px] font-semibold uppercase tracking-wider text-zinc-500",children:"Latest sync run"}),l.latestRun?e.jsxs($,{className:"p-4 text-sm",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-x-6 gap-y-1.5",children:[e.jsxs("span",{className:"text-zinc-100",children:["Status: ",e.jsx("span",{className:"font-medium",children:l.latestRun.status})]}),e.jsxs("span",{className:"text-zinc-500",children:["Started: ",T(l.latestRun.startedAt)]}),l.latestRun.finishedAt?e.jsxs("span",{className:"text-zinc-500",children:["Finished: ",T(l.latestRun.finishedAt)]}):null,e.jsx("span",{className:"font-mono text-[11px] text-zinc-600",children:l.latestRun.runId})]}),l.latestRun.error?e.jsx("p",{className:"mt-2 rounded border border-rose-900/40 bg-rose-950/30 px-3 py-2 text-xs text-rose-300",children:l.latestRun.error}):null]}):e.jsx($,{className:"px-4 py-3 text-sm text-zinc-500",children:'No traffic-sync runs recorded yet. Hit "Sync now" above to create one.'})]}),e.jsxs("section",{children:[e.jsxs("div",{className:"mb-4 flex flex-wrap items-end justify-between gap-x-4 gap-y-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-wider text-zinc-500",children:"Events"}),e.jsx("h2",{className:"mt-1 text-base font-semibold text-zinc-50",children:b.granularity==="day"?"Daily rollups":"Hourly rollups"}),S?e.jsxs("p",{className:"mt-1.5 text-xs text-zinc-500",children:[S.crawlerHits.toLocaleString("en-US")," crawler ·"," ",S.aiUserFetchHits.toLocaleString("en-US")," AI user fetches ·"," ",S.aiReferralHits.toLocaleString("en-US")," AI referral sessions · last ",b.label," · ",re]}):null]}),e.jsx("div",{className:"filter-row mb-0",role:"toolbar","aria-label":"Window",children:U.map(s=>e.jsx("button",{type:"button",className:`filter-chip ${u===s.value?"filter-chip-active":""}`,"aria-pressed":u===s.value,onClick:()=>{i(s.value),w(null)},children:s.label},s.value))})]}),e.jsxs($,{className:"p-4",children:[e.jsxs("div",{className:"mb-3 flex flex-wrap items-center gap-2",role:"toolbar","aria-label":"Series",children:[e.jsx(_,{label:"Crawler",color:te,count:S?.crawlerHits??0,active:c.has("crawler"),onToggle:()=>E("crawler")}),e.jsx(_,{label:"AI user fetches",color:se,count:S?.aiUserFetchHits??0,active:c.has("ai-user-fetch"),onToggle:()=>E("ai-user-fetch")}),e.jsx(_,{label:"AI referral sessions",color:ae,count:S?.aiReferralHits??0,active:c.has("ai-referral"),onToggle:()=>E("ai-referral")})]}),j.isError?e.jsxs("p",{className:"py-12 text-center text-xs text-rose-400",children:["Failed to load events: ",j.error instanceof Error?j.error.message:"Unknown error"]}):j.isLoading?e.jsx("p",{className:"py-12 text-center text-xs text-zinc-500",children:"Loading events…"}):z.length===0?e.jsx("p",{className:"py-12 text-center text-xs text-zinc-500",children:"No events in this window."}):e.jsx("div",{className:"h-72",children:e.jsx($e,{children:e.jsxs(Le,{data:z,margin:{top:4,right:4,bottom:0,left:0},onClick:xe,style:{cursor:"pointer"},children:[e.jsx(Ue,{stroke:ke,strokeDasharray:"3 3"}),e.jsx(Fe,{dataKey:"label",tick:q,stroke:Z,interval:"preserveStartEnd",minTickGap:b.granularity==="day"?24:32}),e.jsx(He,{tick:q,stroke:Z,allowDecimals:!1}),e.jsx(Me,{...ze}),c.has("crawler")?e.jsx(O,{dataKey:"crawler",name:"Crawler",fill:te,stackId:"a",children:z.map(s=>e.jsx(B,{fillOpacity:d&&d!==s.bucket?.25:1},s.bucket))}):null,c.has("ai-user-fetch")?e.jsx(O,{dataKey:"aiUserFetch",name:"AI user fetch",fill:se,stackId:"a",children:z.map(s=>e.jsx(B,{fillOpacity:d&&d!==s.bucket?.25:1},s.bucket))}):null,c.has("ai-referral")?e.jsx(O,{dataKey:"aiReferral",name:"AI referral",fill:ae,stackId:"a",children:z.map(s=>e.jsx(B,{fillOpacity:d&&d!==s.bucket?.25:1},s.bucket))}):null]})})})]})]}),e.jsxs("section",{children:[e.jsxs("div",{className:"mb-4 flex flex-wrap items-end justify-between gap-x-4 gap-y-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-wider text-zinc-500",children:"Event rows"}),e.jsxs("p",{className:"mt-1 text-xs text-zinc-500",children:["Showing ",e.jsx("span",{className:"tabular-nums text-zinc-300",children:Q.length.toLocaleString("en-US")})," of"," ",e.jsx("span",{className:"tabular-nums text-zinc-500",children:D.length.toLocaleString("en-US")})," events · ",re]})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("select",{"aria-label":"Filter by identity",value:f,onChange:s=>y(s.target.value),className:"rounded-md border border-zinc-800 bg-zinc-950 px-2.5 py-1.5 text-xs text-zinc-200 focus:outline-none focus-visible:ring-1 focus-visible:ring-zinc-600",children:[e.jsx("option",{value:"",children:"All identities"}),ue.map(s=>e.jsx("option",{value:s,children:s},s))]}),e.jsxs("select",{"aria-label":"Filter by operator",value:h,onChange:s=>C(s.target.value),className:"rounded-md border border-zinc-800 bg-zinc-950 px-2.5 py-1.5 text-xs text-zinc-200 focus:outline-none focus-visible:ring-1 focus-visible:ring-zinc-600",children:[e.jsx("option",{value:"",children:"All operators"}),de.map(s=>e.jsx("option",{value:s,children:s},s))]}),e.jsx("select",{"aria-label":"Filter by HTTP status class",value:k,onChange:s=>H(s.target.value),className:"rounded-md border border-zinc-800 bg-zinc-950 px-2.5 py-1.5 text-xs text-zinc-200 focus:outline-none focus-visible:ring-1 focus-visible:ring-zinc-600",children:De.map(s=>e.jsx("option",{value:s.value,children:s.label},s.value))}),e.jsx("input",{type:"search","aria-label":"Filter by path",placeholder:"path contains…",value:N,onChange:s=>F(s.target.value),className:"w-44 rounded-md border border-zinc-800 bg-zinc-950 px-2.5 py-1.5 text-xs text-zinc-200 placeholder:text-zinc-600 focus:outline-none focus-visible:ring-1 focus-visible:ring-zinc-600"})]})]}),me?e.jsxs("div",{className:"mb-3 flex flex-wrap items-center gap-2",children:[V?e.jsx(A,{label:`Bucket: ${V}`,onClear:()=>w(null)}):null,f?e.jsx(A,{label:`Identity: ${f}`,onClear:()=>y("")}):null,h?e.jsx(A,{label:`Operator: ${h}`,onClear:()=>C("")}):null,N.trim()?e.jsx(A,{label:`Path: ${N.trim()}`,onClear:()=>F("")}):null,k!=="all"?e.jsx(A,{label:`Status: ${k}`,onClear:()=>H("all")}):null,e.jsx("button",{type:"button",onClick:he,className:"text-xs text-zinc-500 underline-offset-4 hover:text-zinc-200 hover:underline",children:"Clear all"})]}):null,e.jsx(Ye,{events:Q})]})]})}function P(t,a){return{bucket:t,label:a,crawler:0,aiUserFetch:0,aiReferral:0}}function L(t,a){return a==="day"?Pe(t):oe(t)}function _e(t,a,n,r){const u=new Map;for(const i of t){const c=ce(i.tsHour,a);let o=u.get(c);switch(o||(o=P(c,L(c,a)),u.set(c,o)),i.kind){case x.crawler:o.crawler+=i.hits;break;case x["ai-user-fetch"]:o.aiUserFetch+=i.hits;break;case x["ai-referral"]:o.aiReferral+=i.hits;break}}if(n&&r){const i=new Date(n),c=new Date(r);if(a==="day"){const o=new Date(Date.UTC(i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate())),d=new Date(Date.UTC(c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()));for(;o<=d;){const w=o.getUTCFullYear(),f=String(o.getUTCMonth()+1).padStart(2,"0"),y=String(o.getUTCDate()).padStart(2,"0"),h=`${w}-${f}-${y}`;u.has(h)||u.set(h,P(h,L(h,a))),o.setUTCDate(o.getUTCDate()+1)}}else{const o=new Date(i);for(o.setUTCMinutes(0,0,0);o<=c;){const d=o.toISOString();u.has(d)||u.set(d,P(d,L(d,a))),o.setUTCHours(o.getUTCHours()+1)}}}return[...u.values()].sort((i,c)=>i.bucket<c.bucket?-1:i.bucket>c.bucket?1:0)}function A({label:t,onClear:a}){return e.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-full border border-zinc-700 bg-zinc-800/60 px-2.5 py-1 text-[11px] text-zinc-200",children:[t,e.jsx("button",{type:"button",onClick:a,"aria-label":`Clear ${t}`,className:"rounded-full p-0.5 text-zinc-400 hover:bg-zinc-700/60 hover:text-zinc-100",children:e.jsx(Se,{className:"size-3"})})]})}function _({label:t,color:a,count:n,active:r,onToggle:u}){return e.jsxs("button",{type:"button",onClick:u,"aria-pressed":r,className:`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium transition ${r?"border-zinc-700 bg-zinc-800/60 text-zinc-100":"border-zinc-800 bg-transparent text-zinc-500 hover:text-zinc-300"}`,children:[e.jsx("span",{"aria-hidden":"true",className:`size-2 rounded-full transition-opacity ${r?"opacity-100":"opacity-30"}`,style:{backgroundColor:a}}),e.jsx("span",{children:t}),e.jsx("span",{className:`tabular-nums ${r?"text-zinc-400":"text-zinc-600"}`,children:n.toLocaleString("en-US")})]})}function Ke(t){switch(t){case x.crawler:return"Crawler";case x["ai-user-fetch"]:return"AI hit";case x["ai-referral"]:return"AI referral"}}function We(t){switch(t.kind){case x.crawler:case x["ai-user-fetch"]:return t.botId;case x["ai-referral"]:return t.product}}function Ge(t){switch(t.kind){case x.crawler:case x["ai-user-fetch"]:return`${t.verificationStatus} · HTTP ${t.status}`;case x["ai-referral"]:return`${t.evidenceType} · ${t.sourceDomain}`}}function Ye({events:t}){return t.length===0?e.jsx($,{className:"p-6 text-center text-sm text-zinc-500",children:"No event rows match the current filters."}):e.jsx("div",{className:"rounded-xl border border-zinc-800/60 bg-zinc-900/30 overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{className:"bg-zinc-900/50 text-[10px] font-semibold uppercase tracking-wider text-zinc-500",children:e.jsxs("tr",{children:[e.jsx("th",{className:"px-4 py-2 text-left",children:"Hour"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Kind"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Identity"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Evidence / status"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Path"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"Hits"})]})}),e.jsx("tbody",{className:"divide-y divide-zinc-800/60",children:t.map((a,n)=>e.jsxs("tr",{className:"hover:bg-zinc-900/40 transition-colors",children:[e.jsx("td",{className:"px-4 py-2 font-mono text-xs text-zinc-300",children:oe(a.tsHour)}),e.jsx("td",{className:"px-4 py-2 text-zinc-300",children:Ke(a.kind)}),e.jsxs("td",{className:"px-4 py-2 text-zinc-100",children:[We(a),e.jsx("span",{className:"ml-2 text-[11px] text-zinc-500",children:a.operator})]}),e.jsx("td",{className:"px-4 py-2 text-zinc-300",children:Ge(a)}),e.jsx("td",{className:"px-4 py-2 truncate font-mono text-xs text-zinc-300",children:ie(a)}),e.jsx("td",{className:"px-4 py-2 text-right tabular-nums text-zinc-100",children:a.hits})]},`${a.kind}:${a.tsHour}:${n}`))})]})})}export{st as TrafficSourceDetailPage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{c as o}from"./index-DLPKqyhx.js";const e=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],r=o("arrow-left",e);export{r as A};
|