버전명 표시
This commit is contained in:
@@ -10,7 +10,9 @@ RUN npm ci
|
||||
# Docker 환경에서 /api 는 nginx 가 backend:8080 으로 프록시하므로 그대로 사용
|
||||
COPY . .
|
||||
ARG VITE_API_BASE_URL=/api
|
||||
ARG VITE_APP_VERSION=
|
||||
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
|
||||
ENV VITE_APP_VERSION=${VITE_APP_VERSION}
|
||||
RUN npm run build
|
||||
|
||||
# ── 2단계: 서비스 ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -206,6 +206,14 @@ html.theme-blue {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tmb-logo-version {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text3);
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
html.desktop-client .tmb-logo-text {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
@@ -213,10 +221,6 @@ html.desktop-client .tmb-logo-text {
|
||||
|
||||
html.desktop-client .tmb-logo-version {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text3);
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 구분선 */
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { AuthSession } from '../utils/auth';
|
||||
import type { TradeAlertPopupLayout, TradeAlertPopupPosition } from '../utils/tradeAlertPopupLayout';
|
||||
import { canAccessMenu } from '../utils/permissions';
|
||||
import { isDesktop } from '../utils/platform';
|
||||
import { useDesktopAppTitle } from '../hooks/useDesktopAppTitle';
|
||||
import { useAppTitleBarVersion } from '../hooks/useDesktopAppTitle';
|
||||
import DesktopUpdateModal from './DesktopUpdateModal';
|
||||
import TopMenuBarNavGroups from './TopMenuBarNavGroups';
|
||||
|
||||
@@ -298,7 +298,7 @@ export const TopMenuBar = memo(function TopMenuBar({
|
||||
const [isFullscreen, setIsFullscreen] = useState(() => !!document.fullscreenElement);
|
||||
const [desktopUpdateOpen, setDesktopUpdateOpen] = useState(false);
|
||||
const desktopClient = isDesktop();
|
||||
const desktopAppVersion = useDesktopAppTitle();
|
||||
const appVersion = useAppTitleBarVersion();
|
||||
|
||||
useEffect(() => {
|
||||
const sync = () => setIsFullscreen(!!document.fullscreenElement);
|
||||
@@ -320,8 +320,8 @@ export const TopMenuBar = memo(function TopMenuBar({
|
||||
<polyline points="3,14 7,9 10,12 14,6 17,6" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
||||
</svg>
|
||||
<span className="tmb-logo-text">GoldenChart</span>
|
||||
{desktopClient && desktopAppVersion ? (
|
||||
<span className="tmb-logo-version">(v{desktopAppVersion})</span>
|
||||
{appVersion ? (
|
||||
<span className="tmb-logo-version">(v{appVersion})</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { fetchPublishedAppVersion, readBakedAppVersion } from '../utils/appVersion';
|
||||
import { isDesktop } from '../utils/platform';
|
||||
import { getDesktopAppVersion, syncDesktopMainWindowTitle } from '../utils/desktopBridge';
|
||||
|
||||
/** Desktop — Tauri getVersion() 기준 (설치된 앱 버전). baked 0.1.4 등 git 고정값 사용 안 함 */
|
||||
export function useDesktopAppTitle(): string | null {
|
||||
const [version, setVersion] = useState<string | null>(null);
|
||||
/** 타이틀바 GoldenChart 옆 (vX.Y.Z) — Desktop: Tauri, Web: latest.json */
|
||||
export function useAppTitleBarVersion(): string | null {
|
||||
const desktop = isDesktop();
|
||||
const [version, setVersion] = useState<string | null>(() => readBakedAppVersion());
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktop()) return;
|
||||
let cancelled = false;
|
||||
|
||||
if (desktop) {
|
||||
void (async () => {
|
||||
const v = await getDesktopAppVersion();
|
||||
if (cancelled || !v?.trim()) return;
|
||||
if (cancelled) return;
|
||||
if (v?.trim()) {
|
||||
setVersion(v.trim());
|
||||
await syncDesktopMainWindowTitle();
|
||||
return;
|
||||
}
|
||||
setVersion(readBakedAppVersion());
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
const published = await fetchPublishedAppVersion();
|
||||
if (cancelled) return;
|
||||
setVersion(published ?? readBakedAppVersion());
|
||||
})();
|
||||
|
||||
return () => { cancelled = true; };
|
||||
}, [desktop]);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/** @deprecated useAppTitleBarVersion 사용 */
|
||||
export function useDesktopAppTitle(): string | null {
|
||||
return useAppTitleBarVersion();
|
||||
}
|
||||
|
||||
export function formatDesktopAppLabel(version: string | null | undefined): string {
|
||||
if (!version?.trim()) return 'GoldenChart';
|
||||
return `GoldenChart (v${version.trim()})`;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { readBakedDesktopAppVersion } from './desktopAppVersion';
|
||||
|
||||
const LATEST_JSON_PATH = '/desktop/updates/latest.json';
|
||||
|
||||
/** Vite 빌드·desktop 번들에 주입된 앱 버전 */
|
||||
export function readBakedAppVersion(): string | null {
|
||||
const fromVite = import.meta.env.VITE_APP_VERSION?.trim();
|
||||
if (fromVite) return fromVite;
|
||||
return readBakedDesktopAppVersion();
|
||||
}
|
||||
|
||||
/** 서버에 배포된 desktop latest.json — 웹 타이틀바 런타임 버전 */
|
||||
export async function fetchPublishedAppVersion(): Promise<string | null> {
|
||||
try {
|
||||
const res = await fetch(LATEST_JSON_PATH, { cache: 'no-store' });
|
||||
if (!res.ok) return null;
|
||||
const data = (await res.json()) as { version?: string };
|
||||
const v = data.version?.trim();
|
||||
return v || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -1 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_VERSION?: string;
|
||||
readonly VITE_API_BASE_URL?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
import { defineConfig, createLogger } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
|
||||
function readAppVersionForBuild(): string {
|
||||
const fromEnv =
|
||||
process.env.VITE_APP_VERSION?.trim()
|
||||
|| process.env.FRONTEND_APP_VERSION?.trim()
|
||||
|| process.env.DESKTOP_APP_VERSION?.trim();
|
||||
if (fromEnv) return fromEnv;
|
||||
|
||||
try {
|
||||
const latestPath = path.resolve(__dirname, 'public/desktop/updates/latest.json');
|
||||
const latest = JSON.parse(readFileSync(latestPath, 'utf8')) as { version?: string };
|
||||
const fromLatest = latest.version?.trim();
|
||||
if (fromLatest) return fromLatest;
|
||||
} catch {
|
||||
/* optional */
|
||||
}
|
||||
|
||||
try {
|
||||
const confPath = path.resolve(repoRoot, 'desktop/src-tauri/tauri.conf.json');
|
||||
const conf = JSON.parse(readFileSync(confPath, 'utf8')) as { version?: string };
|
||||
return String(conf.version ?? '').trim();
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const appVersion = readAppVersionForBuild();
|
||||
|
||||
// WebSocket proxy 재연결/종료 시 발생하는 ECONNRESET·EPIPE 는 정상 TCP 이벤트이므로 로그 억제
|
||||
const SUPPRESS = ['ECONNRESET', 'EPIPE', 'ws proxy socket error'];
|
||||
@@ -13,6 +46,9 @@ logger.error = (msg, opts) => {
|
||||
|
||||
export default defineConfig({
|
||||
customLogger: logger,
|
||||
define: {
|
||||
'import.meta.env.VITE_APP_VERSION': JSON.stringify(appVersion),
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
visualizer({ filename: 'dist/stats.html', open: false, gzipSize: true, brotliSize: true }),
|
||||
|
||||
Reference in New Issue
Block a user