feat:移除了弹窗,服务器添加sls

This commit is contained in:
2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 changed files with 1322534 additions and 0 deletions

View File

@ -0,0 +1,159 @@
<script setup lang="ts">
import {
Activity,
CalendarDays,
Clock3,
Database,
PackagePlus,
Users,
} from 'lucide-vue-next'
import AppState from '~/components/AppState.vue'
import DistributionChart from '~/components/charts/DistributionChart.vue'
import TrendChart from '~/components/charts/TrendChart.vue'
import MetricCard from '~/components/MetricCard.vue'
import PageHeader from '~/components/PageHeader.vue'
import Alert from '~/components/ui/Alert.vue'
import Badge from '~/components/ui/Badge.vue'
import Card from '~/components/ui/Card.vue'
import Skeleton from '~/components/ui/Skeleton.vue'
import type { ActivityDto, DistributionsDto, OverviewDto } from '~/shared/types/telemetry'
import { formatUtcTimestamp } from '~/utils/format'
const range = useDashboardRange()
const refreshEpoch = useDashboardRefresh()
const requestFetch = useRequestFetch()
const { data, status, error, refresh } = await useAsyncData(
'overview-data',
async () => {
const query = { range: range.value }
const [overview, activity, distributions] = await Promise.all([
requestFetch<OverviewDto>('/api/admin/overview', { query }),
requestFetch<ActivityDto>('/api/admin/activity', { query }),
requestFetch<DistributionsDto>('/api/admin/distributions', { query }),
])
return { overview, activity, distributions }
},
{ watch: [range, refreshEpoch] },
)
const metricDefinitions = [
{ key: 'totalInstallations', title: '累计同意遥测安装', icon: Database, tone: 'green' },
{ key: 'dau', title: '日活跃安装DAU', icon: Activity, tone: 'cyan' },
{ key: 'wau', title: '周活跃安装WAU', icon: CalendarDays, tone: 'cyan' },
{ key: 'mau', title: '月活跃安装MAU', icon: Users, tone: 'cyan' },
{ key: 'newInstallationsToday', title: '今日新增安装', icon: PackagePlus, tone: 'green' },
] as const
const trendCharts = [
{
title: '每日活跃安装趋势',
description: '按 UTC 自然日统计的唯一活跃安装。',
series: [
{
key: 'activeInstallations' as const,
label: '活跃安装',
color: 'var(--chart-green)',
},
],
},
{
title: '每日新增安装趋势',
description: '按 UTC 自然日首次出现的同意遥测安装。',
series: [{ key: 'newInstallations' as const, label: '新增安装', color: 'var(--chart-cyan)' }],
},
]
const distributionCharts = computed(() => [
{
title: '启动器版本',
description: '按上报版本统计的唯一活跃安装。',
items: data.value?.distributions.versions ?? [],
color: 'var(--chart-green)',
},
{
title: '操作系统',
description: '按上报平台统计的唯一活跃安装。',
items: data.value?.distributions.platforms ?? [],
color: 'var(--chart-gold)',
},
{
title: 'CPU 架构',
description: '按上报架构统计的唯一活跃安装。',
items: data.value?.distributions.architectures ?? [],
color: 'var(--chart-cyan)',
},
])
</script>
<template>
<div>
<PageHeader
title="数据总览"
description="查看匿名启动器遥测的活跃度、增长和运行环境分布。"
>
<Badge v-if="data" variant="secondary" class="gap-1.5 px-2.5 py-1">
<Clock3 class="size-3.5" />更新于 {{ formatUtcTimestamp(data.overview.generatedAt) }}
</Badge>
</PageHeader>
<Alert title="统计口径" class="mb-5">
所有统计仅包含主动同意遥测的匿名安装时间均按 UTC 计算
</Alert>
<div v-if="status === 'pending' && !data" data-state="loading">
<div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
<Skeleton v-for="index in 5" :key="index" class="h-32" />
</div>
<div class="mt-5 grid gap-4 xl:grid-cols-2">
<Skeleton v-for="index in 4" :key="index" class="h-80" />
</div>
</div>
<AppState v-else-if="error || !data" compact kind="error" @retry="refresh" />
<template v-else>
<div
class="grid grid-cols-2 gap-3 lg:grid-cols-4"
:class="status === 'pending' && 'opacity-65'"
:aria-busy="status === 'pending'"
>
<MetricCard
v-for="metric in metricDefinitions"
:key="metric.key"
:title="metric.title"
:value="data.overview.metrics[metric.key].value"
:detail="data.overview.metrics[metric.key].label"
:icon="metric.icon"
:tone="metric.tone"
/>
</div>
<section class="mt-6">
<div class="mb-3">
<h2 class="text-sm font-semibold">使用者趋势</h2>
<p class="mt-0.5 text-xs text-muted-foreground">当前范围 {{ range }}截止今日 UTC</p>
</div>
<div class="grid gap-4 xl:grid-cols-2">
<Card v-for="chart in trendCharts" :key="chart.title" class="min-w-0 p-4">
<h3 class="text-sm font-semibold">{{ chart.title }}</h3>
<p class="mt-1 text-xs text-muted-foreground">{{ chart.description }}</p>
<TrendChart class="mt-3" :data="data.activity.points" :series="chart.series" />
</Card>
</div>
</section>
<section class="mt-6">
<div class="mb-3">
<h2 class="text-sm font-semibold">运行环境分布</h2>
<p class="mt-0.5 text-xs text-muted-foreground">所选时间范围内的唯一活跃安装</p>
</div>
<div class="grid gap-4 lg:grid-cols-3">
<Card v-for="chart in distributionCharts" :key="chart.title" class="min-w-0 p-4">
<h3 class="text-sm font-semibold">{{ chart.title }}</h3>
<p class="mt-1 text-xs text-muted-foreground">{{ chart.description }}</p>
<DistributionChart class="mt-4" :items="chart.items" :color="chart.color" />
</Card>
</div>
</section>
</template>
</div>
</template>

