mirror of
https://github.com/remnawave/frontend.git
synced 2026-05-13 12:16:40 +00:00
refactor: build info modal
This commit is contained in:
parent
0db4f67329
commit
f934f2dce9
19 changed files with 407 additions and 312 deletions
23
.github/workflows/release-dev.yml
vendored
23
.github/workflows/release-dev.yml
vendored
|
|
@ -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 <<EOF > 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
|
||||
|
|
|
|||
27
.github/workflows/release-frontend.yml
vendored
27
.github/workflows/release-frontend.yml
vendored
|
|
@ -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 <<EOF > 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: |
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</AppShell.Section>
|
||||
|
||||
<AppShell.Section className={classes.footerSection}>
|
||||
<GameModalShared />
|
||||
{isSocialButton && (
|
||||
<Group justify="center" mt="md" style={{ flexShrink: 0 }}>
|
||||
<HeaderControls
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
GetBandwidthStatsCommand,
|
||||
GetMetadataCommand,
|
||||
GetNodesMetricsCommand,
|
||||
GetNodesStatisticsCommand,
|
||||
GetRemnawaveHealthCommand,
|
||||
|
|
@ -30,6 +31,9 @@ export const systemQueryKeys = createQueryKeys('system', {
|
|||
},
|
||||
getNodesMetrics: {
|
||||
queryKey: null
|
||||
},
|
||||
getRemnawaveMetadata: {
|
||||
queryKey: null
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -104,3 +108,15 @@ export const useGetNodesMetrics = createGetQueryHook({
|
|||
},
|
||||
errorHandler: (error) => 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')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<CopyButton timeout={2000} value={value}>
|
||||
{({ copied, copy }) => (
|
||||
<Box className={styles.container} onClick={copy}>
|
||||
<Box
|
||||
className={clsx(styles.container, {
|
||||
[styles.small]: isSmall
|
||||
})}
|
||||
onClick={copy}
|
||||
>
|
||||
<Box className={styles.codeWrapper}>
|
||||
<Box className={styles.code}>{value}</Box>
|
||||
</Box>
|
||||
<ActionIcon
|
||||
className={styles.copyButton}
|
||||
data-copied={copied}
|
||||
size="sm"
|
||||
size={isSmall ? 'xs' : 'sm'}
|
||||
variant="transparent"
|
||||
>
|
||||
{copied ? <PiCheck size={18} /> : <PiCopy size={18} />}
|
||||
{copied ? <PiCheck size={iconSize} /> : <PiCopy size={iconSize} />}
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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%);
|
||||
}
|
||||
}
|
||||
11
src/shared/ui/header-buttons/SkeletonHeaderControl.tsx
Normal file
11
src/shared/ui/header-buttons/SkeletonHeaderControl.tsx
Normal file
|
|
@ -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 <Box className={classes.skeleton} h={44} w={width} />
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <SkeletonHeaderControl width={85} />
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
modals.open({
|
||||
title: (
|
||||
<BaseOverlayHeader
|
||||
IconComponent={Logo}
|
||||
iconVariant="gradient-teal"
|
||||
title="Build Info"
|
||||
/>
|
||||
),
|
||||
centered: true,
|
||||
size: 'md',
|
||||
withCloseButton: true,
|
||||
children: (
|
||||
<BuildInfoModal
|
||||
isNewVersionAvailable={isNewVersionAvailable}
|
||||
remnawaveMetadata={remnawaveMetadata}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderControl
|
||||
className={clsx(classes.version, {
|
||||
[classes.newVersion]: isNewVersionAvailable && !isDev
|
||||
[classes.newVersion]: isNewVersionAvailable && !isDev,
|
||||
[classes.dev]: isDev
|
||||
})}
|
||||
onClick={() => setBuildInfoModalOpened(true)}
|
||||
onClick={handleClick}
|
||||
w="auto"
|
||||
>
|
||||
<Group gap={8} ml={10} mr={10} wrap="nowrap">
|
||||
<Logo color={isNewVersionAvailable && !isDev ? 'cyan' : undefined} size={20} />
|
||||
<Text
|
||||
c={isNewVersionAvailable && !isDev ? 'cyan' : undefined}
|
||||
ff="text"
|
||||
fw={600}
|
||||
size="sm"
|
||||
>
|
||||
{isDev ? 'dev' : packageJson.version}
|
||||
<Logo size={20} />
|
||||
<Text ff="text" fw={600} size="sm">
|
||||
{remnawaveMetadata.version}
|
||||
</Text>
|
||||
</Group>
|
||||
</HeaderControl>
|
||||
|
||||
<BuildInfoModal
|
||||
buildInfo={buildInfo}
|
||||
isNewVersionAvailable={isNewVersionAvailable}
|
||||
onClose={() => setBuildInfoModalOpened(false)}
|
||||
opened={buildInfoModalOpened}
|
||||
/>
|
||||
|
||||
<GameModalShared />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
57
src/shared/ui/sidebar/build-info-modal.module.css
Normal file
57
src/shared/ui/sidebar/build-info-modal.module.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
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))
|
||||
remnawaveMetadata: GetMetadataCommand.Response['response']
|
||||
}
|
||||
|
||||
export function BuildInfoModal({ remnawaveMetadata, isNewVersionAvailable }: BuildInfoModalProps) {
|
||||
return (
|
||||
<Modal
|
||||
centered
|
||||
onClose={onClose}
|
||||
opened={opened}
|
||||
padding="xl"
|
||||
title={
|
||||
<Group justify="space-between" w="100%">
|
||||
<Title c={theme.primaryColor} fw={700} order={3}>
|
||||
Build Info
|
||||
</Title>
|
||||
<Tooltip label={clipboard.copied ? 'Copied!' : 'Copy build info'}>
|
||||
<Button
|
||||
color={clipboard.copied ? 'green' : 'gray'}
|
||||
leftSection={
|
||||
clipboard.copied ? <TbCheck size={16} /> : <TbCopy size={16} />
|
||||
}
|
||||
onClick={copyBuildInfo}
|
||||
radius="xl"
|
||||
size="compact-sm"
|
||||
variant="light"
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
}
|
||||
withCloseButton
|
||||
>
|
||||
<Stack gap="xl">
|
||||
<Stack gap="md">
|
||||
{isNewVersionAvailable && (
|
||||
<Paper
|
||||
bg="rgba(0, 180, 160, 0.05)"
|
||||
p="lg"
|
||||
style={{ border: `1px solid ${theme.colors.teal[3]}` }}
|
||||
withBorder
|
||||
>
|
||||
<Group align="flex-start" gap="md">
|
||||
<ThemeIcon color="cyan" radius="xl" size={48} variant="outline">
|
||||
<Logo size={24} />
|
||||
</ThemeIcon>
|
||||
<Stack gap="xs" style={{ flex: 1 }}>
|
||||
<Text c="teal.5" fw={700} size="md">
|
||||
<Paper className={classes.updateCard} p="md" radius="md">
|
||||
<Group align="center" gap="md" wrap="wrap">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Box className={classes.updateIconBox}>
|
||||
<Logo color="var(--mantine-color-teal-4)" size={24} />
|
||||
</Box>
|
||||
<Stack className={classes.updateTextWrapper} gap={4}>
|
||||
<Text c="teal.4" fw={600} size="sm">
|
||||
Update available
|
||||
</Text>
|
||||
<Text c="dimmed" size="md">
|
||||
A new version is available.
|
||||
<Text c="dimmed" size="xs">
|
||||
A new version is available
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
color="teal"
|
||||
component="a"
|
||||
fullWidth={false}
|
||||
href="https://t.me/remnalog"
|
||||
leftSection={<TbBrandTelegram size={16} />}
|
||||
mt="sm"
|
||||
size="sm"
|
||||
style={{ alignSelf: 'flex-start' }}
|
||||
leftSection={<TbBrandTelegram size={14} />}
|
||||
ml="auto"
|
||||
radius="md"
|
||||
size="xs"
|
||||
target="_blank"
|
||||
variant="light"
|
||||
>
|
||||
Check out
|
||||
</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
<Card padding="lg" shadow="sm" withBorder>
|
||||
<Stack gap="lg">
|
||||
<Group align="center" gap="lg">
|
||||
<ThemeIcon
|
||||
color={theme.primaryColor}
|
||||
radius="xl"
|
||||
size={48}
|
||||
style={{
|
||||
border: `1px solid ${theme.colors[theme.primaryColor][5]}`
|
||||
}}
|
||||
<Paper className={classes.mainCard} p="md">
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between">
|
||||
<Group gap="sm">
|
||||
<Badge
|
||||
color="cyan"
|
||||
leftSection={<Logo size={16} />}
|
||||
size="lg"
|
||||
variant="light"
|
||||
>
|
||||
<TbCalendar size={24} />
|
||||
</ThemeIcon>
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text fw={700} size="md">
|
||||
{remnawaveMetadata.version}
|
||||
</Badge>
|
||||
|
||||
<Badge
|
||||
color={
|
||||
remnawaveMetadata.git.backend.branch === 'dev' ? 'red' : 'teal'
|
||||
}
|
||||
leftSection={<TbGitBranch size={16} />}
|
||||
size="lg"
|
||||
variant="light"
|
||||
>
|
||||
{remnawaveMetadata.git.backend.branch}
|
||||
</Badge>
|
||||
</Group>
|
||||
<CopyButton
|
||||
timeout={2000}
|
||||
value={JSON.stringify(remnawaveMetadata, null, 2)}
|
||||
>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label="Copy build info">
|
||||
<ActionIcon
|
||||
color={copied ? 'teal' : 'gray'}
|
||||
onClick={copy}
|
||||
size="md"
|
||||
variant="subtle"
|
||||
>
|
||||
{copied ? <TbCheck size={14} /> : <TbCopy size={14} />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
</Group>
|
||||
|
||||
<Divider className={classes.divider} />
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
|
||||
<Paper className={classes.buildTimeCard} p="sm" radius="md">
|
||||
<Group gap="xs" mb={6}>
|
||||
<TbCalendar color="var(--mantine-color-indigo-5)" size={14} />
|
||||
<Text c="indigo.5" fw={600} size="xs" tt="uppercase">
|
||||
Build Time
|
||||
</Text>
|
||||
<Text c="dimmed" mt={4} size="sm">
|
||||
{buildDate}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Divider variant="dashed" />
|
||||
|
||||
<Group align="center" gap="lg">
|
||||
<ThemeIcon
|
||||
color={theme.primaryColor}
|
||||
radius="xl"
|
||||
size={48}
|
||||
style={{
|
||||
border: `1px solid ${theme.colors[theme.primaryColor][5]}`
|
||||
}}
|
||||
variant="light"
|
||||
>
|
||||
<TbGitBranch size={24} />
|
||||
</ThemeIcon>
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text fw={700} size="md">
|
||||
Branch
|
||||
</Text>
|
||||
<Flex gap="xs" mt={6}>
|
||||
<Badge
|
||||
color="blue"
|
||||
px="md"
|
||||
radius="xl"
|
||||
size="lg"
|
||||
variant="light"
|
||||
>
|
||||
{buildInfo.branch}
|
||||
</Badge>
|
||||
{buildInfo.tag && (
|
||||
<Badge
|
||||
color="green"
|
||||
px="md"
|
||||
radius="xl"
|
||||
size="lg"
|
||||
variant="light"
|
||||
>
|
||||
{buildInfo.tag}
|
||||
</Badge>
|
||||
<Text c="gray.3" ff="monospace" size="xs">
|
||||
{formatTimeUtil(
|
||||
remnawaveMetadata.build.time,
|
||||
'DD.MM.YYYY HH:mm:ss'
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Divider variant="dashed" />
|
||||
|
||||
<Group align="center" gap="lg">
|
||||
<ThemeIcon
|
||||
color={theme.primaryColor}
|
||||
radius="xl"
|
||||
size={48}
|
||||
style={{
|
||||
border: `1px solid ${theme.colors[theme.primaryColor][5]}`
|
||||
}}
|
||||
variant="light"
|
||||
>
|
||||
<TbHash size={24} />
|
||||
</ThemeIcon>
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text fw={700} size="md">
|
||||
Commit
|
||||
</Text>
|
||||
<Code fz="sm">{buildInfo.commit}</Code>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Paper className={classes.buildNumberCard} p="sm" radius="md">
|
||||
<Group gap="xs" mb={6}>
|
||||
<TbHash color="var(--mantine-color-violet-5)" size={14} />
|
||||
<Text c="violet.5" fw={600} size="xs" tt="uppercase">
|
||||
Build
|
||||
</Text>
|
||||
</Group>
|
||||
<Text c="gray.3" ff="monospace" size="xs">
|
||||
{remnawaveMetadata.build.number}
|
||||
</Text>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Paper>
|
||||
|
||||
<Group gap="md" grow preventGrowOverflow={false} wrap="wrap">
|
||||
<Button
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
|
||||
<Paper className={classes.backendCard} p="md" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs" justify="space-between">
|
||||
<Group gap="xs">
|
||||
<TbServer color="var(--mantine-color-teal-5)" size={16} />
|
||||
<Text c="teal.5" fw={600} size="sm">
|
||||
Backend
|
||||
</Text>
|
||||
</Group>
|
||||
<Tooltip label="View on GitHub">
|
||||
<ActionIcon
|
||||
color="teal"
|
||||
component="a"
|
||||
href={buildInfo.commitUrl}
|
||||
leftSection={<TbBrandGithub size={18} />}
|
||||
size="md"
|
||||
href={remnawaveMetadata.git.backend.commitUrl}
|
||||
size="sm"
|
||||
target="_blank"
|
||||
variant="outline"
|
||||
variant="subtle"
|
||||
>
|
||||
View on GitHub
|
||||
</Button>
|
||||
<TbBrandGithub size={14} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<CopyableCodeBlock
|
||||
size="small"
|
||||
value={remnawaveMetadata.git.backend.commitSha}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<Paper className={classes.frontendCard} p="md" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs" justify="space-between">
|
||||
<Group gap="xs">
|
||||
<TbWorld color="var(--mantine-color-cyan-5)" size={16} />
|
||||
<Text c="cyan.5" fw={600} size="sm">
|
||||
Frontend
|
||||
</Text>
|
||||
</Group>
|
||||
<Tooltip label="View on GitHub">
|
||||
<ActionIcon
|
||||
color="cyan"
|
||||
component="a"
|
||||
href={remnawaveMetadata.git.frontend.commitUrl}
|
||||
size="sm"
|
||||
target="_blank"
|
||||
variant="subtle"
|
||||
>
|
||||
<TbBrandGithub size={14} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<CopyableCodeBlock
|
||||
size="small"
|
||||
value={remnawaveMetadata.git.frontend.commitSha}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
|
||||
<Group gap="sm" grow>
|
||||
<Button
|
||||
color="cyan"
|
||||
component="a"
|
||||
href="https://t.me/remnawave"
|
||||
leftSection={<TbBrandTelegram size={18} />}
|
||||
size="md"
|
||||
leftSection={<TbBrandTelegram size={16} />}
|
||||
radius="md"
|
||||
size="sm"
|
||||
target="_blank"
|
||||
variant="light"
|
||||
>
|
||||
Ask Community
|
||||
Community
|
||||
</Button>
|
||||
<Button
|
||||
component="a"
|
||||
href="https://github.com/remnawave"
|
||||
leftSection={<TbBrandGithub size={16} />}
|
||||
radius="md"
|
||||
size="sm"
|
||||
target="_blank"
|
||||
variant="default"
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import { IBuildInfo } from './interfaces/build-info.interface'
|
||||
import buildInfo from '../../../../build.info.json'
|
||||
|
||||
export function getBuildInfo(): IBuildInfo {
|
||||
return buildInfo
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
export interface IBuildInfo {
|
||||
branch: string
|
||||
buildTime: string
|
||||
commit: string
|
||||
commitFull: string
|
||||
commitUrl: string
|
||||
tag: null | string
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue