diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index a4af1ecc..d0381250 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -35,29 +35,6 @@ jobs: with: node-version: '22.18.0' - - name: Generate build-info.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8) - BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - BRANCH="${{ github.ref_name }}" - FULL_SHA="${{ github.sha }}" - TAG=$(grep -m1 '"version":' package.json | cut -d'"' -f4) - - COMMIT_URL="https://github.com/${{ github.repository }}/commit/$SHORT_SHA" - - cat < build.info.json - { - "buildTime": "$BUILD_TIME", - "commitFull": "$FULL_SHA", - "commit": "$SHORT_SHA", - "tag": $( [ "$TAG" = "null" ] && echo null || echo "\"$TAG\"" ), - "branch": "$BRANCH", - "commitUrl": "$COMMIT_URL" - } - EOF - - name: Install dependencies run: | npm ci diff --git a/.github/workflows/release-frontend.yml b/.github/workflows/release-frontend.yml index e4c8fd20..0a536029 100644 --- a/.github/workflows/release-frontend.yml +++ b/.github/workflows/release-frontend.yml @@ -40,33 +40,6 @@ jobs: with: node-version: '22.x' - - name: Generate build-info.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8) - BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - BRANCH="main" - FULL_SHA="${{ github.sha }}" - TAG="null" - - if [[ "${{ github.ref }}" == refs/tags/* ]]; then - TAG="${{ github.ref_name }}" - fi - - COMMIT_URL="https://github.com/${{ github.repository }}/commit/$SHORT_SHA" - - cat < build.info.json - { - "buildTime": "$BUILD_TIME", - "commitFull": "$FULL_SHA", - "commit": "$SHORT_SHA", - "tag": $( [ "$TAG" = "null" ] && echo null || echo "\"$TAG\"" ), - "branch": "$BRANCH", - "commitUrl": "$COMMIT_URL" - } - EOF - - name: Generate changelog id: changelog run: | diff --git a/.gitignore b/.gitignore index 8056a03c..839c5f86 100644 --- a/.gitignore +++ b/.gitignore @@ -137,8 +137,6 @@ fsd-high-level-dependencies.html wip/** wip/ -build.info.json - public/wasm_exec.js public/xray.schema.json public/main.wasm diff --git a/package-lock.json b/package-lock.json index 1f9bec09..55d9586d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@monaco-editor/react": "^4.7.0", "@noble/post-quantum": "^0.5.2", "@paralleldrive/cuid2": "2.2.2", - "@remnawave/backend-contract": "2.5.4", + "@remnawave/backend-contract": "2.5.5", "@remnawave/subscription-page-types": "0.3.3", "@simplewebauthn/browser": "^13.2.2", "@stablelib/base64": "^2.0.1", @@ -2839,9 +2839,9 @@ } }, "node_modules/@remnawave/backend-contract": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@remnawave/backend-contract/-/backend-contract-2.5.4.tgz", - "integrity": "sha512-ON0Ui/9o/ef+SxYBdil8S9yQseMBvfy9UKpXJ+QfU+IqZ5cOVbGOEwtuqR1FHs5Z2kHTZ0qY9RXEoPxp7cJyGw==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@remnawave/backend-contract/-/backend-contract-2.5.5.tgz", + "integrity": "sha512-6CL7WOsY+T7YuPH7w2aRirhhxTvbRUQFOZUxVulGbspOmzcMzSPiG1NRLdkJ5FVBnx4EpprQsydoE45OLuAJ2A==", "license": "AGPL-3.0-only", "dependencies": { "zod": "3.25.76" diff --git a/package.json b/package.json index 2f29b068..819793fa 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@monaco-editor/react": "^4.7.0", "@noble/post-quantum": "^0.5.2", "@paralleldrive/cuid2": "2.2.2", - "@remnawave/backend-contract": "2.5.4", + "@remnawave/backend-contract": "2.5.5", "@remnawave/subscription-page-types": "0.3.3", "@simplewebauthn/browser": "^13.2.2", "@stablelib/base64": "^2.0.1", diff --git a/src/app/layouts/dashboard/main-layout/main.layout.tsx b/src/app/layouts/dashboard/main-layout/main.layout.tsx index 6c40bc15..b7700a96 100644 --- a/src/app/layouts/dashboard/main-layout/main.layout.tsx +++ b/src/app/layouts/dashboard/main-layout/main.layout.tsx @@ -10,6 +10,7 @@ import { SidebarTitleShared } from '@shared/ui/sidebar/sidebar-title' import { SidebarLogoShared } from '@shared/ui/sidebar/sidebar-logo' import { HeaderControls } from '@shared/ui/header-buttons' import { HelpDrawerShared } from '@shared/ui/help-drawer' +import { GameModalShared } from '@shared/ui/sidebar' import { Navigation } from './navbar/navigation.layout' import classes from './Main.module.css' @@ -118,6 +119,7 @@ export function MainLayout() { + {isSocialButton && ( errorHandler(error, 'Get Nodes Metrics') }) + +export const useGetRemnawaveMetadata = createGetQueryHook({ + endpoint: GetMetadataCommand.TSQ_url, + responseSchema: GetMetadataCommand.ResponseSchema, + getQueryKey: () => systemQueryKeys.getRemnawaveMetadata.queryKey, + rQueryParams: { + placeholderData: keepPreviousData, + refetchOnMount: false, + staleTime: sToMs(3_600) + }, + errorHandler: (error) => errorHandler(error, 'Get Remnawave Metadata') +}) diff --git a/src/shared/ui/copyable-code-block/copyable-code-block.module.css b/src/shared/ui/copyable-code-block/copyable-code-block.module.css index f9c3f2f1..f5b6c37a 100644 --- a/src/shared/ui/copyable-code-block/copyable-code-block.module.css +++ b/src/shared/ui/copyable-code-block/copyable-code-block.module.css @@ -14,6 +14,13 @@ transition: border-color 150ms ease; } +.container.small { + gap: 8px; + padding: 8px 12px; + font-size: var(--mantine-font-size-xs); + border-radius: var(--mantine-radius-sm); +} + .container:hover { border-color: var(--mantine-color-dark-4); } @@ -36,6 +43,10 @@ z-index: 1; } +.small .codeWrapper::after { + width: 24px; +} + .code { overflow-x: auto; white-space: nowrap; diff --git a/src/shared/ui/copyable-code-block/copyable-code-block.tsx b/src/shared/ui/copyable-code-block/copyable-code-block.tsx index 43a6b519..f4ec195e 100644 --- a/src/shared/ui/copyable-code-block/copyable-code-block.tsx +++ b/src/shared/ui/copyable-code-block/copyable-code-block.tsx @@ -1,27 +1,37 @@ import { ActionIcon, Box, CopyButton } from '@mantine/core' import { PiCheck, PiCopy } from 'react-icons/pi' +import clsx from 'clsx' import styles from './copyable-code-block.module.css' interface IProps { + size?: 'normal' | 'small' value: string } -export function CopyableCodeBlock({ value }: IProps) { +export function CopyableCodeBlock({ value, size = 'normal' }: IProps) { + const isSmall = size === 'small' + const iconSize = isSmall ? 14 : 18 + return ( {({ copied, copy }) => ( - + {value} - {copied ? : } + {copied ? : } )} diff --git a/src/shared/ui/header-buttons/SkeletonHeaderControl.module.css b/src/shared/ui/header-buttons/SkeletonHeaderControl.module.css new file mode 100644 index 00000000..af34bdd2 --- /dev/null +++ b/src/shared/ui/header-buttons/SkeletonHeaderControl.module.css @@ -0,0 +1,34 @@ +.skeleton { + position: relative; + overflow: hidden; + border: 1px solid; + border-color: var(--mantine-color-dark-4); + border-radius: var(--mantine-radius-lg); + background: linear-gradient( + 135deg, + var(--mantine-color-dark-6) 0%, + var(--mantine-color-dark-7) 100% + ); +} + +.skeleton::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.06) 50%, + transparent 100% + ); + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} diff --git a/src/shared/ui/header-buttons/SkeletonHeaderControl.tsx b/src/shared/ui/header-buttons/SkeletonHeaderControl.tsx new file mode 100644 index 00000000..a8bdda8f --- /dev/null +++ b/src/shared/ui/header-buttons/SkeletonHeaderControl.tsx @@ -0,0 +1,11 @@ +import { Box } from '@mantine/core' + +import classes from './SkeletonHeaderControl.module.css' + +interface SkeletonHeaderControlProps { + width?: number | string +} + +export function SkeletonHeaderControl({ width = 44 }: SkeletonHeaderControlProps) { + return +} diff --git a/src/shared/ui/header-buttons/VersionControl.module.css b/src/shared/ui/header-buttons/VersionControl.module.css index 2c513095..03847603 100644 --- a/src/shared/ui/header-buttons/VersionControl.module.css +++ b/src/shared/ui/header-buttons/VersionControl.module.css @@ -15,11 +15,18 @@ .version { cursor: pointer; + min-width: 85px; &.newVersion { @mixin dark { animation: glowPulse 3s ease-in-out infinite; border-color: var(--mantine-color-cyan-5); + color: var(--mantine-color-cyan-5); } } + + &.dev { + border-color: var(--mantine-color-red-4); + color: var(--mantine-color-red-4); + } } diff --git a/src/shared/ui/header-buttons/VersionControl.tsx b/src/shared/ui/header-buttons/VersionControl.tsx index d0900793..87e6e63a 100644 --- a/src/shared/ui/header-buttons/VersionControl.tsx +++ b/src/shared/ui/header-buttons/VersionControl.tsx @@ -1,62 +1,71 @@ import { Group, Text } from '@mantine/core' -import { useMemo, useState } from 'react' +import { modals } from '@mantine/modals' +import { useMemo } from 'react' import semver from 'semver' import clsx from 'clsx' -import { getBuildInfo } from '@shared/utils/get-build-info/get-build-info.util' import { useRemnawaveInfo } from '@entities/dashboard/updates-store' +import { useGetRemnawaveMetadata } from '@shared/api/hooks' +import { BaseOverlayHeader } from '../overlays/base-overlay-header' +import { SkeletonHeaderControl } from './SkeletonHeaderControl' import { BuildInfoModal } from '../sidebar/build-info-modal' -import { GameModalShared } from '../sidebar/game-modal' -import packageJson from '../../../../package.json' import classes from './VersionControl.module.css' import { HeaderControl } from './HeaderControl' import { Logo } from '../logo' export function VersionControl() { - const [buildInfoModalOpened, setBuildInfoModalOpened] = useState(false) - - const buildInfo = useMemo(() => getBuildInfo(), []) - const isDev = buildInfo.branch === 'dev' - const remnawaveInfo = useRemnawaveInfo() + const { data: remnawaveMetadata, isLoading } = useGetRemnawaveMetadata() - const isNewVersionAvailable = useMemo(() => { - const currentVersion = buildInfo.tag ?? '0.0.0' + const [isNewVersionAvailable, isDev] = useMemo(() => { + if (!remnawaveMetadata) return [false, false] + + const currentVersion = remnawaveMetadata.version const latest = remnawaveInfo.latestVersion || '0.0.0' - return semver.gt(latest, currentVersion) - }, [remnawaveInfo.latestVersion, buildInfo.tag]) + return [semver.gt(latest, currentVersion), remnawaveMetadata.git.backend.branch === 'dev'] + }, [remnawaveInfo.latestVersion, remnawaveMetadata]) + + if (isLoading || !remnawaveMetadata) { + return + } + + const handleClick = () => { + modals.open({ + title: ( + + ), + centered: true, + size: 'md', + withCloseButton: true, + children: ( + + ) + }) + } return ( - <> - setBuildInfoModalOpened(true)} - w="auto" - > - - - - {isDev ? 'dev' : packageJson.version} - - - - - setBuildInfoModalOpened(false)} - opened={buildInfoModalOpened} - /> - - - + + + + + {remnawaveMetadata.version} + + + ) } diff --git a/src/shared/ui/header-buttons/index.ts b/src/shared/ui/header-buttons/index.ts index 9018b702..06c1fd29 100644 --- a/src/shared/ui/header-buttons/index.ts +++ b/src/shared/ui/header-buttons/index.ts @@ -4,6 +4,7 @@ export { HeaderControls } from './HeaderControls' export { LanguageControl } from './LanguageControl' export { LogoutControl } from './LogoutControl' export { RefreshControl } from './RefreshControl' +export { SkeletonHeaderControl } from './SkeletonHeaderControl' export { SupportControl } from './SupportControl' export { TelegramControl } from './TelegramControl' export { VersionControl } from './VersionControl' diff --git a/src/shared/ui/sidebar/build-info-modal.module.css b/src/shared/ui/sidebar/build-info-modal.module.css new file mode 100644 index 00000000..077d743b --- /dev/null +++ b/src/shared/ui/sidebar/build-info-modal.module.css @@ -0,0 +1,57 @@ +.updateCard { + background: rgba(45, 212, 191, 0.08); + border: 1px solid rgba(45, 212, 191, 0.2); +} + +.updateIconBox { + padding: 10px; + border-radius: var(--mantine-radius-md); + background: rgba(45, 212, 191, 0.15); + display: flex; + align-items: center; + justify-content: center; +} + +.updateTextWrapper { + flex: 1; +} + +.mainCard { + background: linear-gradient( + 135deg, + var(--mantine-color-dark-6) 0%, + var(--mantine-color-dark-7) 100% + ); + border: 1px solid var(--mantine-color-dark-4); +} + +.divider { + opacity: 0.3; +} + +.buildTimeCard { + background: rgba(99, 102, 241, 0.08); + border: 1px solid rgba(99, 102, 241, 0.2); +} + +.buildNumberCard { + background: rgba(139, 92, 246, 0.08); + border: 1px solid rgba(139, 92, 246, 0.2); +} + +.backendCard { + background: rgba(45, 212, 191, 0.06); + border: 1px solid rgba(45, 212, 191, 0.15); +} + +.frontendCard { + background: rgba(6, 182, 212, 0.06); + border: 1px solid rgba(6, 182, 212, 0.15); +} + +.commitSha { + background: rgba(0, 0, 0, 0.2); + padding: 4px 8px; + border-radius: var(--mantine-radius-sm); + flex: 1; +} diff --git a/src/shared/ui/sidebar/build-info-modal.tsx b/src/shared/ui/sidebar/build-info-modal.tsx index 63f3f017..d5a9211a 100644 --- a/src/shared/ui/sidebar/build-info-modal.tsx +++ b/src/shared/ui/sidebar/build-info-modal.tsx @@ -1,20 +1,16 @@ import { + ActionIcon, Badge, Box, Button, - Card, - Code, + CopyButton, Divider, - Flex, Group, - Modal, Paper, + SimpleGrid, Stack, Text, - ThemeIcon, - Title, - Tooltip, - useMantineTheme + Tooltip } from '@mantine/core' import { TbBrandGithub, @@ -23,216 +19,223 @@ import { TbCheck, TbCopy, TbGitBranch, - TbHash + TbHash, + TbServer, + TbWorld } from 'react-icons/tb' -import { useClipboard } from '@mantine/hooks' +import { GetMetadataCommand } from '@remnawave/backend-contract' -import { IBuildInfo } from '@shared/utils/get-build-info/interfaces/build-info.interface' +import { formatTimeUtil } from '@shared/utils/time-utils' +import { CopyableCodeBlock } from '../copyable-code-block' +import classes from './build-info-modal.module.css' import { Logo } from '../logo' interface BuildInfoModalProps { - buildInfo: IBuildInfo isNewVersionAvailable: boolean - onClose: () => void - opened: boolean + remnawaveMetadata: GetMetadataCommand.Response['response'] } -export function BuildInfoModal({ - opened, - onClose, - buildInfo, - isNewVersionAvailable -}: BuildInfoModalProps) { - const buildDate = new Date(buildInfo.buildTime).toLocaleString() - const clipboard = useClipboard({ timeout: 1000 }) - const theme = useMantineTheme() - - const copyBuildInfo = () => { - clipboard.copy(JSON.stringify(buildInfo, null, 2)) - } - +export function BuildInfoModal({ remnawaveMetadata, isNewVersionAvailable }: BuildInfoModalProps) { return ( - - - Build Info - - - - - - } - withCloseButton - > - - {isNewVersionAvailable && ( - - - - - - - + + {isNewVersionAvailable && ( + + + + + + + + Update available - - A new version is available. + + A new version is available - - - )} - - - - } + ml="auto" + radius="md" + size="xs" + target="_blank" + variant="light" + > + Check out + + + + )} + + + + + + } + size="lg" variant="light" > - - - - + {remnawaveMetadata.version} + + + } + size="lg" + variant="light" + > + {remnawaveMetadata.git.backend.branch} + + + + {({ copied, copy }) => ( + + + {copied ? : } + + + )} + + + + + + + + + + Build Time - - {buildDate} + + + {formatTimeUtil( + remnawaveMetadata.build.time, + 'DD.MM.YYYY HH:mm:ss' + )} + + + + + + + + Build - + + + {remnawaveMetadata.build.number} + + + + + + + + + + + + + + Backend + + + + + + + - - - - - - - - - Branch - - - - {buildInfo.branch} - - {buildInfo.tag && ( - - {buildInfo.tag} - - )} - - - - - - - - - - - - - Commit - - {buildInfo.commit} - - + - + - - + + + + + + + Frontend + + + + + + + + - - - - + + + + + + + + + + ) } diff --git a/src/shared/utils/get-build-info/get-build-info.util.ts b/src/shared/utils/get-build-info/get-build-info.util.ts deleted file mode 100644 index fc1d7f36..00000000 --- a/src/shared/utils/get-build-info/get-build-info.util.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IBuildInfo } from './interfaces/build-info.interface' -import buildInfo from '../../../../build.info.json' - -export function getBuildInfo(): IBuildInfo { - return buildInfo -} diff --git a/src/shared/utils/get-build-info/interfaces/build-info.interface.ts b/src/shared/utils/get-build-info/interfaces/build-info.interface.ts deleted file mode 100644 index 0a87380e..00000000 --- a/src/shared/utils/get-build-info/interfaces/build-info.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface IBuildInfo { - branch: string - buildTime: string - commit: string - commitFull: string - commitUrl: string - tag: null | string -} diff --git a/src/shared/utils/time-utils/format-time.util.ts b/src/shared/utils/time-utils/format-time.util.ts index 527dd021..a30f74ce 100644 --- a/src/shared/utils/time-utils/format-time.util.ts +++ b/src/shared/utils/time-utils/format-time.util.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs' -type TTemplatePreset = 'D MMM' | 'D MMMM YYYY' +type TTemplatePreset = 'DD.MM.YYYY HH:mm:ss' | 'D MMM' | 'D MMMM YYYY' export const formatTimeUtil = ( time: null | number | string | undefined,