瀏覽代碼

Improve overall dashboard design

Nicolas Meienberger 3 年之前
父節點
當前提交
1f1c44be6c
共有 35 個文件被更改,包括 303 次插入58 次删除
  1. 0 1
      apps/nextcloud/docker-compose.yml
  2. 1 2
      apps/pihole/docker-compose.yml
  3. 2 0
      dashboard/.eslintignore
  4. 2 2
      dashboard/next.config.js
  5. 1 0
      dashboard/package.json
  6. 二進制
      dashboard/public/android-chrome-192x192.png
  7. 二進制
      dashboard/public/android-chrome-512x512.png
  8. 二進制
      dashboard/public/apple-touch-icon.png
  9. 9 0
      dashboard/public/browserconfig.xml
  10. 二進制
      dashboard/public/favicon-16x16.png
  11. 二進制
      dashboard/public/favicon-32x32.png
  12. 二進制
      dashboard/public/favicon.ico
  13. 二進制
      dashboard/public/logo.png
  14. 二進制
      dashboard/public/mstile-150x150.png
  15. 20 0
      dashboard/public/safari-pinned-tab.svg
  16. 19 0
      dashboard/public/site.webmanifest
  17. 二進制
      dashboard/public/tipi.png
  18. 4 2
      dashboard/src/components/AppTile/index.tsx
  19. 4 4
      dashboard/src/components/Layout/Header.tsx
  20. 21 15
      dashboard/src/components/Layout/Layout.tsx
  21. 36 12
      dashboard/src/components/Layout/Menu.tsx
  22. 5 6
      dashboard/src/components/Layout/MenuDrawer.tsx
  23. 1 3
      dashboard/src/core/api.ts
  24. 1 1
      dashboard/src/modules/Apps/components/AppActions.tsx
  25. 1 1
      dashboard/src/modules/Apps/containers/AppDetails.tsx
  26. 5 2
      dashboard/src/pages/_app.tsx
  27. 25 0
      dashboard/src/pages/_document.tsx
  28. 2 2
      dashboard/src/pages/apps/index.tsx
  29. 61 1
      dashboard/src/pages/index.tsx
  30. 41 0
      dashboard/src/state/systemStore.ts
  31. 18 1
      dashboard/src/styles/globals.css
  32. 17 0
      dashboard/src/styles/theme.tsx
  33. 2 1
      dashboard/tsconfig.json
  34. 5 0
      dashboard/yarn.lock
  35. 0 2
      scripts/app.sh

+ 0 - 1
apps/nextcloud/docker-compose.yml

@@ -44,7 +44,6 @@ services:
       - ${APP_PORT}:80
     volumes:
       - ${APP_DATA_DIR}/data/nextcloud:/var/www/html
-      - /volumes/nfs:/nfs
     environment:
       - POSTGRES_HOST=db-nextcloud
       - REDIS_HOST=redis-nextcloud

+ 1 - 2
apps/pihole/docker-compose.yml

@@ -16,8 +16,7 @@ services:
       TZ: ${TZ}
       WEBPASSWORD: ${APP_PASSWORD}
       PIHOLE_DNS_: 127.0.0.1#5335
-      FTLCONF_REPLY_ADDR4: 192.168.2.132
-      PIHOLE_DNS_: 127.0.0.1#5335
+      FTLCONF_REPLY_ADDR4: ${INTERNAL_IP}
       DNSSEC: "true"
       DNSMASQ_LISTENING: single
     networks:

+ 2 - 0
dashboard/.eslintignore

@@ -0,0 +1,2 @@
+*.config.js
+.eslintrc.js

+ 2 - 2
dashboard/next.config.js

@@ -1,10 +1,10 @@
 /** @type {import('next').NextConfig} */
-console.log(process.env);
+const { NODE_ENV, INTERNAL_IP } = process.env;
 
 const nextConfig = {
   reactStrictMode: true,
   env: {
-    INTERNAL_IP: process.env.INTERNAL_IP,
+    INTERNAL_IP: NODE_ENV === 'development' ? 'localhost' : INTERNAL_IP,
   },
 };
 

