feat(web): show build info and theme menu [CI]
This commit is contained in:
1 parent
98a5e67f82
commit
89cd92f7ee
10 files changed
+80
-3
No files matched your search
@@ -41,9 +41,11 @@ steps:
|
|||||||
- printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY"
|
- 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
|
- trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT
|
||||||
- sha="${CI_COMMIT_SHA:0:7}"
|
- sha="${CI_COMMIT_SHA:0:7}"
|
||||||
|
- build_tag="dev-$sha"
|
||||||
- image="$ZOT_REGISTRY/justme/webdrop-api:dev-$sha"
|
- image="$ZOT_REGISTRY/justme/webdrop-api:dev-$sha"
|
||||||
- dev_image="$ZOT_REGISTRY/justme/webdrop-api:dev"
|
- 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 tag "$image" "$dev_image"
|
||||||
- buildah push "$image" "docker://$image"
|
- buildah push "$image" "docker://$image"
|
||||||
- buildah push "$dev_image" "docker://$dev_image"
|
- buildah push "$dev_image" "docker://$dev_image"
|
||||||
@@ -56,7 +56,9 @@ steps:
|
|||||||
- trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT
|
- trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT
|
||||||
- image="$ZOT_REGISTRY/justme/webdrop-api:$release_tag"
|
- image="$ZOT_REGISTRY/justme/webdrop-api:$release_tag"
|
||||||
- latest_image="$ZOT_REGISTRY/justme/webdrop-api:latest"
|
- 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 tag "$image" "$latest_image"
|
||||||
- buildah push "$image" "docker://$image"
|
- buildah push "$image" "docker://$image"
|
||||||
- buildah push "$latest_image" "docker://$latest_image"
|
- buildah push "$latest_image" "docker://$latest_image"
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
**依赖策略**:新增运行时依赖需要书面理由(解决什么、为何不能自写、体积)。不引入 UI 组件库、动画库、图标库、日期库。依赖越少,越容易审计;前端是整个方案的信任根。
|
**依赖策略**:新增运行时依赖需要书面理由(解决什么、为何不能自写、体积)。不引入 UI 组件库、动画库、图标库、日期库。依赖越少,越容易审计;前端是整个方案的信任根。
|
||||||
|
|
||||||
|
生产构建将 `web/package.json` 的版本和构建时注入的 commit、tag、UTC 时间编译进前端,首页右下角展示。部署构建通过 `WEBDROP_BUILD_COMMIT`、`WEBDROP_BUILD_TAG`、`WEBDROP_BUILD_TIME` 提供元数据;本地构建没有 CI 环境时尝试读取 Git,时间取本次构建时间。
|
||||||
|
|
||||||
## 2. 目录与分层
|
## 2. 目录与分层
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ WebDrop 是**暂存区**,不是聊天室:内容按类型与待办动作组织,
|
|||||||
|
|
||||||
首页把「临时分享」与「设备频道」作为两个并列入口:前者用于发一段临时文本或文件,后者用于多设备长期同步。
|
首页把「临时分享」与「设备频道」作为两个并列入口:前者用于发一段临时文本或文件,后者用于多设备长期同步。
|
||||||
|
|
||||||
|
主页右上角提供外观菜单,可跟随系统或切换亮色/暗色。右下角显示构建来源:前端包版本、构建 tag、commit 与 UTC 构建时间;这些值来自构建环境,无法取得时如实显示「未标记」或「未知」。
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────┐
|
┌──────────────────────────────────┐
|
||||||
│ WebDrop │
|
│ WebDrop │
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ COPY packages/core/package.json ./packages/core/package.json
|
|||||||
RUN cd web && npm ci --no-audit --no-fund
|
RUN cd web && npm ci --no-audit --no-fund
|
||||||
COPY packages/core/ ./packages/core/
|
COPY packages/core/ ./packages/core/
|
||||||
COPY web/ ./web/
|
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
|
RUN npm run build --prefix web
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-aot AS build
|
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-aot AS build
|
||||||
|
|||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
declare const __WEBDROP_BUILD_INFO__: {
|
||||||
|
readonly frontendVersion: string;
|
||||||
|
readonly commit: string;
|
||||||
|
readonly tag: string;
|
||||||
|
readonly builtAt: string;
|
||||||
|
};
|
||||||
@@ -6,13 +6,38 @@
|
|||||||
animation: page-in 0.21s ease-out;
|
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 {
|
.main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
max-width: 860px;
|
max-width: 860px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: var(--space-6) var(--space-4);
|
padding: var(--space-6) var(--space-4) calc(var(--space-6) + 32px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { lazy, Suspense, useCallback, useState } from 'react';
|
import { lazy, Suspense, useCallback, useState } from 'react';
|
||||||
import { channelPath, navigate, sharePath } from '@/app/navigation';
|
import { channelPath, navigate, sharePath } from '@/app/navigation';
|
||||||
|
import { buildInfo } from '@/lib/buildInfo';
|
||||||
import type { ChannelInfo, Share } from '@/core-bridge/types';
|
import type { ChannelInfo, Share } from '@/core-bridge/types';
|
||||||
import type { ClipboardPayload } from '@/lib/clipboard';
|
import type { ClipboardPayload } from '@/lib/clipboard';
|
||||||
import { useGlobalPaste } from '@/lib/useGlobalPaste';
|
import { useGlobalPaste } from '@/lib/useGlobalPaste';
|
||||||
@@ -8,6 +9,7 @@ import { useShares } from '@/store/shares';
|
|||||||
import { solveCreationCaptcha } from '@/core-bridge/captcha';
|
import { solveCreationCaptcha } from '@/core-bridge/captcha';
|
||||||
import { errorMessage } from '@/core-bridge/errors';
|
import { errorMessage } from '@/core-bridge/errors';
|
||||||
import { Button } from '@/ui/Button';
|
import { Button } from '@/ui/Button';
|
||||||
|
import { ThemeMenu } from '@/ui/ThemeMenu';
|
||||||
import { ChannelList } from './ChannelList';
|
import { ChannelList } from './ChannelList';
|
||||||
import { CreateSheet } from './CreateSheet';
|
import { CreateSheet } from './CreateSheet';
|
||||||
import { QuickShare } from './QuickShare';
|
import { QuickShare } from './QuickShare';
|
||||||
@@ -58,6 +60,7 @@ export function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
|
<div className={styles.appearance}><ThemeMenu /></div>
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<header className={styles.head}>
|
<header className={styles.head}>
|
||||||
<h1 className={styles.logo}>WebDrop</h1>
|
<h1 className={styles.logo}>WebDrop</h1>
|
||||||
@@ -95,6 +98,13 @@ export function Home() {
|
|||||||
) : null}
|
) : null}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<aside className={styles.buildInfo} aria-label="构建信息">
|
||||||
|
<span>前端 v{buildInfo.frontendVersion}</span>
|
||||||
|
<span>tag {buildInfo.tag && buildInfo.tag !== 'unknown' ? buildInfo.tag : '未标记'}</span>
|
||||||
|
<span>commit {buildInfo.commit}</span>
|
||||||
|
<time dateTime={buildInfo.builtAt}>{buildInfo.builtAt} UTC</time>
|
||||||
|
</aside>
|
||||||
|
|
||||||
{createOpen ? <CreateSheet onClose={() => setCreateOpen(false)} onCreated={open} /> : null}
|
{createOpen ? <CreateSheet onClose={() => setCreateOpen(false)} onCreated={open} /> : null}
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
{scanOpen ? <ScanSheet onClose={() => setScanOpen(false)} onChannel={open} onShare={open} /> : null}
|
{scanOpen ? <ScanSheet onClose={() => setScanOpen(false)} onChannel={open} onShare={open} /> : null}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const buildInfo = __WEBDROP_BUILD_INFO__;
|
||||||
@@ -1,9 +1,30 @@
|
|||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
const webRoot = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
const webPackage = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) as { version: string };
|
||||||
|
|
||||||
|
function gitValue(...args: string[]): string {
|
||||||
|
try {
|
||||||
|
return execFileSync('git', args, { cwd: webRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || 'unknown';
|
||||||
|
} catch {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildInfo = {
|
||||||
|
frontendVersion: webPackage.version,
|
||||||
|
commit: process.env.WEBDROP_BUILD_COMMIT ?? gitValue('rev-parse', '--short=12', 'HEAD'),
|
||||||
|
tag: process.env.WEBDROP_BUILD_TAG ?? gitValue('describe', '--tags', '--exact-match', 'HEAD'),
|
||||||
|
builtAt: process.env.WEBDROP_BUILD_TIME ?? new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
define: { __WEBDROP_BUILD_INFO__: JSON.stringify(buildInfo) },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
|||||||
Reference in new issue
Block a user