From 79fa16176f184dcd8576799f75d4dcdfbf7cde59 Mon Sep 17 00:00:00 2001 From: kastov Date: Wed, 9 Apr 2025 13:42:38 +0300 Subject: [PATCH] feat: add version check and alert for updates in MainLayout and BuildInfoModal - Integrated semver to compare current and latest versions. - Added an alert in BuildInfoModal to notify users of available updates. - Updated MainLayout to manage version state and display indicators for new versions. --- .github/workflows/release-frontend.yml | 20 +++- changelog.config.json | 4 + package-lock.json | 14 ++- package.json | 4 +- .../dashboard/main-layout/main.layout.tsx | 94 +++++++++++++++---- .../ui/build-info-modal/build-info-modal.tsx | 47 +++++++++- 6 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 changelog.config.json diff --git a/.github/workflows/release-frontend.yml b/.github/workflows/release-frontend.yml index a6b9bcc1..a970ab3f 100644 --- a/.github/workflows/release-frontend.yml +++ b/.github/workflows/release-frontend.yml @@ -65,6 +65,14 @@ jobs: } EOF + - name: Generate changelog + id: changelog + run: | + CHANGELOG=$(npx changelogen --from=${{ steps.tag.outputs.previousTag }} --to=${{ steps.tag.outputs.latestTag }}) + echo "CHANGELOG<> $GITHUB_ENV + echo "$CHANGELOG" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Install dependencies and build run: | npm install @@ -88,13 +96,17 @@ jobs: prerelease: false name: ${{ github.ref_name }} body: | - 🎉 Automatic release of Remnawave Frontend, v${{ github.ref_name }} + 🌊 Remnawave Frontend v${{ github.ref_name }} - This release was automatically created through GitHub Actions. +

+ + Join community on Telegram + +

