@gscdump/nuxt 0.22.4 → 1.5.3
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/README.md +10 -137
- package/dist/module.d.mts +22 -0
- package/dist/module.mjs +30 -0
- package/dist/runtime-config.d.mts +12 -0
- package/dist/runtime-config.mjs +27 -0
- package/package.json +26 -176
- package/app/assets/css/main.css +0 -2
- package/app/components/GscAnalyzerPanel.vue +0 -94
- package/app/components/GscAnonymizationBanner.vue +0 -46
- package/app/components/GscBootProgress.vue +0 -297
- package/app/components/GscCommandPalette.vue +0 -77
- package/app/components/GscDashboardPage.vue +0 -26
- package/app/components/GscEngineBadge.vue +0 -28
- package/app/components/GscHero.vue +0 -214
- package/app/components/GscLazyTopResult.vue +0 -47
- package/app/components/GscPerformanceChart.vue +0 -532
- package/app/components/GscPositionDistributionChart.vue +0 -63
- package/app/components/GscQueryLabel.vue +0 -63
- package/app/components/GscSitePageHeader.vue +0 -40
- package/app/components/GscSourceDebugPanel.vue +0 -196
- package/app/components/GscSyncStatusCell.vue +0 -54
- package/app/components/GscTopRollupCard.vue +0 -90
- package/app/composables/_useGscBackfill.ts +0 -119
- package/app/composables/_useGscQueryDispatcher.ts +0 -123
- package/app/composables/_useGscResource.ts +0 -202
- package/app/composables/_useGscSharedSiteResource.ts +0 -136
- package/app/composables/useGscAnalytics.ts +0 -206
- package/app/composables/useGscAnalyticsClient.ts +0 -9
- package/app/composables/useGscAnalyticsConfig.ts +0 -8
- package/app/composables/useGscAnalyticsSourceInfo.ts +0 -165
- package/app/composables/useGscAnalyzer.ts +0 -460
- package/app/composables/useGscAnalyzerBatch.ts +0 -106
- package/app/composables/useGscAnalyzerDefs.ts +0 -119
- package/app/composables/useGscAuth.ts +0 -115
- package/app/composables/useGscConsoleUrl.ts +0 -45
- package/app/composables/useGscCountries.ts +0 -46
- package/app/composables/useGscCurrentSite.ts +0 -36
- package/app/composables/useGscInspectionHistory.ts +0 -42
- package/app/composables/useGscInspections.ts +0 -52
- package/app/composables/useGscPanelContext.ts +0 -31
- package/app/composables/useGscPeriod.ts +0 -243
- package/app/composables/useGscQuery.ts +0 -301
- package/app/composables/useGscQueryDispatcher.ts +0 -14
- package/app/composables/useGscRequestIndexingInspect.ts +0 -29
- package/app/composables/useGscResource.ts +0 -11
- package/app/composables/useGscRollup.ts +0 -237
- package/app/composables/useGscSearchAppearance.ts +0 -46
- package/app/composables/useGscSiteAnalyzer.ts +0 -456
- package/app/composables/useGscSitemapHistory.ts +0 -41
- package/app/composables/useGscSitemaps.ts +0 -45
- package/app/composables/useGscTableState.ts +0 -119
- package/app/plugins/analytics.ts +0 -57
- package/app/queries/gsc.ts +0 -163
- package/app/utils/anonymization.ts +0 -24
- package/app/utils/country-names.ts +0 -56
- package/app/utils/duckdb-wasm.ts +0 -166
- package/app/utils/gsc-constants.ts +0 -10
- package/app/utils/gsc-error.ts +0 -58
- package/app/utils/gsc-fetch.ts +0 -94
- package/app/utils/gsc-filters.ts +0 -32
- package/app/utils/gsc-rows.ts +0 -72
- package/app/utils/gsc-rpc.ts +0 -18
- package/app/utils/position.ts +0 -7
- package/module.ts +0 -81
- package/nuxt.config.ts +0 -61
- package/types.ts +0 -115
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// Run N analyzers in parallel against a single analyzer runner, with per-id
|
|
2
|
-
// status tracking, optional concurrency cap, and stale-token discard so
|
|
3
|
-
// late-completing tasks from a superseded `run()` don't overwrite fresher state.
|
|
4
|
-
//
|
|
5
|
-
// Caller owns: result shaping (summarize, scoring), the trigger watcher,
|
|
6
|
-
// and which ids to run.
|
|
7
|
-
|
|
8
|
-
import type { MaybeRefOrGetter, Ref } from '@vue/runtime-core'
|
|
9
|
-
|
|
10
|
-
export type GscAnalyzerBatchStatus = 'idle' | 'pending' | 'running' | 'done' | 'error' | 'skipped'
|
|
11
|
-
|
|
12
|
-
export interface GscAnalyzerBatchEntry<TResult> {
|
|
13
|
-
status: GscAnalyzerBatchStatus
|
|
14
|
-
result: TResult | null
|
|
15
|
-
error: Error | null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface GscAnalyzerBatchRunner {
|
|
19
|
-
analyze: (params: { type: string, startDate?: string, endDate?: string }) => Promise<unknown>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface UseGscAnalyzerBatchOptions {
|
|
23
|
-
/** Cap parallel analyzer runs. DuckDB-WASM is single-threaded; high parallelism just serializes behind one connection. Default 2. */
|
|
24
|
-
concurrency?: number
|
|
25
|
-
/** Drop ids before running (e.g. analyzers the current source can't serve). Returning `false` marks them `skipped`. */
|
|
26
|
-
filter?: (id: string) => boolean
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface UseGscAnalyzerBatchReturn<TResult> {
|
|
30
|
-
states: Ref<Record<string, GscAnalyzerBatchEntry<TResult>>>
|
|
31
|
-
running: Ref<boolean>
|
|
32
|
-
run: () => Promise<void>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function useGscAnalyzerBatch<TResult = unknown>(
|
|
36
|
-
runner: GscAnalyzerBatchRunner,
|
|
37
|
-
ids: MaybeRefOrGetter<readonly string[]>,
|
|
38
|
-
dateRange: MaybeRefOrGetter<{ start: string, end: string }>,
|
|
39
|
-
opts: UseGscAnalyzerBatchOptions = {},
|
|
40
|
-
): UseGscAnalyzerBatchReturn<TResult> {
|
|
41
|
-
const states = ref<Record<string, GscAnalyzerBatchEntry<TResult>>>({}) as Ref<Record<string, GscAnalyzerBatchEntry<TResult>>>
|
|
42
|
-
const running = ref(false)
|
|
43
|
-
let token = 0
|
|
44
|
-
|
|
45
|
-
function reset(currentIds: readonly string[]): void {
|
|
46
|
-
const next: Record<string, GscAnalyzerBatchEntry<TResult>> = {}
|
|
47
|
-
for (const id of currentIds)
|
|
48
|
-
next[id] = { status: 'pending', result: null, error: null }
|
|
49
|
-
states.value = next
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function run(): Promise<void> {
|
|
53
|
-
const myToken = ++token
|
|
54
|
-
const currentIds = toValue(ids)
|
|
55
|
-
const range = toValue(dateRange)
|
|
56
|
-
running.value = true
|
|
57
|
-
reset(currentIds)
|
|
58
|
-
|
|
59
|
-
const filter = opts.filter
|
|
60
|
-
const concurrency = Math.max(1, opts.concurrency ?? 2)
|
|
61
|
-
|
|
62
|
-
const queue: string[] = []
|
|
63
|
-
for (const id of currentIds) {
|
|
64
|
-
if (filter && !filter(id))
|
|
65
|
-
states.value = { ...states.value, [id]: { status: 'skipped', result: null, error: null } }
|
|
66
|
-
else
|
|
67
|
-
queue.push(id)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function runOne(id: string): Promise<void> {
|
|
71
|
-
if (token !== myToken)
|
|
72
|
-
return
|
|
73
|
-
states.value = { ...states.value, [id]: { status: 'running', result: null, error: null } }
|
|
74
|
-
try {
|
|
75
|
-
const result = await runner.analyze({ type: id, startDate: range.start, endDate: range.end })
|
|
76
|
-
if (token !== myToken)
|
|
77
|
-
return
|
|
78
|
-
states.value = { ...states.value, [id]: { status: 'done', result: result as TResult, error: null } }
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
if (token !== myToken)
|
|
82
|
-
return
|
|
83
|
-
const error = err instanceof Error ? err : new Error(String(err))
|
|
84
|
-
states.value = { ...states.value, [id]: { status: 'error', result: null, error } }
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const workers = Array.from({ length: concurrency }, async () => {
|
|
89
|
-
while (queue.length) {
|
|
90
|
-
if (token !== myToken)
|
|
91
|
-
return
|
|
92
|
-
const id = queue.shift()
|
|
93
|
-
if (!id)
|
|
94
|
-
break
|
|
95
|
-
await runOne(id)
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
await Promise.all(workers)
|
|
100
|
-
|
|
101
|
-
if (token === myToken)
|
|
102
|
-
running.value = false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return { states, running, run }
|
|
106
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// Registry of analyzer definitions. The layer owns the type shape and the
|
|
2
|
-
// reader; hosts provide the array via a Nuxt plugin (`$gscAnalyzers`) or via
|
|
3
|
-
// the module's `analyzers` option (which generates the plugin at build time).
|
|
4
|
-
//
|
|
5
|
-
// Callers:
|
|
6
|
-
// - `/analyze` page builds its tab list from `useGscAnalyzerDefs()` and
|
|
7
|
-
// renders each tab via `<GscAnalyzerPanel :def>` driven by `panel`.
|
|
8
|
-
// - `/insights` page filters via `useGscAnalyzerDefsWithCapability('insightCard')`.
|
|
9
|
-
// - `useActionPriority` filters via `useGscAnalyzerDefsWithCapability('actionPriority')`.
|
|
10
|
-
//
|
|
11
|
-
// Capability slots are typed; e.g. `actionPriority` only accepts the
|
|
12
|
-
// `ActionSource` union from `@gscdump/analysis`, so misspelt or unknown
|
|
13
|
-
// sources fail at typecheck instead of at runtime in the priority runner.
|
|
14
|
-
|
|
15
|
-
import type { ActionSource } from '@gscdump/analysis'
|
|
16
|
-
import type { AnalysisTool } from '@gscdump/engine/analysis-types'
|
|
17
|
-
import type { Component } from '@vue/runtime-core'
|
|
18
|
-
|
|
19
|
-
export type GscAnalyzerKind = 'analyzer' | 'semantic' | 'action'
|
|
20
|
-
|
|
21
|
-
export type GscAnalyzerAccent = 'primary' | 'warning' | 'success' | 'error' | 'neutral'
|
|
22
|
-
|
|
23
|
-
export interface GscAnalyzerInsightCard {
|
|
24
|
-
icon: string
|
|
25
|
-
accent: GscAnalyzerAccent
|
|
26
|
-
description: string
|
|
27
|
-
summarize: (res: { results: unknown[], meta: Record<string, unknown> }) => { headline: string, tagline: string }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface GscAnalyzerStatTile {
|
|
31
|
-
label: string
|
|
32
|
-
value: string | number
|
|
33
|
-
/**
|
|
34
|
-
* Optional CSS color string for the value (used by the few panels that
|
|
35
|
-
* color-code direction, e.g. improved/worsened in change-points).
|
|
36
|
-
*/
|
|
37
|
-
valueColor?: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface GscAnalyzerPanelResult {
|
|
41
|
-
results: unknown[]
|
|
42
|
-
meta: Record<string, unknown>
|
|
43
|
-
queryMs?: number | null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface GscAnalyzerPanelSpec {
|
|
47
|
-
/**
|
|
48
|
-
* Body component. Receives `{ rows, meta, range }` as props. Lazy-import
|
|
49
|
-
* via `defineAsyncComponent(() => import(...))` to preserve route-level
|
|
50
|
-
* codesplitting — pages that don't render the analyzer never pay for its
|
|
51
|
-
* chart code.
|
|
52
|
-
*/
|
|
53
|
-
component: Component
|
|
54
|
-
/** Project a result to the header stat tiles. Empty array = no tiles. */
|
|
55
|
-
summarize?: (res: GscAnalyzerPanelResult) => GscAnalyzerStatTile[]
|
|
56
|
-
/** Italic footer caption explaining the underlying method. */
|
|
57
|
-
caption?: string
|
|
58
|
-
/**
|
|
59
|
-
* When true, the shell skips its loading/error/empty gating and renders
|
|
60
|
-
* the body component immediately — the panel manages its own phase state
|
|
61
|
-
* (pipeline panels: action priority, content gap).
|
|
62
|
-
*/
|
|
63
|
-
ownsLifecycle?: boolean
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface GscAnalyzerCapabilities {
|
|
67
|
-
/** Opt into the `/insights` curated grid by providing a card config. */
|
|
68
|
-
insightCard?: GscAnalyzerInsightCard
|
|
69
|
-
/** Opt into `useActionPriority`. The value is the typed `ActionSource` slug. */
|
|
70
|
-
actionPriority?: ActionSource
|
|
71
|
-
/**
|
|
72
|
-
* Opt into the unified `/analyze` panel renderer. Without this, the tab
|
|
73
|
-
* falls back to the generic table renderer in `<GscAnalyzerPanel>`.
|
|
74
|
-
*/
|
|
75
|
-
panel?: GscAnalyzerPanelSpec
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export type GscAnalyzerCapability = keyof GscAnalyzerCapabilities
|
|
79
|
-
|
|
80
|
-
export interface GscAnalyzerDefinition {
|
|
81
|
-
/** Stable analyzer id. Must match an `AnalysisTool` slug for built-in analyzers. */
|
|
82
|
-
id: AnalysisTool | (string & {})
|
|
83
|
-
label: string
|
|
84
|
-
kind: GscAnalyzerKind
|
|
85
|
-
/** Reads the per-query table; sums silently drop GSC-anonymized impressions. */
|
|
86
|
-
isQueryGrained?: boolean
|
|
87
|
-
/** Capability opt-ins. Each key gates a downstream consumer. */
|
|
88
|
-
capabilities?: GscAnalyzerCapabilities
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export type GscAnalyzerDefinitionWithCapability<K extends GscAnalyzerCapability>
|
|
92
|
-
= GscAnalyzerDefinition & {
|
|
93
|
-
capabilities: { [P in K]: NonNullable<GscAnalyzerCapabilities[P]> }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function defineGscAnalyzer(def: GscAnalyzerDefinition): GscAnalyzerDefinition {
|
|
97
|
-
return def
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function useGscAnalyzerDefs(): GscAnalyzerDefinition[] {
|
|
101
|
-
return (useNuxtApp().$gscAnalyzers as GscAnalyzerDefinition[] | undefined) ?? []
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function useGscAnalyzerDef(id: string): GscAnalyzerDefinition | undefined {
|
|
105
|
-
return useGscAnalyzerDefs().find(a => a.id === id)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Typed filter that narrows defs to those with `capability` opted in.
|
|
110
|
-
* The returned defs have `capabilities[capability]` typed as non-nullable.
|
|
111
|
-
*/
|
|
112
|
-
export function useGscAnalyzerDefsWithCapability<K extends GscAnalyzerCapability>(
|
|
113
|
-
capability: K,
|
|
114
|
-
): GscAnalyzerDefinitionWithCapability<K>[] {
|
|
115
|
-
return useGscAnalyzerDefs().filter(
|
|
116
|
-
(d): d is GscAnalyzerDefinitionWithCapability<K> =>
|
|
117
|
-
d.capabilities?.[capability] != null,
|
|
118
|
-
)
|
|
119
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// Reactive auth primitive for the @gscdump/nuxt layer.
|
|
2
|
-
//
|
|
3
|
-
// `useState`-backed so the value is SSR-safe and shared across the page tree.
|
|
4
|
-
// Hosts call `setGscAuth(getter)` from a `'pre'`-enforced plugin so downstream
|
|
5
|
-
// `useGscFetch`, `useGscQuery`, and `useGscAnalyzer` reactively re-read auth
|
|
6
|
-
// on every change (no plugin race).
|
|
7
|
-
//
|
|
8
|
-
// `apiKey` populates `x-api-key` for cross-origin deployments.
|
|
9
|
-
// `headers` is an opaque bag for hosts that need additional/alternative
|
|
10
|
-
// auth schemes (e.g. `Authorization: Bearer …`, CSRF). Merged after `apiKey`.
|
|
11
|
-
// `browserAnalyzerEnabled` is the user-level opt-in for DuckDB-WASM.
|
|
12
|
-
//
|
|
13
|
-
// `apiBase` is *not* on this state — it's a build-time runtimeConfig value
|
|
14
|
-
// (`runtimeConfig.public.analytics.apiBase`). Per-user dynamic apiBase would
|
|
15
|
-
// reintroduce a plugin-order race where the SDK client gets a stale prefix.
|
|
16
|
-
|
|
17
|
-
import type { Ref } from '@vue/runtime-core'
|
|
18
|
-
|
|
19
|
-
export interface GscAuthState {
|
|
20
|
-
apiKey: string | null
|
|
21
|
-
browserAnalyzerEnabled: boolean
|
|
22
|
-
userId?: string | null
|
|
23
|
-
/**
|
|
24
|
-
* Extra request headers to attach to every `/api/__gsc/*` call and parquet
|
|
25
|
-
* fetch. Use for non-`x-api-key` auth schemes. `apiKey` (above) is the
|
|
26
|
-
* common case — set both when needed.
|
|
27
|
-
*/
|
|
28
|
-
headers?: Record<string, string>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface InternalAuthState extends GscAuthState {
|
|
32
|
-
/**
|
|
33
|
-
* True once the host has called `setGscAuth` at least once. Layer
|
|
34
|
-
* consumers (e.g. `useGscQuery` engine derivation) treat unconfigured
|
|
35
|
-
* state as "no host wiring" and fall back to legacy behavior, rather
|
|
36
|
-
* than honoring the default `browserAnalyzerEnabled: false`.
|
|
37
|
-
*/
|
|
38
|
-
_initialized: boolean
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const STATE_KEY = 'gsc:auth'
|
|
42
|
-
|
|
43
|
-
const DEFAULT_AUTH: InternalAuthState = {
|
|
44
|
-
apiKey: null,
|
|
45
|
-
browserAnalyzerEnabled: false,
|
|
46
|
-
userId: null,
|
|
47
|
-
headers: undefined,
|
|
48
|
-
_initialized: false,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function authState(): Ref<InternalAuthState> {
|
|
52
|
-
return useState<InternalAuthState>(STATE_KEY, () => ({ ...DEFAULT_AUTH }))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function useGscAuth(): Readonly<Ref<GscAuthState>> {
|
|
56
|
-
return readonly(authState()) as unknown as Readonly<Ref<GscAuthState>>
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Layer-internal: returns the underlying state including `_initialized`.
|
|
61
|
-
*/
|
|
62
|
-
export function _useGscAuthInternal(): Readonly<Ref<InternalAuthState>> {
|
|
63
|
-
return readonly(authState()) as Readonly<Ref<InternalAuthState>>
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Set the layer's auth state. Accepts a static value, a ref, or a getter.
|
|
68
|
-
* Getter form is reactive — re-runs whenever its source updates.
|
|
69
|
-
*/
|
|
70
|
-
export function setGscAuth(
|
|
71
|
-
source: GscAuthState | Ref<GscAuthState> | (() => GscAuthState),
|
|
72
|
-
): void {
|
|
73
|
-
const state = authState()
|
|
74
|
-
const apply = (v: GscAuthState): void => {
|
|
75
|
-
state.value = { ...v, _initialized: true }
|
|
76
|
-
}
|
|
77
|
-
if (typeof source === 'function') {
|
|
78
|
-
watchEffect(() => apply((source as () => GscAuthState)()))
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
if (isRef(source)) {
|
|
82
|
-
watch(source, v => apply(v), { immediate: true, deep: true })
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
apply(source)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Read auth state from any callsite — composable setup, fetch interceptor,
|
|
90
|
-
* worker fetch callback. Resolves the ambient NuxtApp via async-local-storage
|
|
91
|
-
* (server) or the global app (client) and reads the `useState` cell directly.
|
|
92
|
-
* Returns the default sentinel when no NuxtApp context is available.
|
|
93
|
-
*/
|
|
94
|
-
export function readGscAuth(): GscAuthState {
|
|
95
|
-
const nuxt = tryUseNuxtApp()
|
|
96
|
-
if (!nuxt)
|
|
97
|
-
return { ...DEFAULT_AUTH }
|
|
98
|
-
return authState().value
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Resolve the auth headers to attach to a request — `x-api-key` from `apiKey`
|
|
103
|
-
* (if set) merged with the opaque `headers` bag. Returns `{}` when no auth
|
|
104
|
-
* is wired, signalling callers to fall back to cookie credentials.
|
|
105
|
-
*/
|
|
106
|
-
export function resolveGscAuthHeaders(auth: GscAuthState = readGscAuth()): Record<string, string> {
|
|
107
|
-
const out: Record<string, string> = {}
|
|
108
|
-
if (auth.apiKey)
|
|
109
|
-
out['x-api-key'] = auth.apiKey
|
|
110
|
-
if (auth.headers) {
|
|
111
|
-
for (const [k, v] of Object.entries(auth.headers))
|
|
112
|
-
out[k] = v
|
|
113
|
-
}
|
|
114
|
-
return out
|
|
115
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// Builds `search.google.com/search-console?...` deep-links for a site +
|
|
2
|
-
// optional page / query. Portable across consumers — no layer state, pure
|
|
3
|
-
// URL string concat.
|
|
4
|
-
|
|
5
|
-
export interface GscConsoleUrlOpts {
|
|
6
|
-
/** The GSC property — either `sc-domain:example.com` or a URL-prefix. */
|
|
7
|
-
siteLabel: string
|
|
8
|
-
/** Optional page to open Performance drilldown for. */
|
|
9
|
-
page?: string
|
|
10
|
-
/** Optional query filter. */
|
|
11
|
-
query?: string
|
|
12
|
-
/** Resource: `performance`, `url-inspection`, `sitemaps`, … */
|
|
13
|
-
resource?: 'performance' | 'url-inspection' | 'sitemaps' | 'index'
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function gscConsoleUrl(opts: GscConsoleUrlOpts): string {
|
|
17
|
-
const base = 'https://search.google.com/search-console'
|
|
18
|
-
const resource = opts.resource ?? 'performance'
|
|
19
|
-
const params = new URLSearchParams()
|
|
20
|
-
// Default `sc-domain:` prefix when caller passes a bare hostname.
|
|
21
|
-
const siteLabel = /^(?:https?:|sc-domain:)/.test(opts.siteLabel)
|
|
22
|
-
? opts.siteLabel
|
|
23
|
-
: `sc-domain:${opts.siteLabel}`
|
|
24
|
-
params.set('resource_id', siteLabel)
|
|
25
|
-
if (resource === 'url-inspection') {
|
|
26
|
-
// Inspect tool expects the absolute URL under `id`, not Performance's
|
|
27
|
-
// `page=*<url>` wildcard filter.
|
|
28
|
-
if (opts.page)
|
|
29
|
-
params.set('id', opts.page)
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
if (opts.page)
|
|
33
|
-
params.set('page', `*${opts.page}`)
|
|
34
|
-
if (opts.query)
|
|
35
|
-
params.set('query', `*${opts.query}`)
|
|
36
|
-
}
|
|
37
|
-
const path = resource === 'performance'
|
|
38
|
-
? '/performance/search-analytics'
|
|
39
|
-
: resource === 'url-inspection'
|
|
40
|
-
? '/inspect'
|
|
41
|
-
: resource === 'sitemaps'
|
|
42
|
-
? '/sitemaps'
|
|
43
|
-
: '/index'
|
|
44
|
-
return `${base}${path}?${params.toString()}`
|
|
45
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// Fetches per-country search performance for a site over a date window.
|
|
2
|
-
// Server dispatches tier-aware: free → GSC API live, pro → engine parquet.
|
|
3
|
-
// The page sees a uniform row shape regardless of which backend served it.
|
|
4
|
-
|
|
5
|
-
import type { CountriesResponse, CountryRow, GscApiRange } from '@gscdump/contracts'
|
|
6
|
-
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
|
-
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
-
import { gscQueries } from '../queries/gsc'
|
|
9
|
-
import { useGscResource } from './_useGscResource'
|
|
10
|
-
|
|
11
|
-
export interface UseGscCountriesReturn {
|
|
12
|
-
response: Readonly<Ref<CountriesResponse | null>>
|
|
13
|
-
rows: ComputedRef<CountryRow[]>
|
|
14
|
-
range: ComputedRef<GscApiRange | null>
|
|
15
|
-
source: ComputedRef<CountriesResponse['source'] | null>
|
|
16
|
-
loading: ComputedRef<boolean>
|
|
17
|
-
status: Readonly<Ref<GscResourceStatus>>
|
|
18
|
-
error: Readonly<Ref<Error | null>>
|
|
19
|
-
refresh: () => Promise<void>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function useGscCountries(
|
|
23
|
-
siteId: MaybeRefOrGetter<string | null | undefined>,
|
|
24
|
-
range: MaybeRefOrGetter<{ start: string, end: string } | null | undefined>,
|
|
25
|
-
): UseGscCountriesReturn {
|
|
26
|
-
const { data, status, loading, error, refresh } = useGscResource<[string, string, string], CountriesResponse>({
|
|
27
|
-
keys: [
|
|
28
|
-
siteId,
|
|
29
|
-
() => toValue(range)?.start,
|
|
30
|
-
() => toValue(range)?.end,
|
|
31
|
-
] as const,
|
|
32
|
-
operation: (id, start, end) => gscQueries.countries(id, { start, end }),
|
|
33
|
-
isEmpty: r => r.rows.length === 0,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
response: data as Readonly<Ref<CountriesResponse | null>>,
|
|
38
|
-
rows: computed(() => data.value?.rows ?? []),
|
|
39
|
-
range: computed(() => data.value?.range ?? null),
|
|
40
|
-
source: computed(() => data.value?.source ?? null),
|
|
41
|
-
loading,
|
|
42
|
-
status,
|
|
43
|
-
error,
|
|
44
|
-
refresh,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef, InjectionKey } from 'vue'
|
|
2
|
-
|
|
3
|
-
export interface GscCurrentSiteContext {
|
|
4
|
-
siteId: ComputedRef<string>
|
|
5
|
-
site: ReturnType<typeof useGscSite>
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const gscCurrentSiteKey: InjectionKey<GscCurrentSiteContext> = Symbol('gsc-current-site')
|
|
9
|
-
|
|
10
|
-
function createGscCurrentSiteContext(siteId: MaybeRefOrGetter<string | null | undefined>): GscCurrentSiteContext {
|
|
11
|
-
const resolvedSiteId = computed(() => String(toValue(siteId) ?? ''))
|
|
12
|
-
return { siteId: resolvedSiteId, site: useGscSite(resolvedSiteId) }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Provide the current-site context for a dashboard subtree. Hosts with a route
|
|
17
|
-
* shape other than `/sites/[id]/*` should call this from their layout/page.
|
|
18
|
-
*/
|
|
19
|
-
export function provideGscCurrentSite(siteId: MaybeRefOrGetter<string | null | undefined>): GscCurrentSiteContext {
|
|
20
|
-
const ctx = createGscCurrentSiteContext(siteId)
|
|
21
|
-
provide(gscCurrentSiteKey, ctx)
|
|
22
|
-
return ctx
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Read the current-site context. Falls back to the historical `/sites/[id]/*`
|
|
27
|
-
* route convention for existing hosts that have not added a provider yet.
|
|
28
|
-
*/
|
|
29
|
-
export function useGscCurrentSite(): GscCurrentSiteContext {
|
|
30
|
-
const provided = inject(gscCurrentSiteKey, null)
|
|
31
|
-
if (provided)
|
|
32
|
-
return provided
|
|
33
|
-
|
|
34
|
-
const route = useRoute()
|
|
35
|
-
return createGscCurrentSiteContext(() => route.params.id as string | undefined)
|
|
36
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// Fetches the inspection timeline for a single URL (by hash) from the
|
|
2
|
-
// site's history shards. Oldest → newest. 404 / empty history is a normal
|
|
3
|
-
// state on the first dashboard visit before a second snapshot runs.
|
|
4
|
-
|
|
5
|
-
import type { InspectionHistoryRecord, InspectionHistoryResponse } from '@gscdump/contracts'
|
|
6
|
-
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
|
-
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
-
import { gscQueries } from '../queries/gsc'
|
|
9
|
-
import { useGscResource } from './_useGscResource'
|
|
10
|
-
|
|
11
|
-
export interface UseGscInspectionHistoryReturn {
|
|
12
|
-
response: Readonly<Ref<InspectionHistoryResponse | null>>
|
|
13
|
-
records: ComputedRef<InspectionHistoryRecord[]>
|
|
14
|
-
url: ComputedRef<string | null>
|
|
15
|
-
loading: ComputedRef<boolean>
|
|
16
|
-
status: Readonly<Ref<GscResourceStatus>>
|
|
17
|
-
error: Readonly<Ref<Error | null>>
|
|
18
|
-
refresh: () => Promise<void>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useGscInspectionHistory(
|
|
22
|
-
siteId: MaybeRefOrGetter<string | null | undefined>,
|
|
23
|
-
urlHash: MaybeRefOrGetter<string | null | undefined>,
|
|
24
|
-
): UseGscInspectionHistoryReturn {
|
|
25
|
-
const { data, status, loading, error, refresh } = useGscResource<[string, string], InspectionHistoryResponse>({
|
|
26
|
-
keys: [siteId, urlHash] as const,
|
|
27
|
-
operation: (id, hash) => gscQueries.inspectionHistory(id, hash),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const records = computed(() => data.value?.records ?? [])
|
|
31
|
-
const url = computed(() => data.value?.url ?? null)
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
response: data as Readonly<Ref<InspectionHistoryResponse | null>>,
|
|
35
|
-
records,
|
|
36
|
-
url,
|
|
37
|
-
loading,
|
|
38
|
-
status,
|
|
39
|
-
error,
|
|
40
|
-
refresh,
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Fetches the URL-inspection entity index for a site, with PASS/NEUTRAL/FAIL
|
|
2
|
-
// counts derived from the records. Split from the previous `useEntity(kind)`
|
|
3
|
-
// discriminated API so each entity stays its own typed hook.
|
|
4
|
-
|
|
5
|
-
import type { InspectionHistoryRecord, InspectionIndex } from '@gscdump/contracts'
|
|
6
|
-
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
|
-
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
-
import { gscQueries } from '../queries/gsc'
|
|
9
|
-
import { useGscResource } from './_useGscResource'
|
|
10
|
-
|
|
11
|
-
export interface UseGscInspectionsReturn {
|
|
12
|
-
index: Readonly<Ref<InspectionIndex | null>>
|
|
13
|
-
records: ComputedRef<InspectionHistoryRecord[]>
|
|
14
|
-
statusCounts: ComputedRef<{ PASS: number, NEUTRAL: number, FAIL: number, unknown: number }>
|
|
15
|
-
loading: ComputedRef<boolean>
|
|
16
|
-
status: Readonly<Ref<GscResourceStatus>>
|
|
17
|
-
error: Readonly<Ref<Error | null>>
|
|
18
|
-
refresh: () => Promise<void>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useGscInspections(siteId: MaybeRefOrGetter<string | null | undefined>): UseGscInspectionsReturn {
|
|
22
|
-
const { data, status, loading, error, refresh } = useGscResource<[string], InspectionIndex>({
|
|
23
|
-
keys: [siteId] as const,
|
|
24
|
-
operation: (id: string) => gscQueries.inspections(id),
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const records = computed<InspectionHistoryRecord[]>(() =>
|
|
28
|
-
data.value ? Object.values(data.value.records) : [],
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
const statusCounts = computed(() => {
|
|
32
|
-
const counts = { PASS: 0, NEUTRAL: 0, FAIL: 0, unknown: 0 }
|
|
33
|
-
for (const r of records.value) {
|
|
34
|
-
const key = r.indexStatus as keyof typeof counts | undefined
|
|
35
|
-
if (key && key in counts)
|
|
36
|
-
counts[key]++
|
|
37
|
-
else
|
|
38
|
-
counts.unknown++
|
|
39
|
-
}
|
|
40
|
-
return counts
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
index: data as Readonly<Ref<InspectionIndex | null>>,
|
|
45
|
-
records,
|
|
46
|
-
statusCounts,
|
|
47
|
-
loading,
|
|
48
|
-
status,
|
|
49
|
-
error,
|
|
50
|
-
refresh,
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Provide/inject seam for analyzer panels. Pipeline panels (those declared
|
|
2
|
-
// with `panel.ownsLifecycle: true`) need access to the analyzer runner +
|
|
3
|
-
// long-running composable instances so they can fire `analyze`/`query` and
|
|
4
|
-
// keep their phase state alive across tab switches.
|
|
5
|
-
//
|
|
6
|
-
// `/analyze` provides these once; panels inject. Type-only — runtime values
|
|
7
|
-
// are the InjectionKey Symbols themselves.
|
|
8
|
-
|
|
9
|
-
import type { AnalysisParams, AnalysisResult } from '@gscdump/engine/analysis-types'
|
|
10
|
-
import type { InjectionKey } from '@vue/runtime-core'
|
|
11
|
-
|
|
12
|
-
export interface GscPanelRunner {
|
|
13
|
-
/** Positional-form SQL runner — `{ rows, queryMs }`. */
|
|
14
|
-
query: (sql: string, params?: readonly unknown[]) => Promise<{ rows: Record<string, unknown>[], queryMs: number }>
|
|
15
|
-
/** Registry-driven analyzer dispatch over the same per-site DuckDB-WASM runtime. */
|
|
16
|
-
analyze: (params: AnalysisParams, opts?: { signal?: AbortSignal }) => Promise<AnalysisResult & { queryMs: number }>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface GscPanelRunnerContext {
|
|
20
|
-
runner: GscPanelRunner
|
|
21
|
-
ready: Ref<boolean>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const gscPanelRunnerKey: InjectionKey<GscPanelRunnerContext> = Symbol('gscPanelRunnerKey')
|
|
25
|
-
|
|
26
|
-
export function useGscPanelRunner(): GscPanelRunnerContext {
|
|
27
|
-
const ctx = inject(gscPanelRunnerKey, null)
|
|
28
|
-
if (!ctx)
|
|
29
|
-
throw new Error('useGscPanelRunner() called outside <GscAnalyzerPanel> context')
|
|
30
|
-
return ctx
|
|
31
|
-
}
|