View File

@ -0,0 +1,52 @@
<script setup lang="ts">
import { Database, ShieldCheck } from 'lucide-vue-next'
import AppState from '~/components/AppState.vue'
import PageHeader from '~/components/PageHeader.vue'
import StatusRow from '~/components/system/StatusRow.vue'
import Card from '~/components/ui/Card.vue'
import Skeleton from '~/components/ui/Skeleton.vue'
import type { SystemDto } from '~/shared/types/telemetry'
import { formatUtcTimestamp } from '~/utils/format'
const refreshEpoch = useDashboardRefresh()
const requestFetch = useRequestFetch()
const { data, status, error, refresh } = await useAsyncData(
'system-data',
() => requestFetch<SystemDto>('/api/admin/system'),
{ watch: [refreshEpoch] },
)
</script>
<template>
<div>
<PageHeader title="系统状态" description="检查采集服务、聚合任务和数据状态。" />
<div v-if="status === 'pending' && !data" class="grid gap-4 lg:grid-cols-2" data-state="loading">
<Skeleton v-for="index in 3" :key="index" class="h-48" />
</div>
<AppState v-else-if="error || !data" compact kind="error" @retry="refresh" />
<template v-else>
<div class="grid gap-4 lg:grid-cols-2" :class="status === 'pending' && 'opacity-65'">
<Card class="overflow-hidden">
<div class="border-b px-4 py-3">
<h2 class="text-sm font-semibold">服务检查</h2>
<p class="mt-0.5 text-xs text-muted-foreground">检查时间 {{ formatUtcTimestamp(data.generatedAt) }}</p>
</div>
<StatusRow name="公开遥测 Worker" :check="data.publicWorker" />
<StatusRow name="D1 只读查询" :check="data.d1" />
<StatusRow name="Cron 聚合任务" :check="data.cron" />
<StatusRow name="Cloudflare 账户用量" :check="data.accountUsage" />
</Card>
<Card class="p-4">
<h2 class="text-sm font-semibold">数据状态</h2>
<p class="mt-1 text-xs text-muted-foreground">每日聚合的最近可用数据</p>
<div class="mt-7 flex items-center justify-between gap-4 text-sm">
<span class="text-muted-foreground">最新数据日期</span>
<span class="flex items-center gap-2 tabular-nums"><Database class="size-4" />{{ data.latestDataDay ? `${data.latestDataDay}UTC` : '暂无' }}</span>
</div>
<div class="mt-7 flex items-center gap-2 border-t pt-4 text-xs text-muted-foreground"><ShieldCheck class="size-4 text-emerald-600" />管理 API 只读访问</div>
</Card>
</div>
</template>
</div>
</template>