- ### 📝 Changes + 📝 Compare changes: [${{ steps.tag.outputs.previousTag }}...${{ steps.tag.outputs.latestTag }}](https://github.com/${{ github.repository }}/compare/${{ steps.tag.outputs.previousTag }}...${{ steps.tag.outputs.latestTag }}) - ✏️ Compare: [${{ steps.tag.outputs.previousTag }}...${{ steps.tag.outputs.latestTag }}](https://github.com/${{ github.repository }}/compare/${{ steps.tag.outputs.previousTag }}...${{ steps.tag.outputs.latestTag }}) + ${{ env.CHANGELOG }} ### 📦 Artifacts - remnawave-frontend.zip - archive with built frontend diff --git a/changelog.config.json b/changelog.config.json new file mode 100644 index 00000000..c39a351e --- /dev/null +++ b/changelog.config.json @@ -0,0 +1,4 @@ +{ + "hideAuthorEmail": true, + "noAuthors": true +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ad217071..a09c4043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@remnawave/frontend", - "version": "1.5.4", + "version": "1.5.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@remnawave/frontend", - "version": "1.5.4", + "version": "1.5.5", "license": "AGPL-3.0-only", "dependencies": { "@gfazioli/mantine-text-animate": "^1.0.2", @@ -59,6 +59,7 @@ "react-imask": "^7.6.1", "react-router-dom": "6.27.0", "recharts": "^2.15.1", + "semver": "^7.7.1", "tiny-invariant": "^1.3.3", "uqr": "^0.1.2", "vite-plugin-deadfile": "^1.4.0", @@ -79,6 +80,7 @@ "@types/node": "^22.13.17", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@types/semver": "^7.7.0", "@typescript-eslint/eslint-plugin": "^8.29.0", "@typescript-eslint/parser": "^8.29.0", "@vitejs/plugin-react": "^4.3.4", @@ -2996,6 +2998,13 @@ "@types/react": "*" } }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -9814,7 +9823,6 @@ "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index d015f829..db20cf43 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@remnawave/frontend", "private": false, "type": "module", - "version": "1.5.4", + "version": "1.5.5", "license": "AGPL-3.0-only", "author": "REMNAWAVE ", "homepage": "https://github.com/remnawave", @@ -80,6 +80,7 @@ "react-imask": "^7.6.1", "react-router-dom": "6.27.0", "recharts": "^2.15.1", + "semver": "^7.7.1", "tiny-invariant": "^1.3.3", "uqr": "^0.1.2", "vite-plugin-deadfile": "^1.4.0", @@ -100,6 +101,7 @@ "@types/node": "^22.13.17", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@types/semver": "^7.7.0", "@typescript-eslint/eslint-plugin": "^8.29.0", "@typescript-eslint/parser": "^8.29.0", "@vitejs/plugin-react": "^4.3.4", diff --git a/src/app/layouts/dashboard/main-layout/main.layout.tsx b/src/app/layouts/dashboard/main-layout/main.layout.tsx index 9067fc66..8d7adc4c 100644 --- a/src/app/layouts/dashboard/main-layout/main.layout.tsx +++ b/src/app/layouts/dashboard/main-layout/main.layout.tsx @@ -1,8 +1,19 @@ -import { AppShell, Badge, Burger, Code, Container, Group, ScrollArea, Text } from '@mantine/core' +import { + AppShell, + Badge, + Burger, + Code, + Container, + Group, + Indicator, + ScrollArea, + Text +} from '@mantine/core' import { useClickOutside, useDisclosure, useMediaQuery } from '@mantine/hooks' import { useQuery } from '@tanstack/react-query' import { useEffect, useState } from 'react' import { Outlet } from 'react-router-dom' +import semver from 'semver' import axios from 'axios' import { getBuildInfo } from '@shared/utils/get-build-info/get-build-info.util' @@ -21,6 +32,15 @@ export function MainLayout() { const [buildInfoModalOpened, setBuildInfoModalOpened] = useState(false) const [isMediaQueryReady, setIsMediaQueryReady] = useState(false) + const [versions, setVersions] = useState<{ + currentVersion: string + latestVersion: string + }>({ + currentVersion: '0.0.0', + latestVersion: '0.0.0' + }) + const [isNewVersionAvailable, setIsNewVersionAvailable] = useState(false) + const buildInfo = getBuildInfo() const isMobile = useMediaQuery(`(max-width: 64rem)`, undefined, { @@ -52,6 +72,31 @@ export function MainLayout() { } }) + const { data: latestVersion } = useQuery({ + queryKey: ['github-latest-version'], + staleTime: sToMs(3600), + refetchInterval: sToMs(3600), + queryFn: async () => { + const response = await axios.get<{ + release: { + tag: string + } + }>('https://ungh.cc/repos/remnawave/panel/releases/latest') + return response.data.release.tag + } + }) + + useEffect(() => { + setVersions({ + currentVersion: buildInfo.tag ?? '0.0.0', + latestVersion: latestVersion ?? '0.0.0' + }) + }, [latestVersion, buildInfo.tag]) + + useEffect(() => { + setIsNewVersionAvailable(semver.gt(versions.latestVersion, versions.currentVersion)) + }, [versions]) + return isMediaQueryReady ? ( {buildInfo.branch === 'dev' && ( - setBuildInfoModalOpened(true)} - radius="sm" - size="lg" - style={{ cursor: 'help', marginLeft: 'auto' }} - variant="light" + - dev - + setBuildInfoModalOpened(true)} + radius="sm" + size="lg" + style={{ cursor: 'help', marginLeft: 'auto' }} + variant="light" + > + dev + + )} {buildInfo.branch !== 'dev' && ( - setBuildInfoModalOpened(true)} - style={{ cursor: 'pointer', marginLeft: 'auto' }} + - {`v${packageJson.version}`} - + setBuildInfoModalOpened(true)} + style={{ cursor: 'pointer', marginLeft: 'auto' }} + > + {`v${packageJson.version}`} + + )} {isSocialButton && ( @@ -175,6 +234,7 @@ export function MainLayout() { setBuildInfoModalOpened(false)} opened={buildInfoModalOpened} /> diff --git a/src/shared/ui/build-info-modal/build-info-modal.tsx b/src/shared/ui/build-info-modal/build-info-modal.tsx index bde85a7a..c9bc1cb5 100644 --- a/src/shared/ui/build-info-modal/build-info-modal.tsx +++ b/src/shared/ui/build-info-modal/build-info-modal.tsx @@ -5,20 +5,39 @@ import { TbCheck as IconCheck, TbCopy as IconCopy, TbGitBranch as IconGitBranch, - TbHash as IconHash + TbHash as IconHash, + TbRipple } from 'react-icons/tb' -import { Badge, Box, Button, Code, Group, Modal, Stack, Text, Title, Tooltip } from '@mantine/core' +import { + Alert, + Badge, + Box, + Button, + Code, + Group, + Modal, + Stack, + Text, + Title, + Tooltip +} from '@mantine/core' import { useClipboard } from '@mantine/hooks' import { IBuildInfo } from '@shared/utils/get-build-info/interfaces/build-info.interface' interface BuildInfoModalProps { buildInfo: IBuildInfo + isNewVersionAvailable: boolean onClose: () => void opened: boolean } -export function BuildInfoModal({ opened, onClose, buildInfo }: BuildInfoModalProps) { +export function BuildInfoModal({ + opened, + onClose, + buildInfo, + isNewVersionAvailable +}: BuildInfoModalProps) { const buildDate = new Date(buildInfo.buildTime).toLocaleString() const clipboard = useClipboard({ timeout: 1000 }) @@ -54,6 +73,28 @@ export function BuildInfoModal({ opened, onClose, buildInfo }: BuildInfoModalPro withCloseButton > + {isNewVersionAvailable && ( + } + title="Update available" + variant="outline" + > + A new version of Remnawave is available. + + + )} +