+ 1 - 0
dashboard/package.json

@@ -12,6 +12,7 @@
     "@chakra-ui/react": "^1.8.7",
     "@emotion/react": "^11",
     "@emotion/styled": "^11",
+    "@fontsource/open-sans": "^4.5.8",
     "axios": "^0.26.1",
     "clsx": "^1.1.1",
     "final-form": "^4.20.6",

二進制
dashboard/public/android-chrome-192x192.png


二進制
dashboard/public/android-chrome-512x512.png


二進制
dashboard/public/apple-touch-icon.png


+ 9 - 0
dashboard/public/browserconfig.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+    <msapplication>
+        <tile>
+            <square150x150logo src="/mstile-150x150.png"/>
+            <TileColor>#da532c</TileColor>
+        </tile>
+    </msapplication>
+</browserconfig>

二進制
dashboard/public/favicon-16x16.png


二進制
dashboard/public/favicon-32x32.png


二進制
dashboard/public/favicon.ico


二進制
dashboard/public/logo.png


二進制
dashboard/public/mstile-150x150.png


+ 20 - 0
dashboard/public/safari-pinned-tab.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.14, written by Peter Selinger 2001-2017
+</metadata>
+<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M2263 5057 c-67 -34 -125 -65 -128 -68 -3 -4 53 -126 125 -271 l132
+-263 -122 -275 c-66 -151 -130 -295 -142 -320 -11 -25 -37 -83 -58 -130 -21
+-47 -347 -706 -725 -1465 -378 -759 -786 -1578 -906 -1820 l-219 -440 2337 -3
+c1285 -1 2338 0 2340 2 2 2 -397 809 -888 1792 -706 1417 -931 1879 -1085
+2224 l-194 434 134 267 133 267 -135 66 c-103 51 -137 64 -143 54 -5 -7 -41
+-79 -81 -160 -40 -81 -75 -147 -78 -148 -3 0 -27 44 -54 98 -101 203 -111 222
+-116 221 -3 0 -60 -29 -127 -62z"/>
+</g>
+</svg>

+ 19 - 0
dashboard/public/site.webmanifest

@@ -0,0 +1,19 @@
+{
+    "name": "",
+    "short_name": "",
+    "icons": [
+        {
+            "src": "/android-chrome-192x192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "/android-chrome-512x512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        }
+    ],
+    "theme_color": "#ffffff",
+    "background_color": "#ffffff",
+    "display": "standalone"
+}

二進制
dashboard/public/tipi.png


+ 4 - 2
dashboard/src/components/AppTile/index.tsx

@@ -1,4 +1,4 @@
-import { Box, SlideFade, Image } from '@chakra-ui/react';
+import { Box, SlideFade, Image, useColorModeValue } from '@chakra-ui/react';
 import Link from 'next/link';
 import React from 'react';
 import { FiChevronRight } from 'react-icons/fi';
