import fs from 'fs'; import { GetServerSidePropsContext } from 'next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import React, { useEffect, useState } from 'react'; import { Alert, Anchor, AppShell, Badge, Box, Button, Container, createStyles, Group, Header, List, Loader, Paper, Progress, Space, Stack, Stepper, Switch, Text, ThemeIcon, Title, useMantineColorScheme, useMantineTheme, } from '@mantine/core'; import { IconAlertCircle, IconBrandDiscord, IconCheck, IconCircleCheck, IconMoonStars, IconSun, } from '@tabler/icons'; import { motion } from 'framer-motion'; import { Logo } from '../components/layout/Logo'; import { usePrimaryGradient } from '../components/layout/useGradient'; import { backendMigrateConfig } from '../tools/config/backendMigrateConfig'; import axios from 'axios'; const useStyles = createStyles((theme) => ({ root: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background, }, label: { textAlign: 'center', color: theme.colors[theme.primaryColor][8], fontWeight: 900, fontSize: 110, lineHeight: 1, marginBottom: theme.spacing.xl * 1.5, [theme.fn.smallerThan('sm')]: { fontSize: 60, }, }, title: { fontFamily: `Greycliff CF, ${theme.fontFamily}`, textAlign: 'center', fontWeight: 900, fontSize: 38, [theme.fn.smallerThan('sm')]: { fontSize: 32, }, }, card: { position: 'relative', overflow: 'visible', padding: theme.spacing.xl, }, icon: { position: 'absolute', top: -ICON_SIZE / 3, left: `calc(50% - ${ICON_SIZE / 2}px)`, }, description: { maxWidth: 700, margin: 'auto', marginTop: theme.spacing.xl, marginBottom: theme.spacing.xl * 1.5, }, })); export default function ServerError({ configs }: { configs: any }) { const { classes } = useStyles(); const [active, setActive] = React.useState(0); const gradient = usePrimaryGradient(); const [progress, setProgress] = React.useState(0); const [isUpgrading, setIsUpgrading] = React.useState(false); const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current)); const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current)); return ( ({ main: { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0], }, })} > {/* Header content */} } styles={(theme) => ({ main: { backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }) .background, }, })} >
Homarr v0.11 {active === 0 && "Good to see you back! Let's get started"} {active === 1 && progress !== 100 && 'Migrating your configs'} {active === 1 && progress === 100 && 'Migration complete!'} A few things have changed since the last time you used Homarr. We'll help you migrate your old configuration to the new format. This process is automatic and should take less than a minute. Then, you'll be able to use the new features of Homarr! } title="Please make a backup of your configs!" color="red" radius="md" variant="outline" > Please make sure to have a backup of your configs in case something goes wrong.{' '} Not all settings can be migrated, so you'll have to re-do some configuration yourself. } label="Step 2" description="Migrating your configs" > Homarr v0.11 brings a lot of new features, if you are interested in learning about them, please check out the{' '} documentation page That's it ! We hope you enjoy the new flexibility v0.11 brings. If you spot any bugs make sure to report them as a{' '} github issue {' '} or directly on the discord !
); } function SwitchToggle() { const { colorScheme, toggleColorScheme } = useMantineColorScheme(); const theme = useMantineTheme(); return ( toggleColorScheme()} size="lg" onLabel={} offLabel={} /> ); } export async function getServerSideProps({ req, res, locale }: GetServerSidePropsContext) { // Get all the configs in the /data/configs folder const configs = fs.readdirSync('./data/configs'); if (configs.length === 0) { res.writeHead(302, { Location: '/', }); res.end(); return { props: {} }; } // If all the configs are migrated (contains a schemaVersion), redirect to the index if ( configs.every( (config) => JSON.parse(fs.readFileSync(`./data/configs/${config}`, 'utf8')).schemaVersion ) ) { res.writeHead(302, { Location: '/', }); res.end(); return { processed: true, }; } return { props: { configs: configs.map( // Get all the file names in ./data/configs (config) => config.replace('.json', '') ), ...(await serverSideTranslations(locale!, [])), // Will be passed to the page component as props }, }; } const ICON_SIZE = 60; export function StatsCard({ configs, progress, setProgress, }: { configs: string[]; progress: number; setProgress: (progress: number) => void; }) { const { classes } = useStyles(); const numberOfConfigs = configs.length; // Update the progress every 100ms const [treatedConfigs, setTreatedConfigs] = useState([]); // Stop the progress at 100% useEffect(() => { const data = axios.post('/api/migrate').then((response) => { setProgress(100); }); const interval = setInterval(() => { if (configs.length === 0) { clearInterval(interval); setProgress(100); return; } // Add last element of configs to the treatedConfigs array setTreatedConfigs((treatedConfigs) => [...treatedConfigs, configs[configs.length - 1]]); // Remove last element of configs configs.pop(); }, 500); return () => clearInterval(interval); }, [configs]); return ( Progress {(100 / (numberOfConfigs + 1)).toFixed(1)}% } > {configs.map((config, index) => ( }> {config ?? 'Unknown'} ))} {treatedConfigs.map((config, index) => ( {config ?? 'Unknown'} ))} {configs.length} configs left ); }