diff --git a/src/client/pages/Layout/DesktopLayout.tsx b/src/client/components/layout/DesktopLayout.tsx similarity index 100% rename from src/client/pages/Layout/DesktopLayout.tsx rename to src/client/components/layout/DesktopLayout.tsx diff --git a/src/client/pages/Layout/Header.tsx b/src/client/components/layout/Header.tsx similarity index 100% rename from src/client/pages/Layout/Header.tsx rename to src/client/components/layout/Header.tsx diff --git a/src/client/pages/Layout/Menu.tsx b/src/client/components/layout/Menu.tsx similarity index 100% rename from src/client/pages/Layout/Menu.tsx rename to src/client/components/layout/Menu.tsx diff --git a/src/client/pages/Layout/MobileLayout.tsx b/src/client/components/layout/MobileLayout.tsx similarity index 100% rename from src/client/pages/Layout/MobileLayout.tsx rename to src/client/components/layout/MobileLayout.tsx diff --git a/src/client/pages/Layout/Nav.tsx b/src/client/components/layout/Nav.tsx similarity index 100% rename from src/client/pages/Layout/Nav.tsx rename to src/client/components/layout/Nav.tsx diff --git a/src/client/pages/Layout/UserConfig.tsx b/src/client/components/layout/UserConfig.tsx similarity index 100% rename from src/client/pages/Layout/UserConfig.tsx rename to src/client/components/layout/UserConfig.tsx diff --git a/src/client/components/layout/index.tsx b/src/client/components/layout/index.tsx new file mode 100644 index 0000000..091fdfd --- /dev/null +++ b/src/client/components/layout/index.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { DesktopLayout } from './DesktopLayout'; +import { LayoutProps } from './types'; +import { useIsMobile } from '@/hooks/useIsMobile'; +import { MobileLayout } from './MobileLayout'; + +export const Layout: React.FC = React.memo((props) => { + const isMobile = useIsMobile(); + + if (isMobile) { + return ; + } + + return ; +}); +Layout.displayName = 'Layout'; diff --git a/src/client/pages/Layout/types.ts b/src/client/components/layout/types.ts similarity index 100% rename from src/client/pages/Layout/types.ts rename to src/client/components/layout/types.ts diff --git a/src/client/components/server/AddServerStep.tsx b/src/client/components/server/AddServerStep.tsx new file mode 100644 index 0000000..fc539d3 --- /dev/null +++ b/src/client/components/server/AddServerStep.tsx @@ -0,0 +1,118 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { Button, Steps, Typography } from 'antd'; +import { useCurrentWorkspaceId } from '../../store/user'; +import { useWatch } from '../../hooks/useWatch'; +import { Loading } from '../Loading'; +import { without } from 'lodash-es'; +import { useTranslation } from '@i18next-toolkit/react'; +import { useSocketSubscribe } from '@/api/socketio'; +import { ServerStatusInfo } from '../../../types'; + +function useServerMap(): Record { + const serverMap = useSocketSubscribe>( + 'onServerStatusUpdate', + {} + ); + + return serverMap; +} + +export const AddServerStep: React.FC = React.memo(() => { + const { t } = useTranslation(); + const workspaceId = useCurrentWorkspaceId(); + const [current, setCurrent] = useState(0); + const serverMap = useServerMap(); + const [checking, setChecking] = useState(false); + const oldServerMapNames = useRef([]); + const [diffServerNames, setDiffServerNames] = useState([]); + + const allServerNames = useMemo(() => Object.keys(serverMap), [serverMap]); + + useWatch([checking], () => { + if (checking === true) { + oldServerMapNames.current = [...allServerNames]; + } + }); + + useWatch([allServerNames], () => { + if (checking === true) { + setDiffServerNames(without(allServerNames, ...oldServerMapNames.current)); + } + }); + + const command = `./tianji-reporter --url ${window.location.origin} --workspace ${workspaceId}`; + + return ( + + {t('Download reporter from')}{' '} + { + if (current === 0) { + setCurrent(1); + setChecking(true); + } + }} + > + {t('Releases Page')} + + + ), + }, + { + title: t('Run'), + description: ( +
+ {t('run reporter with')}:{' '} + + {command} + + +
+ ), + }, + { + title: t('Waiting for receive report pack'), + description: ( +
+ {diffServerNames.length === 0 || checking === false ? ( + + ) : ( +
+ {t('Is this your servers?')} + {diffServerNames.map((n) => ( +
- {n}
+ ))} +
+ )} +
+ ), + }, + ]} + /> + ); +}); +AddServerStep.displayName = 'AddServerStep'; diff --git a/src/client/components/server/InstallScript.tsx b/src/client/components/server/InstallScript.tsx new file mode 100644 index 0000000..5d347db --- /dev/null +++ b/src/client/components/server/InstallScript.tsx @@ -0,0 +1,33 @@ +import { useCurrentWorkspaceId } from '@/store/user'; +import { useTranslation } from '@i18next-toolkit/react'; +import { Typography } from 'antd'; +import React from 'react'; + +export const InstallScript: React.FC = React.memo(() => { + const { t } = useTranslation(); + const workspaceId = useCurrentWorkspaceId(); + const command = `curl -o- ${window.location.origin}/serverStatus/${workspaceId}/install.sh?url=${window.location.origin} | bash`; + + return ( +
+
{t('Run this command in your linux machine')}
+ + + {command} + + +
+ {t( + 'Or you wanna report server status in windows server? switch to Manual tab' + )} +
+
+ ); +}); +InstallScript.displayName = 'InstallScript'; diff --git a/src/client/pages/Dashboard.tsx b/src/client/pages/Dashboard.tsx deleted file mode 100644 index 6cc62a5..0000000 --- a/src/client/pages/Dashboard.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import { Dashboard } from '../components/dashboard/Dashboard'; - -export const DashboardPage: React.FC = React.memo(() => { - return ; -}); -DashboardPage.displayName = 'DashboardPage'; diff --git a/src/client/pages/Layout.tsx b/src/client/pages/Layout.tsx deleted file mode 100644 index aee0cf5..0000000 --- a/src/client/pages/Layout.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, { useState } from 'react'; -import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'; -import { NavItem } from '../components/NavItem'; -import { MobileNavItem } from '../components/MobileNavItem'; -import { UserOutlined } from '@ant-design/icons'; -import { Button, Divider, Drawer, Dropdown } from 'antd'; -import { useUserStore } from '../store/user'; -import { useLogout } from '../api/model/user'; -import { ColorSchemeSwitcher } from '../components/ColorSchemeSwitcher'; -import { version } from '@/utils/env'; -import { useIsMobile } from '../hooks/useIsMobile'; -import { RiMenuUnfoldLine } from 'react-icons/ri'; -import { useTranslation } from '@i18next-toolkit/react'; -import { LanguageSelector } from '../components/LanguageSelector'; - -export const Layout: React.FC = React.memo(() => { - const [params] = useSearchParams(); - const workspaces = useUserStore((state) => { - const userInfo = state.info; - if (userInfo) { - return userInfo.workspaces.map((w) => ({ - id: w.workspace.id, - name: w.workspace.name, - role: w.role, - current: userInfo.currentWorkspace?.id === w.workspace.id, - })); - } - - return []; - }); - const [openDraw, setOpenDraw] = useState(false); - const logout = useLogout(); - const isMobile = useIsMobile(); - const showHeader = !params.has('hideHeader'); - const navigate = useNavigate(); - const { t } = useTranslation(); - - const accountEl = ( - ({ - key: w.id, - label: `${w.name}${w.current ? '(current)' : ''}`, - disabled: w.current, - })), - }, - { - key: 'settings', - label: t('Settings'), - onClick: () => { - navigate('/settings'); - }, - }, - { - key: 'logout', - label: t('Logout'), - onClick: () => { - logout(); - }, - }, - { - type: 'divider', - }, - { - key: 'version', - label: `v${version}`, - disabled: true, - }, - ], - }} - > - - - - {allowRegister && ( - - - - )} - - - - ); -}); -Login.displayName = 'Login'; diff --git a/src/client/pages/Monitor/Add.tsx b/src/client/pages/Monitor/Add.tsx deleted file mode 100644 index bf2ef8d..0000000 --- a/src/client/pages/Monitor/Add.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router'; -import { useMonitorUpsert } from '../../api/model/monitor'; -import { MonitorInfoEditor } from '../../components/monitor/MonitorInfoEditor'; -import { useCurrentWorkspaceId } from '../../store/user'; - -export const MonitorAdd: React.FC = React.memo(() => { - const currentWorkspaceId = useCurrentWorkspaceId()!; - const mutation = useMonitorUpsert(); - const navigate = useNavigate(); - - return ( -
- { - await mutation.mutateAsync({ - ...value, - workspaceId: currentWorkspaceId, - }); - navigate('/monitor', { replace: true }); - }} - /> -
- ); -}); -MonitorAdd.displayName = 'MonitorAdd'; diff --git a/src/client/pages/Monitor/Detail.tsx b/src/client/pages/Monitor/Detail.tsx deleted file mode 100644 index 17257a2..0000000 --- a/src/client/pages/Monitor/Detail.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { useParams } from 'react-router'; -import { ErrorTip } from '../../components/ErrorTip'; -import { MonitorInfo } from '../../components/monitor/MonitorInfo'; - -export const MonitorDetail: React.FC = React.memo(() => { - const { monitorId } = useParams(); - - if (!monitorId) { - return ; - } - - return ( -
- -
- ); -}); -MonitorDetail.displayName = 'MonitorDetail'; diff --git a/src/client/pages/Monitor/Edit.tsx b/src/client/pages/Monitor/Edit.tsx deleted file mode 100644 index dc3ef5d..0000000 --- a/src/client/pages/Monitor/Edit.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { useNavigate, useParams } from 'react-router'; -import { useMonitorUpsert } from '../../api/model/monitor'; -import { trpc } from '../../api/trpc'; -import { ErrorTip } from '../../components/ErrorTip'; -import { Loading } from '../../components/Loading'; -import { - MonitorInfoEditor, - MonitorInfoEditorValues, -} from '../../components/monitor/MonitorInfoEditor'; -import { useCurrentWorkspaceId } from '../../store/user'; - -export const MonitorEdit: React.FC = React.memo(() => { - const { monitorId } = useParams<{ monitorId: string }>(); - const workspaceId = useCurrentWorkspaceId(); - const { data: monitor, isLoading } = trpc.monitor.get.useQuery({ - monitorId: monitorId!, - workspaceId, - }); - const mutation = useMonitorUpsert(); - const navigate = useNavigate(); - - if (isLoading) { - return ; - } - - if (!monitor) { - return ; - } - - return ( -
- n.id), - } as MonitorInfoEditorValues - } - onSave={async (value) => { - const monitor = await mutation.mutateAsync({ - ...value, - workspaceId, - }); - navigate(`/monitor/${monitor.id}`, { replace: true }); - }} - /> -
- ); -}); -MonitorEdit.displayName = 'MonitorEdit'; diff --git a/src/client/pages/Monitor/Overview.tsx b/src/client/pages/Monitor/Overview.tsx deleted file mode 100644 index dc30687..0000000 --- a/src/client/pages/Monitor/Overview.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { trpc } from '../../api/trpc'; -import { Card } from 'antd'; -import { MonitorEventList } from '../../components/monitor/MonitorEventList'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const MonitorOverview: React.FC = React.memo(() => { - const { t } = useTranslation(); - const currentWorkspaceId = useCurrentWorkspaceId()!; - const { data: monitors = [] } = trpc.monitor.all.useQuery({ - workspaceId: currentWorkspaceId, - }); - - return ( -
-
- -
{t('Monitors')}
-
{monitors.length}
-
- -
{t('Available')}
-
- {monitors.filter((m) => m.active).length} -
-
-
-
- -
-
- ); -}); -MonitorOverview.displayName = 'MonitorOverview'; diff --git a/src/client/pages/Monitor/PageAdd.tsx b/src/client/pages/Monitor/PageAdd.tsx deleted file mode 100644 index a25ff42..0000000 --- a/src/client/pages/Monitor/PageAdd.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { trpc } from '../../api/trpc'; -import { useEvent } from '../../hooks/useEvent'; -import { - MonitorStatusPageEditForm, - MonitorStatusPageEditFormValues, -} from '../../components/monitor/StatusPage/EditForm'; - -export const MonitorPageAdd: React.FC = React.memo(() => { - const workspaceId = useCurrentWorkspaceId()!; - const navigate = useNavigate(); - - const createPageMutation = trpc.monitor.createPage.useMutation(); - const trpcUtils = trpc.useContext(); - - const handleFinish = useEvent( - async (values: MonitorStatusPageEditFormValues) => { - await createPageMutation.mutateAsync({ - ...values, - workspaceId, - }); - - trpcUtils.monitor.getAllPages.refetch(); - - navigate('/monitor/pages'); - } - ); - - return ( -
- -
- ); -}); -MonitorPageAdd.displayName = 'MonitorPageAdd'; diff --git a/src/client/pages/Monitor/PageList.tsx b/src/client/pages/Monitor/PageList.tsx deleted file mode 100644 index 4a80a24..0000000 --- a/src/client/pages/Monitor/PageList.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { trpc } from '../../api/trpc'; -import { Button, Card, Popconfirm } from 'antd'; -import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons'; -import { useEvent } from '../../hooks/useEvent'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const MonitorPageList: React.FC = React.memo(() => { - const { t } = useTranslation(); - const workspaceId = useCurrentWorkspaceId(); - const navigate = useNavigate(); - const { data: pages = [], refetch } = trpc.monitor.getAllPages.useQuery({ - workspaceId, - }); - const deletePageMutation = trpc.monitor.deletePage.useMutation(); - - const handleDeletePage = useEvent(async (monitorId: string) => { - await deletePageMutation.mutateAsync({ - workspaceId, - id: monitorId, - }); - - refetch(); - }); - - return ( -
- - -
- {pages.map((p) => ( - -
-
{p.title}
-
- handleDeletePage(p.id)} - okButtonProps={{ - danger: true, - loading: deletePageMutation.isLoading, - }} - > -
-
-
- ))} -
-
- ); -}); -MonitorPageList.displayName = 'MonitorPageList'; diff --git a/src/client/pages/Monitor/index.tsx b/src/client/pages/Monitor/index.tsx deleted file mode 100644 index ae743c3..0000000 --- a/src/client/pages/Monitor/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Route, Routes, useNavigate } from 'react-router'; -import { MonitorList } from '../../components/monitor/MonitorList'; -import { MonitorAdd } from './Add'; -import { MonitorDetail } from './Detail'; -import { MonitorEdit } from './Edit'; -import { MonitorOverview } from './Overview'; -import { Button } from 'antd'; -import { MonitorPageList } from './PageList'; -import { MonitorPageAdd } from './PageAdd'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const MonitorPage: React.FC = React.memo(() => { - const { t } = useTranslation(); - const navigate = useNavigate(); - - return ( -
-
-
- - -
-
-
-
- -
-
- - } /> - } /> - } /> - } /> - } /> - } /> - -
-
-
- ); -}); -MonitorPage.displayName = 'MonitorPage'; diff --git a/src/client/pages/Register.tsx b/src/client/pages/Register.tsx deleted file mode 100644 index 331467e..0000000 --- a/src/client/pages/Register.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Button, Form, Input, Typography } from 'antd'; -import React from 'react'; -import { useNavigate } from 'react-router'; -import { useRequest } from '../hooks/useRequest'; -import { trpc } from '../api/trpc'; -import { setJWT } from '../api/auth'; -import { setUserInfo } from '../store/user'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const Register: React.FC = React.memo(() => { - const { t } = useTranslation(); - const navigate = useNavigate(); - - const mutation = trpc.user.register.useMutation(); - - const [{ loading }, handleRegister] = useRequest(async (values: any) => { - const res = await mutation.mutateAsync({ - username: values.username, - password: values.password, - }); - setJWT(res.token); - setUserInfo(res.info); - - navigate('/dashboard'); - }); - - return ( -
-
-
- -
- - {t('Register Account')} - -
- - - - - - - - - -
-
-
- ); -}); -Register.displayName = 'Register'; diff --git a/src/client/pages/Servers.tsx b/src/client/pages/Servers.tsx deleted file mode 100644 index d817e79..0000000 --- a/src/client/pages/Servers.tsx +++ /dev/null @@ -1,429 +0,0 @@ -import React, { useMemo, useRef, useState } from 'react'; -import { - Badge, - Button, - Divider, - Empty, - Modal, - Popconfirm, - Steps, - Switch, - Table, - Tabs, - Tooltip, - Typography, -} from 'antd'; -import { ColumnsType } from 'antd/es/table'; -import { PlusOutlined } from '@ant-design/icons'; -import { ServerStatusInfo } from '../../types'; -import { useSocketSubscribe } from '../api/socketio'; -import { filesize } from 'filesize'; -import prettyMilliseconds from 'pretty-ms'; -import { UpDownCounter } from '../components/UpDownCounter'; -import { max } from 'lodash-es'; -import dayjs from 'dayjs'; -import { useCurrentWorkspaceId } from '../store/user'; -import { useWatch } from '../hooks/useWatch'; -import { Loading } from '../components/Loading'; -import { without } from 'lodash-es'; -import { useIntervalUpdate } from '../hooks/useIntervalUpdate'; -import clsx from 'clsx'; -import { isServerOnline } from '@tianji/shared'; -import { defaultErrorHandler, trpc } from '../api/trpc'; -import { useRequest } from '../hooks/useRequest'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const Servers: React.FC = React.memo(() => { - const { t } = useTranslation(); - const [isModalOpen, setIsModalOpen] = useState(false); - const [hideOfflineServer, setHideOfflineServer] = useState(false); - const workspaceId = useCurrentWorkspaceId(); - - const handleOk = () => { - setIsModalOpen(false); - }; - - const clearOfflineNodeMutation = - trpc.serverStatus.clearOfflineServerStatus.useMutation({ - onError: defaultErrorHandler, - }); - - const [{ loading }, handleClearOfflineNode] = useRequest(async (e) => { - await clearOfflineNodeMutation.mutateAsync({ - workspaceId, - }); - }); - - return ( -
-
-
{t('Servers')}
-
-
- - {t('Hide Offline')} -
- -
- - - -
- - - - -
-
- - - - setIsModalOpen(false)} - > -
- , - }, - { - key: 'manual', - label: t('Manual'), - children: , - }, - ]} - /> -
-
-
- ); -}); -Servers.displayName = 'Servers'; - -function useServerMap(): Record { - const serverMap = useSocketSubscribe>( - 'onServerStatusUpdate', - {} - ); - - return serverMap; -} - -/** - * @deprecated - */ -export const ServerList: React.FC<{ - hideOfflineServer: boolean; -}> = React.memo((props) => { - const { t } = useTranslation(); - const serverMap = useServerMap(); - const inc = useIntervalUpdate(2 * 1000); - const { hideOfflineServer } = props; - - const dataSource = useMemo( - () => - Object.values(serverMap) - .sort((info) => (isServerOnline(info) ? -1 : 1)) - .filter((info) => { - if (hideOfflineServer) { - return isServerOnline(info); - } - - return true; - }), // make online server is up and offline is down - [serverMap, inc, hideOfflineServer] - ); - const lastUpdatedAt = max(dataSource.map((d) => d.updatedAt)); - - const columns = useMemo((): ColumnsType => { - return [ - { - key: 'status', - title: t('Status'), - width: 90, - render: (val, record) => { - return isServerOnline(record) ? ( - - ) : ( - - - - ); - }, - }, - { - dataIndex: 'name', - title: t('Node Name'), - width: 150, - ellipsis: true, - }, - { - dataIndex: 'hostname', - title: t('Host Name'), - width: 150, - ellipsis: true, - }, - // { - // dataIndex: ['payload', 'system'], - // title: 'System', - // }, - { - dataIndex: ['payload', 'uptime'], - title: t('Uptime'), - width: 150, - render: (val) => prettyMilliseconds(Number(val) * 1000), - }, - { - dataIndex: ['payload', 'load'], - title: t('Load'), - width: 70, - }, - { - key: 'nework', - title: t('Network'), - width: 110, - render: (_, record) => { - return ( - - ); - }, - }, - { - key: 'traffic', - title: t('Traffic'), - width: 130, - render: (_, record) => { - return ( - - ); - }, - }, - { - dataIndex: ['payload', 'cpu'], - title: 'CPU', - width: 80, - render: (val) => `${val}%`, - }, - { - key: 'ram', - title: 'RAM', - width: 120, - render: (_, record) => { - return ( -
-
{filesize(record.payload.memory_used * 1000)} /
-
{filesize(record.payload.memory_total * 1000)}
-
- ); - }, - }, - { - key: 'hdd', - title: 'HDD', - width: 120, - render: (_, record) => { - return ( -
-
{filesize(record.payload.hdd_used * 1000 * 1000)} /
-
{filesize(record.payload.hdd_total * 1000 * 1000)}
-
- ); - }, - }, - { - dataIndex: 'updatedAt', - title: t('updatedAt'), - width: 130, - render: (val) => { - return dayjs(val).format('MMM D HH:mm:ss'); - }, - }, - ]; - }, []); - - return ( -
-
- {t('Last updated at: {{date}}', { - date: dayjs(lastUpdatedAt).format('YYYY-MM-DD HH:mm:ss'), - })} -
-
- }} - rowClassName={(record) => - clsx(!isServerOnline(record) && 'opacity-60') - } - /> - - - ); -}); -ServerList.displayName = 'ServerList'; - -export const InstallScript: React.FC = React.memo(() => { - const { t } = useTranslation(); - const workspaceId = useCurrentWorkspaceId(); - const command = `curl -o- ${window.location.origin}/serverStatus/${workspaceId}/install.sh?url=${window.location.origin} | bash`; - - return ( -
-
{t('Run this command in your linux machine')}
- - - {command} - - -
- {t( - 'Or you wanna report server status in windows server? switch to Manual tab' - )} -
-
- ); -}); - -export const AddServerStep: React.FC = React.memo(() => { - const { t } = useTranslation(); - const workspaceId = useCurrentWorkspaceId(); - const [current, setCurrent] = useState(0); - const serverMap = useServerMap(); - const [checking, setChecking] = useState(false); - const oldServerMapNames = useRef([]); - const [diffServerNames, setDiffServerNames] = useState([]); - - const allServerNames = useMemo(() => Object.keys(serverMap), [serverMap]); - - useWatch([checking], () => { - if (checking === true) { - oldServerMapNames.current = [...allServerNames]; - } - }); - - useWatch([allServerNames], () => { - if (checking === true) { - setDiffServerNames(without(allServerNames, ...oldServerMapNames.current)); - } - }); - - const command = `./tianji-reporter --url ${window.location.origin} --workspace ${workspaceId}`; - - return ( - - {t('Download reporter from')}{' '} - { - if (current === 0) { - setCurrent(1); - setChecking(true); - } - }} - > - {t('Releases Page')} - - - ), - }, - { - title: t('Run'), - description: ( -
- {t('run reporter with')}:{' '} - - {command} - - -
- ), - }, - { - title: t('Waiting for receive report pack'), - description: ( -
- {diffServerNames.length === 0 || checking === false ? ( - - ) : ( -
- {t('Is this your servers?')} - {diffServerNames.map((n) => ( -
- {n}
- ))} -
- )} -
- ), - }, - ]} - /> - ); -}); -AddServerStep.displayName = 'AddServerStep'; diff --git a/src/client/pages/Settings/AuditLog.tsx b/src/client/pages/Settings/AuditLog.tsx deleted file mode 100644 index e39ee2b..0000000 --- a/src/client/pages/Settings/AuditLog.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { Card, Empty, List } from 'antd'; -import React, { useMemo, useRef } from 'react'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { PageHeader } from '../../components/PageHeader'; -import { trpc } from '../../api/trpc'; -import { useVirtualizer } from '@tanstack/react-virtual'; -import { last } from 'lodash-es'; -import { useWatch } from '../../hooks/useWatch'; -import { ColorTag } from '../../components/ColorTag'; -import dayjs from 'dayjs'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const AuditLog: React.FC = React.memo(() => { - const { t } = useTranslation(); - const workspaceId = useCurrentWorkspaceId(); - const parentRef = useRef(null); - - const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = - trpc.auditLog.fetchByCursor.useInfiniteQuery({ - workspaceId, - }); - - const allData = useMemo(() => { - if (!data) { - return []; - } - - return [...data.pages.flatMap((p) => p.items)]; - }, [data]); - - const rowVirtualizer = useVirtualizer({ - count: hasNextPage ? allData.length + 1 : allData.length, - getScrollElement: () => parentRef.current, - estimateSize: () => 48, - overscan: 5, - }); - - const virtualItems = rowVirtualizer.getVirtualItems(); - - useWatch([virtualItems], () => { - const lastItem = last(virtualItems); - - if (!lastItem) { - return; - } - - if ( - lastItem.index >= allData.length - 1 && - hasNextPage && - !isFetchingNextPage - ) { - fetchNextPage(); - } - }); - - return ( -
- - - - -
- {virtualItems.length === 0 && } - -
- {virtualItems.map((virtualRow) => { - const isLoaderRow = virtualRow.index > allData.length - 1; - const item = allData[virtualRow.index]; - - return ( - - {isLoaderRow ? ( - hasNextPage ? ( - t('Loading more...') - ) : ( - t('Nothing more to load') - ) - ) : ( -
- {item.relatedType && ( - - )} -
- {dayjs(item.createdAt).format('MM-DD HH:mm')} -
-
- {item.content} -
-
- )} -
- ); - })} -
-
-
-
-
- ); -}); -AuditLog.displayName = 'AuditLog'; diff --git a/src/client/pages/Settings/NotificationList.tsx b/src/client/pages/Settings/NotificationList.tsx deleted file mode 100644 index 07d784a..0000000 --- a/src/client/pages/Settings/NotificationList.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'; -import { Button, List, Popconfirm } from 'antd'; -import React, { useState } from 'react'; -import { trpc } from '../../api/trpc'; -import { - NotificationFormValues, - NotificationInfoModal, -} from '../../components/modals/NotificationInfo'; -import { NoWorkspaceTip } from '../../components/NoWorkspaceTip'; -import { PageHeader } from '../../components/PageHeader'; -import { useEvent } from '../../hooks/useEvent'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const NotificationList: React.FC = React.memo(() => { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const currentWorkspaceId = useCurrentWorkspaceId(); - const { data: list = [], refetch } = trpc.notification.all.useQuery({ - workspaceId: currentWorkspaceId!, - }); - const [editingFormData, setEditingFormData] = useState< - NotificationFormValues | undefined - >(undefined); - - const upsertMutation = trpc.notification.upsert.useMutation(); - const deleteMutation = trpc.notification.delete.useMutation(); - - const handleOpenModal = useEvent((initValues?: NotificationFormValues) => { - setEditingFormData(initValues); - setOpen(true); - }); - - const handleCloseModal = useEvent(() => { - setEditingFormData(undefined); - setOpen(false); - }); - - const handleSubmit = useEvent(async (values: NotificationFormValues) => { - await upsertMutation.mutateAsync({ - workspaceId: currentWorkspaceId!, - ...values, - }); - handleCloseModal(); - refetch(); - }); - - const handleDelete = useEvent(async (notificationId: string) => { - await deleteMutation.mutateAsync({ - workspaceId: currentWorkspaceId!, - id: notificationId, - }); - refetch(); - }); - - if (!currentWorkspaceId) { - return ; - } - - return ( -
- - -
- } - /> - - ( - } - onClick={() => { - handleOpenModal({ - id: item.id, - name: item.name, - type: item.type, - payload: item.payload as Record, - }); - }} - > - {t('Edit')} - , - { - handleDelete(item.id); - }} - > - - - - - - setOpenChangePassword(false)} - destroyOnClose={true} - > -
{ - const { oldPassword, newPassword } = values; - await changePasswordMutation.mutateAsync({ - oldPassword, - newPassword, - }); - logout(); - }} - > - - - - - - - ({ - validator(rule, value) { - if (!value || form.getFieldValue('newPassword') === value) { - return Promise.resolve(); - } - - return Promise.reject( - t('The two passwords are not consistent') - ); - }, - }), - ]} - > - - - - - - -
- - ); -}); -Profile.displayName = 'Profile'; diff --git a/src/client/pages/Settings/Usage.tsx b/src/client/pages/Settings/Usage.tsx deleted file mode 100644 index 572e36b..0000000 --- a/src/client/pages/Settings/Usage.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Card, Statistic } from 'antd'; -import React, { useMemo } from 'react'; -import { PageHeader } from '../../components/PageHeader'; -import { useTranslation } from '@i18next-toolkit/react'; -import { trpc } from '../../api/trpc'; -import { useCurrentWorkspaceId } from '../../store/user'; -import dayjs from 'dayjs'; -import { formatNumber } from '../../utils/common'; - -export const Usage: React.FC = React.memo(() => { - const { t } = useTranslation(); - const workspaceId = useCurrentWorkspaceId(); - const [startDate, endDate] = useMemo( - () => [dayjs().startOf('month'), dayjs().endOf('day')], - [] - ); - - const { data } = trpc.billing.usage.useQuery({ - workspaceId, - startAt: startDate.valueOf(), - endAt: endDate.valueOf(), - }); - - return ( -
- - - -
- {t('Statistic Date')}: - - {startDate.format('YYYY/MM/DD')} - {endDate.format('YYYY/MM/DD')} - -
- -
- - - - - - - - - - - -
-
-
- ); -}); -Usage.displayName = 'Usage'; diff --git a/src/client/pages/Settings/index.tsx b/src/client/pages/Settings/index.tsx deleted file mode 100644 index eea97ce..0000000 --- a/src/client/pages/Settings/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Menu, MenuProps } from 'antd'; -import React, { useMemo } from 'react'; -import { Routes, Route, useLocation, useNavigate } from 'react-router-dom'; -import { WebsiteInfo } from '../../components/website/WebsiteInfo'; -import { WebsiteList } from '../../components/website/WebsiteList'; -import { useEvent } from '../../hooks/useEvent'; -import { NotificationList } from './NotificationList'; -import { Profile } from './Profile'; -import { AuditLog } from './AuditLog'; -import { Trans } from '@i18next-toolkit/react'; -import { compact } from 'lodash-es'; -import { useGlobalConfig } from '../../hooks/useConfig'; -import { Usage } from './Usage'; - -export const SettingsPage: React.FC = React.memo(() => { - const navigate = useNavigate(); - const { pathname } = useLocation(); - const { alphaMode } = useGlobalConfig(); - - const onClick: MenuProps['onClick'] = useEvent((e) => { - navigate(`/settings/${e.key}`); - }); - - const items: MenuProps['items'] = useMemo( - () => - compact([ - { - key: 'websites', - label: Websites, - }, - { - key: 'notifications', - label: Notifications, - }, - { - key: 'auditLog', - label: Audit Log, - }, - { - key: 'profile', - label: Profile, - }, - alphaMode && { - key: 'usage', - label: Usage, - }, - ]), - [alphaMode] - ); - - const selectedKey = - (items.find((item) => pathname.startsWith(`/settings/${item?.key}`)) - ?.key as string) ?? 'websites'; - - return ( -
-
- -
-
- - } /> - } /> - } /> - } /> - } /> - } /> - - {alphaMode && } />} - -
-
- ); -}); -SettingsPage.displayName = 'SettingsPage'; diff --git a/src/client/pages/Status/index.tsx b/src/client/pages/Status/index.tsx deleted file mode 100644 index a2b5c35..0000000 --- a/src/client/pages/Status/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { useParams } from 'react-router'; -import { MonitorStatusPage } from '../../components/monitor/StatusPage'; - -export const StatusPage: React.FC = React.memo(() => { - const { slug } = useParams<{ slug: string }>(); - - return ; -}); -StatusPage.displayName = 'StatusPage'; diff --git a/src/client/pages/Telemetry/Detail.tsx b/src/client/pages/Telemetry/Detail.tsx deleted file mode 100644 index f11a6d4..0000000 --- a/src/client/pages/Telemetry/Detail.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Card } from 'antd'; -import React from 'react'; -import { useParams } from 'react-router'; -import { NotFoundTip } from '../../components/NotFoundTip'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { TelemetryOverview } from '../../components/telemetry/TelemetryOverview'; -import { TelemetryMetricsTable } from '../../components/telemetry/TelemetryMetricsTable'; -import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const TelemetryDetailPage: React.FC = React.memo(() => { - const { telemetryId } = useParams(); - const workspaceId = useCurrentWorkspaceId(); - const { t } = useTranslation(); - const { startDate, endDate } = useGlobalRangeDate(); - - const startAt = startDate.valueOf(); - const endAt = endDate.valueOf(); - - if (!telemetryId) { - return ; - } - - return ( -
- - - - - - - - - - - - - - - - - -
- ); -}); -TelemetryDetailPage.displayName = 'TelemetryDetailPage'; diff --git a/src/client/pages/Telemetry/index.tsx b/src/client/pages/Telemetry/index.tsx deleted file mode 100644 index 5f7c2f9..0000000 --- a/src/client/pages/Telemetry/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { TelemetryList } from '../../components/telemetry/TelemetryList'; -import { Route, Routes } from 'react-router-dom'; -import { TelemetryDetailPage } from './Detail'; - -export const TelemetryPage: React.FC = React.memo(() => { - return ( - - } /> - } /> - - ); -}); -TelemetryPage.displayName = 'TelemetryPage'; diff --git a/src/client/pages/Website/Detail.tsx b/src/client/pages/Website/Detail.tsx deleted file mode 100644 index bd9e297..0000000 --- a/src/client/pages/Website/Detail.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { Button, Card } from 'antd'; -import React from 'react'; -import { useNavigate, useParams } from 'react-router'; -import { trpc } from '../../api/trpc'; -import { ErrorTip } from '../../components/ErrorTip'; -import { Loading } from '../../components/Loading'; -import { NotFoundTip } from '../../components/NotFoundTip'; -import { WebsiteMetricsTable } from '../../components/website/WebsiteMetricsTable'; -import { WebsiteOverview } from '../../components/website/WebsiteOverview'; -import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { RightOutlined } from '@ant-design/icons'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const WebsiteDetail: React.FC = React.memo(() => { - const { t } = useTranslation(); - const { websiteId } = useParams(); - const workspaceId = useCurrentWorkspaceId(); - const { data: website, isLoading } = trpc.website.info.useQuery({ - workspaceId, - websiteId: websiteId!, - }); - const { startDate, endDate } = useGlobalRangeDate(); - const navigate = useNavigate(); - - if (!websiteId) { - return ; - } - - if (isLoading) { - return ; - } - - if (!website) { - return ; - } - - const startAt = startDate.unix() * 1000; - const endAt = endDate.unix() * 1000; - - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -}); -WebsiteDetail.displayName = 'WebsiteDetail'; diff --git a/src/client/pages/Website/Map.tsx b/src/client/pages/Website/Map.tsx deleted file mode 100644 index 4ee0407..0000000 --- a/src/client/pages/Website/Map.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { useNavigate, useParams } from 'react-router'; -import { trpc } from '../../api/trpc'; -import { ErrorTip } from '../../components/ErrorTip'; -import { Loading } from '../../components/Loading'; -import { NotFoundTip } from '../../components/NotFoundTip'; -import { useCurrentWorkspaceId } from '../../store/user'; -import { WebsiteVisitorMap } from '../../components/website/WebsiteVisitorMap'; -import { DateFilter } from '../../components/DateFilter'; -import { LeftOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; -import { useTranslation } from '@i18next-toolkit/react'; - -export const WebsiteVisitorMapPage: React.FC = React.memo(() => { - const { websiteId } = useParams(); - const workspaceId = useCurrentWorkspaceId(); - const { t } = useTranslation(); - const { data: website, isLoading } = trpc.website.info.useQuery({ - workspaceId, - websiteId: websiteId!, - }); - const navigate = useNavigate(); - - if (!websiteId) { - return ; - } - - if (isLoading) { - return ; - } - - if (!website) { - return ; - } - - return ( -
-
-
- -
- ); -}); -WebsiteVisitorMapPage.displayName = 'WebsiteVisitorMapPage'; diff --git a/src/client/pages/Website/index.tsx b/src/client/pages/Website/index.tsx deleted file mode 100644 index ddc99c1..0000000 --- a/src/client/pages/Website/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { Route, Routes } from 'react-router'; -import { WebsiteList } from '../../components/website/WebsiteList'; -import { WebsiteDetail } from './Detail'; -import { WebsiteVisitorMapPage } from './Map'; - -export const WebsitePage: React.FC = React.memo(() => { - return ( -
- - } /> - } /> - } /> - -
- ); -}); -WebsitePage.displayName = 'WebsitePage'; diff --git a/src/client/routes/__root.tsx b/src/client/routes/__root.tsx index 6f0f9d0..049fc19 100644 --- a/src/client/routes/__root.tsx +++ b/src/client/routes/__root.tsx @@ -1,4 +1,4 @@ -import { LayoutHeader } from '@/pages/Layout/Header'; +import { LayoutHeader } from '@/components/layout/Header'; import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'; // import { TanStackRouterDevtools } from '@tanstack/router-devtools'; import { Suspense } from 'react'; diff --git a/src/client/routes/monitor.tsx b/src/client/routes/monitor.tsx index a066248..9b25bbd 100644 --- a/src/client/routes/monitor.tsx +++ b/src/client/routes/monitor.tsx @@ -6,7 +6,7 @@ import { MonitorHealthBar } from '@/components/monitor/MonitorHealthBar'; import { Button } from '@/components/ui/button'; import { useDataReady } from '@/hooks/useDataReady'; import { useEvent } from '@/hooks/useEvent'; -import { LayoutV2 } from '@/pages/LayoutV2'; +import { Layout } from '@/components/layout'; import { useCurrentWorkspaceId } from '@/store/user'; import { routeAuthBeforeLoad } from '@/utils/route'; import { cn } from '@/utils/style'; @@ -71,7 +71,7 @@ function MonitorComponent() { }); return ( - + - + ); } diff --git a/src/client/routes/settings.tsx b/src/client/routes/settings.tsx index 216fbc9..8d15193 100644 --- a/src/client/routes/settings.tsx +++ b/src/client/routes/settings.tsx @@ -1,66 +1,5 @@ -import { CommonHeader } from '@/components/CommonHeader'; -import { CommonList } from '@/components/CommonList'; -import { CommonWrapper } from '@/components/CommonWrapper'; -import { LayoutV2 } from '@/pages/LayoutV2'; -import { routeAuthBeforeLoad } from '@/utils/route'; -import { useTranslation } from '@i18next-toolkit/react'; -import { - createFileRoute, - useNavigate, - useRouterState, -} from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/settings')({ - beforeLoad: routeAuthBeforeLoad, - component: PageComponent, -}); - -function PageComponent() { - const { t } = useTranslation(); - const navigate = useNavigate(); - const pathname = useRouterState({ - select: (state) => state.location.pathname, - }); - - const items = [ - { - id: 'profile', - title: t('Profile'), - href: '/settings/profile', - }, - { - id: 'notifications', - title: t('Notifications'), - href: '/settings/notifications', - }, - { - id: 'auditLog', - title: t('Audit Log'), - href: '/settings/auditLog', - }, - { - id: 'usage', - title: t('Usage'), - href: '/settings/usage', - }, - ]; - - useEffect(() => { - if (pathname === Route.fullPath) { - navigate({ - to: '/settings/profile', - }); - } - }, []); - - return ( - }> - - - } - /> - ); -} + component: () =>
Hello /settings!
+}) \ No newline at end of file diff --git a/src/client/routes/survey.tsx b/src/client/routes/survey.tsx index 24a6a20..406a82a 100644 --- a/src/client/routes/survey.tsx +++ b/src/client/routes/survey.tsx @@ -5,7 +5,7 @@ import { CommonWrapper } from '@/components/CommonWrapper'; import { Button } from '@/components/ui/button'; import { useDataReady } from '@/hooks/useDataReady'; import { useEvent } from '@/hooks/useEvent'; -import { LayoutV2 } from '@/pages/LayoutV2'; +import { Layout } from '@/components/layout'; import { useCurrentWorkspaceId } from '@/store/user'; import { routeAuthBeforeLoad } from '@/utils/route'; import { cn } from '@/utils/style'; @@ -64,7 +64,7 @@ function PageComponent() { }); return ( -