diff --git a/.woodpecker/dev-image.yml b/.woodpecker/dev-image.yml index c4e3c9b..aac864e 100644 --- a/.woodpecker/dev-image.yml +++ b/.woodpecker/dev-image.yml @@ -41,9 +41,11 @@ steps: - printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY" - trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT - sha="${CI_COMMIT_SHA:0:7}" + - build_tag="dev-$sha" - image="$ZOT_REGISTRY/justme/webdrop-api:dev-$sha" - dev_image="$ZOT_REGISTRY/justme/webdrop-api:dev" - - buildah bud --layers --network host --format docker --platform linux/amd64 --tag "$image" --file server/Dockerfile . + - build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + - buildah bud --layers --network host --format docker --platform linux/amd64 --build-arg "WEBDROP_BUILD_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_BUILD_TAG=$build_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Dockerfile . - buildah tag "$image" "$dev_image" - buildah push "$image" "docker://$image" - buildah push "$dev_image" "docker://$dev_image" diff --git a/.woodpecker/release-backend.yml b/.woodpecker/release-backend.yml index 82cf4dd..e9e437a 100644 --- a/.woodpecker/release-backend.yml +++ b/.woodpecker/release-backend.yml @@ -56,7 +56,9 @@ steps: - trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT - image="$ZOT_REGISTRY/justme/webdrop-api:$release_tag" - latest_image="$ZOT_REGISTRY/justme/webdrop-api:latest" - - buildah bud --layers --network host --format docker --platform linux/amd64 --tag "$image" --file server/Dockerfile . + - build_commit="$(git rev-parse HEAD)" + - build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + - buildah bud --layers --network host --format docker --platform linux/amd64 --build-arg "WEBDROP_BUILD_COMMIT=$build_commit" --build-arg "WEBDROP_BUILD_TAG=$release_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Dockerfile . - buildah tag "$image" "$latest_image" - buildah push "$image" "docker://$image" - buildah push "$latest_image" "docker://$latest_image" diff --git a/docs/frontend.md b/docs/frontend.md index ec7446e..eb0a1ac 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -20,6 +20,8 @@ **依赖策略**:新增运行时依赖需要书面理由(解决什么、为何不能自写、体积)。不引入 UI 组件库、动画库、图标库、日期库。依赖越少,越容易审计;前端是整个方案的信任根。 +生产构建将 `web/package.json` 的版本和构建时注入的 commit、tag、UTC 时间编译进前端,首页右下角展示。部署构建通过 `WEBDROP_BUILD_COMMIT`、`WEBDROP_BUILD_TAG`、`WEBDROP_BUILD_TIME` 提供元数据;本地构建没有 CI 环境时尝试读取 Git,时间取本次构建时间。 + ## 2. 目录与分层 ``` diff --git a/docs/ui-design.md b/docs/ui-design.md index d71b596..3bff32b 100644 --- a/docs/ui-design.md +++ b/docs/ui-design.md @@ -138,6 +138,8 @@ WebDrop 是**暂存区**,不是聊天室:内容按类型与待办动作组织, 首页把「临时分享」与「设备频道」作为两个并列入口:前者用于发一段临时文本或文件,后者用于多设备长期同步。 +主页右上角提供外观菜单,可跟随系统或切换亮色/暗色。右下角显示构建来源:前端包版本、构建 tag、commit 与 UTC 构建时间;这些值来自构建环境,无法取得时如实显示「未标记」或「未知」。 + ``` ┌──────────────────────────────────┐ │ WebDrop │ diff --git a/server/Dockerfile b/server/Dockerfile index 5ab9373..c91bc7e 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -5,6 +5,12 @@ COPY packages/core/package.json ./packages/core/package.json RUN cd web && npm ci --no-audit --no-fund COPY packages/core/ ./packages/core/ COPY web/ ./web/ +ARG WEBDROP_BUILD_COMMIT=unknown +ARG WEBDROP_BUILD_TAG=unknown +ARG WEBDROP_BUILD_TIME=unknown +ENV WEBDROP_BUILD_COMMIT=$WEBDROP_BUILD_COMMIT \ + WEBDROP_BUILD_TAG=$WEBDROP_BUILD_TAG \ + WEBDROP_BUILD_TIME=$WEBDROP_BUILD_TIME RUN npm run build --prefix web FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-aot AS build diff --git a/web/src/build-info.d.ts b/web/src/build-info.d.ts new file mode 100644 index 0000000..a3e91f3 --- /dev/null +++ b/web/src/build-info.d.ts @@ -0,0 +1,6 @@ +declare const __WEBDROP_BUILD_INFO__: { + readonly frontendVersion: string; + readonly commit: string; + readonly tag: string; + readonly builtAt: string; +}; diff --git a/web/src/features/home/Home.module.css b/web/src/features/home/Home.module.css index 9b7f71c..36843ac 100644 --- a/web/src/features/home/Home.module.css +++ b/web/src/features/home/Home.module.css @@ -6,13 +6,38 @@ animation: page-in 0.21s ease-out; } +.appearance { + position: fixed; + z-index: 30; + top: max(var(--space-4), env(safe-area-inset-top)); + right: max(var(--space-4), env(safe-area-inset-right)); +} + +.buildInfo { + position: fixed; + z-index: 1; + right: max(var(--space-4), env(safe-area-inset-right)); + bottom: max(var(--space-3), env(safe-area-inset-bottom)); + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: var(--space-1) var(--space-3); + max-width: calc(100vw - var(--space-6)); + color: var(--text-dim); + font-family: var(--font-mono); + font-size: 11px; + line-height: 1.5; + text-align: right; + pointer-events: none; +} + .main { display: flex; flex-direction: column; gap: var(--space-4); max-width: 860px; margin: 0 auto; - padding: var(--space-6) var(--space-4); + padding: var(--space-6) var(--space-4) calc(var(--space-6) + 32px); } .head { diff --git a/web/src/features/home/Home.tsx b/web/src/features/home/Home.tsx index 55813eb..4a3c9ab 100644 --- a/web/src/features/home/Home.tsx +++ b/web/src/features/home/Home.tsx @@ -1,5 +1,6 @@ import { lazy, Suspense, useCallback, useState } from 'react'; import { channelPath, navigate, sharePath } from '@/app/navigation'; +import { buildInfo } from '@/lib/buildInfo'; import type { ChannelInfo, Share } from '@/core-bridge/types'; import type { ClipboardPayload } from '@/lib/clipboard'; import { useGlobalPaste } from '@/lib/useGlobalPaste'; @@ -8,6 +9,7 @@ import { useShares } from '@/store/shares'; import { solveCreationCaptcha } from '@/core-bridge/captcha'; import { errorMessage } from '@/core-bridge/errors'; import { Button } from '@/ui/Button'; +import { ThemeMenu } from '@/ui/ThemeMenu'; import { ChannelList } from './ChannelList'; import { CreateSheet } from './CreateSheet'; import { QuickShare } from './QuickShare'; @@ -58,6 +60,7 @@ export function Home() { return (