feat: add i18n support #3
This commit is contained in:
parent
3d9921f16f
commit
bf6b121041
758
pnpm-lock.yaml
758
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ import { DateRange, useGlobalStateStore } from '../store/global';
|
|||||||
import { compact } from 'lodash-es';
|
import { compact } from 'lodash-es';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useGlobalRangeDate } from '../hooks/useGlobalRangeDate';
|
import { useGlobalRangeDate } from '../hooks/useGlobalRangeDate';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ interface DateFilterProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [showPicker, setShowPicker] = useState(false);
|
const [showPicker, setShowPicker] = useState(false);
|
||||||
const [showDropdown, setShowDropdown] = useState(false);
|
const [showDropdown, setShowDropdown] = useState(false);
|
||||||
|
|
||||||
@ -26,19 +28,19 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
|||||||
},
|
},
|
||||||
items: compact([
|
items: compact([
|
||||||
{
|
{
|
||||||
label: 'Today',
|
label: t('Today'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.Today });
|
useGlobalStateStore.setState({ dateRange: DateRange.Today });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last 24 Hours',
|
label: t('Last 24 Hours'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.Last24Hours });
|
useGlobalStateStore.setState({ dateRange: DateRange.Last24Hours });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Yesterday',
|
label: t('Yesterday'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.Yesterday });
|
useGlobalStateStore.setState({ dateRange: DateRange.Yesterday });
|
||||||
},
|
},
|
||||||
@ -47,13 +49,13 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
|||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'This week',
|
label: t('This week'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.ThisWeek });
|
useGlobalStateStore.setState({ dateRange: DateRange.ThisWeek });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last 7 days',
|
label: t('Last 7 days'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.Last7Days });
|
useGlobalStateStore.setState({ dateRange: DateRange.Last7Days });
|
||||||
},
|
},
|
||||||
@ -62,25 +64,25 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
|||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'This Month',
|
label: t('This Month'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.ThisMonth });
|
useGlobalStateStore.setState({ dateRange: DateRange.ThisMonth });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last 30 days',
|
label: t('Last 30 days'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.Last30Days });
|
useGlobalStateStore.setState({ dateRange: DateRange.Last30Days });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Last 90 days',
|
label: t('Last 90 days'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.Last90Days });
|
useGlobalStateStore.setState({ dateRange: DateRange.Last90Days });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'This year',
|
label: t('This year'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
useGlobalStateStore.setState({ dateRange: DateRange.ThisYear });
|
useGlobalStateStore.setState({ dateRange: DateRange.ThisYear });
|
||||||
},
|
},
|
||||||
@ -89,7 +91,7 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
|||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Custom',
|
label: t('Custom'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setShowPicker(true);
|
setShowPicker(true);
|
||||||
},
|
},
|
||||||
@ -123,7 +125,7 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
|||||||
|
|
||||||
{showPicker && (
|
{showPicker && (
|
||||||
<Modal
|
<Modal
|
||||||
title="Select your date range"
|
title={t('Select your date range')}
|
||||||
open={showPicker}
|
open={showPicker}
|
||||||
onCancel={() => setShowPicker(false)}
|
onCancel={() => setShowPicker(false)}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
|
73
src/client/components/LanguageSelector.tsx
Normal file
73
src/client/components/LanguageSelector.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { setLanguage, useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { Button, Dropdown } from 'antd';
|
||||||
|
import { LuLanguages } from 'react-icons/lu';
|
||||||
|
|
||||||
|
export const LanguageSelector: React.FC = React.memo(() => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger={['click']}
|
||||||
|
placement="bottomRight"
|
||||||
|
menu={{
|
||||||
|
selectedKeys: [i18n.language],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'English',
|
||||||
|
key: 'en',
|
||||||
|
itemIcon: <CountryFlag code="en" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Deutsch',
|
||||||
|
key: 'de',
|
||||||
|
itemIcon: <CountryFlag code="de" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Français',
|
||||||
|
key: 'fr',
|
||||||
|
itemIcon: <CountryFlag code="fr" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '日本語',
|
||||||
|
key: 'jp',
|
||||||
|
itemIcon: <CountryFlag code="jp" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Русский',
|
||||||
|
key: 'ru',
|
||||||
|
itemIcon: <CountryFlag code="ru" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '简体中文',
|
||||||
|
key: 'zh',
|
||||||
|
itemIcon: <CountryFlag code="zh" />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onClick: (info) => {
|
||||||
|
setLanguage(info.key);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<LuLanguages className="anticon" />}
|
||||||
|
shape="circle"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
LanguageSelector.displayName = 'LanguageSelector';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* image is from discord
|
||||||
|
*/
|
||||||
|
export const CountryFlag: React.FC<{ code: string }> = React.memo((props) => {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className="w-[27px] h-[18px] ml-6"
|
||||||
|
src={`/locales/${props.code}/flag.png`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
CountryFlag.displayName = 'CountryFlag';
|
@ -1,6 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const NoWorkspaceTip: React.FC = React.memo(() => {
|
export const NoWorkspaceTip: React.FC = React.memo(() => {
|
||||||
return <div>Please Select Workspace</div>;
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <div>{t('Please Select Workspace')}</div>;
|
||||||
});
|
});
|
||||||
NoWorkspaceTip.displayName = 'NoWorkspaceTip';
|
NoWorkspaceTip.displayName = 'NoWorkspaceTip';
|
||||||
|
@ -5,8 +5,10 @@ import { useCurrentWorkspaceId } from '../../store/user';
|
|||||||
import { useDashboardStore } from '../../store/dashboard';
|
import { useDashboardStore } from '../../store/dashboard';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const DashboardItemAddButton: React.FC = React.memo(() => {
|
export const DashboardItemAddButton: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data: websites = [], isLoading: isWebsiteLoading } =
|
const { data: websites = [], isLoading: isWebsiteLoading } =
|
||||||
trpc.website.all.useQuery({
|
trpc.website.all.useQuery({
|
||||||
@ -25,7 +27,7 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'website',
|
key: 'website',
|
||||||
label: 'Website',
|
label: t('Website'),
|
||||||
children:
|
children:
|
||||||
websites.length > 0
|
websites.length > 0
|
||||||
? websites.map((website) => ({
|
? websites.map((website) => ({
|
||||||
@ -34,7 +36,7 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: `website#${website.id}#overview`,
|
key: `website#${website.id}#overview`,
|
||||||
label: 'Overview',
|
label: t('Overview'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addItem(
|
addItem(
|
||||||
'websiteOverview',
|
'websiteOverview',
|
||||||
@ -45,7 +47,7 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: `website#${website.id}#events`,
|
key: `website#${website.id}#events`,
|
||||||
label: 'Events',
|
label: t('Events'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addItem(
|
addItem(
|
||||||
'websiteEvents',
|
'websiteEvents',
|
||||||
@ -59,14 +61,14 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
key: `website#none`,
|
key: `website#none`,
|
||||||
label: '(None)',
|
label: t('(None)'),
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'monitor',
|
key: 'monitor',
|
||||||
label: 'Monitor',
|
label: t('Monitor'),
|
||||||
children:
|
children:
|
||||||
monitors.length > 0
|
monitors.length > 0
|
||||||
? monitors.map((monitor) => ({
|
? monitors.map((monitor) => ({
|
||||||
@ -75,45 +77,53 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: `monitor#${monitor.id}#healthBar`,
|
key: `monitor#${monitor.id}#healthBar`,
|
||||||
label: 'Health Bar',
|
label: t('Health Bar'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addItem(
|
addItem(
|
||||||
'monitorHealthBar',
|
'monitorHealthBar',
|
||||||
monitor.id,
|
monitor.id,
|
||||||
`${monitor.name}'s Health`
|
t("{{monitorName}}'s Health", {
|
||||||
|
monitorName: monitor.name,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: `monitor#${monitor.id}#metrics`,
|
key: `monitor#${monitor.id}#metrics`,
|
||||||
label: 'Metrics',
|
label: t('Metrics'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addItem(
|
addItem(
|
||||||
'monitorMetrics',
|
'monitorMetrics',
|
||||||
monitor.id,
|
monitor.id,
|
||||||
`${monitor.name}'s Metrics`
|
t("{{monitorName}}'s Metrics", {
|
||||||
|
monitorName: monitor.name,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: `monitor#${monitor.id}#chart`,
|
key: `monitor#${monitor.id}#chart`,
|
||||||
label: 'Chart',
|
label: t('Chart'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addItem(
|
addItem(
|
||||||
'monitorChart',
|
'monitorChart',
|
||||||
monitor.id,
|
monitor.id,
|
||||||
`${monitor.name}'s Chart`
|
t("{{monitorName}}'s Chart", {
|
||||||
|
monitorName: monitor.name,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: `monitor#${monitor.id}#events`,
|
key: `monitor#${monitor.id}#events`,
|
||||||
label: 'Events',
|
label: t('Events'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
addItem(
|
addItem(
|
||||||
'monitorEvents',
|
'monitorEvents',
|
||||||
monitor.id,
|
monitor.id,
|
||||||
`${monitor.name}'s Events`
|
t("{{monitorName}}'s Events", {
|
||||||
|
monitorName: monitor.name,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -122,7 +132,7 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
key: `monitor#none`,
|
key: `monitor#none`,
|
||||||
label: '(None)',
|
label: t('(None)'),
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -141,7 +151,7 @@ export const DashboardItemAddButton: React.FC = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Button type="primary" size="large" className="w-32">
|
<Button type="primary" size="large" className="w-32">
|
||||||
<Space>
|
<Space>
|
||||||
<span>Add</span>
|
<span>{t('Add')}</span>
|
||||||
<DownOutlined
|
<DownOutlined
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'transition-transform scale-y-75',
|
'transition-transform scale-y-75',
|
||||||
|
@ -9,8 +9,10 @@ import { DateFilter } from '../DateFilter';
|
|||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
import { useCurrentWorkspace, useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspace, useCurrentWorkspaceId } from '../../store/user';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const Dashboard: React.FC = React.memo(() => {
|
export const Dashboard: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { isEditMode, switchEditMode, layouts, items } = useDashboardStore();
|
const { isEditMode, switchEditMode, layouts, items } = useDashboardStore();
|
||||||
const mutation = trpc.workspace.saveDashboardLayout.useMutation();
|
const mutation = trpc.workspace.saveDashboardLayout.useMutation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
@ -42,7 +44,7 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
switchEditMode();
|
switchEditMode();
|
||||||
message.success('Layout saved success');
|
message.success(t('Layout saved success'));
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,7 +65,7 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
disabled={mutation.isLoading}
|
disabled={mutation.isLoading}
|
||||||
onClick={handleSaveDashboardLayout}
|
onClick={handleSaveDashboardLayout}
|
||||||
>
|
>
|
||||||
Done
|
{t('Done')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -75,7 +77,7 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={switchEditMode}
|
onClick={switchEditMode}
|
||||||
>
|
>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -88,7 +90,11 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{items.length === 0 && (
|
{items.length === 0 && (
|
||||||
<Empty description="You have not dashboard item yet, please enter edit mode and add you item." />
|
<Empty
|
||||||
|
description={t(
|
||||||
|
'You have not dashboard item yet, please enter edit mode and add you item.'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -7,10 +7,12 @@ import { WebsiteOverview } from '../../website/WebsiteOverview';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteOverviewItem: React.FC<{
|
export const WebsiteOverviewItem: React.FC<{
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -39,7 +41,7 @@ export const WebsiteOverviewItem: React.FC<{
|
|||||||
navigate(`/website/${websiteInfo.id}`);
|
navigate(`/website/${websiteInfo.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View Details <ArrowRightOutlined />
|
{t('View Details')} <ArrowRightOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
import { useEvent } from '../../../hooks/useEvent';
|
import { useEvent } from '../../../hooks/useEvent';
|
||||||
import { useCurrentWorkspaceId } from '../../../store/user';
|
import { useCurrentWorkspaceId } from '../../../store/user';
|
||||||
import { notificationStrategies } from './strategies';
|
import { notificationStrategies } from './strategies';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export interface NotificationFormValues {
|
export interface NotificationFormValues {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -36,6 +37,7 @@ interface NotificationInfoModalProps
|
|||||||
}
|
}
|
||||||
export const NotificationInfoModal: React.FC<NotificationInfoModalProps> =
|
export const NotificationInfoModal: React.FC<NotificationInfoModalProps> =
|
||||||
React.memo((props) => {
|
React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const typeValue = Form.useWatch('type', form);
|
const typeValue = Form.useWatch('type', form);
|
||||||
const currentWorkspaceId = useCurrentWorkspaceId()!;
|
const currentWorkspaceId = useCurrentWorkspaceId()!;
|
||||||
@ -85,7 +87,7 @@ export const NotificationInfoModal: React.FC<NotificationInfoModalProps> =
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Notification"
|
title={t('Notification')}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
centered={true}
|
centered={true}
|
||||||
@ -94,10 +96,10 @@ export const NotificationInfoModal: React.FC<NotificationInfoModalProps> =
|
|||||||
footer={
|
footer={
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button loading={testMutation.isLoading} onClick={handleTest}>
|
<Button loading={testMutation.isLoading} onClick={handleTest}>
|
||||||
Test
|
{t('Test')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" onClick={handleSave}>
|
<Button type="primary" onClick={handleSave}>
|
||||||
Save
|
{t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -110,7 +112,7 @@ export const NotificationInfoModal: React.FC<NotificationInfoModalProps> =
|
|||||||
initialValues={props.initialValues ?? defaultValues}
|
initialValues={props.initialValues ?? defaultValues}
|
||||||
>
|
>
|
||||||
<Form.Item hidden name="id" />
|
<Form.Item hidden name="id" />
|
||||||
<Form.Item label="Notification Type" name="type">
|
<Form.Item label={t('Notification Type')} name="type">
|
||||||
<Select>
|
<Select>
|
||||||
{notificationStrategies.map((s) => (
|
{notificationStrategies.map((s) => (
|
||||||
<Select.Option key={s.name} value={s.name}>
|
<Select.Option key={s.name} value={s.name}>
|
||||||
@ -120,7 +122,7 @@ export const NotificationInfoModal: React.FC<NotificationInfoModalProps> =
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="Display Name" name="name">
|
<Form.Item label={t('Display Name')} name="name">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import { Form, Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const NotificationApprise: React.FC = React.memo(() => {
|
export const NotificationApprise: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Apprise URL"
|
label={t('Apprise URL')}
|
||||||
name={['payload', 'appriseUrl']}
|
name={['payload', 'appriseUrl']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input placeholder="For example: pushdeer://pushKey" />
|
<Input placeholder={t('For example: pushdeer://pushKey')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div className="text-sm opacity-80">
|
<div className="text-sm opacity-80">
|
||||||
Read more:{' '}
|
{t('Read more')}:{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/caronc/apprise/wiki#notification-services"
|
href="https://github.com/caronc/apprise/wiki#notification-services"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -1,68 +1,73 @@
|
|||||||
import { Checkbox, Form, Input, InputNumber, Select } from 'antd';
|
import { Checkbox, Form, Input, InputNumber, Select } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const NotificationSMTP: React.FC = React.memo(() => {
|
export const NotificationSMTP: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Host"
|
label={t('Host')}
|
||||||
name={['payload', 'hostname']}
|
name={['payload', 'hostname']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Port"
|
label={t('Port')}
|
||||||
name={['payload', 'port']}
|
name={['payload', 'port']}
|
||||||
rules={[{ required: true }, { type: 'number', min: 0, max: 65535 }]}
|
rules={[{ required: true }, { type: 'number', min: 0, max: 65535 }]}
|
||||||
>
|
>
|
||||||
<InputNumber max={65535} min={1} />
|
<InputNumber max={65535} min={1} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Security"
|
label={t('Security')}
|
||||||
name={['payload', 'security']}
|
name={['payload', 'security']}
|
||||||
initialValue={false}
|
initialValue={false}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select>
|
||||||
<Select.Option value={false}>None / STARTTLS (25, 587)</Select.Option>
|
<Select.Option value={false}>
|
||||||
|
{t('None / STARTTLS')} (25, 587)
|
||||||
|
</Select.Option>
|
||||||
<Select.Option value={true}>TLS (465)</Select.Option>
|
<Select.Option value={true}>TLS (465)</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name={['payload', 'ignoreTLS']} valuePropName="checked">
|
<Form.Item name={['payload', 'ignoreTLS']} valuePropName="checked">
|
||||||
<Checkbox>Ignore TLS Error</Checkbox>
|
<Checkbox>{t('Ignore TLS Error')}</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Username"
|
label={t('Username')}
|
||||||
name={['payload', 'username']}
|
name={['payload', 'username']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Password"
|
label={t('Password')}
|
||||||
name={['payload', 'password']}
|
name={['payload', 'password']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input.Password />
|
<Input.Password />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="From Email"
|
label={t('From Email')}
|
||||||
name={['payload', 'from']}
|
name={['payload', 'from']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="To Email"
|
label={t('To Email')}
|
||||||
name={['payload', 'to']}
|
name={['payload', 'to']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="CC" name={['payload', 'cc']}>
|
<Form.Item label={t('CC')} name={['payload', 'cc']}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="BCC" name={['payload', 'bcc']}>
|
<Form.Item label={t('BCC')} name={['payload', 'bcc']}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
@ -4,8 +4,10 @@ import { useEvent } from '../../../../hooks/useEvent';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { AutoLoadingButton } from '../../../AutoLoadingButton';
|
import { AutoLoadingButton } from '../../../AutoLoadingButton';
|
||||||
import { get, last } from 'lodash-es';
|
import { get, last } from 'lodash-es';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const NotificationTelegram: React.FC = React.memo(() => {
|
export const NotificationTelegram: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const token = Form.useWatch(['payload', 'botToken']);
|
const token = Form.useWatch(['payload', 'botToken']);
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
|
|
||||||
@ -32,16 +34,16 @@ export const NotificationTelegram: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Bot Token"
|
label={t('Bot Token')}
|
||||||
name={['payload', 'botToken']}
|
name={['payload', 'botToken']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input.Password />
|
<Input.Password />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Typography.Paragraph className="text-neutral-500">
|
<Typography.Paragraph className="text-neutral-500">
|
||||||
You can get a token from https://t.me/BotFather.
|
{t('You can get a token from https://t.me/BotFather.')}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Form.Item label="Chat ID" required={true}>
|
<Form.Item label={t('Chat ID')} required={true}>
|
||||||
<div className="flex gap-2 overflow-hidden">
|
<div className="flex gap-2 overflow-hidden">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
@ -54,17 +56,19 @@ export const NotificationTelegram: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
{token && (
|
{token && (
|
||||||
<AutoLoadingButton onClick={handleAutoGet}>
|
<AutoLoadingButton onClick={handleAutoGet}>
|
||||||
Auto Fetch
|
{t('Auto Fetch')}
|
||||||
</AutoLoadingButton>
|
</AutoLoadingButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Typography.Paragraph className="text-neutral-500">
|
<Typography.Paragraph className="text-neutral-500">
|
||||||
Support Direct Chat / Group / Channel's Chat ID
|
{t("Support Direct Chat / Group / Channel's Chat ID")}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Typography.Paragraph className="text-neutral-500">
|
<Typography.Paragraph className="text-neutral-500">
|
||||||
You can get your chat ID by sending a message to the bot and going to
|
{t(
|
||||||
this URL to view the chat_id:
|
'You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id'
|
||||||
|
)}
|
||||||
|
:
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Typography.Link href={getUpdatesUrl(token)} target="_blank">
|
<Typography.Link href={getUpdatesUrl(token)} target="_blank">
|
||||||
{getUpdatesUrl('*'.repeat(token?.length ?? 0))}
|
{getUpdatesUrl('*'.repeat(token?.length ?? 0))}
|
||||||
|
@ -2,12 +2,14 @@ import { Checkbox, Divider, Input, message } from 'antd';
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useEvent } from '../../hooks/useEvent';
|
import { useEvent } from '../../hooks/useEvent';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorBadgeView: React.FC<{
|
export const MonitorBadgeView: React.FC<{
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
monitorName?: string;
|
monitorName?: string;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { workspaceId, monitorId, monitorName = '' } = props;
|
const { workspaceId, monitorId, monitorName = '' } = props;
|
||||||
|
|
||||||
const [showDetail, setShowDetail] = useState(false);
|
const [showDetail, setShowDetail] = useState(false);
|
||||||
@ -24,7 +26,7 @@ export const MonitorBadgeView: React.FC<{
|
|||||||
|
|
||||||
const handleCopy = useEvent((text: string) => {
|
const handleCopy = useEvent((text: string) => {
|
||||||
copy(text);
|
copy(text);
|
||||||
message.success('Copy success!');
|
message.success(t('Copy success!'));
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -32,19 +34,19 @@ export const MonitorBadgeView: React.FC<{
|
|||||||
<div>
|
<div>
|
||||||
<img src={url} />
|
<img src={url} />
|
||||||
</div>
|
</div>
|
||||||
<p>This will show your recent result of your monitor</p>
|
<p>{t('This will show your recent result of your monitor')}</p>
|
||||||
<div>
|
<div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={showDetail}
|
checked={showDetail}
|
||||||
onChange={(e) => setShowDetail(e.target.checked)}
|
onChange={(e) => setShowDetail(e.target.checked)}
|
||||||
>
|
>
|
||||||
Show Detail Number
|
{t('Show Detail Number')}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<p>Share with...</p>
|
<p>{t('Share with...')}</p>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Input
|
<Input
|
||||||
@ -57,7 +59,7 @@ export const MonitorBadgeView: React.FC<{
|
|||||||
handleCopy(`<img src="${url}" title="${monitorName}" />`)
|
handleCopy(`<img src="${url}" title="${monitorName}" />`)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Copy
|
{t('Copy')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -70,7 +72,7 @@ export const MonitorBadgeView: React.FC<{
|
|||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => handleCopy(`![${monitorName}](${url})`)}
|
onClick={() => handleCopy(`![${monitorName}](${url})`)}
|
||||||
>
|
>
|
||||||
Copy
|
{t('Copy')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -83,7 +85,7 @@ export const MonitorBadgeView: React.FC<{
|
|||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => handleCopy(`[img]${url}[/img]`)}
|
onClick={() => handleCopy(`[img]${url}[/img]`)}
|
||||||
>
|
>
|
||||||
Copy
|
{t('Copy')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -93,7 +95,7 @@ export const MonitorBadgeView: React.FC<{
|
|||||||
value={url}
|
value={url}
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<div className="cursor-pointer" onClick={() => handleCopy(url)}>
|
<div className="cursor-pointer" onClick={() => handleCopy(url)}>
|
||||||
Copy
|
{t('Copy')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -7,9 +7,11 @@ import { useSocketSubscribeList } from '../../api/socketio';
|
|||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
import { getMonitorProvider, getProviderDisplay } from './provider';
|
import { getMonitorProvider, getProviderDisplay } from './provider';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { monitorId } = props;
|
const { monitorId } = props;
|
||||||
const [rangeType, setRangeType] = useState('recent');
|
const [rangeType, setRangeType] = useState('recent');
|
||||||
@ -145,11 +147,11 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
|||||||
value={rangeType}
|
value={rangeType}
|
||||||
onChange={(val) => setRangeType(val)}
|
onChange={(val) => setRangeType(val)}
|
||||||
>
|
>
|
||||||
<Select.Option value="recent">Recent</Select.Option>
|
<Select.Option value="recent">{t('Recent')}</Select.Option>
|
||||||
<Select.Option value="3h">3h</Select.Option>
|
<Select.Option value="3h">{t('3h')}</Select.Option>
|
||||||
<Select.Option value="6h">6h</Select.Option>
|
<Select.Option value="6h">{t('6h')}</Select.Option>
|
||||||
<Select.Option value="24h">24h</Select.Option>
|
<Select.Option value="24h">{t('24h')}</Select.Option>
|
||||||
<Select.Option value="1w">1w</Select.Option>
|
<Select.Option value="1w">{t('1w')}</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ import { ErrorTip } from '../ErrorTip';
|
|||||||
import { Loading } from '../Loading';
|
import { Loading } from '../Loading';
|
||||||
import { getMonitorProvider } from './provider';
|
import { getMonitorProvider } from './provider';
|
||||||
import { MonitorStatsBlock } from './MonitorStatsBlock';
|
import { MonitorStatsBlock } from './MonitorStatsBlock';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorDataMetrics: React.FC<{
|
export const MonitorDataMetrics: React.FC<{
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
monitorType: string;
|
monitorType: string;
|
||||||
currectResponse?: number;
|
currectResponse?: number;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { monitorId, monitorType, currectResponse } = props;
|
const { monitorId, monitorType, currectResponse } = props;
|
||||||
const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({
|
const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({
|
||||||
@ -53,20 +55,20 @@ export const MonitorDataMetrics: React.FC<{
|
|||||||
<div className="flex justify-between text-center">
|
<div className="flex justify-between text-center">
|
||||||
{typeof currectResponse === 'number' && (
|
{typeof currectResponse === 'number' && (
|
||||||
<MonitorStatsBlock
|
<MonitorStatsBlock
|
||||||
title="Response"
|
title={t('Response')}
|
||||||
desc="(Current)"
|
desc={t('(Current)')}
|
||||||
text={formatterFn(currectResponse)}
|
text={formatterFn(currectResponse)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MonitorStatsBlock
|
<MonitorStatsBlock
|
||||||
title="Avg. Response"
|
title={t('Avg. Response')}
|
||||||
desc="(24 hour)"
|
desc={t('(24 hour)')}
|
||||||
text={formatterFn(parseFloat(data.recent1DayAvg.toFixed(0)))}
|
text={formatterFn(parseFloat(data.recent1DayAvg.toFixed(0)))}
|
||||||
/>
|
/>
|
||||||
<MonitorStatsBlock
|
<MonitorStatsBlock
|
||||||
title="Uptime"
|
title={t('Uptime')}
|
||||||
desc="(24 hour)"
|
desc={t('(24 hour)')}
|
||||||
text={`${parseFloat(
|
text={`${parseFloat(
|
||||||
(
|
(
|
||||||
(data.recent1DayOnlineCount /
|
(data.recent1DayOnlineCount /
|
||||||
@ -76,8 +78,8 @@ export const MonitorDataMetrics: React.FC<{
|
|||||||
)} %`}
|
)} %`}
|
||||||
/>
|
/>
|
||||||
<MonitorStatsBlock
|
<MonitorStatsBlock
|
||||||
title="Uptime"
|
title={t('Uptime')}
|
||||||
desc="(30 days)"
|
desc={t('(30 days)')}
|
||||||
text={`${parseFloat(
|
text={`${parseFloat(
|
||||||
(
|
(
|
||||||
(data.recent30DayOnlineCount /
|
(data.recent30DayOnlineCount /
|
||||||
|
@ -5,12 +5,14 @@ import clsx from 'clsx';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Card, Empty } from 'antd';
|
import { Card, Empty } from 'antd';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface MonitorEventListProps {
|
interface MonitorEventListProps {
|
||||||
monitorId?: string;
|
monitorId?: string;
|
||||||
}
|
}
|
||||||
export const MonitorEventList: React.FC<MonitorEventListProps> = React.memo(
|
export const MonitorEventList: React.FC<MonitorEventListProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data = [], isLoading } = trpc.monitor.events.useQuery({
|
const { data = [], isLoading } = trpc.monitor.events.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -19,7 +21,7 @@ export const MonitorEventList: React.FC<MonitorEventListProps> = React.memo(
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
if (isLoading === false && data.length === 0) {
|
if (isLoading === false && data.length === 0) {
|
||||||
return <Empty description="No events" />;
|
return <Empty description={t('No events')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,6 +7,7 @@ import { trpc } from '../../api/trpc';
|
|||||||
import { useWatch } from '../../hooks/useWatch';
|
import { useWatch } from '../../hooks/useWatch';
|
||||||
import { getMonitorProvider, getProviderDisplay } from './provider';
|
import { getMonitorProvider, getProviderDisplay } from './provider';
|
||||||
import { MonitorProvider } from './provider/types';
|
import { MonitorProvider } from './provider/types';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface MonitorHealthBarProps {
|
interface MonitorHealthBarProps {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -21,6 +22,7 @@ interface MonitorHealthBarProps {
|
|||||||
}
|
}
|
||||||
export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
|
export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
monitorId,
|
monitorId,
|
||||||
@ -92,15 +94,15 @@ export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
|
|||||||
<>
|
<>
|
||||||
{last(beats)?.status === 'health' ? (
|
{last(beats)?.status === 'health' ? (
|
||||||
<div className="bg-green-500 text-white px-4 py-1 rounded-full text-lg font-bold">
|
<div className="bg-green-500 text-white px-4 py-1 rounded-full text-lg font-bold">
|
||||||
UP
|
{t('UP')}
|
||||||
</div>
|
</div>
|
||||||
) : last(beats)?.status === 'error' ? (
|
) : last(beats)?.status === 'error' ? (
|
||||||
<div className="bg-red-600 text-white px-4 py-1 rounded-full text-lg font-bold">
|
<div className="bg-red-600 text-white px-4 py-1 rounded-full text-lg font-bold">
|
||||||
DOWN
|
{t('DOWN')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-gray-400 text-white px-4 py-1 rounded-full text-lg font-bold">
|
<div className="bg-gray-400 text-white px-4 py-1 rounded-full text-lg font-bold">
|
||||||
NONE
|
{t('NONE')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -21,11 +21,13 @@ import { MonitorDataMetrics } from './MonitorDataMetrics';
|
|||||||
import { MonitorDataChart } from './MonitorDataChart';
|
import { MonitorDataChart } from './MonitorDataChart';
|
||||||
import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
|
||||||
import { MonitorBadgeView } from './MonitorBadgeView';
|
import { MonitorBadgeView } from './MonitorBadgeView';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface MonitorInfoProps {
|
interface MonitorInfoProps {
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
}
|
}
|
||||||
export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { monitorId } = props;
|
const { monitorId } = props;
|
||||||
const [currectResponse, setCurrentResponse] = useState(0);
|
const [currectResponse, setCurrentResponse] = useState(0);
|
||||||
@ -95,8 +97,8 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
|
|
||||||
const handleDelete = useEvent(async () => {
|
const handleDelete = useEvent(async () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: 'Warning',
|
title: t('Warning'),
|
||||||
content: 'Did you sure delete this monitor?',
|
content: t('Did you sure delete this monitor?'),
|
||||||
okButtonProps: {
|
okButtonProps: {
|
||||||
danger: true,
|
danger: true,
|
||||||
},
|
},
|
||||||
@ -116,8 +118,8 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
|
|
||||||
const handleClearEvents = useEvent(() => {
|
const handleClearEvents = useEvent(() => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: 'Warning',
|
title: t('Warning'),
|
||||||
content: 'Are you sure want to delete all events for this monitor?',
|
content: t('Are you sure want to delete all events for this monitor?'),
|
||||||
okButtonProps: {
|
okButtonProps: {
|
||||||
danger: true,
|
danger: true,
|
||||||
},
|
},
|
||||||
@ -136,8 +138,10 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
|
|
||||||
const handleClearData = useEvent(() => {
|
const handleClearData = useEvent(() => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: 'Warning',
|
title: t('Warning'),
|
||||||
content: 'Are you sure want to delete all heartbeats for this monitor?',
|
content: t(
|
||||||
|
'Are you sure want to delete all heartbeats for this monitor?'
|
||||||
|
),
|
||||||
okButtonProps: {
|
okButtonProps: {
|
||||||
danger: true,
|
danger: true,
|
||||||
},
|
},
|
||||||
@ -170,7 +174,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
<span>{monitorInfo.name}</span>
|
<span>{monitorInfo.name}</span>
|
||||||
{monitorInfo.active === false && (
|
{monitorInfo.active === false && (
|
||||||
<div className="bg-red-500 rounded-full px-2 py-0.5 text-white text-xs">
|
<div className="bg-red-500 rounded-full px-2 py-0.5 text-white text-xs">
|
||||||
Stopped
|
{t('Stopped')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -184,8 +188,9 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<div className="text-black dark:text-gray-200 text-opacity-75">
|
<div className="text-black dark:text-gray-200 text-opacity-75">
|
||||||
Monitored for {dayjs().diff(dayjs(monitorInfo.createdAt), 'days')}{' '}
|
{t('Monitored for {{dayNum}} days', {
|
||||||
days
|
dayNum: dayjs().diff(dayjs(monitorInfo.createdAt), 'days'),
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -196,7 +201,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
navigate(`/monitor/${monitorInfo.id}/edit`);
|
navigate(`/monitor/${monitorInfo.id}/edit`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{monitorInfo.active ? (
|
{monitorInfo.active ? (
|
||||||
@ -204,14 +209,14 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
loading={changeActiveMutation.isLoading}
|
loading={changeActiveMutation.isLoading}
|
||||||
onClick={handleStop}
|
onClick={handleStop}
|
||||||
>
|
>
|
||||||
Stop
|
{t('Stop')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
loading={changeActiveMutation.isLoading}
|
loading={changeActiveMutation.isLoading}
|
||||||
onClick={handleStart}
|
onClick={handleStart}
|
||||||
>
|
>
|
||||||
Start
|
{t('Start')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -222,7 +227,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'badge',
|
key: 'badge',
|
||||||
label: 'Show Badge',
|
label: t('Show Badge'),
|
||||||
onClick: () => setShowBadge(true),
|
onClick: () => setShowBadge(true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -230,7 +235,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: 'Delete',
|
label: t('Delete'),
|
||||||
danger: true,
|
danger: true,
|
||||||
onClick: handleDelete,
|
onClick: handleDelete,
|
||||||
},
|
},
|
||||||
@ -288,19 +293,19 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'events',
|
key: 'events',
|
||||||
label: 'Events',
|
label: t('Events'),
|
||||||
onClick: handleClearEvents,
|
onClick: handleClearEvents,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'heartbeats',
|
key: 'heartbeats',
|
||||||
label: 'Heartbeats',
|
label: t('Heartbeats'),
|
||||||
onClick: handleClearData,
|
onClick: handleClearData,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button icon={<DeleteOutlined />} danger={true}>
|
<Button icon={<DeleteOutlined />} danger={true}>
|
||||||
Clear Data
|
{t('Clear Data')}
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import { Button, Form, Input, InputNumber, Select } from 'antd';
|
|||||||
import { getMonitorProvider, monitorProviders } from './provider';
|
import { getMonitorProvider, monitorProviders } from './provider';
|
||||||
import { useEventWithLoading } from '../../hooks/useEvent';
|
import { useEventWithLoading } from '../../hooks/useEvent';
|
||||||
import { NotificationPicker } from '../notification/NotificationPicker';
|
import { NotificationPicker } from '../notification/NotificationPicker';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export type MonitorInfoEditorValues = Omit<
|
export type MonitorInfoEditorValues = Omit<
|
||||||
Monitor,
|
Monitor,
|
||||||
@ -28,6 +29,7 @@ interface MonitorInfoEditorProps {
|
|||||||
}
|
}
|
||||||
export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const typeValue = Form.useWatch('type', form);
|
const typeValue = Form.useWatch('type', form);
|
||||||
const initialValues = props.initialValues ?? defaultValues;
|
const initialValues = props.initialValues ?? defaultValues;
|
||||||
@ -65,7 +67,7 @@ export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
|||||||
>
|
>
|
||||||
<Form.Item hidden name="id" />
|
<Form.Item hidden name="id" />
|
||||||
|
|
||||||
<Form.Item label="Monitor Type" name="type">
|
<Form.Item label={t('Monitor Type')} name="type">
|
||||||
<Select disabled={isEdit}>
|
<Select disabled={isEdit}>
|
||||||
{monitorProviders.map((m) => (
|
{monitorProviders.map((m) => (
|
||||||
<Select.Option key={m.name} value={m.name}>
|
<Select.Option key={m.name} value={m.name}>
|
||||||
@ -75,12 +77,12 @@ export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="Name" name="name" rules={[{ required: true }]}>
|
<Form.Item label={t('Name')} name="name" rules={[{ required: true }]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Check Interval(s)"
|
label={t('Check Interval(s)')}
|
||||||
name="interval"
|
name="interval"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
@ -92,21 +94,23 @@ export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Max Retries"
|
label={t('Max Retries')}
|
||||||
name="maxRetries"
|
name="maxRetries"
|
||||||
tooltip="Maximum retries before the service is marked as down and a notification is sent"
|
tooltip={t(
|
||||||
|
'Maximum retries before the service is marked as down and a notification is sent'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<InputNumber min={0} max={10} defaultValue={0} />
|
<InputNumber min={0} max={10} defaultValue={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{formEl}
|
{formEl}
|
||||||
|
|
||||||
<Form.Item label="Notification" name="notificationIds">
|
<Form.Item label={t('Notification')} name="notificationIds">
|
||||||
<NotificationPicker allowClear={true} mode="multiple" />
|
<NotificationPicker allowClear={true} mode="multiple" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Button type="primary" htmlType="submit" loading={isLoading}>
|
<Button type="primary" htmlType="submit" loading={isLoading}>
|
||||||
Save
|
{t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,8 +7,10 @@ import { Empty } from 'antd';
|
|||||||
import { MonitorListItem } from './MonitorListItem';
|
import { MonitorListItem } from './MonitorListItem';
|
||||||
import { useNavigate, useParams } from 'react-router';
|
import { useNavigate, useParams } from 'react-router';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorList: React.FC = React.memo(() => {
|
export const MonitorList: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data: monitors = [], isLoading } = trpc.monitor.all.useQuery({
|
const { data: monitors = [], isLoading } = trpc.monitor.all.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -32,7 +34,9 @@ export const MonitorList: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
{monitors.length === 0 && <Empty description="Here is no monitor yet." />}
|
{monitors.length === 0 && (
|
||||||
|
<Empty description={t('Here is no monitor yet.')} />
|
||||||
|
)}
|
||||||
|
|
||||||
{monitors.map((monitor) => (
|
{monitors.map((monitor) => (
|
||||||
<MonitorListItem
|
<MonitorListItem
|
||||||
|
@ -4,6 +4,7 @@ import { MonitorHealthBar } from './MonitorHealthBar';
|
|||||||
import { last } from 'lodash-es';
|
import { last } from 'lodash-es';
|
||||||
import { getMonitorProvider, getProviderDisplay } from './provider';
|
import { getMonitorProvider, getProviderDisplay } from './provider';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorListItem: React.FC<{
|
export const MonitorListItem: React.FC<{
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -14,6 +15,7 @@ export const MonitorListItem: React.FC<{
|
|||||||
showCurrentResponse?: boolean;
|
showCurrentResponse?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -98,7 +100,7 @@ export const MonitorListItem: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showCurrentResponse && latestResponse && (
|
{showCurrentResponse && latestResponse && (
|
||||||
<Tooltip title="Current">
|
<Tooltip title={t('Current')}>
|
||||||
<div className="px-2 text-sm text-gray-800 dark:text-gray-400">
|
<div className="px-2 text-sm text-gray-800 dark:text-gray-400">
|
||||||
{latestResponse}
|
{latestResponse}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,17 +3,19 @@ import React from 'react';
|
|||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
import { ColorTag } from '../ColorTag';
|
import { ColorTag } from '../ColorTag';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface MonitorPickerProps extends SelectProps<string> {}
|
interface MonitorPickerProps extends SelectProps<string> {}
|
||||||
export const MonitorPicker: React.FC<MonitorPickerProps> = React.memo(
|
export const MonitorPicker: React.FC<MonitorPickerProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data: allMonitor = [] } = trpc.monitor.all.useQuery({
|
const { data: allMonitor = [] } = trpc.monitor.all.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select placeholder="Select monitor" {...props}>
|
<Select placeholder={t('Select monitor')} {...props}>
|
||||||
{allMonitor.map((m) => (
|
{allMonitor.map((m) => (
|
||||||
<Select.Option key={m.id} value={m.id}>
|
<Select.Option key={m.id} value={m.id}>
|
||||||
<ColorTag label={m.type} />
|
<ColorTag label={m.type} />
|
||||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { MonitorPicker } from '../MonitorPicker';
|
import { MonitorPicker } from '../MonitorPicker';
|
||||||
import { urlSlugValidator } from '../../../utils/validator';
|
import { urlSlugValidator } from '../../../utils/validator';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@ -22,6 +23,8 @@ interface MonitorStatusPageEditFormProps {
|
|||||||
|
|
||||||
export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps> =
|
export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps> =
|
||||||
React.memo((props) => {
|
React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form<MonitorStatusPageEditFormValues>
|
<Form<MonitorStatusPageEditFormValues>
|
||||||
@ -30,7 +33,7 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
onFinish={props.onFinish}
|
onFinish={props.onFinish}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Title"
|
label={t('Title')}
|
||||||
name="title"
|
name="title"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
@ -47,11 +50,11 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
extra={
|
extra={
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<div>
|
<div>
|
||||||
Accept characters: <Text code>a-z</Text> <Text code>0-9</Text>{' '}
|
{t('Accept characters')}: <Text code>a-z</Text>{' '}
|
||||||
<Text code>-</Text>
|
<Text code>0-9</Text> <Text code>-</Text>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
No consecutive dashes <Text code>--</Text>
|
{t('No consecutive dashes')} <Text code>--</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -71,7 +74,7 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
{(fields, { add, remove }, { errors }) => {
|
{(fields, { add, remove }, { errors }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item label="Monitors">
|
<Form.Item label={t('Monitors')}>
|
||||||
<div className="flex flex-col gap-2 mb-2">
|
<div className="flex flex-col gap-2 mb-2">
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
// monitor item
|
// monitor item
|
||||||
@ -84,7 +87,7 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: 'Please select monitor',
|
message: t('Please select monitor'),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
noStyle={true}
|
noStyle={true}
|
||||||
@ -103,7 +106,7 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<span className="text-sm align-middle ml-1">
|
<span className="text-sm align-middle ml-1">
|
||||||
Show Current Response
|
{t('Show Current Response')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
style={{ width: '60%' }}
|
style={{ width: '60%' }}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
>
|
>
|
||||||
Add Monitor
|
{t('Add Monitor')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Form.ErrorList errors={errors} />
|
<Form.ErrorList errors={errors} />
|
||||||
@ -135,12 +138,12 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
|
|||||||
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Button type="primary" htmlType="submit" loading={props.isLoading}>
|
<Button type="primary" htmlType="submit" loading={props.isLoading}>
|
||||||
{props.saveButtonLabel ?? 'Save'}
|
{props.saveButtonLabel ?? t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{props.onCancel && (
|
{props.onCancel && (
|
||||||
<Button htmlType="button" onClick={props.onCancel}>
|
<Button htmlType="button" onClick={props.onCancel}>
|
||||||
Cancel
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import { trpc } from '../../../api/trpc';
|
|||||||
import { Loading } from '../../Loading';
|
import { Loading } from '../../Loading';
|
||||||
import { MonitorListItem } from '../MonitorListItem';
|
import { MonitorListItem } from '../MonitorListItem';
|
||||||
import { keyBy } from 'lodash-es';
|
import { keyBy } from 'lodash-es';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface StatusPageServicesProps {
|
interface StatusPageServicesProps {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -14,6 +15,7 @@ interface StatusPageServicesProps {
|
|||||||
}
|
}
|
||||||
export const StatusPageServices: React.FC<StatusPageServicesProps> = React.memo(
|
export const StatusPageServices: React.FC<StatusPageServicesProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { workspaceId, monitorList } = props;
|
const { workspaceId, monitorList } = props;
|
||||||
|
|
||||||
const { data: list = [], isLoading } = trpc.monitor.getPublicInfo.useQuery({
|
const { data: list = [], isLoading } = trpc.monitor.getPublicInfo.useQuery({
|
||||||
@ -40,7 +42,7 @@ export const StatusPageServices: React.FC<StatusPageServicesProps> = React.memo(
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Empty description="No any monitor has been set" />
|
<Empty description={t('No any monitor has been set')} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import { useRequest } from '../../../hooks/useRequest';
|
|||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { ColorSchemeSwitcher } from '../../ColorSchemeSwitcher';
|
import { ColorSchemeSwitcher } from '../../ColorSchemeSwitcher';
|
||||||
import { StatusPageServices } from './Services';
|
import { StatusPageServices } from './Services';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface MonitorStatusPageProps {
|
interface MonitorStatusPageProps {
|
||||||
slug: string;
|
slug: string;
|
||||||
@ -18,6 +19,7 @@ interface MonitorStatusPageProps {
|
|||||||
|
|
||||||
export const MonitorStatusPage: React.FC<MonitorStatusPageProps> = React.memo(
|
export const MonitorStatusPage: React.FC<MonitorStatusPageProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { slug } = props;
|
const { slug } = props;
|
||||||
|
|
||||||
const { data: info } = trpc.monitor.getPageInfo.useQuery({
|
const { data: info } = trpc.monitor.getPageInfo.useQuery({
|
||||||
@ -88,16 +90,16 @@ export const MonitorStatusPage: React.FC<MonitorStatusPageProps> = React.memo(
|
|||||||
{allowEdit && !editMode && (
|
{allowEdit && !editMode && (
|
||||||
<div className="mb-4 flex gap-2">
|
<div className="mb-4 flex gap-2">
|
||||||
<Button type="primary" onClick={() => setEditMode(true)}>
|
<Button type="primary" onClick={() => setEditMode(true)}>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button type="default" onClick={() => navigate(`/`)}>
|
<Button type="default" onClick={() => navigate(`/`)}>
|
||||||
Back to Admin
|
{t('Back to Admin')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="text-lg mb-2">Services</div>
|
<div className="text-lg mb-2">{t('Services')}</div>
|
||||||
|
|
||||||
{info && (
|
{info && (
|
||||||
<StatusPageServices
|
<StatusPageServices
|
||||||
|
@ -8,8 +8,10 @@ import { useCurrentWorkspaceId } from '../../../store/user';
|
|||||||
import { useEvent } from '../../../hooks/useEvent';
|
import { useEvent } from '../../../hooks/useEvent';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { ColorTag } from '../../ColorTag';
|
import { ColorTag } from '../../ColorTag';
|
||||||
|
import { Trans, useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorCustom: React.FC = React.memo(() => {
|
export const MonitorCustom: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const testScriptMutation = trpc.monitor.testCustomScript.useMutation({
|
const testScriptMutation = trpc.monitor.testCustomScript.useMutation({
|
||||||
onError: defaultErrorHandler,
|
onError: defaultErrorHandler,
|
||||||
@ -26,7 +28,7 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
|||||||
modal.info({
|
modal.info({
|
||||||
centered: true,
|
centered: true,
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
title: 'Run Completed',
|
title: t('Run Completed'),
|
||||||
width: 'clamp(320px, 60vw, 860px)',
|
width: 'clamp(320px, 60vw, 860px)',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
@ -48,9 +50,9 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div>Usage: {usage}ms</div>
|
<div>{t('Usage: {{usage}}ms', { usage })} </div>
|
||||||
<div>
|
<div>
|
||||||
Result: <span className="font-semibold">{result}</span>
|
{t('Result')}: <span className="font-semibold">{result}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -60,7 +62,7 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Script JS Code"
|
label={t('Script JS Code')}
|
||||||
name={['payload', 'code']}
|
name={['payload', 'code']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
@ -71,7 +73,7 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
|||||||
icon={<PlayCircleOutlined />}
|
icon={<PlayCircleOutlined />}
|
||||||
onClick={handleTestCode}
|
onClick={handleTestCode}
|
||||||
>
|
>
|
||||||
Test Code
|
{t('Test Code')}
|
||||||
</Button>
|
</Button>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
</>
|
</>
|
||||||
@ -80,7 +82,7 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
|||||||
MonitorCustom.displayName = 'MonitorCustom';
|
MonitorCustom.displayName = 'MonitorCustom';
|
||||||
|
|
||||||
export const customProvider: MonitorProvider = {
|
export const customProvider: MonitorProvider = {
|
||||||
label: 'Custom',
|
label: <Trans>Custom</Trans>,
|
||||||
name: 'custom',
|
name: 'custom',
|
||||||
form: MonitorCustom,
|
form: MonitorCustom,
|
||||||
valueLabel: 'Result',
|
valueLabel: 'Result',
|
||||||
|
@ -7,12 +7,15 @@ import dayjs from 'dayjs';
|
|||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
import { useCurrentWorkspaceId } from '../../../store/user';
|
import { useCurrentWorkspaceId } from '../../../store/user';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
const MonitorHttp: React.FC = React.memo(() => {
|
const MonitorHttp: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Url"
|
label={t('Url')}
|
||||||
name={['payload', 'url']}
|
name={['payload', 'url']}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
@ -45,11 +48,11 @@ const MonitorHttp: React.FC = React.memo(() => {
|
|||||||
<Select.Option value="options">OPTIONS</Select.Option>
|
<Select.Option value="options">OPTIONS</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Request Timeout(s)" name={['payload', 'timeout']}>
|
<Form.Item label={t('Request Timeout(s)')} name={['payload', 'timeout']}>
|
||||||
<InputNumber defaultValue={30} />
|
<InputNumber defaultValue={30} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Ignore TLS/SSL error"
|
label={t('Ignore TLS/SSL error')}
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
name={['payload', 'ignoreTLS']}
|
name={['payload', 'ignoreTLS']}
|
||||||
>
|
>
|
||||||
@ -137,6 +140,7 @@ MonitorHttp.displayName = 'MonitorHttp';
|
|||||||
|
|
||||||
export const MonitorHttpOverview: MonitorOverviewComponent = React.memo(
|
export const MonitorHttpOverview: MonitorOverviewComponent = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data } = trpc.monitor.getStatus.useQuery({
|
const { data } = trpc.monitor.getStatus.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -156,9 +160,11 @@ export const MonitorHttpOverview: MonitorOverviewComponent = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MonitorStatsBlock
|
<MonitorStatsBlock
|
||||||
title="Cert Exp."
|
title={t('Cert Exp.')}
|
||||||
desc={dayjs(payload.certInfo?.validTo).format('YYYY-MM-DD')}
|
desc={dayjs(payload.certInfo?.validTo).format('YYYY-MM-DD')}
|
||||||
text={`${payload.certInfo?.daysRemaining} days`}
|
text={t('{{num}} days', {
|
||||||
|
num: payload.certInfo?.daysRemaining,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,15 @@ import { useCurrentWorkspaceId } from '../../../store/user';
|
|||||||
import { trpc } from '../../../api/trpc';
|
import { trpc } from '../../../api/trpc';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { MonitorStatsBlock } from '../MonitorStatsBlock';
|
import { MonitorStatsBlock } from '../MonitorStatsBlock';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorOpenai: React.FC = React.memo(() => {
|
export const MonitorOpenai: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Session Key"
|
label={t('Session Key')}
|
||||||
name={['payload', 'sessionKey']}
|
name={['payload', 'sessionKey']}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
@ -26,6 +29,7 @@ MonitorOpenai.displayName = 'MonitorOpenai';
|
|||||||
|
|
||||||
export const MonitorOpenaiOverview: MonitorOverviewComponent = React.memo(
|
export const MonitorOpenaiOverview: MonitorOverviewComponent = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data } = trpc.monitor.getStatus.useQuery({
|
const { data } = trpc.monitor.getStatus.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -41,7 +45,7 @@ export const MonitorOpenaiOverview: MonitorOverviewComponent = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MonitorStatsBlock
|
<MonitorStatsBlock
|
||||||
title="Usage"
|
title={t('Usage')}
|
||||||
desc={dayjs(data.updatedAt).format('YYYY-MM-DD')}
|
desc={dayjs(data.updatedAt).format('YYYY-MM-DD')}
|
||||||
text={`$${payload.totalUsed} / $${payload.allUSD}`}
|
text={`$${payload.totalUsed} / $${payload.allUSD}`}
|
||||||
/>
|
/>
|
||||||
|
@ -2,12 +2,15 @@ import { Form, Input } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MonitorProvider } from './types';
|
import { MonitorProvider } from './types';
|
||||||
import { hostnameValidator } from '../../../utils/validator';
|
import { hostnameValidator } from '../../../utils/validator';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorPing: React.FC = React.memo(() => {
|
export const MonitorPing: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Host"
|
label={t('Host')}
|
||||||
name={['payload', 'hostname']}
|
name={['payload', 'hostname']}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
|
@ -2,12 +2,15 @@ import { Form, Input, InputNumber } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MonitorProvider } from './types';
|
import { MonitorProvider } from './types';
|
||||||
import { hostnameValidator, portValidator } from '../../../utils/validator';
|
import { hostnameValidator, portValidator } from '../../../utils/validator';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorTCP: React.FC = React.memo(() => {
|
export const MonitorTCP: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Host"
|
label={t('Host')}
|
||||||
name={['payload', 'hostname']}
|
name={['payload', 'hostname']}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
@ -19,7 +22,7 @@ export const MonitorTCP: React.FC = React.memo(() => {
|
|||||||
<Input placeholder="example.com or 1.2.3.4" />
|
<Input placeholder="example.com or 1.2.3.4" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Host"
|
label={t('Port')}
|
||||||
name={['payload', 'port']}
|
name={['payload', 'port']}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { MonitorInfo } from '../../../../types';
|
import { MonitorInfo } from '../../../../types';
|
||||||
|
|
||||||
export interface MonitorProvider {
|
export interface MonitorProvider {
|
||||||
label: string;
|
label: React.ReactNode;
|
||||||
name: string;
|
name: string;
|
||||||
link?: (info: MonitorInfo) => React.ReactNode;
|
link?: (info: MonitorInfo) => React.ReactNode;
|
||||||
form: React.ComponentType;
|
form: React.ComponentType;
|
||||||
|
@ -5,10 +5,12 @@ import { useCurrentWorkspaceId } from '../../store/user';
|
|||||||
import { ColorTag } from '../ColorTag';
|
import { ColorTag } from '../ColorTag';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface NotificationPickerProps extends SelectProps<string> {}
|
interface NotificationPickerProps extends SelectProps<string> {}
|
||||||
export const NotificationPicker: React.FC<NotificationPickerProps> = React.memo(
|
export const NotificationPicker: React.FC<NotificationPickerProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: allNotification = [] } = trpc.notification.all.useQuery({
|
const { data: allNotification = [] } = trpc.notification.all.useQuery({
|
||||||
@ -21,12 +23,12 @@ export const NotificationPicker: React.FC<NotificationPickerProps> = React.memo(
|
|||||||
<Empty
|
<Empty
|
||||||
description={
|
description={
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<div className="mb-1">Not found any notification</div>
|
<div className="mb-1">{t('Not found any notification')}</div>
|
||||||
<Button
|
<Button
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => navigate('/settings/notifications')}
|
onClick={() => navigate('/settings/notifications')}
|
||||||
>
|
>
|
||||||
Create Now
|
{t('Create Now')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Tag } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { formatNumber } from '../../utils/common';
|
import { formatNumber } from '../../utils/common';
|
||||||
import { useGlobalStateStore } from '../../store/global';
|
import { useGlobalStateStore } from '../../store/global';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
interface MetricCardProps {
|
interface MetricCardProps {
|
||||||
value?: number;
|
value?: number;
|
||||||
@ -22,6 +23,7 @@ export const MetricCard: React.FC<MetricCardProps> = React.memo((props) => {
|
|||||||
format = formatNumber,
|
format = formatNumber,
|
||||||
hideComparison = false,
|
hideComparison = false,
|
||||||
} = props;
|
} = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
const showPreviousPeriod = useGlobalStateStore(
|
const showPreviousPeriod = useGlobalStateStore(
|
||||||
(state) => state.showPreviousPeriod
|
(state) => state.showPreviousPeriod
|
||||||
);
|
);
|
||||||
@ -47,7 +49,7 @@ export const MetricCard: React.FC<MetricCardProps> = React.memo((props) => {
|
|||||||
{format(prev)}
|
{format(prev)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center whitespace-nowrap font-bold">
|
<div className="flex items-center whitespace-nowrap font-bold">
|
||||||
<span className="mr-2">Previous {label}</span>
|
<span className="mr-2">{t('Previous {{label}}', { label })} </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -42,7 +42,8 @@ export const MetricsTable: React.FC<MetricsTableProps> = React.memo((props) => {
|
|||||||
title: title[0],
|
title: title[0],
|
||||||
dataIndex: 'x',
|
dataIndex: 'x',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (val) => val ?? <span className="italic opacity-60">(None)</span>,
|
render: (val) =>
|
||||||
|
val ?? <span className="italic opacity-60">{t('(None)')}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: title[1],
|
title: title[1],
|
||||||
|
@ -17,8 +17,10 @@ import {
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useEvent } from '../../hooks/useEvent';
|
import { useEvent } from '../../hooks/useEvent';
|
||||||
import { hostnameValidator } from '../../utils/validator';
|
import { hostnameValidator } from '../../utils/validator';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteInfo: React.FC = React.memo(() => {
|
export const WebsiteInfo: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { websiteId } = useParams<{
|
const { websiteId } = useParams<{
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
@ -54,7 +56,7 @@ export const WebsiteInfo: React.FC = React.memo(() => {
|
|||||||
const [, handleDeleteWebsite] = useRequest(async () => {
|
const [, handleDeleteWebsite] = useRequest(async () => {
|
||||||
await deleteWorkspaceWebsite(workspaceId, websiteId!);
|
await deleteWorkspaceWebsite(workspaceId, websiteId!);
|
||||||
|
|
||||||
message.success('Delete Success');
|
message.success(t('Delete Success'));
|
||||||
|
|
||||||
navigate('/settings/websites');
|
navigate('/settings/websites');
|
||||||
});
|
});
|
||||||
@ -78,7 +80,7 @@ export const WebsiteInfo: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-24 flex items-center">
|
<div className="h-24 flex items-center">
|
||||||
<div className="text-2xl flex-1">Website Info</div>
|
<div className="text-2xl flex-1">{t('Website Info')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -94,14 +96,18 @@ export const WebsiteInfo: React.FC = React.memo(() => {
|
|||||||
}}
|
}}
|
||||||
onFinish={handleSave}
|
onFinish={handleSave}
|
||||||
>
|
>
|
||||||
<Form.Item label="Website ID" name="id">
|
<Form.Item label={t('Website ID')} name="id">
|
||||||
<Input size="large" disabled={true} />
|
<Input size="large" disabled={true} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Name" name="name" rules={[{ required: true }]}>
|
<Form.Item
|
||||||
|
label={t('Name')}
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
<Input size="large" />
|
<Input size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Domain"
|
label={t('Domain')}
|
||||||
name="domain"
|
name="domain"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
@ -114,16 +120,18 @@ export const WebsiteInfo: React.FC = React.memo(() => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Monitor"
|
label={t('Monitor')}
|
||||||
name="monitorId"
|
name="monitorId"
|
||||||
tooltip="You can bind a monitor which will display health status in website overview"
|
tooltip={t(
|
||||||
|
'You can bind a monitor which will display health status in website overview'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MonitorPicker size="large" allowClear={true} />
|
<MonitorPicker size="large" allowClear={true} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button size="large" htmlType="submit">
|
<Button size="large" htmlType="submit">
|
||||||
Save
|
{t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
@ -131,11 +139,11 @@ export const WebsiteInfo: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
<Tabs.TabPane key={'data'} tab={'Data'}>
|
<Tabs.TabPane key={'data'} tab={'Data'}>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="Delete Website"
|
title={t('Delete Website')}
|
||||||
onConfirm={() => handleDeleteWebsite()}
|
onConfirm={() => handleDeleteWebsite()}
|
||||||
>
|
>
|
||||||
<Button type="primary" danger={true}>
|
<Button type="primary" danger={true}>
|
||||||
Delete Website
|
{t('Delete Website')}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
@ -16,8 +16,10 @@ import { PageHeader } from '../PageHeader';
|
|||||||
import { ModalButton } from '../ModalButton';
|
import { ModalButton } from '../ModalButton';
|
||||||
import { hostnameValidator } from '../../utils/validator';
|
import { hostnameValidator } from '../../utils/validator';
|
||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteList: React.FC = React.memo(() => {
|
export const WebsiteList: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@ -44,7 +46,7 @@ export const WebsiteList: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Websites"
|
title={t('Websites')}
|
||||||
action={
|
action={
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@ -53,7 +55,7 @@ export const WebsiteList: React.FC = React.memo(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={() => setIsModalOpen(true)}
|
onClick={() => setIsModalOpen(true)}
|
||||||
>
|
>
|
||||||
Add Website
|
{t('Add Website')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -62,7 +64,7 @@ export const WebsiteList: React.FC = React.memo(() => {
|
|||||||
<WebsiteListTable workspaceId={workspaceId} />
|
<WebsiteListTable workspaceId={workspaceId} />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Add Website"
|
title={t('Add Website')}
|
||||||
open={isModalOpen}
|
open={isModalOpen}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
loading: addWebsiteMutation.isLoading,
|
loading: addWebsiteMutation.isLoading,
|
||||||
@ -72,17 +74,17 @@ export const WebsiteList: React.FC = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<Form layout="vertical" form={form}>
|
<Form layout="vertical" form={form}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Website Name"
|
label={t('Website Name')}
|
||||||
name="name"
|
name="name"
|
||||||
tooltip="Website Name to Display"
|
tooltip={t('Website Name to Display')}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Domain"
|
label={t('Domain')}
|
||||||
name="domain"
|
name="domain"
|
||||||
tooltip="Your server domain, or ip."
|
tooltip={t('Your server domain, or ip.')}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
{
|
{
|
||||||
@ -105,6 +107,7 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
|
|||||||
workspaceId: props.workspaceId,
|
workspaceId: props.workspaceId,
|
||||||
});
|
});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleEdit = useEvent((websiteId) => {
|
const handleEdit = useEvent((websiteId) => {
|
||||||
navigate(`/settings/website/${websiteId}`);
|
navigate(`/settings/website/${websiteId}`);
|
||||||
@ -114,11 +117,11 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: 'Name',
|
title: t('Name'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'domain',
|
dataIndex: 'domain',
|
||||||
title: 'Domain',
|
title: t('Domain'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'action',
|
key: 'action',
|
||||||
@ -135,9 +138,9 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
|
|||||||
modalProps={{
|
modalProps={{
|
||||||
children: (
|
children: (
|
||||||
<div>
|
<div>
|
||||||
<div>Tracking code</div>
|
<div>{t('Tracking code')}</div>
|
||||||
<div className="text-sm opacity-60">
|
<div className="text-sm opacity-60">
|
||||||
Add this code into your website head script
|
{t('Add this code into your website head script')}
|
||||||
</div>
|
</div>
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
copyable={{
|
copyable={{
|
||||||
@ -156,7 +159,7 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
|
|||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => handleEdit(record.id)}
|
onClick={() => handleEdit(record.id)}
|
||||||
>
|
>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
icon={<BarChartOutlined />}
|
icon={<BarChartOutlined />}
|
||||||
@ -164,7 +167,7 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
|
|||||||
navigate(`/website/${record.id}`);
|
navigate(`/website/${record.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View
|
{t('View')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteOnlineCount: React.FC<{
|
export const WebsiteOnlineCount: React.FC<{
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { workspaceId, websiteId } = props;
|
const { workspaceId, websiteId } = props;
|
||||||
|
|
||||||
const { data: count } = trpc.website.onlineCount.useQuery({
|
const { data: count } = trpc.website.onlineCount.useQuery({
|
||||||
@ -16,7 +18,9 @@ export const WebsiteOnlineCount: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-green-500" />
|
<div className="w-2.5 h-2.5 rounded-full bg-green-500" />
|
||||||
<span>{count} current visitor</span>
|
<span>
|
||||||
|
{count} {t('current visitor')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,14 @@ import { useNavigate } from 'react-router';
|
|||||||
import { AppRouterOutput, trpc } from '../../api/trpc';
|
import { AppRouterOutput, trpc } from '../../api/trpc';
|
||||||
import { getUserTimezone } from '../../api/model/user';
|
import { getUserTimezone } from '../../api/model/user';
|
||||||
import { useGlobalStateStore } from '../../store/global';
|
import { useGlobalStateStore } from '../../store/global';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteOverview: React.FC<{
|
export const WebsiteOverview: React.FC<{
|
||||||
website: WebsiteInfo;
|
website: WebsiteInfo;
|
||||||
showDateFilter?: boolean;
|
showDateFilter?: boolean;
|
||||||
actions?: React.ReactNode;
|
actions?: React.ReactNode;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { website, showDateFilter = false, actions } = props;
|
const { website, showDateFilter = false, actions } = props;
|
||||||
const { startDate, endDate, unit, refresh } = useGlobalRangeDate();
|
const { startDate, endDate, unit, refresh } = useGlobalRangeDate();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -68,7 +70,7 @@ export const WebsiteOverview: React.FC<{
|
|||||||
|
|
||||||
await Promise.all([refetchPageview(), refetchStats()]);
|
await Promise.all([refetchPageview(), refetchStats()]);
|
||||||
|
|
||||||
message.success('Refreshed');
|
message.success(t('Refreshed'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
@ -127,7 +129,7 @@ export const WebsiteOverview: React.FC<{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span className="ml-1">Previous period</span>
|
<span className="ml-1">{t('Previous period')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -155,6 +157,7 @@ WebsiteOverview.displayName = 'WebsiteOverview';
|
|||||||
export const MetricsBar: React.FC<{
|
export const MetricsBar: React.FC<{
|
||||||
stats: AppRouterOutput['website']['stats'];
|
stats: AppRouterOutput['website']['stats'];
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { pageviews, uniques, bounces, totaltime } = props.stats || {};
|
const { pageviews, uniques, bounces, totaltime } = props.stats || {};
|
||||||
const bouncesNum = Math.min(uniques.value, bounces.value) / uniques.value;
|
const bouncesNum = Math.min(uniques.value, bounces.value) / uniques.value;
|
||||||
const prevBouncesNum = Math.min(uniques.prev, bounces.prev) / uniques.prev;
|
const prevBouncesNum = Math.min(uniques.prev, bounces.prev) / uniques.prev;
|
||||||
@ -162,19 +165,19 @@ export const MetricsBar: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<div className="flex gap-5 flex-wrap w-full">
|
<div className="flex gap-5 flex-wrap w-full">
|
||||||
<MetricCard
|
<MetricCard
|
||||||
label="views"
|
label={t('views')}
|
||||||
value={pageviews.value}
|
value={pageviews.value}
|
||||||
prev={pageviews.prev}
|
prev={pageviews.prev}
|
||||||
change={pageviews.value - pageviews.prev}
|
change={pageviews.value - pageviews.prev}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
label="visitors"
|
label={t('visitors')}
|
||||||
value={uniques.value}
|
value={uniques.value}
|
||||||
prev={uniques.prev}
|
prev={uniques.prev}
|
||||||
change={uniques.value - uniques.prev}
|
change={uniques.value - uniques.prev}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
label="bounce rate"
|
label={t('bounce rate')}
|
||||||
reverseColors={true}
|
reverseColors={true}
|
||||||
value={uniques.value ? bouncesNum * 100 : 0}
|
value={uniques.value ? bouncesNum * 100 : 0}
|
||||||
prev={uniques.prev ? prevBouncesNum * 100 : 0}
|
prev={uniques.prev ? prevBouncesNum * 100 : 0}
|
||||||
@ -186,7 +189,7 @@ export const MetricsBar: React.FC<{
|
|||||||
format={(n) => formatNumber(n) + '%'}
|
format={(n) => formatNumber(n) + '%'}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
label="average visit time"
|
label={t('average visit time')}
|
||||||
value={
|
value={
|
||||||
totaltime.value && pageviews.value
|
totaltime.value && pageviews.value
|
||||||
? totaltime.value / (pageviews.value - bounces.value)
|
? totaltime.value / (pageviews.value - bounces.value)
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AppRouterOutput } from '../../../api/trpc';
|
import { AppRouterOutput } from '../../../api/trpc';
|
||||||
import {
|
import { MapContainer, CircleMarker, Popup, TileLayer } from 'react-leaflet';
|
||||||
MapContainer,
|
|
||||||
CircleMarker,
|
|
||||||
Popup,
|
|
||||||
TileLayer,
|
|
||||||
useMap,
|
|
||||||
} from 'react-leaflet';
|
|
||||||
import { mapCenter } from './utils';
|
import { mapCenter } from './utils';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import './VisitorLeafletMap.css';
|
import './VisitorLeafletMap.css';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const UserDataPoint: React.FC<{
|
export const UserDataPoint: React.FC<{
|
||||||
longitude: number;
|
longitude: number;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
count: number;
|
count: number;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const map = useMap();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CircleMarker
|
<CircleMarker
|
||||||
@ -30,7 +25,11 @@ export const UserDataPoint: React.FC<{
|
|||||||
fillColor="rgb(236,112,20)"
|
fillColor="rgb(236,112,20)"
|
||||||
fillOpacity={0.8}
|
fillOpacity={0.8}
|
||||||
>
|
>
|
||||||
<Popup>{props.count} users</Popup>
|
<Popup>
|
||||||
|
{t('{{num}} users', {
|
||||||
|
num: props.count,
|
||||||
|
})}
|
||||||
|
</Popup>
|
||||||
</CircleMarker>
|
</CircleMarker>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { useMemo, useReducer } from 'react';
|
|||||||
import { getMinimumUnit } from '@tianji/shared';
|
import { getMinimumUnit } from '@tianji/shared';
|
||||||
import { DateRange, useGlobalStateStore } from '../store/global';
|
import { DateRange, useGlobalStateStore } from '../store/global';
|
||||||
import { DateUnit } from '../utils/date';
|
import { DateUnit } from '../utils/date';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export function useGlobalRangeDate(): {
|
export function useGlobalRangeDate(): {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
@ -18,6 +19,7 @@ export function useGlobalRangeDate(): {
|
|||||||
endDate: globalEndDate,
|
endDate: globalEndDate,
|
||||||
} = useGlobalStateStore();
|
} = useGlobalStateStore();
|
||||||
const [updateInc, refresh] = useReducer((state: number) => state + 1, 0);
|
const [updateInc, refresh] = useReducer((state: number) => state + 1, 0);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { label, startDate, endDate, unit } = useMemo(() => {
|
const { label, startDate, endDate, unit } = useMemo(() => {
|
||||||
if (dateRange === DateRange.Custom) {
|
if (dateRange === DateRange.Custom) {
|
||||||
@ -45,7 +47,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.Today) {
|
if (dateRange === DateRange.Today) {
|
||||||
return {
|
return {
|
||||||
label: 'Today',
|
label: t('Today'),
|
||||||
startDate: dayjs().startOf('day'),
|
startDate: dayjs().startOf('day'),
|
||||||
endDate: dayjs().endOf('day'),
|
endDate: dayjs().endOf('day'),
|
||||||
unit: 'hour' as const,
|
unit: 'hour' as const,
|
||||||
@ -54,7 +56,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.Yesterday) {
|
if (dateRange === DateRange.Yesterday) {
|
||||||
return {
|
return {
|
||||||
label: 'Yesterday',
|
label: t('Yesterday'),
|
||||||
startDate: dayjs().subtract(1, 'day').startOf('day'),
|
startDate: dayjs().subtract(1, 'day').startOf('day'),
|
||||||
endDate: dayjs().subtract(1, 'day').endOf('day'),
|
endDate: dayjs().subtract(1, 'day').endOf('day'),
|
||||||
unit: 'hour' as const,
|
unit: 'hour' as const,
|
||||||
@ -63,7 +65,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.ThisWeek) {
|
if (dateRange === DateRange.ThisWeek) {
|
||||||
return {
|
return {
|
||||||
label: 'This week',
|
label: t('This week'),
|
||||||
startDate: dayjs().startOf('week'),
|
startDate: dayjs().startOf('week'),
|
||||||
endDate: dayjs().endOf('week'),
|
endDate: dayjs().endOf('week'),
|
||||||
unit: 'day' as const,
|
unit: 'day' as const,
|
||||||
@ -72,7 +74,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.Last7Days) {
|
if (dateRange === DateRange.Last7Days) {
|
||||||
return {
|
return {
|
||||||
label: 'Last 7 days',
|
label: t('Last 7 days'),
|
||||||
startDate: dayjs().subtract(7, 'day').startOf('day'),
|
startDate: dayjs().subtract(7, 'day').startOf('day'),
|
||||||
endDate: dayjs().endOf('day'),
|
endDate: dayjs().endOf('day'),
|
||||||
unit: 'day' as const,
|
unit: 'day' as const,
|
||||||
@ -81,7 +83,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.ThisMonth) {
|
if (dateRange === DateRange.ThisMonth) {
|
||||||
return {
|
return {
|
||||||
label: 'This month',
|
label: t('This month'),
|
||||||
startDate: dayjs().startOf('month'),
|
startDate: dayjs().startOf('month'),
|
||||||
endDate: dayjs().endOf('month'),
|
endDate: dayjs().endOf('month'),
|
||||||
unit: 'day' as const,
|
unit: 'day' as const,
|
||||||
@ -90,7 +92,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.Last30Days) {
|
if (dateRange === DateRange.Last30Days) {
|
||||||
return {
|
return {
|
||||||
label: 'Last 30 days',
|
label: t('Last 30 days'),
|
||||||
startDate: dayjs().subtract(30, 'day').startOf('day'),
|
startDate: dayjs().subtract(30, 'day').startOf('day'),
|
||||||
endDate: dayjs().endOf('day'),
|
endDate: dayjs().endOf('day'),
|
||||||
unit: 'day' as const,
|
unit: 'day' as const,
|
||||||
@ -99,7 +101,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.Last90Days) {
|
if (dateRange === DateRange.Last90Days) {
|
||||||
return {
|
return {
|
||||||
label: 'Last 90 days',
|
label: t('Last 90 days'),
|
||||||
startDate: dayjs().subtract(90, 'day').startOf('day'),
|
startDate: dayjs().subtract(90, 'day').startOf('day'),
|
||||||
endDate: dayjs().endOf('day'),
|
endDate: dayjs().endOf('day'),
|
||||||
unit: 'day' as const,
|
unit: 'day' as const,
|
||||||
@ -108,7 +110,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
if (dateRange === DateRange.ThisYear) {
|
if (dateRange === DateRange.ThisYear) {
|
||||||
return {
|
return {
|
||||||
label: 'This year',
|
label: t('This year'),
|
||||||
startDate: dayjs().startOf('year'),
|
startDate: dayjs().startOf('year'),
|
||||||
endDate: dayjs().endOf('year'),
|
endDate: dayjs().endOf('year'),
|
||||||
unit: 'month' as const,
|
unit: 'month' as const,
|
||||||
@ -117,7 +119,7 @@ export function useGlobalRangeDate(): {
|
|||||||
|
|
||||||
// default last 24 hour
|
// default last 24 hour
|
||||||
return {
|
return {
|
||||||
label: 'Last 24 hours',
|
label: t('Last 24 hours'),
|
||||||
startDate: dayjs().subtract(1, 'day'),
|
startDate: dayjs().subtract(1, 'day'),
|
||||||
endDate: dayjs(),
|
endDate: dayjs(),
|
||||||
unit: 'hour' as const,
|
unit: 'hour' as const,
|
||||||
|
44
src/client/i18next-toolkit.config.ts
Normal file
44
src/client/i18next-toolkit.config.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import type { I18nextToolkitConfig } from '@i18next-toolkit/cli';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
locales: ['en', 'zh', 'jp', 'fr', 'de', 'ru'],
|
||||||
|
scanner: {
|
||||||
|
verbose: false,
|
||||||
|
autoImport: false,
|
||||||
|
ignoreText: [
|
||||||
|
'Tianji',
|
||||||
|
'(25, 587)',
|
||||||
|
'TLS (465)',
|
||||||
|
'https://github.com/caronc/apprise/wiki#notification-services',
|
||||||
|
'Slug',
|
||||||
|
'--',
|
||||||
|
'a-z',
|
||||||
|
'0-9',
|
||||||
|
'80',
|
||||||
|
'example.com or 1.2.3.4',
|
||||||
|
'TCP Port',
|
||||||
|
'OpenAI',
|
||||||
|
'sess-************',
|
||||||
|
'Ping',
|
||||||
|
'For example: { "key": "value" }',
|
||||||
|
'Body',
|
||||||
|
'Headers',
|
||||||
|
'Content-Type',
|
||||||
|
'Method',
|
||||||
|
'https://example.com',
|
||||||
|
'HTTP',
|
||||||
|
'text/xml',
|
||||||
|
'application/x-www-form-urlencoded',
|
||||||
|
'application/json',
|
||||||
|
'OPTIONS',
|
||||||
|
'HEAD',
|
||||||
|
'DELETE',
|
||||||
|
'PATCH',
|
||||||
|
'PUT',
|
||||||
|
'POST',
|
||||||
|
'GET',
|
||||||
|
'HH:mm',
|
||||||
|
'YYYY-MM-DD HH:mm',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} satisfies I18nextToolkitConfig;
|
@ -21,6 +21,7 @@ a {
|
|||||||
|
|
||||||
#root, .App {
|
#root, .App {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
font-family: 'gg sans', 'Noto Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App.dark {
|
.App.dark {
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 10000",
|
"dev": "vite --port 10000",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"translation:extract": "i18next-toolkit extract",
|
||||||
|
"translation:scan": "i18next-toolkit scan",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@ -16,6 +18,7 @@
|
|||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
"@antv/l7": "^2.20.14",
|
"@antv/l7": "^2.20.14",
|
||||||
"@antv/larkmap": "^1.4.13",
|
"@antv/larkmap": "^1.4.13",
|
||||||
|
"@i18next-toolkit/react": "^1.0.4",
|
||||||
"@loadable/component": "^5.16.3",
|
"@loadable/component": "^5.16.3",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@tanstack/react-query": "4.33.0",
|
"@tanstack/react-query": "4.33.0",
|
||||||
@ -52,6 +55,7 @@
|
|||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@i18next-toolkit/cli": "1.0.1",
|
||||||
"@types/leaflet": "^1.9.8",
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/loadable__component": "^5.13.8",
|
"@types/loadable__component": "^5.13.8",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
@ -10,6 +10,8 @@ import { ColorSchemeSwitcher } from '../components/ColorSchemeSwitcher';
|
|||||||
import { version } from '@tianji/shared';
|
import { version } from '@tianji/shared';
|
||||||
import { useIsMobile } from '../hooks/useIsMobile';
|
import { useIsMobile } from '../hooks/useIsMobile';
|
||||||
import { RiMenuUnfoldLine } from 'react-icons/ri';
|
import { RiMenuUnfoldLine } from 'react-icons/ri';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { LanguageSelector } from '../components/LanguageSelector';
|
||||||
|
|
||||||
export const Layout: React.FC = React.memo(() => {
|
export const Layout: React.FC = React.memo(() => {
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
@ -31,6 +33,7 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const showHeader = !params.has('hideHeader');
|
const showHeader = !params.has('hideHeader');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const accountEl = (
|
const accountEl = (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -39,7 +42,7 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'workspaces',
|
key: 'workspaces',
|
||||||
label: 'Workspaces',
|
label: t('Workspaces'),
|
||||||
children: workspaces.map((w) => ({
|
children: workspaces.map((w) => ({
|
||||||
key: w.id,
|
key: w.id,
|
||||||
label: `${w.name}${w.current ? '(current)' : ''}`,
|
label: `${w.name}${w.current ? '(current)' : ''}`,
|
||||||
@ -48,14 +51,14 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'settings',
|
key: 'settings',
|
||||||
label: 'Settings',
|
label: t('Settings'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
navigate('/settings');
|
navigate('/settings');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'logout',
|
key: 'logout',
|
||||||
label: 'Logout',
|
label: t('Logout'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
logout();
|
logout();
|
||||||
},
|
},
|
||||||
@ -96,27 +99,27 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<MobileNavItem
|
<MobileNavItem
|
||||||
to="/dashboard"
|
to="/dashboard"
|
||||||
label="Dashboard"
|
label={t('Dashboard')}
|
||||||
onClick={() => setOpenDraw(false)}
|
onClick={() => setOpenDraw(false)}
|
||||||
/>
|
/>
|
||||||
<MobileNavItem
|
<MobileNavItem
|
||||||
to="/monitor"
|
to="/monitor"
|
||||||
label="Monitor"
|
label={t('Monitor')}
|
||||||
onClick={() => setOpenDraw(false)}
|
onClick={() => setOpenDraw(false)}
|
||||||
/>
|
/>
|
||||||
<MobileNavItem
|
<MobileNavItem
|
||||||
to="/website"
|
to="/website"
|
||||||
label="Website"
|
label={t('Website')}
|
||||||
onClick={() => setOpenDraw(false)}
|
onClick={() => setOpenDraw(false)}
|
||||||
/>
|
/>
|
||||||
<MobileNavItem
|
<MobileNavItem
|
||||||
to="/servers"
|
to="/servers"
|
||||||
label="Servers"
|
label={t('Servers')}
|
||||||
onClick={() => setOpenDraw(false)}
|
onClick={() => setOpenDraw(false)}
|
||||||
/>
|
/>
|
||||||
<MobileNavItem
|
<MobileNavItem
|
||||||
to="/settings"
|
to="/settings"
|
||||||
label="Settings"
|
label={t('Settings')}
|
||||||
onClick={() => setOpenDraw(false)}
|
onClick={() => setOpenDraw(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -140,16 +143,18 @@ export const Layout: React.FC = React.memo(() => {
|
|||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-8">
|
<div className="flex gap-8">
|
||||||
<NavItem to="/dashboard" label="Dashboard" />
|
<NavItem to="/dashboard" label={t('Dashboard')} />
|
||||||
<NavItem to="/monitor" label="Monitor" />
|
<NavItem to="/monitor" label={t('Monitor')} />
|
||||||
<NavItem to="/website" label="Website" />
|
<NavItem to="/website" label={t('Website')} />
|
||||||
<NavItem to="/servers" label="Servers" />
|
<NavItem to="/servers" label={t('Servers')} />
|
||||||
<NavItem to="/settings" label="Settings" />
|
<NavItem to="/settings" label={t('Settings')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
<LanguageSelector />
|
||||||
|
|
||||||
<ColorSchemeSwitcher />
|
<ColorSchemeSwitcher />
|
||||||
|
|
||||||
{accountEl}
|
{accountEl}
|
||||||
|
@ -6,9 +6,11 @@ import { trpc } from '../api/trpc';
|
|||||||
import { setJWT } from '../api/auth';
|
import { setJWT } from '../api/auth';
|
||||||
import { setUserInfo } from '../store/user';
|
import { setUserInfo } from '../store/user';
|
||||||
import { useGlobalConfig } from '../hooks/useConfig';
|
import { useGlobalConfig } from '../hooks/useConfig';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const Login: React.FC = React.memo(() => {
|
export const Login: React.FC = React.memo(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const mutation = trpc.user.login.useMutation();
|
const mutation = trpc.user.login.useMutation();
|
||||||
const [{ loading }, handleLogin] = useRequest(async (values: any) => {
|
const [{ loading }, handleLogin] = useRequest(async (values: any) => {
|
||||||
@ -34,14 +36,14 @@ export const Login: React.FC = React.memo(() => {
|
|||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Form layout="vertical" disabled={loading} onFinish={handleLogin}>
|
<Form layout="vertical" disabled={loading} onFinish={handleLogin}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Username"
|
label={t('Username')}
|
||||||
name="username"
|
name="username"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input size="large" />
|
<Input size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Password"
|
label={t('Password')}
|
||||||
name="password"
|
name="password"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
@ -55,7 +57,7 @@ export const Login: React.FC = React.memo(() => {
|
|||||||
block={true}
|
block={true}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
Login
|
{t('Login')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ export const Login: React.FC = React.memo(() => {
|
|||||||
navigate('/register');
|
navigate('/register');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Register
|
{t('Register')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
@ -3,8 +3,10 @@ import { useCurrentWorkspaceId } from '../../store/user';
|
|||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import { MonitorEventList } from '../../components/monitor/MonitorEventList';
|
import { MonitorEventList } from '../../components/monitor/MonitorEventList';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorOverview: React.FC = React.memo(() => {
|
export const MonitorOverview: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const currentWorkspaceId = useCurrentWorkspaceId()!;
|
const currentWorkspaceId = useCurrentWorkspaceId()!;
|
||||||
const { data: monitors = [] } = trpc.monitor.all.useQuery({
|
const { data: monitors = [] } = trpc.monitor.all.useQuery({
|
||||||
workspaceId: currentWorkspaceId,
|
workspaceId: currentWorkspaceId,
|
||||||
@ -14,11 +16,11 @@ export const MonitorOverview: React.FC = React.memo(() => {
|
|||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<div className="grid gap-4 grid-cols-2">
|
<div className="grid gap-4 grid-cols-2">
|
||||||
<Card hoverable={true}>
|
<Card hoverable={true}>
|
||||||
<div>Monitors</div>
|
<div>{t('Monitors')}</div>
|
||||||
<div className="text-2xl font-semibold">{monitors.length}</div>
|
<div className="text-2xl font-semibold">{monitors.length}</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card hoverable={true}>
|
<Card hoverable={true}>
|
||||||
<div>Available</div>
|
<div>{t('Available')}</div>
|
||||||
<div className="text-2xl font-semibold">
|
<div className="text-2xl font-semibold">
|
||||||
{monitors.filter((m) => m.active).length}
|
{monitors.filter((m) => m.active).length}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,8 +5,10 @@ import { trpc } from '../../api/trpc';
|
|||||||
import { Button, Card, Popconfirm } from 'antd';
|
import { Button, Card, Popconfirm } from 'antd';
|
||||||
import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
import { useEvent } from '../../hooks/useEvent';
|
import { useEvent } from '../../hooks/useEvent';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorPageList: React.FC = React.memo(() => {
|
export const MonitorPageList: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: pages = [], refetch } = trpc.monitor.getAllPages.useQuery({
|
const { data: pages = [], refetch } = trpc.monitor.getAllPages.useQuery({
|
||||||
@ -26,7 +28,7 @@ export const MonitorPageList: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<div className="px-8 py-4">
|
<div className="px-8 py-4">
|
||||||
<Button type="primary" onClick={() => navigate('/monitor/pages/add')}>
|
<Button type="primary" onClick={() => navigate('/monitor/pages/add')}>
|
||||||
New page
|
{t('New page')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-col gap-2">
|
<div className="mt-4 flex flex-col gap-2">
|
||||||
@ -36,7 +38,7 @@ export const MonitorPageList: React.FC = React.memo(() => {
|
|||||||
<div className="flex-1">{p.title}</div>
|
<div className="flex-1">{p.title}</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="Did you sure delete this page?"
|
title={t('Did you sure delete this page?')}
|
||||||
onConfirm={() => handleDeletePage(p.id)}
|
onConfirm={() => handleDeletePage(p.id)}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
danger: true,
|
danger: true,
|
||||||
|
@ -8,8 +8,10 @@ import { MonitorOverview } from './Overview';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { MonitorPageList } from './PageList';
|
import { MonitorPageList } from './PageList';
|
||||||
import { MonitorPageAdd } from './PageAdd';
|
import { MonitorPageAdd } from './PageAdd';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const MonitorPage: React.FC = React.memo(() => {
|
export const MonitorPage: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -21,14 +23,14 @@ export const MonitorPage: React.FC = React.memo(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={() => navigate('/monitor/add')}
|
onClick={() => navigate('/monitor/add')}
|
||||||
>
|
>
|
||||||
Add new Monitor
|
{t('Add new Monitor')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => navigate('/monitor/pages')}
|
onClick={() => navigate('/monitor/pages')}
|
||||||
>
|
>
|
||||||
Pages
|
{t('Pages')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,8 +5,10 @@ import { useRequest } from '../hooks/useRequest';
|
|||||||
import { trpc } from '../api/trpc';
|
import { trpc } from '../api/trpc';
|
||||||
import { setJWT } from '../api/auth';
|
import { setJWT } from '../api/auth';
|
||||||
import { setUserInfo } from '../store/user';
|
import { setUserInfo } from '../store/user';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const Register: React.FC = React.memo(() => {
|
export const Register: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const mutation = trpc.user.register.useMutation();
|
const mutation = trpc.user.register.useMutation();
|
||||||
@ -29,18 +31,18 @@ export const Register: React.FC = React.memo(() => {
|
|||||||
<img className="w-24 h-24" src="/icon.svg" />
|
<img className="w-24 h-24" src="/icon.svg" />
|
||||||
</div>
|
</div>
|
||||||
<Typography.Title className="text-center" level={2}>
|
<Typography.Title className="text-center" level={2}>
|
||||||
Register Account
|
{t('Register Account')}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Form layout="vertical" disabled={loading} onFinish={handleRegister}>
|
<Form layout="vertical" disabled={loading} onFinish={handleRegister}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Username"
|
label={t('Username')}
|
||||||
name="username"
|
name="username"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input size="large" />
|
<Input size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Password"
|
label={t('Password')}
|
||||||
name="password"
|
name="password"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
@ -54,7 +56,7 @@ export const Register: React.FC = React.memo(() => {
|
|||||||
block={true}
|
block={true}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
Register
|
{t('Register')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -31,8 +31,10 @@ import clsx from 'clsx';
|
|||||||
import { isServerOnline } from '@tianji/shared';
|
import { isServerOnline } from '@tianji/shared';
|
||||||
import { defaultErrorHandler, trpc } from '../api/trpc';
|
import { defaultErrorHandler, trpc } from '../api/trpc';
|
||||||
import { useRequest } from '../hooks/useRequest';
|
import { useRequest } from '../hooks/useRequest';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const Servers: React.FC = React.memo(() => {
|
export const Servers: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [hideOfflineServer, setHideOfflineServer] = useState(false);
|
const [hideOfflineServer, setHideOfflineServer] = useState(false);
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
@ -55,25 +57,25 @@ export const Servers: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-24 flex items-center">
|
<div className="h-24 flex items-center">
|
||||||
<div className="text-2xl flex-1">Servers</div>
|
<div className="text-2xl flex-1">{t('Servers')}</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-1 text-gray-500">
|
<div className="flex items-center gap-1 text-gray-500">
|
||||||
<Switch
|
<Switch
|
||||||
checked={hideOfflineServer}
|
checked={hideOfflineServer}
|
||||||
onChange={setHideOfflineServer}
|
onChange={setHideOfflineServer}
|
||||||
/>
|
/>
|
||||||
Hide Offline
|
{t('Hide Offline')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="Clear Offline Node"
|
title={t('Clear Offline Node')}
|
||||||
description="Are you sure to clear all offline node?"
|
description={t('Are you sure to clear all offline node?')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onConfirm={handleClearOfflineNode}
|
onConfirm={handleClearOfflineNode}
|
||||||
>
|
>
|
||||||
<Button size="large" loading={loading}>
|
<Button size="large" loading={loading}>
|
||||||
Clear Offline
|
{t('Clear Offline')}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</div>
|
</div>
|
||||||
@ -86,7 +88,7 @@ export const Servers: React.FC = React.memo(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={() => setIsModalOpen(true)}
|
onClick={() => setIsModalOpen(true)}
|
||||||
>
|
>
|
||||||
Add Server
|
{t('Add Server')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -94,7 +96,7 @@ export const Servers: React.FC = React.memo(() => {
|
|||||||
<ServerList hideOfflineServer={hideOfflineServer} />
|
<ServerList hideOfflineServer={hideOfflineServer} />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Add Server"
|
title={t('Add Server')}
|
||||||
open={isModalOpen}
|
open={isModalOpen}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
okText="Done"
|
okText="Done"
|
||||||
@ -106,12 +108,12 @@ export const Servers: React.FC = React.memo(() => {
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'auto',
|
key: 'auto',
|
||||||
label: 'Auto',
|
label: t('Auto'),
|
||||||
children: <InstallScript />,
|
children: <InstallScript />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'manual',
|
key: 'manual',
|
||||||
label: 'Manual',
|
label: t('Manual'),
|
||||||
children: <AddServerStep />,
|
children: <AddServerStep />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@ -135,6 +137,7 @@ function useServerMap(): Record<string, ServerStatusInfo> {
|
|||||||
export const ServerList: React.FC<{
|
export const ServerList: React.FC<{
|
||||||
hideOfflineServer: boolean;
|
hideOfflineServer: boolean;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const serverMap = useServerMap();
|
const serverMap = useServerMap();
|
||||||
const inc = useIntervalUpdate(2 * 1000);
|
const inc = useIntervalUpdate(2 * 1000);
|
||||||
const { hideOfflineServer } = props;
|
const { hideOfflineServer } = props;
|
||||||
@ -158,16 +161,16 @@ export const ServerList: React.FC<{
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
title: 'Status',
|
title: t('Status'),
|
||||||
width: 90,
|
width: 90,
|
||||||
render: (val, record) => {
|
render: (val, record) => {
|
||||||
return isServerOnline(record) ? (
|
return isServerOnline(record) ? (
|
||||||
<Badge status="success" text="online" />
|
<Badge status="success" text={t('online')} />
|
||||||
) : (
|
) : (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={`Last online: ${dayjs(record.updatedAt).format(
|
title={t('Last online: {{time}}', {
|
||||||
'YYYY-MM-DD HH:mm:ss'
|
time: dayjs(record.updatedAt).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
)}`}
|
})}
|
||||||
>
|
>
|
||||||
<Badge status="error" text="offline" />
|
<Badge status="error" text="offline" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -176,13 +179,13 @@ export const ServerList: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: 'Node Name',
|
title: t('Node Name'),
|
||||||
width: 150,
|
width: 150,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'hostname',
|
dataIndex: 'hostname',
|
||||||
title: 'Host Name',
|
title: t('Host Name'),
|
||||||
width: 150,
|
width: 150,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
@ -192,18 +195,18 @@ export const ServerList: React.FC<{
|
|||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
dataIndex: ['payload', 'uptime'],
|
dataIndex: ['payload', 'uptime'],
|
||||||
title: 'Uptime',
|
title: t('Uptime'),
|
||||||
width: 150,
|
width: 150,
|
||||||
render: (val) => prettyMilliseconds(Number(val) * 1000),
|
render: (val) => prettyMilliseconds(Number(val) * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: ['payload', 'load'],
|
dataIndex: ['payload', 'load'],
|
||||||
title: 'Load',
|
title: t('Load'),
|
||||||
width: 70,
|
width: 70,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'nework',
|
key: 'nework',
|
||||||
title: 'Network',
|
title: t('Network'),
|
||||||
width: 110,
|
width: 110,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
@ -216,7 +219,7 @@ export const ServerList: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'traffic',
|
key: 'traffic',
|
||||||
title: 'Traffic',
|
title: t('Traffic'),
|
||||||
width: 130,
|
width: 130,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
@ -229,13 +232,13 @@ export const ServerList: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: ['payload', 'cpu'],
|
dataIndex: ['payload', 'cpu'],
|
||||||
title: 'CPU',
|
title: t('CPU'),
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (val) => `${val}%`,
|
render: (val) => `${val}%`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'ram',
|
key: 'ram',
|
||||||
title: 'RAM',
|
title: t('RAM'),
|
||||||
width: 120,
|
width: 120,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
@ -248,7 +251,7 @@ export const ServerList: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'hdd',
|
key: 'hdd',
|
||||||
title: 'HDD',
|
title: t('HDD'),
|
||||||
width: 120,
|
width: 120,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
@ -261,7 +264,7 @@ export const ServerList: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'updatedAt',
|
dataIndex: 'updatedAt',
|
||||||
title: 'updatedAt',
|
title: t('updatedAt'),
|
||||||
width: 130,
|
width: 130,
|
||||||
render: (val) => {
|
render: (val) => {
|
||||||
return dayjs(val).format('MMM D HH:mm:ss');
|
return dayjs(val).format('MMM D HH:mm:ss');
|
||||||
@ -273,7 +276,9 @@ export const ServerList: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-right text-sm opacity-80">
|
<div className="text-right text-sm opacity-80">
|
||||||
Last updated at: {dayjs(lastUpdatedAt).format('YYYY-MM-DD HH:mm:ss')}
|
{t('Last updated at: {{date}}', {
|
||||||
|
date: dayjs(lastUpdatedAt).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-auto">
|
<div className="overflow-auto">
|
||||||
<Table
|
<Table
|
||||||
@ -281,7 +286,7 @@ export const ServerList: React.FC<{
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
locale={{ emptyText: <Empty description="No server online" /> }}
|
locale={{ emptyText: <Empty description={t('No server online')} /> }}
|
||||||
rowClassName={(record) =>
|
rowClassName={(record) =>
|
||||||
clsx(!isServerOnline(record) && 'opacity-60')
|
clsx(!isServerOnline(record) && 'opacity-60')
|
||||||
}
|
}
|
||||||
@ -293,12 +298,13 @@ export const ServerList: React.FC<{
|
|||||||
ServerList.displayName = 'ServerList';
|
ServerList.displayName = 'ServerList';
|
||||||
|
|
||||||
export const InstallScript: React.FC = React.memo(() => {
|
export const InstallScript: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const command = `curl -o- ${window.location.origin}/serverStatus/${workspaceId}/install.sh?url=${window.location.origin} | bash`;
|
const command = `curl -o- ${window.location.origin}/serverStatus/${workspaceId}/install.sh?url=${window.location.origin} | bash`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>Run this command in your linux machine</div>
|
<div>{t('Run this command in your linux machine')}</div>
|
||||||
|
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
copyable={{
|
copyable={{
|
||||||
@ -311,14 +317,16 @@ export const InstallScript: React.FC = React.memo(() => {
|
|||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Or you wanna report server status in windows server? switch to Manual
|
{t(
|
||||||
tab
|
'Or you wanna report server status in windows server? switch to Manual tab'
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AddServerStep: React.FC = React.memo(() => {
|
export const AddServerStep: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const serverMap = useServerMap();
|
const serverMap = useServerMap();
|
||||||
@ -348,10 +356,10 @@ export const AddServerStep: React.FC = React.memo(() => {
|
|||||||
current={current}
|
current={current}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: 'Download Client Reportor',
|
title: t('Download Client Reportor'),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
Download reporter from{' '}
|
{t('Download reporter from')}{' '}
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
href="https://github.com/msgbyte/tianji/releases"
|
href="https://github.com/msgbyte/tianji/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -362,16 +370,16 @@ export const AddServerStep: React.FC = React.memo(() => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Releases Page
|
{t('Releases Page')}
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Run',
|
title: t('Run'),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
run reporter with:{' '}
|
{t('run reporter with')}:{' '}
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
code={true}
|
code={true}
|
||||||
copyable={{ format: 'text/plain', text: command }}
|
copyable={{ format: 'text/plain', text: command }}
|
||||||
@ -389,20 +397,20 @@ export const AddServerStep: React.FC = React.memo(() => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Next step
|
{t('Next step')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Waiting for receive UDP pack',
|
title: t('Waiting for receive UDP pack'),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
{diffServerNames.length === 0 || checking === false ? (
|
{diffServerNames.length === 0 || checking === false ? (
|
||||||
<Loading />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
Is this your servers?
|
{t('Is this your servers?')}
|
||||||
{diffServerNames.map((n) => (
|
{diffServerNames.map((n) => (
|
||||||
<div key={n}>- {n}</div>
|
<div key={n}>- {n}</div>
|
||||||
))}
|
))}
|
||||||
|
@ -8,8 +8,10 @@ import { last } from 'lodash-es';
|
|||||||
import { useWatch } from '../../hooks/useWatch';
|
import { useWatch } from '../../hooks/useWatch';
|
||||||
import { ColorTag } from '../../components/ColorTag';
|
import { ColorTag } from '../../components/ColorTag';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const AuditLog: React.FC = React.memo(() => {
|
export const AuditLog: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const parentRef = useRef<HTMLDivElement>(null);
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ export const AuditLog: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader title="Audit Log" />
|
<PageHeader title={t('Audit Log')} />
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<List>
|
<List>
|
||||||
@ -81,9 +83,9 @@ export const AuditLog: React.FC = React.memo(() => {
|
|||||||
>
|
>
|
||||||
{isLoaderRow ? (
|
{isLoaderRow ? (
|
||||||
hasNextPage ? (
|
hasNextPage ? (
|
||||||
'Loading more...'
|
t('Loading more...')
|
||||||
) : (
|
) : (
|
||||||
'Nothing more to load'
|
t('Nothing more to load')
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
@ -10,8 +10,10 @@ import { NoWorkspaceTip } from '../../components/NoWorkspaceTip';
|
|||||||
import { PageHeader } from '../../components/PageHeader';
|
import { PageHeader } from '../../components/PageHeader';
|
||||||
import { useEvent } from '../../hooks/useEvent';
|
import { useEvent } from '../../hooks/useEvent';
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const NotificationList: React.FC = React.memo(() => {
|
export const NotificationList: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const currentWorkspaceId = useCurrentWorkspaceId();
|
const currentWorkspaceId = useCurrentWorkspaceId();
|
||||||
const { data: list = [], refetch } = trpc.notification.all.useQuery({
|
const { data: list = [], refetch } = trpc.notification.all.useQuery({
|
||||||
@ -58,7 +60,7 @@ export const NotificationList: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Notification List"
|
title={t('Notification List')}
|
||||||
action={
|
action={
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
@ -67,7 +69,7 @@ export const NotificationList: React.FC = React.memo(() => {
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={() => handleOpenModal()}
|
onClick={() => handleOpenModal()}
|
||||||
>
|
>
|
||||||
New
|
{t('New')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -90,10 +92,10 @@ export const NotificationList: React.FC = React.memo(() => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="Is delete this item?"
|
title={t('Is delete this item?')}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
danger: true,
|
danger: true,
|
||||||
}}
|
}}
|
||||||
|
@ -8,8 +8,10 @@ import {
|
|||||||
trpc,
|
trpc,
|
||||||
} from '../../api/trpc';
|
} from '../../api/trpc';
|
||||||
import { useLogout } from '../../api/model/user';
|
import { useLogout } from '../../api/model/user';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const Profile: React.FC = React.memo(() => {
|
export const Profile: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const userInfo = useUserStore((state) => state.info);
|
const userInfo = useUserStore((state) => state.info);
|
||||||
const [openChangePassword, setOpenChangePassword] = useState(false);
|
const [openChangePassword, setOpenChangePassword] = useState(false);
|
||||||
|
|
||||||
@ -22,23 +24,23 @@ export const Profile: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader title="Profile" />
|
<PageHeader title={t('Profile')} />
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Form layout="vertical">
|
<Form layout="vertical">
|
||||||
<Form.Item label="Current Workspace Id">
|
<Form.Item label={t('Current Workspace Id')}>
|
||||||
<Typography.Text copyable={true} code={true}>
|
<Typography.Text copyable={true} code={true}>
|
||||||
{userInfo?.currentWorkspace?.id}
|
{userInfo?.currentWorkspace?.id}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="User Id">
|
<Form.Item label={t('User Id')}>
|
||||||
<Typography.Text copyable={true} code={true}>
|
<Typography.Text copyable={true} code={true}>
|
||||||
{userInfo?.id}
|
{userInfo?.id}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Password">
|
<Form.Item label={t('Password')}>
|
||||||
<Button danger={true} onClick={() => setOpenChangePassword(true)}>
|
<Button danger={true} onClick={() => setOpenChangePassword(true)}>
|
||||||
Change Password
|
{t('Change Password')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
@ -46,7 +48,7 @@ export const Profile: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={openChangePassword}
|
open={openChangePassword}
|
||||||
title="Change password"
|
title={t('Change password')}
|
||||||
footer={null}
|
footer={null}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
onCancel={() => setOpenChangePassword(false)}
|
onCancel={() => setOpenChangePassword(false)}
|
||||||
@ -64,21 +66,21 @@ export const Profile: React.FC = React.memo(() => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Old Password"
|
label={t('Old Password')}
|
||||||
name="oldPassword"
|
name="oldPassword"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input.Password />
|
<Input.Password />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="New Password"
|
label={t('New Password')}
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input.Password />
|
<Input.Password />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="New Password Repeat"
|
label={t('New Password Repeat')}
|
||||||
name="newPasswordRepeat"
|
name="newPasswordRepeat"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
@ -88,7 +90,9 @@ export const Profile: React.FC = React.memo(() => {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject('The two passwords are not consistent');
|
return Promise.reject(
|
||||||
|
t('The two passwords are not consistent')
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
@ -101,7 +105,7 @@ export const Profile: React.FC = React.memo(() => {
|
|||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={changePasswordMutation.isLoading}
|
loading={changePasswordMutation.isLoading}
|
||||||
>
|
>
|
||||||
Submit
|
{t('Submit')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -7,23 +7,24 @@ import { useEvent } from '../../hooks/useEvent';
|
|||||||
import { NotificationList } from './NotificationList';
|
import { NotificationList } from './NotificationList';
|
||||||
import { Profile } from './Profile';
|
import { Profile } from './Profile';
|
||||||
import { AuditLog } from './AuditLog';
|
import { AuditLog } from './AuditLog';
|
||||||
|
import { Trans } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
key: 'websites',
|
key: 'websites',
|
||||||
label: 'Websites',
|
label: <Trans>Websites</Trans>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'notifications',
|
key: 'notifications',
|
||||||
label: 'Notifications',
|
label: <Trans>Notifications</Trans>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'auditLog',
|
key: 'auditLog',
|
||||||
label: 'Audit Log',
|
label: <Trans>Audit Log</Trans>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'profile',
|
key: 'profile',
|
||||||
label: 'Profile',
|
label: <Trans>Profile</Trans>,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -10,8 +10,10 @@ import { WebsiteOverview } from '../../components/website/WebsiteOverview';
|
|||||||
import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate';
|
import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate';
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
import { RightOutlined } from '@ant-design/icons';
|
import { RightOutlined } from '@ant-design/icons';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteDetail: React.FC = React.memo(() => {
|
export const WebsiteDetail: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { websiteId } = useParams();
|
const { websiteId } = useParams();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data: website, isLoading } = trpc.website.info.useQuery({
|
const { data: website, isLoading } = trpc.website.info.useQuery({
|
||||||
@ -46,7 +48,7 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="url"
|
type="url"
|
||||||
title={['Pages', 'Views']}
|
title={[t('Pages'), t('Views')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
@ -55,7 +57,7 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="referrer"
|
type="referrer"
|
||||||
title={['Referrers', 'Views']}
|
title={[t('Referrers'), t('Views')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
@ -64,7 +66,7 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="browser"
|
type="browser"
|
||||||
title={['Browser', 'Visitors']}
|
title={[t('Browser'), t('Visitors')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
@ -73,7 +75,7 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="os"
|
type="os"
|
||||||
title={['OS', 'Visitors']}
|
title={[t('OS'), t('Visitors')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
@ -82,7 +84,7 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="device"
|
type="device"
|
||||||
title={['Devices', 'Visitors']}
|
title={[t('Devices'), t('Visitors')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
@ -91,7 +93,7 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="country"
|
type="country"
|
||||||
title={['Countries', 'Visitors']}
|
title={[t('Countries'), t('Visitors')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
@ -102,14 +104,14 @@ export const WebsiteDetail: React.FC = React.memo(() => {
|
|||||||
icon={<RightOutlined className="m-0" />}
|
icon={<RightOutlined className="m-0" />}
|
||||||
onClick={() => navigate(`/website/${websiteId}/map`)}
|
onClick={() => navigate(`/website/${websiteId}/map`)}
|
||||||
>
|
>
|
||||||
Visitor Map
|
{t('Visitor Map')}
|
||||||
</Button>
|
</Button>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid hoverable={false} className="!w-1/2 min-h-[470px]">
|
<Card.Grid hoverable={false} className="!w-1/2 min-h-[470px]">
|
||||||
<MetricsTable
|
<MetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
type="event"
|
type="event"
|
||||||
title={['Events', 'Actions']}
|
title={[t('Events'), t('Actions')]}
|
||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
endAt={endAt}
|
endAt={endAt}
|
||||||
/>
|
/>
|
||||||
|
@ -9,10 +9,12 @@ import { WebsiteVisitorMap } from '../../components/website/WebsiteVisitorMap';
|
|||||||
import { DateFilter } from '../../components/DateFilter';
|
import { DateFilter } from '../../components/DateFilter';
|
||||||
import { LeftOutlined } from '@ant-design/icons';
|
import { LeftOutlined } from '@ant-design/icons';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const WebsiteVisitorMapPage: React.FC = React.memo(() => {
|
export const WebsiteVisitorMapPage: React.FC = React.memo(() => {
|
||||||
const { websiteId } = useParams();
|
const { websiteId } = useParams();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { data: website, isLoading } = trpc.website.info.useQuery({
|
const { data: website, isLoading } = trpc.website.info.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
websiteId: websiteId!,
|
websiteId: websiteId!,
|
||||||
@ -40,7 +42,8 @@ export const WebsiteVisitorMapPage: React.FC = React.memo(() => {
|
|||||||
onClick={() => navigate(`/website/${websiteId}`)}
|
onClick={() => navigate(`/website/${websiteId}`)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-bold">{website.name}</span>'s visitor map
|
<span className="font-bold">{website.name}</span>
|
||||||
|
{t("'s visitor map")}
|
||||||
</div>
|
</div>
|
||||||
<DateFilter />
|
<DateFilter />
|
||||||
</div>
|
</div>
|
||||||
|
BIN
src/client/public/locales/de/flag.png
Normal file
BIN
src/client/public/locales/de/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 B |
218
src/client/public/locales/de/translation.json
Normal file
218
src/client/public/locales/de/translation.json
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"k10336b1": "Nichts mehr zu laden",
|
||||||
|
"k10c85f27": "TLS-Fehler ignorieren",
|
||||||
|
"k112a7174": "Nutzung",
|
||||||
|
"k1192dd43": "Bitte Arbeitsbereich auswählen",
|
||||||
|
"k11e887ab": "Dieses Element löschen?",
|
||||||
|
"k11f09c87": "Ansichten",
|
||||||
|
"k1286421": "Benutzername",
|
||||||
|
"k134e0d97": "Aktuell",
|
||||||
|
"k14c6c425": "Ergebnis",
|
||||||
|
"k158336d6": "Anfrage-Timeout(s)",
|
||||||
|
"k1598e726": "aktueller Besucher",
|
||||||
|
"k15ae36a6": "Browser",
|
||||||
|
"k16c8909f": "Anzeigename",
|
||||||
|
"k1777bbf2": "Manuell",
|
||||||
|
"k186365b3": "{{monitorName}}'s Diagramm",
|
||||||
|
"k1964b988": "Stopp",
|
||||||
|
"k1bd89236": "Reporter mit ausführen",
|
||||||
|
"k1c33c293": "Einstellungen",
|
||||||
|
"k1eb5b3ed": "Übersicht",
|
||||||
|
"k20edf271": "24h",
|
||||||
|
"k21077124": "Bearbeiten",
|
||||||
|
"k2264aac": "Dieses Jahr",
|
||||||
|
"k246063be": "Chat-ID",
|
||||||
|
"k2497052e": "Diagramm",
|
||||||
|
"k25b3dc00": "Skript JS-Code",
|
||||||
|
"k264de775": "Mehr laden...",
|
||||||
|
"k277e2626": "Verfügbar",
|
||||||
|
"k28059d49": "Aktuelle Arbeitsbereichs-ID",
|
||||||
|
"k2813d1f7": "Dieser Monat",
|
||||||
|
"k2a6a7d8f": "UNTEN",
|
||||||
|
"k2b2d40d4": "Testcode",
|
||||||
|
"k2c2712a4": "CPU",
|
||||||
|
"k2e6dbf02": "An E-Mail",
|
||||||
|
"k2ea8a019": "Monitor",
|
||||||
|
"k30b5f01b": "Arbeitsbereiche",
|
||||||
|
"k30e234ee": "Sie haben noch kein Dashboard-Element, bitte treten Sie in den Bearbeitungsmodus und fügen Sie Ihr Element hinzu.",
|
||||||
|
"k310fee": "Letzte 30 Tage",
|
||||||
|
"k32344f64": "Daten löschen",
|
||||||
|
"k3260f019": "Abmelden",
|
||||||
|
"k3471e956": "Neues Passwort wiederholen",
|
||||||
|
"k389db675": "Einreichen",
|
||||||
|
"k38c62888": "Bitte Monitor auswählen",
|
||||||
|
"k3cedb797": "Sicherheit",
|
||||||
|
"k3d3baf52": "durchschnittliche Besuchszeit",
|
||||||
|
"k3dbe79b1": "Löschen",
|
||||||
|
"k3de768a1": "Passwort ändern",
|
||||||
|
"k3e757ddf": "Hier gibt es noch keinen Monitor.",
|
||||||
|
"k43e21ee9": "Client-Reporter herunterladen",
|
||||||
|
"k44cad477": "(Aktuell)",
|
||||||
|
"k46f0adde": "Besucherkarte",
|
||||||
|
"k47f43dbc": "Gesundheit von {{monitorName}}",
|
||||||
|
"k4905ed7b": "KEINE",
|
||||||
|
"k490ada32": "Website hinzufügen",
|
||||||
|
"k49e5f1d2": "1w",
|
||||||
|
"k4ac4dd36": "Verweisende Seiten",
|
||||||
|
"k4de48e75": "Maximale Wiederholungen",
|
||||||
|
"k4e08cf58": "Detailnummer anzeigen",
|
||||||
|
"k4eea9393": "Profil",
|
||||||
|
"k506a90b2": "Ihr Server-Domain oder IP.",
|
||||||
|
"k517747e1": "Letzte 24 Stunden",
|
||||||
|
"k51bac044": "Abbrechen",
|
||||||
|
"k53ae02a5": "Neu",
|
||||||
|
"k542b527c": "Ereignisse",
|
||||||
|
"k58f90514": "Bot-Token",
|
||||||
|
"k593cf342": "Sind Sie sicher, diesen Monitor zu löschen?",
|
||||||
|
"k5a839f71": "Betriebszeit",
|
||||||
|
"k5eb87a8b": "Start",
|
||||||
|
"k5ecf04b0": "Ansicht",
|
||||||
|
"k6067f0ff": "TLS/SSL-Fehler ignorieren",
|
||||||
|
"k621317b5": "Neue Seite",
|
||||||
|
"k62e19375": "Letzte Aktualisierung: {{date}}",
|
||||||
|
"k646a3a80": "Metriken von {{monitorName}}",
|
||||||
|
"k646c0ae2": "HDD",
|
||||||
|
"k67c5a895": "Gestern",
|
||||||
|
"k683be220": "Ausführen",
|
||||||
|
"k691b7170": "Gestoppt",
|
||||||
|
"k698348ff": "Warten auf UDP-Paketempfang",
|
||||||
|
"k6acf5248": "Kürzlich",
|
||||||
|
"k6b36580f": "Apprise-URL",
|
||||||
|
"k6bc9e414": "Anmelden",
|
||||||
|
"k6f15bcc3": "Host",
|
||||||
|
"k717660a5": "RAM",
|
||||||
|
"k721589c1": "Heute",
|
||||||
|
"k74a240": "Gesundheitsbalken",
|
||||||
|
"k75581e13": "CC",
|
||||||
|
"k75bfaaa6": "Fügen Sie diesen Code in das Kopf-Skript Ihrer Website ein",
|
||||||
|
"k784dd132": "Test",
|
||||||
|
"k7927b824": "Sind Sie sicher, alle Offline-Knoten zu löschen?",
|
||||||
|
"k7ac44a6e": "Sitzungsschlüssel",
|
||||||
|
"k7b74a43f": "Besucher",
|
||||||
|
"k7c95e6a5": "Sind Sie sicher, diese Seite zu löschen?",
|
||||||
|
"k7cac602a": "Status",
|
||||||
|
"k7f01b47c": "Prüfprotokoll",
|
||||||
|
"k7f4bcf6b": "Monitore",
|
||||||
|
"k8037cc6b": "Server",
|
||||||
|
"k8202c669": "Besucher",
|
||||||
|
"k845abd5b": "Nächster Schritt",
|
||||||
|
"k84ce1618": "(24 Stunden)",
|
||||||
|
"k85344b23": "Laden",
|
||||||
|
"k85c5fd4c": "Noch kein Monitor eingerichtet",
|
||||||
|
"k8746ec38": "Monitor auswählen",
|
||||||
|
"k88a9bf01": "Abzeichen anzeigen",
|
||||||
|
"k88d2647b": "Website",
|
||||||
|
"k89056082": "(30 Tage)",
|
||||||
|
"k8a44833f": "Dienste",
|
||||||
|
"k8bac6ae0": "6h",
|
||||||
|
"k8ef56a20": "Maximale Wiederholungen, bevor der Dienst als ausgefallen markiert wird und eine Benachrichtigung gesendet wird",
|
||||||
|
"k8f8fbf6": "Teilen mit...",
|
||||||
|
"k8ff3a55a": "Warnung",
|
||||||
|
"k9022468f": "Offline löschen",
|
||||||
|
"k90873752": "Altes Passwort",
|
||||||
|
"k90a82c67": "Konto registrieren",
|
||||||
|
"k90b668e5": "Letzte 24 Stunden",
|
||||||
|
"k93374bc9": "Website löschen",
|
||||||
|
"k97ddb155": "Aktuelle Antwort anzeigen",
|
||||||
|
"k98f433ee": "Reporter herunterladen von",
|
||||||
|
"k9a272ecf": "Sind das Ihre Server?",
|
||||||
|
"k9a3cc801": "Zeichen akzeptieren",
|
||||||
|
"k9add1fac": "Verkehr",
|
||||||
|
"k9be2209c": "Anzuzeigender Website-Name",
|
||||||
|
"k9e32beea": "online",
|
||||||
|
"k9e759f8": "Benachrichtigungsliste",
|
||||||
|
"k9fa794aa": "Website-Name",
|
||||||
|
"ka0051b3d": "Domain",
|
||||||
|
"ka0ddbfb": "Website-Infos",
|
||||||
|
"ka2b9bc3c": "Vorherige Periode",
|
||||||
|
"ka2fae1c6": "Benutzer-ID",
|
||||||
|
"ka388d3bf": "Sie können einen Monitor verbinden, der den Gesundheitsstatus in der Website-Übersicht anzeigt",
|
||||||
|
"ka40aea11": "Oder möchten Sie den Serverstatus in einem Windows-Server melden? Wechseln Sie zum manuellen Tab",
|
||||||
|
"ka44150a0": "Letzte 90 Tage",
|
||||||
|
"ka68f2242": "Registrieren",
|
||||||
|
"ka6ee7455": "Website-ID",
|
||||||
|
"ka71c12e1": "Die beiden Passwörter stimmen nicht überein",
|
||||||
|
"ka765ad32": "Benachrichtigung",
|
||||||
|
"ka9d081ac": "Überprüfungsintervall(s)",
|
||||||
|
"kaa0788e9": "Layout erfolgreich gespeichert",
|
||||||
|
"kaa0ccaab": "Von E-Mail",
|
||||||
|
"kab56db46": "Herzschläge",
|
||||||
|
"kacbdae07": "Durchschn. Antwort",
|
||||||
|
"kadef6c48": "Vorherige {{label}}",
|
||||||
|
"kaf39be20": "Netzwerk",
|
||||||
|
"kb01f4f95": "Fertig",
|
||||||
|
"kb0e351e0": "Aktualisiert",
|
||||||
|
"kb320aac4": "Überwacht seit {{dayNum}} Tagen",
|
||||||
|
"kb5673707": "Letzte 7 Tage",
|
||||||
|
"kb659c1bc": "Zert. Ablauf",
|
||||||
|
"kb8de8c50": "BCC",
|
||||||
|
"kbb58c99c": "Erfolgreich gelöscht",
|
||||||
|
"kbcf67f53": "Neues Passwort",
|
||||||
|
"kbd1e7dee": "Nutzung: {{usage}}ms",
|
||||||
|
"kbd425e0e": "aktualisiertAm",
|
||||||
|
"kc00cf2c7": "Dies zeigt Ihr letztes Ergebnis Ihres Monitors an",
|
||||||
|
"kc0c6a913": "Knotenname",
|
||||||
|
"kc1f1f6c9": "Benachrichtigungstyp",
|
||||||
|
"kc45a417b": "BS",
|
||||||
|
"kc4910af7": "Passwort ändern",
|
||||||
|
"kc4ab7848": "Offline-Knoten löschen",
|
||||||
|
"kc4e91854": "Sind Sie sicher, dass Sie alle Herzschläge für diesen Monitor löschen möchten?",
|
||||||
|
"kc5573507": "Hinzufügen",
|
||||||
|
"kc5f82d53": "Zum Beispiel: pushdeer://pushKey",
|
||||||
|
"kc6888ac4": "Auto",
|
||||||
|
"kc6cac621": "(Keine)",
|
||||||
|
"kc70d69ad": "Antwort",
|
||||||
|
"kc9b446d1": "Ausführung abgeschlossen",
|
||||||
|
"kcacbfde1": "Jetzt erstellen",
|
||||||
|
"kcaf5c873": "Aktionen",
|
||||||
|
"kcb8fd4ce": "Kein Server online",
|
||||||
|
"kcbc00b39": "Wählen Sie Ihren Datumsbereich aus",
|
||||||
|
"kcc3b034e": "Url",
|
||||||
|
"kcc50957": "Neuen Monitor hinzufügen",
|
||||||
|
"kccaa732a": "Keine aufeinanderfolgenden Bindestriche",
|
||||||
|
"kccb42483": "Passwort",
|
||||||
|
"kd031b383": "Ansichten",
|
||||||
|
"kd211e2d4": "Versionsseite",
|
||||||
|
"kd37efb26": "Benachrichtigungen",
|
||||||
|
"kd46ab159": "Keine Ereignisse",
|
||||||
|
"kd7985726": "{{num}} Benutzer",
|
||||||
|
"kd92fa3e7": "Host-Name",
|
||||||
|
"kdaa949e5": "Ereignisse von {{monitorName}}",
|
||||||
|
"kdb61adbb": "Offline verbergen",
|
||||||
|
"kdc51b5db": "Websites",
|
||||||
|
"kde37bc27": "Zurück zum Admin",
|
||||||
|
"kde657d5b": "Dashboard",
|
||||||
|
"kdeba7706": "Geräte",
|
||||||
|
"kdf5da1d2": "Keine / STARTTLS",
|
||||||
|
"kdf97690e": "Länder",
|
||||||
|
"ke188f24b": "Absprungrate",
|
||||||
|
"ke1b5ca71": "Seiten",
|
||||||
|
"ke2fe505b": "Diese Woche",
|
||||||
|
"ke3a3f2f2": "Port",
|
||||||
|
"ke46232fe": "Server hinzufügen",
|
||||||
|
"ke5b015e9": "Sie können einen Token von https://t.me/BotFather erhalten.",
|
||||||
|
"ke6797c65": "Sie können Ihre Chat-ID erhalten, indem Sie eine Nachricht an den Bot senden und diese URL besuchen, um die chat_id zu sehen",
|
||||||
|
"ke9d2fef3": "Dieser Monat",
|
||||||
|
"ke9dcaa64": "Keine Benachrichtigung gefunden",
|
||||||
|
"keaf7576f": "Titel",
|
||||||
|
"ked37937b": "Metriken",
|
||||||
|
"ked7eea1a": "OBEN",
|
||||||
|
"ked8814bc": "Kopieren",
|
||||||
|
"kedc69eb6": "Tracking-Code",
|
||||||
|
"kef701e50": "Monitor hinzufügen",
|
||||||
|
"kf22813ad": "Benutzerdefiniert",
|
||||||
|
"kf3b749ef": "Unterstützt Direktchat / Gruppe / Kanal-Chat-ID",
|
||||||
|
"kf55495e0": "Speichern",
|
||||||
|
"kf5bbb568": "Besucherkarte von",
|
||||||
|
"kf6bc1610": "Monitortyp",
|
||||||
|
"kf6db9ea5": "3h",
|
||||||
|
"kf7d5dbf8": "Mehr lesen",
|
||||||
|
"kf97b6f71": "Führen Sie diesen Befehl auf Ihrer Linux-Maschine aus",
|
||||||
|
"kf9877f28": "Details anzeigen",
|
||||||
|
"kfc98929b": "{{num}} Tage",
|
||||||
|
"kfd33c459": "Kopieren erfolgreich!",
|
||||||
|
"kfdaf0bb3": "Zuletzt online: {{time}}",
|
||||||
|
"kfe11d138": "Name",
|
||||||
|
"kfedb6cd8": "Sind Sie sicher, dass Sie alle Ereignisse für diesen Monitor löschen möchten?",
|
||||||
|
"kff849f78": "Automatisch abrufen"
|
||||||
|
}
|
BIN
src/client/public/locales/en/flag.png
Normal file
BIN
src/client/public/locales/en/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 288 B |
218
src/client/public/locales/en/translation.json
Normal file
218
src/client/public/locales/en/translation.json
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"k10336b1": "Nothing more to load",
|
||||||
|
"k10c85f27": "Ignore TLS Error",
|
||||||
|
"k112a7174": "Usage",
|
||||||
|
"k1192dd43": "Please Select Workspace",
|
||||||
|
"k11e887ab": "Is delete this item?",
|
||||||
|
"k11f09c87": "views",
|
||||||
|
"k1286421": "Username",
|
||||||
|
"k134e0d97": "Current",
|
||||||
|
"k14c6c425": "Result",
|
||||||
|
"k158336d6": "Request Timeout(s)",
|
||||||
|
"k1598e726": "current visitor",
|
||||||
|
"k15ae36a6": "Browser",
|
||||||
|
"k16c8909f": "Display Name",
|
||||||
|
"k1777bbf2": "Manual",
|
||||||
|
"k186365b3": "{{monitorName}}'s Chart",
|
||||||
|
"k1964b988": "Stop",
|
||||||
|
"k1bd89236": "run reporter with",
|
||||||
|
"k1c33c293": "Settings",
|
||||||
|
"k1eb5b3ed": "Overview",
|
||||||
|
"k20edf271": "24h",
|
||||||
|
"k21077124": "Edit",
|
||||||
|
"k2264aac": "This year",
|
||||||
|
"k246063be": "Chat ID",
|
||||||
|
"k2497052e": "Chart",
|
||||||
|
"k25b3dc00": "Script JS Code",
|
||||||
|
"k264de775": "Loading more...",
|
||||||
|
"k277e2626": "Available",
|
||||||
|
"k28059d49": "Current Workspace Id",
|
||||||
|
"k2813d1f7": "This Month",
|
||||||
|
"k2a6a7d8f": "DOWN",
|
||||||
|
"k2b2d40d4": "Test Code",
|
||||||
|
"k2c2712a4": "CPU",
|
||||||
|
"k2e6dbf02": "To Email",
|
||||||
|
"k2ea8a019": "Monitor",
|
||||||
|
"k30b5f01b": "Workspaces",
|
||||||
|
"k30e234ee": "You have not dashboard item yet, please enter edit mode and add you item.",
|
||||||
|
"k310fee": "Last 30 days",
|
||||||
|
"k32344f64": "Clear Data",
|
||||||
|
"k3260f019": "Logout",
|
||||||
|
"k3471e956": "New Password Repeat",
|
||||||
|
"k389db675": "Submit",
|
||||||
|
"k38c62888": "Please select monitor",
|
||||||
|
"k3cedb797": "Security",
|
||||||
|
"k3d3baf52": "average visit time",
|
||||||
|
"k3dbe79b1": "Delete",
|
||||||
|
"k3de768a1": "Change password",
|
||||||
|
"k3e757ddf": "Here is no monitor yet.",
|
||||||
|
"k43e21ee9": "Download Client Reportor",
|
||||||
|
"k44cad477": "(Current)",
|
||||||
|
"k46f0adde": "Visitor Map",
|
||||||
|
"k47f43dbc": "{{monitorName}}'s Health",
|
||||||
|
"k4905ed7b": "NONE",
|
||||||
|
"k490ada32": "Add Website",
|
||||||
|
"k49e5f1d2": "1w",
|
||||||
|
"k4ac4dd36": "Referrers",
|
||||||
|
"k4de48e75": "Max Retries",
|
||||||
|
"k4e08cf58": "Show Detail Number",
|
||||||
|
"k4eea9393": "Profile",
|
||||||
|
"k506a90b2": "Your server domain, or ip.",
|
||||||
|
"k517747e1": "Last 24 hours",
|
||||||
|
"k51bac044": "Cancel",
|
||||||
|
"k53ae02a5": "New",
|
||||||
|
"k542b527c": "Events",
|
||||||
|
"k58f90514": "Bot Token",
|
||||||
|
"k593cf342": "Did you sure delete this monitor?",
|
||||||
|
"k5a839f71": "Uptime",
|
||||||
|
"k5eb87a8b": "Start",
|
||||||
|
"k5ecf04b0": "View",
|
||||||
|
"k6067f0ff": "Ignore TLS/SSL error",
|
||||||
|
"k621317b5": "New page",
|
||||||
|
"k62e19375": "Last updated at: {{date}}",
|
||||||
|
"k646a3a80": "{{monitorName}}'s Metrics",
|
||||||
|
"k646c0ae2": "HDD",
|
||||||
|
"k67c5a895": "Yesterday",
|
||||||
|
"k683be220": "Run",
|
||||||
|
"k691b7170": "Stopped",
|
||||||
|
"k698348ff": "Waiting for receive UDP pack",
|
||||||
|
"k6acf5248": "Recent",
|
||||||
|
"k6b36580f": "Apprise URL",
|
||||||
|
"k6bc9e414": "Login",
|
||||||
|
"k6f15bcc3": "Host",
|
||||||
|
"k717660a5": "RAM",
|
||||||
|
"k721589c1": "Today",
|
||||||
|
"k74a240": "Health Bar",
|
||||||
|
"k75581e13": "CC",
|
||||||
|
"k75bfaaa6": "Add this code into your website head script",
|
||||||
|
"k784dd132": "Test",
|
||||||
|
"k7927b824": "Are you sure to clear all offline node?",
|
||||||
|
"k7ac44a6e": "Session Key",
|
||||||
|
"k7b74a43f": "visitors",
|
||||||
|
"k7c95e6a5": "Did you sure delete this page?",
|
||||||
|
"k7cac602a": "Status",
|
||||||
|
"k7f01b47c": "Audit Log",
|
||||||
|
"k7f4bcf6b": "Monitors",
|
||||||
|
"k8037cc6b": "Servers",
|
||||||
|
"k8202c669": "Visitors",
|
||||||
|
"k845abd5b": "Next step",
|
||||||
|
"k84ce1618": "(24 hour)",
|
||||||
|
"k85344b23": "Load",
|
||||||
|
"k85c5fd4c": "No any monitor has been set",
|
||||||
|
"k8746ec38": "Select monitor",
|
||||||
|
"k88a9bf01": "Show Badge",
|
||||||
|
"k88d2647b": "Website",
|
||||||
|
"k89056082": "(30 days)",
|
||||||
|
"k8a44833f": "Services",
|
||||||
|
"k8bac6ae0": "6h",
|
||||||
|
"k8ef56a20": "Maximum retries before the service is marked as down and a notification is sent",
|
||||||
|
"k8f8fbf6": "Share with...",
|
||||||
|
"k8ff3a55a": "Warning",
|
||||||
|
"k9022468f": "Clear Offline",
|
||||||
|
"k90873752": "Old Password",
|
||||||
|
"k90a82c67": "Register Account",
|
||||||
|
"k90b668e5": "Last 24 Hours",
|
||||||
|
"k93374bc9": "Delete Website",
|
||||||
|
"k97ddb155": "Show Current Response",
|
||||||
|
"k98f433ee": "Download reporter from",
|
||||||
|
"k9a272ecf": "Is this your servers?",
|
||||||
|
"k9a3cc801": "Accept characters",
|
||||||
|
"k9add1fac": "Traffic",
|
||||||
|
"k9be2209c": "Website Name to Display",
|
||||||
|
"k9e32beea": "online",
|
||||||
|
"k9e759f8": "Notification List",
|
||||||
|
"k9fa794aa": "Website Name",
|
||||||
|
"ka0051b3d": "Domain",
|
||||||
|
"ka0ddbfb": "Website Info",
|
||||||
|
"ka2b9bc3c": "Previous period",
|
||||||
|
"ka2fae1c6": "User Id",
|
||||||
|
"ka388d3bf": "You can bind a monitor which will display health status in website overview",
|
||||||
|
"ka40aea11": "Or you wanna report server status in windows server? switch to Manual tab",
|
||||||
|
"ka44150a0": "Last 90 days",
|
||||||
|
"ka68f2242": "Register",
|
||||||
|
"ka6ee7455": "Website ID",
|
||||||
|
"ka71c12e1": "The two passwords are not consistent",
|
||||||
|
"ka765ad32": "Notification",
|
||||||
|
"ka9d081ac": "Check Interval(s)",
|
||||||
|
"kaa0788e9": "Layout saved success",
|
||||||
|
"kaa0ccaab": "From Email",
|
||||||
|
"kab56db46": "Heartbeats",
|
||||||
|
"kacbdae07": "Avg. Response",
|
||||||
|
"kadef6c48": "Previous {{label}}",
|
||||||
|
"kaf39be20": "Network",
|
||||||
|
"kb01f4f95": "Done",
|
||||||
|
"kb0e351e0": "Refreshed",
|
||||||
|
"kb320aac4": "Monitored for {{dayNum}} days",
|
||||||
|
"kb5673707": "Last 7 days",
|
||||||
|
"kb659c1bc": "Cert Exp.",
|
||||||
|
"kb8de8c50": "BCC",
|
||||||
|
"kbb58c99c": "Delete Success",
|
||||||
|
"kbcf67f53": "New Password",
|
||||||
|
"kbd1e7dee": "Usage: {{usage}}ms",
|
||||||
|
"kbd425e0e": "updatedAt",
|
||||||
|
"kc00cf2c7": "This will show your recent result of your monitor",
|
||||||
|
"kc0c6a913": "Node Name",
|
||||||
|
"kc1f1f6c9": "Notification Type",
|
||||||
|
"kc45a417b": "OS",
|
||||||
|
"kc4910af7": "Change Password",
|
||||||
|
"kc4ab7848": "Clear Offline Node",
|
||||||
|
"kc4e91854": "Are you sure want to delete all heartbeats for this monitor?",
|
||||||
|
"kc5573507": "Add",
|
||||||
|
"kc5f82d53": "For example: pushdeer://pushKey",
|
||||||
|
"kc6888ac4": "Auto",
|
||||||
|
"kc6cac621": "(None)",
|
||||||
|
"kc70d69ad": "Response",
|
||||||
|
"kc9b446d1": "Run Completed",
|
||||||
|
"kcacbfde1": "Create Now",
|
||||||
|
"kcaf5c873": "Actions",
|
||||||
|
"kcb8fd4ce": "No server online",
|
||||||
|
"kcbc00b39": "Select your date range",
|
||||||
|
"kcc3b034e": "Url",
|
||||||
|
"kcc50957": "Add new Monitor",
|
||||||
|
"kccaa732a": "No consecutive dashes",
|
||||||
|
"kccb42483": "Password",
|
||||||
|
"kd031b383": "Views",
|
||||||
|
"kd211e2d4": "Releases Page",
|
||||||
|
"kd37efb26": "Notifications",
|
||||||
|
"kd46ab159": "No events",
|
||||||
|
"kd7985726": "{{num}} users",
|
||||||
|
"kd92fa3e7": "Host Name",
|
||||||
|
"kdaa949e5": "{{monitorName}}'s Events",
|
||||||
|
"kdb61adbb": "Hide Offline",
|
||||||
|
"kdc51b5db": "Websites",
|
||||||
|
"kde37bc27": "Back to Admin",
|
||||||
|
"kde657d5b": "Dashboard",
|
||||||
|
"kdeba7706": "Devices",
|
||||||
|
"kdf5da1d2": "None / STARTTLS",
|
||||||
|
"kdf97690e": "Countries",
|
||||||
|
"ke188f24b": "bounce rate",
|
||||||
|
"ke1b5ca71": "Pages",
|
||||||
|
"ke2fe505b": "This week",
|
||||||
|
"ke3a3f2f2": "Port",
|
||||||
|
"ke46232fe": "Add Server",
|
||||||
|
"ke5b015e9": "You can get a token from https://t.me/BotFather.",
|
||||||
|
"ke6797c65": "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id",
|
||||||
|
"ke9d2fef3": "This month",
|
||||||
|
"ke9dcaa64": "Not found any notification",
|
||||||
|
"keaf7576f": "Title",
|
||||||
|
"ked37937b": "Metrics",
|
||||||
|
"ked7eea1a": "UP",
|
||||||
|
"ked8814bc": "Copy",
|
||||||
|
"kedc69eb6": "Tracking code",
|
||||||
|
"kef701e50": "Add Monitor",
|
||||||
|
"kf22813ad": "Custom",
|
||||||
|
"kf3b749ef": "Support Direct Chat / Group / Channel's Chat ID",
|
||||||
|
"kf55495e0": "Save",
|
||||||
|
"kf5bbb568": "'s visitor map",
|
||||||
|
"kf6bc1610": "Monitor Type",
|
||||||
|
"kf6db9ea5": "3h",
|
||||||
|
"kf7d5dbf8": "Read more",
|
||||||
|
"kf97b6f71": "Run this command in your linux machine",
|
||||||
|
"kf9877f28": "View Details",
|
||||||
|
"kfc98929b": "{{num}} days",
|
||||||
|
"kfd33c459": "Copy success!",
|
||||||
|
"kfdaf0bb3": "Last online: {{time}}",
|
||||||
|
"kfe11d138": "Name",
|
||||||
|
"kfedb6cd8": "Are you sure want to delete all events for this monitor?",
|
||||||
|
"kff849f78": "Auto Fetch"
|
||||||
|
}
|
BIN
src/client/public/locales/fr/flag.png
Normal file
BIN
src/client/public/locales/fr/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 267 B |
218
src/client/public/locales/fr/translation.json
Normal file
218
src/client/public/locales/fr/translation.json
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"k10336b1": "Plus rien à charger",
|
||||||
|
"k10c85f27": "Ignorer l'erreur TLS",
|
||||||
|
"k112a7174": "Utilisation",
|
||||||
|
"k1192dd43": "Veuillez sélectionner un espace de travail",
|
||||||
|
"k11e887ab": "Voulez-vous supprimer cet élément ?",
|
||||||
|
"k11f09c87": "vues",
|
||||||
|
"k1286421": "Nom d'utilisateur",
|
||||||
|
"k134e0d97": "Actuel",
|
||||||
|
"k14c6c425": "Résultat",
|
||||||
|
"k158336d6": "Délai d'attente de la requête (s)",
|
||||||
|
"k1598e726": "visiteur actuel",
|
||||||
|
"k15ae36a6": "Navigateur",
|
||||||
|
"k16c8909f": "Nom d'affichage",
|
||||||
|
"k1777bbf2": "Manuel",
|
||||||
|
"k186365b3": "Graphique de {{monitorName}}",
|
||||||
|
"k1964b988": "Arrêter",
|
||||||
|
"k1bd89236": "exécuter le rapporteur avec",
|
||||||
|
"k1c33c293": "Paramètres",
|
||||||
|
"k1eb5b3ed": "Aperçu",
|
||||||
|
"k20edf271": "24h",
|
||||||
|
"k21077124": "Éditer",
|
||||||
|
"k2264aac": "Cette année",
|
||||||
|
"k246063be": "ID de chat",
|
||||||
|
"k2497052e": "Graphique",
|
||||||
|
"k25b3dc00": "Code JS du script",
|
||||||
|
"k264de775": "Chargement de plus...",
|
||||||
|
"k277e2626": "Disponible",
|
||||||
|
"k28059d49": "ID de l'espace de travail actuel",
|
||||||
|
"k2813d1f7": "Ce mois-ci",
|
||||||
|
"k2a6a7d8f": "HORS LIGNE",
|
||||||
|
"k2b2d40d4": "Code de test",
|
||||||
|
"k2c2712a4": "CPU",
|
||||||
|
"k2e6dbf02": "À l'email",
|
||||||
|
"k2ea8a019": "Moniteur",
|
||||||
|
"k30b5f01b": "Espaces de travail",
|
||||||
|
"k30e234ee": "Vous n'avez pas encore d'élément de tableau de bord, veuillez entrer en mode édition et ajouter votre élément.",
|
||||||
|
"k310fee": "30 derniers jours",
|
||||||
|
"k32344f64": "Effacer les données",
|
||||||
|
"k3260f019": "Déconnexion",
|
||||||
|
"k3471e956": "Répéter le nouveau mot de passe",
|
||||||
|
"k389db675": "Soumettre",
|
||||||
|
"k38c62888": "Veuillez sélectionner un moniteur",
|
||||||
|
"k3cedb797": "Sécurité",
|
||||||
|
"k3d3baf52": "temps moyen de visite",
|
||||||
|
"k3dbe79b1": "Supprimer",
|
||||||
|
"k3de768a1": "Changer le mot de passe",
|
||||||
|
"k3e757ddf": "Il n'y a pas encore de moniteur ici.",
|
||||||
|
"k43e21ee9": "Télécharger le rapporteur client",
|
||||||
|
"k44cad477": "(Actuel)",
|
||||||
|
"k46f0adde": "Carte des visiteurs",
|
||||||
|
"k47f43dbc": "Santé de {{monitorName}}",
|
||||||
|
"k4905ed7b": "AUCUN",
|
||||||
|
"k490ada32": "Ajouter un site Web",
|
||||||
|
"k49e5f1d2": "1s",
|
||||||
|
"k4ac4dd36": "Référents",
|
||||||
|
"k4de48e75": "Nombre maximum de tentatives",
|
||||||
|
"k4e08cf58": "Afficher le numéro de détail",
|
||||||
|
"k4eea9393": "Profil",
|
||||||
|
"k506a90b2": "Votre domaine de serveur, ou IP.",
|
||||||
|
"k517747e1": "24 dernières heures",
|
||||||
|
"k51bac044": "Annuler",
|
||||||
|
"k53ae02a5": "Nouveau",
|
||||||
|
"k542b527c": "Événements",
|
||||||
|
"k58f90514": "Jeton de bot",
|
||||||
|
"k593cf342": "Êtes-vous sûr de vouloir supprimer ce moniteur ?",
|
||||||
|
"k5a839f71": "Disponibilité",
|
||||||
|
"k5eb87a8b": "Démarrer",
|
||||||
|
"k5ecf04b0": "Vue",
|
||||||
|
"k6067f0ff": "Ignorer l'erreur TLS/SSL",
|
||||||
|
"k621317b5": "Nouvelle page",
|
||||||
|
"k62e19375": "Dernière mise à jour : {{date}}",
|
||||||
|
"k646a3a80": "Métriques de {{monitorName}}",
|
||||||
|
"k646c0ae2": "HDD",
|
||||||
|
"k67c5a895": "Hier",
|
||||||
|
"k683be220": "Exécuter",
|
||||||
|
"k691b7170": "Arrêté",
|
||||||
|
"k698348ff": "En attente de recevoir le pack UDP",
|
||||||
|
"k6acf5248": "Récent",
|
||||||
|
"k6b36580f": "URL Apprise",
|
||||||
|
"k6bc9e414": "Connexion",
|
||||||
|
"k6f15bcc3": "Hôte",
|
||||||
|
"k717660a5": "RAM",
|
||||||
|
"k721589c1": "Aujourd'hui",
|
||||||
|
"k74a240": "Barre de santé",
|
||||||
|
"k75581e13": "CC",
|
||||||
|
"k75bfaaa6": "Ajoutez ce code dans le script de tête de votre site web",
|
||||||
|
"k784dd132": "Test",
|
||||||
|
"k7927b824": "Êtes-vous sûr de vouloir effacer tous les nœuds hors ligne ?",
|
||||||
|
"k7ac44a6e": "Clé de session",
|
||||||
|
"k7b74a43f": "visiteurs",
|
||||||
|
"k7c95e6a5": "Êtes-vous sûr de vouloir supprimer cette page ?",
|
||||||
|
"k7cac602a": "Statut",
|
||||||
|
"k7f01b47c": "Journal d'audit",
|
||||||
|
"k7f4bcf6b": "Moniteurs",
|
||||||
|
"k8037cc6b": "Serveurs",
|
||||||
|
"k8202c669": "Visiteurs",
|
||||||
|
"k845abd5b": "Étape suivante",
|
||||||
|
"k84ce1618": "(24 heures)",
|
||||||
|
"k85344b23": "Charge",
|
||||||
|
"k85c5fd4c": "Aucun moniteur n'a été défini",
|
||||||
|
"k8746ec38": "Sélectionner un moniteur",
|
||||||
|
"k88a9bf01": "Afficher le badge",
|
||||||
|
"k88d2647b": "Site Web",
|
||||||
|
"k89056082": "(30 jours)",
|
||||||
|
"k8a44833f": "Services",
|
||||||
|
"k8bac6ae0": "6h",
|
||||||
|
"k8ef56a20": "Nombre maximal de tentatives avant que le service ne soit marqué comme étant en panne et qu'une notification soit envoyée",
|
||||||
|
"k8f8fbf6": "Partager avec...",
|
||||||
|
"k8ff3a55a": "Avertissement",
|
||||||
|
"k9022468f": "Effacer hors ligne",
|
||||||
|
"k90873752": "Ancien mot de passe",
|
||||||
|
"k90a82c67": "Créer un compte",
|
||||||
|
"k90b668e5": "24 dernières heures",
|
||||||
|
"k93374bc9": "Supprimer le site Web",
|
||||||
|
"k97ddb155": "Afficher la réponse actuelle",
|
||||||
|
"k98f433ee": "Télécharger le rapporteur de",
|
||||||
|
"k9a272ecf": "S'agit-il de vos serveurs ?",
|
||||||
|
"k9a3cc801": "Accepter les caractères",
|
||||||
|
"k9add1fac": "Trafic",
|
||||||
|
"k9be2209c": "Nom du site Web à afficher",
|
||||||
|
"k9e32beea": "en ligne",
|
||||||
|
"k9e759f8": "Liste de notifications",
|
||||||
|
"k9fa794aa": "Nom du site Web",
|
||||||
|
"ka0051b3d": "Domaine",
|
||||||
|
"ka0ddbfb": "Infos sur le site Web",
|
||||||
|
"ka2b9bc3c": "Période précédente",
|
||||||
|
"ka2fae1c6": "ID utilisateur",
|
||||||
|
"ka388d3bf": "Vous pouvez lier un moniteur qui affichera l'état de santé dans l'aperçu du site Web",
|
||||||
|
"ka40aea11": "Ou souhaitez-vous signaler l'état du serveur dans un serveur Windows ? passez à l'onglet Manuel",
|
||||||
|
"ka44150a0": "90 derniers jours",
|
||||||
|
"ka68f2242": "S'inscrire",
|
||||||
|
"ka6ee7455": "ID du site Web",
|
||||||
|
"ka71c12e1": "Les deux mots de passe ne sont pas cohérents",
|
||||||
|
"ka765ad32": "Notification",
|
||||||
|
"ka9d081ac": "Intervalle de vérification (s)",
|
||||||
|
"kaa0788e9": "Disposition enregistrée avec succès",
|
||||||
|
"kaa0ccaab": "De l'email",
|
||||||
|
"kab56db46": "Battrements de cœur",
|
||||||
|
"kacbdae07": "Réponse moyenne",
|
||||||
|
"kadef6c48": "Précédent {{label}}",
|
||||||
|
"kaf39be20": "Réseau",
|
||||||
|
"kb01f4f95": "Terminé",
|
||||||
|
"kb0e351e0": "Rafraîchi",
|
||||||
|
"kb320aac4": "Surveillé pendant {{dayNum}} jours",
|
||||||
|
"kb5673707": "7 derniers jours",
|
||||||
|
"kb659c1bc": "Expiration du cert.",
|
||||||
|
"kb8de8c50": "CCI",
|
||||||
|
"kbb58c99c": "Suppression réussie",
|
||||||
|
"kbcf67f53": "Nouveau mot de passe",
|
||||||
|
"kbd1e7dee": "Utilisation : {{usage}}ms",
|
||||||
|
"kbd425e0e": "updatedAt",
|
||||||
|
"kc00cf2c7": "Cela affichera votre résultat récent de votre moniteur",
|
||||||
|
"kc0c6a913": "Nom du nœud",
|
||||||
|
"kc1f1f6c9": "Type de notification",
|
||||||
|
"kc45a417b": "OS",
|
||||||
|
"kc4910af7": "Changer le mot de passe",
|
||||||
|
"kc4ab7848": "Effacer le nœud hors ligne",
|
||||||
|
"kc4e91854": "Êtes-vous sûr de vouloir supprimer tous les battements de cœur pour ce moniteur ?",
|
||||||
|
"kc5573507": "Ajouter",
|
||||||
|
"kc5f82d53": "Par exemple : pushdeer://pushKey",
|
||||||
|
"kc6888ac4": "Auto",
|
||||||
|
"kc6cac621": "(Aucun)",
|
||||||
|
"kc70d69ad": "Réponse",
|
||||||
|
"kc9b446d1": "Exécution terminée",
|
||||||
|
"kcacbfde1": "Créer maintenant",
|
||||||
|
"kcaf5c873": "Actions",
|
||||||
|
"kcb8fd4ce": "Aucun serveur en ligne",
|
||||||
|
"kcbc00b39": "Sélectionnez votre plage de dates",
|
||||||
|
"kcc3b034e": "Url",
|
||||||
|
"kcc50957": "Ajouter un nouveau moniteur",
|
||||||
|
"kccaa732a": "Pas de tirets consécutifs",
|
||||||
|
"kccb42483": "Mot de passe",
|
||||||
|
"kd031b383": "Vues",
|
||||||
|
"kd211e2d4": "Page des versions",
|
||||||
|
"kd37efb26": "Notifications",
|
||||||
|
"kd46ab159": "Aucun événement",
|
||||||
|
"kd7985726": "{{num}} utilisateurs",
|
||||||
|
"kd92fa3e7": "Nom de l'hôte",
|
||||||
|
"kdaa949e5": "Événements de {{monitorName}}",
|
||||||
|
"kdb61adbb": "Masquer hors ligne",
|
||||||
|
"kdc51b5db": "Sites Web",
|
||||||
|
"kde37bc27": "Retour à l'administrateur",
|
||||||
|
"kde657d5b": "Tableau de bord",
|
||||||
|
"kdeba7706": "Appareils",
|
||||||
|
"kdf5da1d2": "Aucun / STARTTLS",
|
||||||
|
"kdf97690e": "Pays",
|
||||||
|
"ke188f24b": "taux de rebond",
|
||||||
|
"ke1b5ca71": "Pages",
|
||||||
|
"ke2fe505b": "Cette semaine",
|
||||||
|
"ke3a3f2f2": "Port",
|
||||||
|
"ke46232fe": "Ajouter un serveur",
|
||||||
|
"ke5b015e9": "Vous pouvez obtenir un jeton de https://t.me/BotFather.",
|
||||||
|
"ke6797c65": "Vous pouvez obtenir votre ID de chat en envoyant un message au bot et en allant à cette URL pour voir le chat_id",
|
||||||
|
"ke9d2fef3": "Ce mois-ci",
|
||||||
|
"ke9dcaa64": "Aucune notification trouvée",
|
||||||
|
"keaf7576f": "Titre",
|
||||||
|
"ked37937b": "Métriques",
|
||||||
|
"ked7eea1a": "EN LIGNE",
|
||||||
|
"ked8814bc": "Copier",
|
||||||
|
"kedc69eb6": "Code de suivi",
|
||||||
|
"kef701e50": "Ajouter un moniteur",
|
||||||
|
"kf22813ad": "Personnalisé",
|
||||||
|
"kf3b749ef": "Prend en charge le chat direct / groupe / ID de chat de canal",
|
||||||
|
"kf55495e0": "Sauvegarder",
|
||||||
|
"kf5bbb568": "carte des visiteurs de",
|
||||||
|
"kf6bc1610": "Type de moniteur",
|
||||||
|
"kf6db9ea5": "3h",
|
||||||
|
"kf7d5dbf8": "En savoir plus",
|
||||||
|
"kf97b6f71": "Exécutez cette commande sur votre machine Linux",
|
||||||
|
"kf9877f28": "Voir les détails",
|
||||||
|
"kfc98929b": "{{num}} jours",
|
||||||
|
"kfd33c459": "Copie réussie !",
|
||||||
|
"kfdaf0bb3": "Dernière connexion : {{time}}",
|
||||||
|
"kfe11d138": "Nom",
|
||||||
|
"kfedb6cd8": "Êtes-vous sûr de vouloir supprimer tous les événements pour ce moniteur ?",
|
||||||
|
"kff849f78": "Récupération automatique"
|
||||||
|
}
|
BIN
src/client/public/locales/jp/flag.png
Normal file
BIN
src/client/public/locales/jp/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 450 B |
218
src/client/public/locales/jp/translation.json
Normal file
218
src/client/public/locales/jp/translation.json
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"k10336b1": "これ以上読み込むものはありません",
|
||||||
|
"k10c85f27": "TLSエラーを無視",
|
||||||
|
"k112a7174": "使用法",
|
||||||
|
"k1192dd43": "ワークスペースを選択してください",
|
||||||
|
"k11e887ab": "このアイテムを削除しますか?",
|
||||||
|
"k11f09c87": "ビュー",
|
||||||
|
"k1286421": "ユーザー名",
|
||||||
|
"k134e0d97": "現在",
|
||||||
|
"k14c6c425": "結果",
|
||||||
|
"k158336d6": "リクエストタイムアウト(秒)",
|
||||||
|
"k1598e726": "現在の訪問者",
|
||||||
|
"k15ae36a6": "ブラウザ",
|
||||||
|
"k16c8909f": "表示名",
|
||||||
|
"k1777bbf2": "マニュアル",
|
||||||
|
"k186365b3": "{{monitorName}}のチャート",
|
||||||
|
"k1964b988": "停止",
|
||||||
|
"k1bd89236": "レポーターを実行する",
|
||||||
|
"k1c33c293": "設定",
|
||||||
|
"k1eb5b3ed": "概要",
|
||||||
|
"k20edf271": "24時間",
|
||||||
|
"k21077124": "編集",
|
||||||
|
"k2264aac": "今年",
|
||||||
|
"k246063be": "チャットID",
|
||||||
|
"k2497052e": "チャート",
|
||||||
|
"k25b3dc00": "スクリプトJSコード",
|
||||||
|
"k264de775": "さらに読み込む...",
|
||||||
|
"k277e2626": "利用可能",
|
||||||
|
"k28059d49": "現在のワークスペースID",
|
||||||
|
"k2813d1f7": "今月",
|
||||||
|
"k2a6a7d8f": "ダウン",
|
||||||
|
"k2b2d40d4": "テストコード",
|
||||||
|
"k2c2712a4": "CPU",
|
||||||
|
"k2e6dbf02": "メールアドレスへ",
|
||||||
|
"k2ea8a019": "モニター",
|
||||||
|
"k30b5f01b": "ワークスペース",
|
||||||
|
"k30e234ee": "ダッシュボードアイテムがまだありません。編集モードに入り、アイテムを追加してください。",
|
||||||
|
"k310fee": "過去30日間",
|
||||||
|
"k32344f64": "データクリア",
|
||||||
|
"k3260f019": "ログアウト",
|
||||||
|
"k3471e956": "新しいパスワードの再入力",
|
||||||
|
"k389db675": "送信",
|
||||||
|
"k38c62888": "モニターを選択してください",
|
||||||
|
"k3cedb797": "セキュリティ",
|
||||||
|
"k3d3baf52": "平均訪問時間",
|
||||||
|
"k3dbe79b1": "削除",
|
||||||
|
"k3de768a1": "パスワードを変更",
|
||||||
|
"k3e757ddf": "モニターがまだありません。",
|
||||||
|
"k43e21ee9": "クライアントレポーターをダウンロード",
|
||||||
|
"k44cad477": "(現在)",
|
||||||
|
"k46f0adde": "訪問者マップ",
|
||||||
|
"k47f43dbc": "{{monitorName}}の健康状態",
|
||||||
|
"k4905ed7b": "なし",
|
||||||
|
"k490ada32": "ウェブサイトを追加",
|
||||||
|
"k49e5f1d2": "1週間",
|
||||||
|
"k4ac4dd36": "リファラー",
|
||||||
|
"k4de48e75": "最大リトライ回数",
|
||||||
|
"k4e08cf58": "詳細番号を表示",
|
||||||
|
"k4eea9393": "プロファイル",
|
||||||
|
"k506a90b2": "サーバードメインまたはIP。",
|
||||||
|
"k517747e1": "過去24時間",
|
||||||
|
"k51bac044": "キャンセル",
|
||||||
|
"k53ae02a5": "新規",
|
||||||
|
"k542b527c": "イベント",
|
||||||
|
"k58f90514": "ボットトークン",
|
||||||
|
"k593cf342": "このモニターを削除してもよろしいですか?",
|
||||||
|
"k5a839f71": "アップタイム",
|
||||||
|
"k5eb87a8b": "開始",
|
||||||
|
"k5ecf04b0": "ビュー",
|
||||||
|
"k6067f0ff": "TLS/SSLエラーを無視",
|
||||||
|
"k621317b5": "新しいページ",
|
||||||
|
"k62e19375": "最終更新:{{date}}",
|
||||||
|
"k646a3a80": "{{monitorName}}のメトリック",
|
||||||
|
"k646c0ae2": "HDD",
|
||||||
|
"k67c5a895": "昨日",
|
||||||
|
"k683be220": "実行",
|
||||||
|
"k691b7170": "停止済み",
|
||||||
|
"k698348ff": "UDPパックの受信待ち",
|
||||||
|
"k6acf5248": "最近",
|
||||||
|
"k6b36580f": "Apprise URL",
|
||||||
|
"k6bc9e414": "ログイン",
|
||||||
|
"k6f15bcc3": "ホスト",
|
||||||
|
"k717660a5": "RAM",
|
||||||
|
"k721589c1": "今日",
|
||||||
|
"k74a240": "ヘルスバー",
|
||||||
|
"k75581e13": "CC",
|
||||||
|
"k75bfaaa6": "このコードをウェブサイトのヘッドスクリプトに追加してください",
|
||||||
|
"k784dd132": "テスト",
|
||||||
|
"k7927b824": "すべてのオフラインノードをクリアしてもよろしいですか?",
|
||||||
|
"k7ac44a6e": "セッションキー",
|
||||||
|
"k7b74a43f": "訪問者",
|
||||||
|
"k7c95e6a5": "このページを削除してもよろしいですか?",
|
||||||
|
"k7cac602a": "ステータス",
|
||||||
|
"k7f01b47c": "監査ログ",
|
||||||
|
"k7f4bcf6b": "モニター",
|
||||||
|
"k8037cc6b": "サーバー",
|
||||||
|
"k8202c669": "訪問者",
|
||||||
|
"k845abd5b": "次のステップ",
|
||||||
|
"k84ce1618": "(24時間)",
|
||||||
|
"k85344b23": "ロード",
|
||||||
|
"k85c5fd4c": "まだモニターが設定されていません",
|
||||||
|
"k8746ec38": "モニターを選択",
|
||||||
|
"k88a9bf01": "バッジを表示",
|
||||||
|
"k88d2647b": "ウェブサイト",
|
||||||
|
"k89056082": "(30日間)",
|
||||||
|
"k8a44833f": "サービス",
|
||||||
|
"k8bac6ae0": "6時間",
|
||||||
|
"k8ef56a20": "サービスがダウンとマークされ、通知が送信される前の最大リトライ回数",
|
||||||
|
"k8f8fbf6": "共有...",
|
||||||
|
"k8ff3a55a": "警告",
|
||||||
|
"k9022468f": "オフラインをクリア",
|
||||||
|
"k90873752": "古いパスワード",
|
||||||
|
"k90a82c67": "アカウントを登録",
|
||||||
|
"k90b668e5": "過去24時間",
|
||||||
|
"k93374bc9": "ウェブサイトを削除",
|
||||||
|
"k97ddb155": "現在の応答を表示",
|
||||||
|
"k98f433ee": "からレポーターをダウンロード",
|
||||||
|
"k9a272ecf": "これはあなたのサーバーですか?",
|
||||||
|
"k9a3cc801": "文字を受け入れる",
|
||||||
|
"k9add1fac": "トラフィック",
|
||||||
|
"k9be2209c": "表示するウェブサイト名",
|
||||||
|
"k9e32beea": "オンライン",
|
||||||
|
"k9e759f8": "通知リスト",
|
||||||
|
"k9fa794aa": "ウェブサイト名",
|
||||||
|
"ka0051b3d": "ドメイン",
|
||||||
|
"ka0ddbfb": "ウェブサイト情報",
|
||||||
|
"ka2b9bc3c": "前の期間",
|
||||||
|
"ka2fae1c6": "ユーザーID",
|
||||||
|
"ka388d3bf": "ウェブサイトの概要に表示されるヘルスステータスを表示するモニターをバインドできます",
|
||||||
|
"ka40aea11": "Windowsサーバーでサーバーステータスを報告しますか?マニュアルタブに切り替えてください",
|
||||||
|
"ka44150a0": "過去90日間",
|
||||||
|
"ka68f2242": "登録",
|
||||||
|
"ka6ee7455": "ウェブサイトID",
|
||||||
|
"ka71c12e1": "2つのパスワードが一致しません",
|
||||||
|
"ka765ad32": "通知",
|
||||||
|
"ka9d081ac": "チェック間隔(秒)",
|
||||||
|
"kaa0788e9": "レイアウトが正常に保存されました",
|
||||||
|
"kaa0ccaab": "送信者メール",
|
||||||
|
"kab56db46": "ハートビート",
|
||||||
|
"kacbdae07": "平均応答",
|
||||||
|
"kadef6c48": "前の{{label}}",
|
||||||
|
"kaf39be20": "ネットワーク",
|
||||||
|
"kb01f4f95": "完了",
|
||||||
|
"kb0e351e0": "更新されました",
|
||||||
|
"kb320aac4": "{{dayNum}}日間監視",
|
||||||
|
"kb5673707": "過去7日間",
|
||||||
|
"kb659c1bc": "証明書の有効期限",
|
||||||
|
"kb8de8c50": "BCC",
|
||||||
|
"kbb58c99c": "削除に成功しました",
|
||||||
|
"kbcf67f53": "新しいパスワード",
|
||||||
|
"kbd1e7dee": "使用量:{{usage}}ms",
|
||||||
|
"kbd425e0e": "更新日",
|
||||||
|
"kc00cf2c7": "これはあなたのモニターの最近の結果を表示します",
|
||||||
|
"kc0c6a913": "ノード名",
|
||||||
|
"kc1f1f6c9": "通知タイプ",
|
||||||
|
"kc45a417b": "OS",
|
||||||
|
"kc4910af7": "パスワードを変更する",
|
||||||
|
"kc4ab7848": "オフラインノードをクリア",
|
||||||
|
"kc4e91854": "このモニターのすべてのハートビートを削除してもよろしいですか?",
|
||||||
|
"kc5573507": "追加",
|
||||||
|
"kc5f82d53": "例:pushdeer://pushKey",
|
||||||
|
"kc6888ac4": "自動",
|
||||||
|
"kc6cac621": "(なし)",
|
||||||
|
"kc70d69ad": "応答",
|
||||||
|
"kc9b446d1": "実行完了",
|
||||||
|
"kcacbfde1": "今すぐ作成",
|
||||||
|
"kcaf5c873": "アクション",
|
||||||
|
"kcb8fd4ce": "オンラインのサーバーはありません",
|
||||||
|
"kcbc00b39": "日付範囲を選択",
|
||||||
|
"kcc3b034e": "Url",
|
||||||
|
"kcc50957": "新しいモニターを追加",
|
||||||
|
"kccaa732a": "連続ダッシュなし",
|
||||||
|
"kccb42483": "パスワード",
|
||||||
|
"kd031b383": "ビュー",
|
||||||
|
"kd211e2d4": "リリースページ",
|
||||||
|
"kd37efb26": "通知",
|
||||||
|
"kd46ab159": "イベントなし",
|
||||||
|
"kd7985726": "{{num}}人のユーザー",
|
||||||
|
"kd92fa3e7": "ホスト名",
|
||||||
|
"kdaa949e5": "{{monitorName}}のイベント",
|
||||||
|
"kdb61adbb": "オフラインを隠す",
|
||||||
|
"kdc51b5db": "ウェブサイト",
|
||||||
|
"kde37bc27": "管理者に戻る",
|
||||||
|
"kde657d5b": "ダッシュボード",
|
||||||
|
"kdeba7706": "デバイス",
|
||||||
|
"kdf5da1d2": "なし/STARTTLS",
|
||||||
|
"kdf97690e": "国",
|
||||||
|
"ke188f24b": "直帰率",
|
||||||
|
"ke1b5ca71": "ページ",
|
||||||
|
"ke2fe505b": "今週",
|
||||||
|
"ke3a3f2f2": "ポート",
|
||||||
|
"ke46232fe": "サーバーを追加",
|
||||||
|
"ke5b015e9": "https://t.me/BotFather でトークンを取得できます。",
|
||||||
|
"ke6797c65": "ボットにメッセージを送り、このURLにアクセスしてchat_idを表示することで、チャットIDを取得できます",
|
||||||
|
"ke9d2fef3": "今月",
|
||||||
|
"ke9dcaa64": "通知は見つかりませんでした",
|
||||||
|
"keaf7576f": "タイトル",
|
||||||
|
"ked37937b": "メトリクス",
|
||||||
|
"ked7eea1a": "アップ",
|
||||||
|
"ked8814bc": "コピー",
|
||||||
|
"kedc69eb6": "トラッキングコード",
|
||||||
|
"kef701e50": "モニターを追加",
|
||||||
|
"kf22813ad": "カスタム",
|
||||||
|
"kf3b749ef": "ダイレクトチャット/グループ/チャネルのチャットIDをサポート",
|
||||||
|
"kf55495e0": "保存",
|
||||||
|
"kf5bbb568": "の訪問者マップ",
|
||||||
|
"kf6bc1610": "モニタータイプ",
|
||||||
|
"kf6db9ea5": "3時間",
|
||||||
|
"kf7d5dbf8": "もっと読む",
|
||||||
|
"kf97b6f71": "Linuxマシンでこのコマンドを実行してください",
|
||||||
|
"kf9877f28": "詳細を見る",
|
||||||
|
"kfc98929b": "{{num}}日",
|
||||||
|
"kfd33c459": "コピーに成功しました!",
|
||||||
|
"kfdaf0bb3": "最後のオンライン:{{time}}",
|
||||||
|
"kfe11d138": "名前",
|
||||||
|
"kfedb6cd8": "このモニターのすべてのイベントを削除してもよろしいですか?",
|
||||||
|
"kff849f78": "自動取得"
|
||||||
|
}
|
BIN
src/client/public/locales/ru/flag.png
Normal file
BIN
src/client/public/locales/ru/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 268 B |
218
src/client/public/locales/ru/translation.json
Normal file
218
src/client/public/locales/ru/translation.json
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"k10336b1": "Больше ничего загружать не нужно",
|
||||||
|
"k10c85f27": "Игнорировать ошибку TLS",
|
||||||
|
"k112a7174": "Использование",
|
||||||
|
"k1192dd43": "Пожалуйста, выберите рабочую область",
|
||||||
|
"k11e887ab": "Удалить этот элемент?",
|
||||||
|
"k11f09c87": "просмотры",
|
||||||
|
"k1286421": "Имя пользователя",
|
||||||
|
"k134e0d97": "Текущий",
|
||||||
|
"k14c6c425": "Результат",
|
||||||
|
"k158336d6": "Таймаут запроса (с)",
|
||||||
|
"k1598e726": "текущий посетитель",
|
||||||
|
"k15ae36a6": "Браузер",
|
||||||
|
"k16c8909f": "Отображаемое имя",
|
||||||
|
"k1777bbf2": "Вручную",
|
||||||
|
"k186365b3": "График {{monitorName}}",
|
||||||
|
"k1964b988": "Остановить",
|
||||||
|
"k1bd89236": "запустить репортер с",
|
||||||
|
"k1c33c293": "Настройки",
|
||||||
|
"k1eb5b3ed": "Обзор",
|
||||||
|
"k20edf271": "24ч",
|
||||||
|
"k21077124": "Редактировать",
|
||||||
|
"k2264aac": "Этот год",
|
||||||
|
"k246063be": "ID чата",
|
||||||
|
"k2497052e": "График",
|
||||||
|
"k25b3dc00": "JS код скрипта",
|
||||||
|
"k264de775": "Загружается ещё...",
|
||||||
|
"k277e2626": "Доступно",
|
||||||
|
"k28059d49": "ID текущей рабочей области",
|
||||||
|
"k2813d1f7": "Этот месяц",
|
||||||
|
"k2a6a7d8f": "НИЖЕ",
|
||||||
|
"k2b2d40d4": "Тестовый код",
|
||||||
|
"k2c2712a4": "ЦПУ",
|
||||||
|
"k2e6dbf02": "На Email",
|
||||||
|
"k2ea8a019": "Монитор",
|
||||||
|
"k30b5f01b": "Рабочие области",
|
||||||
|
"k30e234ee": "У вас еще нет элементов на панели инструментов, пожалуйста, войдите в режим редактирования и добавьте свой элемент.",
|
||||||
|
"k310fee": "Последние 30 дней",
|
||||||
|
"k32344f64": "Очистить данные",
|
||||||
|
"k3260f019": "Выйти",
|
||||||
|
"k3471e956": "Повтор нового пароля",
|
||||||
|
"k389db675": "Отправить",
|
||||||
|
"k38c62888": "Пожалуйста, выберите монитор",
|
||||||
|
"k3cedb797": "Безопасность",
|
||||||
|
"k3d3baf52": "среднее время посещения",
|
||||||
|
"k3dbe79b1": "Удалить",
|
||||||
|
"k3de768a1": "Сменить пароль",
|
||||||
|
"k3e757ddf": "Здесь пока нет мониторов.",
|
||||||
|
"k43e21ee9": "Скачать клиентский отчет",
|
||||||
|
"k44cad477": "(Текущий)",
|
||||||
|
"k46f0adde": "Карта посетителей",
|
||||||
|
"k47f43dbc": "Здоровье {{monitorName}}",
|
||||||
|
"k4905ed7b": "НИКАКОЙ",
|
||||||
|
"k490ada32": "Добавить веб-сайт",
|
||||||
|
"k49e5f1d2": "1н",
|
||||||
|
"k4ac4dd36": "Рефереры",
|
||||||
|
"k4de48e75": "Макс. попыток",
|
||||||
|
"k4e08cf58": "Показать подробное количество",
|
||||||
|
"k4eea9393": "Профиль",
|
||||||
|
"k506a90b2": "Ваш домен сервера или ip.",
|
||||||
|
"k517747e1": "Последние 24 часа",
|
||||||
|
"k51bac044": "Отмена",
|
||||||
|
"k53ae02a5": "Новый",
|
||||||
|
"k542b527c": "События",
|
||||||
|
"k58f90514": "Токен бота",
|
||||||
|
"k593cf342": "Вы уверены, что хотите удалить этот монитор?",
|
||||||
|
"k5a839f71": "Время работы",
|
||||||
|
"k5eb87a8b": "Старт",
|
||||||
|
"k5ecf04b0": "Просмотр",
|
||||||
|
"k6067f0ff": "Игнорировать ошибку TLS/SSL",
|
||||||
|
"k621317b5": "Новая страница",
|
||||||
|
"k62e19375": "Последнее обновление: {{date}}",
|
||||||
|
"k646a3a80": "Метрики {{monitorName}}",
|
||||||
|
"k646c0ae2": "HDD",
|
||||||
|
"k67c5a895": "Вчера",
|
||||||
|
"k683be220": "Запустить",
|
||||||
|
"k691b7170": "Остановлено",
|
||||||
|
"k698348ff": "Ожидание получения UDP пакета",
|
||||||
|
"k6acf5248": "Недавние",
|
||||||
|
"k6b36580f": "URL Apprise",
|
||||||
|
"k6bc9e414": "Вход",
|
||||||
|
"k6f15bcc3": "Хост",
|
||||||
|
"k717660a5": "ОЗУ",
|
||||||
|
"k721589c1": "Сегодня",
|
||||||
|
"k74a240": "Полоса здоровья",
|
||||||
|
"k75581e13": "Копия",
|
||||||
|
"k75bfaaa6": "Добавьте этот код в скрипт заголовка вашего веб-сайта",
|
||||||
|
"k784dd132": "Тест",
|
||||||
|
"k7927b824": "Вы уверены, что хотите очистить все офлайн узлы?",
|
||||||
|
"k7ac44a6e": "Ключ сессии",
|
||||||
|
"k7b74a43f": "посетители",
|
||||||
|
"k7c95e6a5": "Вы уверены, что хотите удалить эту страницу?",
|
||||||
|
"k7cac602a": "Статус",
|
||||||
|
"k7f01b47c": "Журнал аудита",
|
||||||
|
"k7f4bcf6b": "Мониторы",
|
||||||
|
"k8037cc6b": "Серверы",
|
||||||
|
"k8202c669": "Посетители",
|
||||||
|
"k845abd5b": "Следующий шаг",
|
||||||
|
"k84ce1618": "(24 часа)",
|
||||||
|
"k85344b23": "Нагрузка",
|
||||||
|
"k85c5fd4c": "Мониторы еще не настроены",
|
||||||
|
"k8746ec38": "Выбрать монитор",
|
||||||
|
"k88a9bf01": "Показать значок",
|
||||||
|
"k88d2647b": "Веб-сайт",
|
||||||
|
"k89056082": "(30 дней)",
|
||||||
|
"k8a44833f": "Сервисы",
|
||||||
|
"k8bac6ae0": "6ч",
|
||||||
|
"k8ef56a20": "Максимальное количество попыток перед тем, как сервис будет помечен как недоступный и будет отправлено уведомление",
|
||||||
|
"k8f8fbf6": "Поделиться с...",
|
||||||
|
"k8ff3a55a": "Предупреждение",
|
||||||
|
"k9022468f": "Очистить офлайн",
|
||||||
|
"k90873752": "Старый пароль",
|
||||||
|
"k90a82c67": "Зарегистрировать аккаунт",
|
||||||
|
"k90b668e5": "Последние 24 часа",
|
||||||
|
"k93374bc9": "Удалить веб-сайт",
|
||||||
|
"k97ddb155": "Показать текущий ответ",
|
||||||
|
"k98f433ee": "Скачать репортер с",
|
||||||
|
"k9a272ecf": "Это ваши серверы?",
|
||||||
|
"k9a3cc801": "Принимаемые символы",
|
||||||
|
"k9add1fac": "Трафик",
|
||||||
|
"k9be2209c": "Отображаемое имя веб-сайта",
|
||||||
|
"k9e32beea": "онлайн",
|
||||||
|
"k9e759f8": "Список уведомлений",
|
||||||
|
"k9fa794aa": "Название веб-сайта",
|
||||||
|
"ka0051b3d": "Домен",
|
||||||
|
"ka0ddbfb": "Информация о веб-сайте",
|
||||||
|
"ka2b9bc3c": "Предыдущий период",
|
||||||
|
"ka2fae1c6": "ID пользователя",
|
||||||
|
"ka388d3bf": "Вы можете привязать монитор, который будет отображать статус здоровья в обзоре веб-сайта",
|
||||||
|
"ka40aea11": "Или вы хотите сообщать о состоянии сервера в Windows сервере? переключитесь на вкладку Ручной ввод",
|
||||||
|
"ka44150a0": "Последние 90 дней",
|
||||||
|
"ka68f2242": "Регистрация",
|
||||||
|
"ka6ee7455": "ID веб-сайта",
|
||||||
|
"ka71c12e1": "Два пароля не совпадают",
|
||||||
|
"ka765ad32": "Уведомления",
|
||||||
|
"ka9d081ac": "Интервал проверки (с)",
|
||||||
|
"kaa0788e9": "Макет успешно сохранен",
|
||||||
|
"kaa0ccaab": "Email отправителя",
|
||||||
|
"kab56db46": "Сердцебиение",
|
||||||
|
"kacbdae07": "Среднее время ответа",
|
||||||
|
"kadef6c48": "Предыдущий {{label}}",
|
||||||
|
"kaf39be20": "Сеть",
|
||||||
|
"kb01f4f95": "Готово",
|
||||||
|
"kb0e351e0": "Обновлено",
|
||||||
|
"kb320aac4": "Мониторинг в течение {{dayNum}} дней",
|
||||||
|
"kb5673707": "Последние 7 дней",
|
||||||
|
"kb659c1bc": "Истечение серт.",
|
||||||
|
"kb8de8c50": "Скрытая копия",
|
||||||
|
"kbb58c99c": "Успешно удалено",
|
||||||
|
"kbcf67f53": "Новый пароль",
|
||||||
|
"kbd1e7dee": "Использование: {{usage}}мс",
|
||||||
|
"kbd425e0e": "Обновлено в",
|
||||||
|
"kc00cf2c7": "Здесь будет показан ваш последний результат мониторинга",
|
||||||
|
"kc0c6a913": "Имя узла",
|
||||||
|
"kc1f1f6c9": "Тип уведомления",
|
||||||
|
"kc45a417b": "ОС",
|
||||||
|
"kc4910af7": "Смена пароля",
|
||||||
|
"kc4ab7848": "Очистить офлайн узлы",
|
||||||
|
"kc4e91854": "Вы уверены, что хотите удалить все сердцебиения для этого монитора?",
|
||||||
|
"kc5573507": "Добавить",
|
||||||
|
"kc5f82d53": "Например: pushdeer://pushKey",
|
||||||
|
"kc6888ac4": "Авто",
|
||||||
|
"kc6cac621": "(Нет)",
|
||||||
|
"kc70d69ad": "Ответ",
|
||||||
|
"kc9b446d1": "Запуск завершен",
|
||||||
|
"kcacbfde1": "Создать сейчас",
|
||||||
|
"kcaf5c873": "Действия",
|
||||||
|
"kcb8fd4ce": "Онлайн серверов нет",
|
||||||
|
"kcbc00b39": "Выберите диапазон дат",
|
||||||
|
"kcc3b034e": "Url",
|
||||||
|
"kcc50957": "Добавить новый монитор",
|
||||||
|
"kccaa732a": "Без последовательных тире",
|
||||||
|
"kccb42483": "Пароль",
|
||||||
|
"kd031b383": "Просмотры",
|
||||||
|
"kd211e2d4": "Страница релизов",
|
||||||
|
"kd37efb26": "Уведомления",
|
||||||
|
"kd46ab159": "Событий нет",
|
||||||
|
"kd7985726": "{{num}} пользователей",
|
||||||
|
"kd92fa3e7": "Имя хоста",
|
||||||
|
"kdaa949e5": "События {{monitorName}}",
|
||||||
|
"kdb61adbb": "Скрыть офлайн",
|
||||||
|
"kdc51b5db": "Веб-сайты",
|
||||||
|
"kde37bc27": "Вернуться к администратору",
|
||||||
|
"kde657d5b": "Панель управления",
|
||||||
|
"kdeba7706": "Устройства",
|
||||||
|
"kdf5da1d2": "Нет / STARTTLS",
|
||||||
|
"kdf97690e": "Страны",
|
||||||
|
"ke188f24b": "коэффициент отказов",
|
||||||
|
"ke1b5ca71": "Страницы",
|
||||||
|
"ke2fe505b": "На этой неделе",
|
||||||
|
"ke3a3f2f2": "Порт",
|
||||||
|
"ke46232fe": "Добавить сервер",
|
||||||
|
"ke5b015e9": "Вы можете получить токен на https://t.me/BotFather.",
|
||||||
|
"ke6797c65": "Вы можете получить свой ID чата, отправив сообщение боту и перейдя по этому URL, чтобы просмотреть chat_id",
|
||||||
|
"ke9d2fef3": "Этот месяц",
|
||||||
|
"ke9dcaa64": "Уведомлений не найдено",
|
||||||
|
"keaf7576f": "Заголовок",
|
||||||
|
"ked37937b": "Метрики",
|
||||||
|
"ked7eea1a": "ВВЕРХ",
|
||||||
|
"ked8814bc": "Копировать",
|
||||||
|
"kedc69eb6": "Код отслеживания",
|
||||||
|
"kef701e50": "Добавить монитор",
|
||||||
|
"kf22813ad": "Пользовательский",
|
||||||
|
"kf3b749ef": "Поддержка прямого чата / группы / ID чата канала",
|
||||||
|
"kf55495e0": "Сохранить",
|
||||||
|
"kf5bbb568": "карта посетителей",
|
||||||
|
"kf6bc1610": "Тип монитора",
|
||||||
|
"kf6db9ea5": "3ч",
|
||||||
|
"kf7d5dbf8": "Читать далее",
|
||||||
|
"kf97b6f71": "Запустите эту команду на вашем Linux-машине",
|
||||||
|
"kf9877f28": "Посмотреть детали",
|
||||||
|
"kfc98929b": "{{num}} дней",
|
||||||
|
"kfd33c459": "Копирование успешно!",
|
||||||
|
"kfdaf0bb3": "Последний онлайн: {{time}}",
|
||||||
|
"kfe11d138": "Имя",
|
||||||
|
"kfedb6cd8": "Вы уверены, что хотите удалить все события для этого монитора?",
|
||||||
|
"kff849f78": "Автоизвлечение"
|
||||||
|
}
|
BIN
src/client/public/locales/zh/flag.png
Normal file
BIN
src/client/public/locales/zh/flag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
218
src/client/public/locales/zh/translation.json
Normal file
218
src/client/public/locales/zh/translation.json
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"k10336b1": "没有更多内容加载",
|
||||||
|
"k10c85f27": "忽略 TLS 错误",
|
||||||
|
"k112a7174": "使用",
|
||||||
|
"k1192dd43": "请选择工作区",
|
||||||
|
"k11e887ab": "确定要删除这个项目吗?",
|
||||||
|
"k11f09c87": "视图",
|
||||||
|
"k1286421": "用户名",
|
||||||
|
"k134e0d97": "当前",
|
||||||
|
"k14c6c425": "结果",
|
||||||
|
"k158336d6": "请求超时(秒)",
|
||||||
|
"k1598e726": "当前访问者",
|
||||||
|
"k15ae36a6": "浏览器",
|
||||||
|
"k16c8909f": "显示名称",
|
||||||
|
"k1777bbf2": "手册",
|
||||||
|
"k186365b3": "{{monitorName}}的图表",
|
||||||
|
"k1964b988": "停止",
|
||||||
|
"k1bd89236": "运行报告器",
|
||||||
|
"k1c33c293": "设置",
|
||||||
|
"k1eb5b3ed": "概览",
|
||||||
|
"k20edf271": "24小时",
|
||||||
|
"k21077124": "编辑",
|
||||||
|
"k2264aac": "今年",
|
||||||
|
"k246063be": "聊天 ID",
|
||||||
|
"k2497052e": "图表",
|
||||||
|
"k25b3dc00": "脚本 JS 代码",
|
||||||
|
"k264de775": "加载更多...",
|
||||||
|
"k277e2626": "可用",
|
||||||
|
"k28059d49": "当前工作区 ID",
|
||||||
|
"k2813d1f7": "本月",
|
||||||
|
"k2a6a7d8f": "下线",
|
||||||
|
"k2b2d40d4": "测试代码",
|
||||||
|
"k2c2712a4": "CPU",
|
||||||
|
"k2e6dbf02": "发邮件到",
|
||||||
|
"k2ea8a019": "监控器",
|
||||||
|
"k30b5f01b": "工作区",
|
||||||
|
"k30e234ee": "您还没有仪表板项目,请进入编辑模式并添加您的项目。",
|
||||||
|
"k310fee": "最近30天",
|
||||||
|
"k32344f64": "清除数据",
|
||||||
|
"k3260f019": "登出",
|
||||||
|
"k3471e956": "重复新密码",
|
||||||
|
"k389db675": "提交",
|
||||||
|
"k38c62888": "请选择监控器",
|
||||||
|
"k3cedb797": "安全",
|
||||||
|
"k3d3baf52": "平均访问时间",
|
||||||
|
"k3dbe79b1": "删除",
|
||||||
|
"k3de768a1": "更改密码",
|
||||||
|
"k3e757ddf": "这里还没有监控器。",
|
||||||
|
"k43e21ee9": "下载客户端报告器",
|
||||||
|
"k44cad477": "(当前)",
|
||||||
|
"k46f0adde": "访问者地图",
|
||||||
|
"k47f43dbc": "{{monitorName}}的健康状况",
|
||||||
|
"k4905ed7b": "无",
|
||||||
|
"k490ada32": "添加网站",
|
||||||
|
"k49e5f1d2": "1周",
|
||||||
|
"k4ac4dd36": "引荐人",
|
||||||
|
"k4de48e75": "最大重试次数",
|
||||||
|
"k4e08cf58": "显示详细数字",
|
||||||
|
"k4eea9393": "个人资料",
|
||||||
|
"k506a90b2": "您的服务器域名或IP。",
|
||||||
|
"k517747e1": "最近24小时",
|
||||||
|
"k51bac044": "取消",
|
||||||
|
"k53ae02a5": "新建",
|
||||||
|
"k542b527c": "事件",
|
||||||
|
"k58f90514": "机器人令牌",
|
||||||
|
"k593cf342": "您确定要删除这个监控器吗?",
|
||||||
|
"k5a839f71": "正常运行时间",
|
||||||
|
"k5eb87a8b": "开始",
|
||||||
|
"k5ecf04b0": "查看",
|
||||||
|
"k6067f0ff": "忽略 TLS/SSL 错误",
|
||||||
|
"k621317b5": "新页面",
|
||||||
|
"k62e19375": "最后更新时间:{{date}}",
|
||||||
|
"k646a3a80": "{{monitorName}}的指标",
|
||||||
|
"k646c0ae2": "HDD",
|
||||||
|
"k67c5a895": "昨天",
|
||||||
|
"k683be220": "运行",
|
||||||
|
"k691b7170": "已停止",
|
||||||
|
"k698348ff": "等待接收 UDP 包",
|
||||||
|
"k6acf5248": "最近",
|
||||||
|
"k6b36580f": "Apprise URL",
|
||||||
|
"k6bc9e414": "登录",
|
||||||
|
"k6f15bcc3": "主机",
|
||||||
|
"k717660a5": "RAM",
|
||||||
|
"k721589c1": "今天",
|
||||||
|
"k74a240": "健康栏",
|
||||||
|
"k75581e13": "CC",
|
||||||
|
"k75bfaaa6": "将此代码添加到您的网站头部脚本中",
|
||||||
|
"k784dd132": "测试",
|
||||||
|
"k7927b824": "您确定要清除所有离线节点吗?",
|
||||||
|
"k7ac44a6e": "会话密钥",
|
||||||
|
"k7b74a43f": "访问者",
|
||||||
|
"k7c95e6a5": "您确定要删除此页面吗?",
|
||||||
|
"k7cac602a": "状态",
|
||||||
|
"k7f01b47c": "审计日志",
|
||||||
|
"k7f4bcf6b": "监控器",
|
||||||
|
"k8037cc6b": "服务器",
|
||||||
|
"k8202c669": "访问者",
|
||||||
|
"k845abd5b": "下一步",
|
||||||
|
"k84ce1618": "(24小时)",
|
||||||
|
"k85344b23": "负载",
|
||||||
|
"k85c5fd4c": "还没有设置任何监控器",
|
||||||
|
"k8746ec38": "选择监控器",
|
||||||
|
"k88a9bf01": "显示徽章",
|
||||||
|
"k88d2647b": "网站",
|
||||||
|
"k89056082": "(30天)",
|
||||||
|
"k8a44833f": "服务",
|
||||||
|
"k8bac6ae0": "6小时",
|
||||||
|
"k8ef56a20": "服务被标记为下线并发送通知前的最大重试次数",
|
||||||
|
"k8f8fbf6": "分享给...",
|
||||||
|
"k8ff3a55a": "警告",
|
||||||
|
"k9022468f": "清除离线",
|
||||||
|
"k90873752": "旧密码",
|
||||||
|
"k90a82c67": "注册账户",
|
||||||
|
"k90b668e5": "最近24小时",
|
||||||
|
"k93374bc9": "删除网站",
|
||||||
|
"k97ddb155": "显示当前响应",
|
||||||
|
"k98f433ee": "从这里下载报告器",
|
||||||
|
"k9a272ecf": "这是您的服务器吗?",
|
||||||
|
"k9a3cc801": "接受字符",
|
||||||
|
"k9add1fac": "流量",
|
||||||
|
"k9be2209c": "要显示的网站名称",
|
||||||
|
"k9e32beea": "在线",
|
||||||
|
"k9e759f8": "通知列表",
|
||||||
|
"k9fa794aa": "网站名称",
|
||||||
|
"ka0051b3d": "域名",
|
||||||
|
"ka0ddbfb": "网站信息",
|
||||||
|
"ka2b9bc3c": "上一个周期",
|
||||||
|
"ka2fae1c6": "用户ID",
|
||||||
|
"ka388d3bf": "您可以绑定一个监控器,它将在网站概览中显示健康状态",
|
||||||
|
"ka40aea11": "或者您想在Windows服务器中报告服务器状态?切换到手动选项卡",
|
||||||
|
"ka44150a0": "最近90天",
|
||||||
|
"ka68f2242": "注册",
|
||||||
|
"ka6ee7455": "网站ID",
|
||||||
|
"ka71c12e1": "两次密码不一致",
|
||||||
|
"ka765ad32": "通知",
|
||||||
|
"ka9d081ac": "检查间隔(秒)",
|
||||||
|
"kaa0788e9": "布局保存成功",
|
||||||
|
"kaa0ccaab": "发件人邮箱",
|
||||||
|
"kab56db46": "心跳",
|
||||||
|
"kacbdae07": "平均响应",
|
||||||
|
"kadef6c48": "上一个{{label}}",
|
||||||
|
"kaf39be20": "网络",
|
||||||
|
"kb01f4f95": "完成",
|
||||||
|
"kb0e351e0": "已刷新",
|
||||||
|
"kb320aac4": "已监控{{dayNum}}天",
|
||||||
|
"kb5673707": "最近7天",
|
||||||
|
"kb659c1bc": "证书到期",
|
||||||
|
"kb8de8c50": "密送",
|
||||||
|
"kbb58c99c": "删除成功",
|
||||||
|
"kbcf67f53": "新密码",
|
||||||
|
"kbd1e7dee": "使用量:{{usage}}毫秒",
|
||||||
|
"kbd425e0e": "更新于",
|
||||||
|
"kc00cf2c7": "这将显示您的监控器的最近结果",
|
||||||
|
"kc0c6a913": "节点名称",
|
||||||
|
"kc1f1f6c9": "通知类型",
|
||||||
|
"kc45a417b": "操作系统",
|
||||||
|
"kc4910af7": "更改密码",
|
||||||
|
"kc4ab7848": "清除离线节点",
|
||||||
|
"kc4e91854": "您确定要删除此监控器的所有心跳吗?",
|
||||||
|
"kc5573507": "添加",
|
||||||
|
"kc5f82d53": "例如:pushdeer://pushKey",
|
||||||
|
"kc6888ac4": "自动",
|
||||||
|
"kc6cac621": "(无)",
|
||||||
|
"kc70d69ad": "响应",
|
||||||
|
"kc9b446d1": "运行完成",
|
||||||
|
"kcacbfde1": "立即创建",
|
||||||
|
"kcaf5c873": "操作",
|
||||||
|
"kcb8fd4ce": "没有在线服务器",
|
||||||
|
"kcbc00b39": "选择您的日期范围",
|
||||||
|
"kcc3b034e": "链接",
|
||||||
|
"kcc50957": "添加新的监控器",
|
||||||
|
"kccaa732a": "无连续破折号",
|
||||||
|
"kccb42483": "密码",
|
||||||
|
"kd031b383": "视图",
|
||||||
|
"kd211e2d4": "发布页面",
|
||||||
|
"kd37efb26": "通知",
|
||||||
|
"kd46ab159": "没有事件",
|
||||||
|
"kd7985726": "{{num}}个用户",
|
||||||
|
"kd92fa3e7": "主机名",
|
||||||
|
"kdaa949e5": "{{monitorName}}的事件",
|
||||||
|
"kdb61adbb": "隐藏离线",
|
||||||
|
"kdc51b5db": "网站",
|
||||||
|
"kde37bc27": "返回管理员",
|
||||||
|
"kde657d5b": "仪表板",
|
||||||
|
"kdeba7706": "设备",
|
||||||
|
"kdf5da1d2": "无/STARTTLS",
|
||||||
|
"kdf97690e": "国家",
|
||||||
|
"ke188f24b": "跳出率",
|
||||||
|
"ke1b5ca71": "页面",
|
||||||
|
"ke2fe505b": "本周",
|
||||||
|
"ke3a3f2f2": "端口",
|
||||||
|
"ke46232fe": "添加服务器",
|
||||||
|
"ke5b015e9": "您可以从 https://t.me/BotFather 获取令牌。",
|
||||||
|
"ke6797c65": "您可以通过向机器人发送消息并访问此URL来查看chat_id以获取您的聊天ID",
|
||||||
|
"ke9d2fef3": "本月",
|
||||||
|
"ke9dcaa64": "没有找到任何通知",
|
||||||
|
"keaf7576f": "标题",
|
||||||
|
"ked37937b": "指标",
|
||||||
|
"ked7eea1a": "上线",
|
||||||
|
"ked8814bc": "复制",
|
||||||
|
"kedc69eb6": "追踪代码",
|
||||||
|
"kef701e50": "添加监控器",
|
||||||
|
"kf22813ad": "自定义",
|
||||||
|
"kf3b749ef": "支持直接聊天/群组/频道的聊天ID",
|
||||||
|
"kf55495e0": "保存",
|
||||||
|
"kf5bbb568": "的访问者地图",
|
||||||
|
"kf6bc1610": "监控器类型",
|
||||||
|
"kf6db9ea5": "3小时",
|
||||||
|
"kf7d5dbf8": "阅读更多",
|
||||||
|
"kf97b6f71": "在您的Linux机器上运行此命令",
|
||||||
|
"kf9877f28": "查看详情",
|
||||||
|
"kfc98929b": "{{num}}天",
|
||||||
|
"kfd33c459": "复制成功!",
|
||||||
|
"kfdaf0bb3": "最后在线时间:{{time}}",
|
||||||
|
"kfe11d138": "名称",
|
||||||
|
"kfedb6cd8": "您确定要删除此监控器的所有事件吗?",
|
||||||
|
"kff849f78": "自动获取"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user