@@ -6,10 +6,12 @@ import { AppConfig } from '../../core/types';
 import AppStatus from './AppStatus';
 
 const AppTile: React.FC<{ app: AppConfig }> = ({ app }) => {
+  const bg = useColorModeValue('white', '#1a202c');
+
   return (
     <Link href={`/apps/${app.id}`} passHref>
       <SlideFade in className="flex flex-1" offsetY="20px">
-        <Box minWidth={400} className="flex flex-1 bg-white drop-shadow-lg rounded-lg p-3 items-center cursor-pointer group hover:drop-shadow-md hover:bg-gray-100 transition-all">
+        <Box minWidth={400} bg={bg} className="flex flex-1 border-2 drop-shadow-sm rounded-lg p-3 items-center cursor-pointer group hover:drop-shadow-md transition-all">
           <Image alt={`${app.name} logo`} className="rounded-md drop-shadow mr-3 group-hover:scale-105 transition-all" src={app.image} width={100} height={100} />
           <div className="mr-3 flex-1">
             <h3 className="font-bold text-xl">{app.name}</h3>

+ 4 - 4
dashboard/src/components/Layout/Header.tsx

@@ -9,14 +9,14 @@ interface IProps {
 
 const Header: React.FC<IProps> = ({ onClickMenu }) => {
   return (
-    <header style={{ width: '100%' }} className="flex">
-      <Flex className="items-center bg-gray-700 drop-shadow-md px-5 flex-1">
+    <header style={{ width: '100%' }} className="flex h-12 md:h-0">
+      <Flex className="items-center border-b-2 bg-graycool px-5 flex-1 py-2">
         <div onClick={onClickMenu} className="visible md:invisible absolute cursor-pointer py-2">
-          <FiMenu color="white" />
+          <FiMenu color="black" />
         </div>
         <Flex justifyContent="center" flex="1">
           <Link href="/" passHref>
-            <img src="/logo.png" alt="Tipi" width={230} height={60} />
+            <img src="/tipi.png" alt="Tipi Logo" width={30} height={30} />
           </Link>
         </Flex>
       </Flex>

+ 21 - 15
dashboard/src/components/Layout/Layout.tsx

@@ -1,4 +1,5 @@
-import { Flex, useDisclosure, Spinner, Breadcrumb, BreadcrumbItem } from '@chakra-ui/react';
+import { Flex, useDisclosure, Spinner, Breadcrumb, BreadcrumbItem, useColorModeValue, Box } from '@chakra-ui/react';
+import Head from 'next/head';
 import Link from 'next/link';
 import React from 'react';
 import { FiChevronRight } from 'react-icons/fi';
@@ -12,7 +13,9 @@ interface IProps {
 }
 
 const Layout: React.FC<IProps> = ({ children, loading, breadcrumbs }) => {
-  const { isOpen, onOpen, onClose } = useDisclosure();
+  const { isOpen, onClose, onOpen } = useDisclosure();
+  const menubg = useColorModeValue('#F1F3F4', '#202736');
+  const bg = useColorModeValue('white', '#1a202c');
 
   const renderContent = () => {
     if (loading) {
@@ -41,23 +44,26 @@ const Layout: React.FC<IProps> = ({ children, loading, breadcrumbs }) => {
   };
 
   return (
-    <Flex height="100vh" className="drop-shadow-md border-r-8" direction="column">
-      <MenuDrawer isOpen={isOpen} onClose={onClose}>
-        <Menu />
-      </MenuDrawer>
-      <Header onClickMenu={onOpen} />
-      <Flex flex="1">
-        <Flex className="invisible md:visible w-0 md:w-56">
+    <>
+      <Head>
+        <title>Tipi</title>
+      </Head>
+      <Flex height="100vh" direction="column">
+        <MenuDrawer isOpen={isOpen} onClose={onClose}>
           <Menu />
-        </Flex>
-        <Flex className="bg-slate-200 flex flex-1 p-5">
-          <div className="flex-1 flex flex-col">
+        </MenuDrawer>
+        <Header onClickMenu={onOpen} />
+        <Flex flex={1}>
+          <Flex height="100vh" bg={menubg} className="sticky top-0 invisible md:visible w-0 md:w-64">
+            <Menu />
+          </Flex>
+          <Box bg={bg} className="flex-1 px-4 py-4 md:px-10 md:py-8">
             {renderBreadcrumbs()}
-            <div className="flex-1 ">{renderContent()}</div>
-          </div>
+            {renderContent()}
+          </Box>
         </Flex>
       </Flex>
-    </Flex>
+    </>
   );
 };
 

+ 36 - 12
dashboard/src/components/Layout/Menu.tsx

@@ -1,5 +1,7 @@
 import { AiOutlineDashboard, AiOutlineSetting, AiOutlineAppstore } from 'react-icons/ai';
-import { Divider, List, ListItem } from '@chakra-ui/react';
+import { FaRegMoon } from 'react-icons/fa';
+import Package from '../../../package.json';
+import { Box, Divider, Flex, List, ListItem, Switch, useColorMode } from '@chakra-ui/react';
 import React from 'react';
 import Link from 'next/link';
 import clsx from 'clsx';
@@ -8,32 +10,54 @@ import { IconType } from 'react-icons';
 
 const SideMenu: React.FC = () => {
   const router = useRouter();
-
+  const { colorMode, setColorMode } = useColorMode();
   const path = router.pathname.split('/')[1];
 
   const renderMenuItem = (title: string, name: string, Icon: IconType) => {
     const selected = path === name;
 
+    const itemClass = clsx('mx-3 border-transparent rounded-lg p-3 transition-colors border-1', {
+      'drop-shadow-sm border-gray-200': selected && colorMode === 'light',
+      'bg-white': selected && colorMode === 'light',
+    });
+
     return (
       <Link href={`/${name}`} passHref>
-        <div className={clsx('mx-3  rounded-lg p-3 transition-colors', { 'bg-slate-200 drop-shadow-sm': selected })}>
+        <div className={itemClass}>
           <ListItem className={'flex items-center cursor-pointer hover:font-bold'}>
-            <Icon size={20} className="mr-3" />
-            <p className={clsx({ 'font-bold': selected })}>{title}</p>
+            <Icon size={20} className={clsx('mr-3', { 'text-red-600': selected && colorMode === 'light', 'text-red-200': selected && colorMode === 'dark' })} />
+            <p className={clsx({ 'font-bold': selected, 'text-red-600': selected && colorMode === 'light', 'text-red-200': selected && colorMode === 'dark' })}>{title}</p>
           </ListItem>
         </div>
       </Link>
     );
   };
 
+  const handleChangeColorMode = (checked: boolean) => {
+    setColorMode(checked ? 'dark' : 'light');
+  };
+
   return (
-    <List spacing={3} className="pt-5 flex-1 bg-white md:border-r-2">
-      {renderMenuItem('Dashboard', '', AiOutlineDashboard)}
-      <Divider />
-      {renderMenuItem('Apps', 'apps', AiOutlineAppstore)}
-      <Divider />
-      {renderMenuItem('Settings', 'settings', AiOutlineSetting)}
-    </List>
+    <Box className="flex-1 flex flex-col p-0 md:p-4">
+      <img className="self-center mb-5 logo mt-0 md:mt-5" src="/tipi.png" width={512} height={512} />
+      <List spacing={3} className="pt-5">
+        {renderMenuItem('Dashboard', '', AiOutlineDashboard)}
+        {renderMenuItem('Apps', 'apps', AiOutlineAppstore)}
+        {renderMenuItem('Settings', 'settings', AiOutlineSetting)}
+      </List>
+      <Divider className="my-3" />
+      <Flex flex="1" />
+      <List>
+        <div className="mx-3">
+          <ListItem className="flex items-center">
+            <FaRegMoon size={20} className="mr-3" />
+            <p className="flex-1">Dark mode</p>
+            <Switch checked={colorMode === 'dark'} onChange={(event) => handleChangeColorMode(event.target.checked)} />
+          </ListItem>
+        </div>
+      </List>
+      <div className="pb-1 text-center text-sm text-gray-400 mt-5">Tipi version {Package.version}</div>
+    </Box>
   );
 };
 

+ 5 - 6
dashboard/src/components/Layout/MenuDrawer.tsx

@@ -1,4 +1,4 @@
-import { Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay } from '@chakra-ui/react';
+import { Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerHeader, DrawerOverlay, useColorModeValue } from '@chakra-ui/react';
 import React from 'react';
 
 interface IProps {
@@ -7,16 +7,15 @@ interface IProps {
 }
 
 const MenuDrawer: React.FC<IProps> = ({ children, isOpen, onClose }) => {
+  const menubg = useColorModeValue('#F1F3F4', '#202736');
+
   return (
     <Drawer size="xs" isOpen={isOpen} placement="left" onClose={onClose}>
       <DrawerOverlay />
-      <DrawerContent>
+      <DrawerContent bg={menubg}>
         <DrawerCloseButton />
         <DrawerHeader>My Tipi</DrawerHeader>
-        <DrawerBody>{children}</DrawerBody>
-        <DrawerFooter>
-          <div>Github</div>
-        </DrawerFooter>
+        <DrawerBody display="flex">{children}</DrawerBody>
       </DrawerContent>
     </Drawer>
   );

+ 1 - 3
dashboard/src/core/api.ts

@@ -1,8 +1,6 @@
 import axios, { Method } from 'axios';
 
-export const BASE_URL = 'http://192.168.2.132:3001';
-
-console.log(process.env);
+export const BASE_URL = `http://${process.env.INTERNAL_IP}:3001`;
 
 interface IFetchParams {
   endpoint: string;

+ 1 - 1
dashboard/src/modules/Apps/components/AppActions.tsx

@@ -28,7 +28,7 @@ const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, on
           <FiTrash2 className="ml-1" />
         </Button>
         {hasSettings && (
-          <Button onClick={onUpdate} width={160} className="mt-3 mr-2">
+          <Button onClick={onUpdate} width={160} colorScheme="gray" className="mt-3 mr-2">
             Settings
             <FiSettings className="ml-1" />
           </Button>

+ 1 - 1
dashboard/src/modules/Apps/containers/AppDetails.tsx

@@ -93,7 +93,7 @@ const AppDetails: React.FC<IProps> = ({ app }) => {
 
   return (
     <SlideFade in className="flex flex-1" offsetY="20px">
-      <div className="flex flex-1 bg-white p-4 mt-3 rounded-lg drop-shadow-xl flex-col">
+      <div className="flex flex-1  p-4 mt-3 rounded-lg flex-col">
         <Flex className="flex-col md:flex-row">
           <Image src={app?.image} height={180} width={180} className="rounded-xl self-center sm:self-auto" alt={app.name} />
           <VStack align="flex-start" justify="space-between" className="ml-0 md:ml-4">

+ 5 - 2
dashboard/src/pages/_app.tsx

@@ -1,8 +1,11 @@
-import { ChakraProvider } from '@chakra-ui/react';
+import '@fontsource/open-sans/700.css';
+import '@fontsource/open-sans/400.css';
 import '../styles/globals.css';
+import { ChakraProvider } from '@chakra-ui/react';
 import type { AppProps } from 'next/app';
 import { useEffect } from 'react';
 import { useNetworkStore } from '../state/networkStore';
+import { theme } from '../styles/theme';
 
 function MyApp({ Component, pageProps }: AppProps) {
   const { fetchInternalIp } = useNetworkStore();
@@ -12,7 +15,7 @@ function MyApp({ Component, pageProps }: AppProps) {
   }, [fetchInternalIp]);
 
   return (
-    <ChakraProvider>
+    <ChakraProvider theme={theme}>
       <Component {...pageProps} />
     </ChakraProvider>
   );

+ 25 - 0
dashboard/src/pages/_document.tsx

@@ -0,0 +1,25 @@
+import React from 'react';
+import { Html, Head, Main, NextScript } from 'next/document';
+import { ColorModeScript } from '@chakra-ui/react';
+import { theme } from '../styles/theme';
+
+export default function MyDocument() {
+  return (
+    <Html lang="en">
+      <Head>
+        <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
+        <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
+        <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
+        <link rel="manifest" href="/site.webmanifest" />
+        <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
+        <meta name="msapplication-TileColor" content="#da532c" />
+        <meta name="theme-color" content="#ffffff" />
+      </Head>
+      <body>
+        <ColorModeScript initialColorMode={theme.config.initialColorMode} />
+        <Main />
+        <NextScript />
+      </body>
+    </Html>
+  );
+}

+ 2 - 2
dashboard/src/pages/apps/index.tsx

@@ -19,13 +19,13 @@ const Apps: NextPage = () => {
   return (
     <Layout loading={loading}>
       <Flex className="flex-col">
-        {installedCount > 0 && <h1 className="font-bold text-2xl mb-3">Your Apps ({installedCount})</h1>}
+        {installedCount > 0 && <h1 className="font-bold text-3xl mb-5">Your Apps ({installedCount})</h1>}
         <SimpleGrid minChildWidth="400px" spacing="20px">
           {installed().map((app) => (
             <AppTile key={app.name} app={app} />
           ))}
         </SimpleGrid>
-        {available().length && <h1 className="font-bold text-2xl mb-3 mt-3">Available Apps</h1>}
+        {available().length && <h1 className="font-bold text-3xl mb-5 mt-5">Available Apps</h1>}
         <SimpleGrid minChildWidth="400px" spacing="20px">
           {available().map((app) => (
             <AppTile key={app.name} app={app} />

+ 61 - 1
dashboard/src/pages/index.tsx

@@ -1,10 +1,70 @@
+import { Progress, SimpleGrid, Stat, StatHelpText, StatLabel, StatNumber, Text } from '@chakra-ui/react';
 import type { NextPage } from 'next';
+import { useEffect } from 'react';
 import Layout from '../components/Layout';
+import { useSytemStore } from '../state/systemStore';
+import { BsCpu } from 'react-icons/bs';
+import { FiHardDrive } from 'react-icons/fi';
+import { FaMemory } from 'react-icons/fa';
 
 const Home: NextPage = () => {
+  const { fetchDiskSpace, fetchCpuLoad, fetchMemoryLoad, disk, cpuLoad, memory } = useSytemStore();
+
+  useEffect(() => {
+    fetchDiskSpace();
+    fetchCpuLoad();
+    fetchMemoryLoad();
+
+    const interval = setInterval(() => {
+      fetchDiskSpace();
+      fetchCpuLoad();
+      fetchMemoryLoad();
+    }, 10000);
+
+    return () => clearInterval(interval);
+  }, [fetchCpuLoad, fetchDiskSpace]);
+
+  // Convert bytes to GB
+  const diskFree = Math.round(disk.available / 1024 / 1024 / 1024);
+  const diskSize = Math.round(disk.size / 1024 / 1024 / 1024);
+  const diskUsed = diskSize - diskFree;
+  const percentUsed = Math.round((diskUsed / diskSize) * 100);
+
+  const memoryTotal = Math.round(memory?.total / 1024 / 1024 / 1024);
+  const memoryUsed = Math.round(memory?.used / 1024 / 1024 / 1024);
+  const percentUsedMemory = Math.round((memoryUsed / memoryTotal) * 100);
+
   return (
     <Layout>
-      <div>Content</div>
+      <Text fontSize="3xl" className="font-bold">
+        Tipi Dashboard
+      </Text>
+      <Text fontSize="xl" color="gray.500">
+        Welcome home!
+      </Text>
+      <SimpleGrid className="mt-5" minChildWidth="180px" spacing="20px">
+        <Stat className="border-2 px-5 py-3 rounded-lg">
+          <StatLabel>Disk space</StatLabel>
+          <StatNumber>{diskUsed} GB</StatNumber>
+          <StatHelpText>Used out of {diskSize} GB</StatHelpText>
+          <Progress value={percentUsed} size="sm" />
+          <FiHardDrive size={30} className="absolute top-3 right-3" />
+        </Stat>
+        <Stat className="border-2 px-5 py-3 rounded-lg">
+          <StatLabel>CPU Load</StatLabel>
+          <StatNumber>{cpuLoad.toFixed(2)}%</StatNumber>
+          <StatHelpText>Uninstall apps if there is to much load</StatHelpText>
+          <Progress value={cpuLoad} size="sm" />
+          <BsCpu size={30} className="absolute top-3 right-3" />
+        </Stat>
+        <Stat className="border-2 px-5 py-3 rounded-lg">
+          <StatLabel>Memory Used</StatLabel>
+          <StatNumber>{percentUsedMemory}%</StatNumber>
+          <StatHelpText>{memoryTotal} GB</StatHelpText>
+          <Progress value={percentUsedMemory} size="sm" />
+          <FaMemory size={30} className="absolute top-3 right-3" />
+        </Stat>
+      </SimpleGrid>
     </Layout>
   );
 };

+ 41 - 0
dashboard/src/state/systemStore.ts

@@ -0,0 +1,41 @@
+import create from 'zustand';
+import api from '../core/api';
+
+type Store = {
+  cpuLoad: number;
+  disk: { size: number; used: number; available: number };
+  memory: { total: number; used: number; free: number };
+  fetchDiskSpace: () => void;
+  fetchCpuLoad: () => void;
+  fetchMemoryLoad: () => void;
+};
+
+export const useSytemStore = create<Store>((set) => ({
+  cpuLoad: 0,
+  memory: { total: 0, used: 0, free: 0 },
+  disk: { size: 0, used: 0, available: 0 },
+  fetchDiskSpace: async () => {
+    const response = await api.fetch<any>({
+      endpoint: '/system/disk',
+      method: 'get',
+    });
+
+    set({ disk: response });
+  },
+  fetchCpuLoad: async () => {
+    const response = await api.fetch<any>({
+      endpoint: '/system/cpu',
+      method: 'get',
+    });
+
+    set({ cpuLoad: response.load });
+  },
+  fetchMemoryLoad: async () => {
+    const response = await api.fetch<any>({
+      endpoint: '/system/memory',
+      method: 'get',
+    });
+
+    set({ memory: response });
+  },
+}));

+ 18 - 1
dashboard/src/styles/globals.css

@@ -7,7 +7,7 @@ body {
   padding: 0;
   overflow-x: hidden;
   margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+  font-family: Open Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
     Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
 }
 
@@ -19,3 +19,20 @@ a {
 * {
   box-sizing: border-box;
 }
+
+.bg-graycool {
+  background-color:#F1F3F4;
+}
+
+.border-graycool {
+  border-color:#F1F3F4;
+}
+
+.border-1 {
+  border-width: 1px;
+}
+
+.logo {
+  width: 80px;
+  height: 80px;
+}

+ 17 - 0
dashboard/src/styles/theme.tsx

@@ -0,0 +1,17 @@
+import { extendTheme, type ThemeConfig, type Theme, withDefaultColorScheme } from '@chakra-ui/react';
+
+const config: ThemeConfig = {
+  initialColorMode: 'light',
+  useSystemColorMode: false,
+};
+
+export const theme: Theme = extendTheme(
+  {
+    config,
+    fonts: {
+      heading: 'Open Sans, sans-serif',
+      body: 'Open Sans, sans-serif',
+    },
+  },
+  withDefaultColorScheme({ colorScheme: 'red' }),
+) as Theme;

+ 2 - 1
dashboard/tsconfig.json

@@ -13,7 +13,8 @@
     "resolveJsonModule": true,
     "isolatedModules": true,
     "jsx": "preserve",
-    "incremental": true
+    "incremental": true,
+    "strictNullChecks": true
   },
   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
   "exclude": ["node_modules"]

+ 5 - 0
dashboard/yarn.lock

@@ -755,6 +755,11 @@
     minimatch "^3.0.4"
     strip-json-comments "^3.1.1"
 
+"@fontsource/open-sans@^4.5.8":
+  version "4.5.8"
+  resolved "https://registry.yarnpkg.com/@fontsource/open-sans/-/open-sans-4.5.8.tgz#31f727353e89ce886e1076bd58536834e0778fda"
+  integrity sha512-3b94XDdRLqL7OlE7OjWg/4pgG825Juw8PLVEDm6h5pio0gMU89ICxfatGxHsBxMGfqad+wnvdmUweZWlELDFpQ==
+
 "@humanwhocodes/config-array@^0.9.2":
   version "0.9.5"
   resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7"

+ 0 - 2
scripts/app.sh

@@ -89,8 +89,6 @@ compose() {
   export APP_DATA_DIR="${app_data_dir}"
   export APP_DIR="${app_dir}"
 
-  # TODO: Fix for dynamic detection
-  export DEVICE_IP="192.168.2.132"
   export ROOT_FOLDER="${ROOT_FOLDER}"
 
   # Docker-compose does not support multiple env files