Skip to content

Commit

Permalink
Merge pull request #53 from micro-in-cn/feature/dashboard
Browse files Browse the repository at this point in the history
Feature/dashboard
  • Loading branch information
Allenxuxu authored Jul 22, 2020
2 parents 5906b0b + 473b893 commit d2eb57b
Show file tree
Hide file tree
Showing 28 changed files with 1,269 additions and 680 deletions.
1,201 changes: 682 additions & 519 deletions dashboard/package-lock.json

Large diffs are not rendered by default.

26 changes: 14 additions & 12 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,39 @@
"version": "0.1.0",
"homepage": "/admin/ui",
"dependencies": {
"@testing-library/jest-dom": "^5.8.0",
"@testing-library/react": "^10.0.4",
"@testing-library/user-event": "^10.3.3",
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/react": "^10.4.5",
"@testing-library/user-event": "^10.4.1",
"@types/codemirror": "0.0.95",
"@types/jest": "^24.9.1",
"@types/node": "^14.0.4",
"@types/react": "^16.9.35",
"@types/node": "^14.0.22",
"@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.5",
"antd": "^4.2.4",
"antd": "^4.4.2",
"babel-plugin-import": "^1.13.0",
"codemirror": "^5.54.0",
"codemirror": "^5.55.0",
"customize-cra": "^0.9.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.4",
"enzyme-to-json": "^3.5.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.20.0",
"http-proxy-middleware": "^1.0.4",
"eslint-plugin-react": "^7.20.3",
"http-proxy-middleware": "^1.0.5",
"husky": "^4.2.5",
"i18next": "^19.6.0",
"jest": "^24.9.0",
"js-yaml": "^3.13.1",
"js-yaml": "^3.14.0",
"jsonlint-mod": "^1.7.5",
"lint-staged": "^10.2.4",
"lint-staged": "^10.2.11",
"node-sass": "^4.14.1",
"prettier": "^2.0.5",
"react": "^16.13.1",
"react-app-rewired": "^2.1.6",
"react-dom": "^16.13.1",
"react-i18next": "^11.7.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"toml": "^3.0.0",
Expand Down
39 changes: 30 additions & 9 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
import React, { Suspense, useMemo } from 'react';
import { Redirect, Route, HashRouter as Router, Switch } from 'react-router-dom';
import React, { Suspense, useEffect, useMemo, useState } from 'react';
import { Redirect, Route, Router, Switch } from 'react-router-dom';
import { ConfigProvider } from 'antd';

import ZH_CN from 'antd/es/locale/zh_CN';
import { getMenus, getRoutes } from '@src/pages';
import { createBrowserHistory } from 'history';
import { useTranslation } from 'react-i18next';

import AppLayout from '@src/AppLayout';
import Loading from '@src/components/Loading';
import i18n, { antLocales } from '@src/i18n';
import { getMenus, getRoutes } from '@src/pages';
import { getLanguage, parseSearch } from '@src/tools';

const history = createBrowserHistory({ basename: '/' });
const getLang = (search: string) => {
const { lang } = parseSearch(search);
return getLanguage(lang);
};

function App() {
const menus = useMemo(getMenus, []);
const [language, changeLanguage] = useState<string>(() => getLang(history.location.search));
const { t } = useTranslation();

const menus = useMemo(() => getMenus(t), [t]);
const routes = useMemo(getRoutes, []);

useEffect(() => {
return history.listen((l) => {
changeLanguage(getLang(l.search));
});
}, []);

useEffect(() => {
i18n.changeLanguage(language);
}, [language]);

return (
<ConfigProvider locale={ZH_CN}>
<Router basename="/">
<AppLayout menus={menus}>
<ConfigProvider locale={antLocales[language]}>
<Router history={history}>
<AppLayout menus={menus} language={language}>
<Suspense fallback={<Loading />}>
<Switch>
{routes.map((r) => (
Expand Down
6 changes: 6 additions & 0 deletions dashboard/src/AppLayout/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
padding: 0;
box-sizing: border-box;
border-bottom: 1px solid $borderColor;
display: flex;
justify-content: space-between;

.trigger {
font-size: 18px;
Expand All @@ -17,6 +19,10 @@
color: #1890ff;
}
}

.rightLayout {
margin-right: 32px;
}
}

.sider {
Expand Down
35 changes: 28 additions & 7 deletions dashboard/src/AppLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { FC, useEffect, useState } from 'react';
import { Link, matchPath, useLocation } from 'react-router-dom';
import { Layout, Menu } from 'antd';
import { Link, matchPath, useHistory, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Layout, Menu, Select } from 'antd';
import { GithubOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';

import { BFS } from '@src/tools';
import { BFS, replaceSearch, setComputerLanguage } from '@src/tools';

import { MenuItem } from '@src/typings';
import styles from './index.module.scss';
import { languages } from '@src/i18n';

const renderMenuItem = (menuItem: MenuItem): React.ReactElement => {
const renderLabel = () => {
Expand Down Expand Up @@ -41,15 +43,18 @@ const renderMenuItem = (menuItem: MenuItem): React.ReactElement => {
};

export interface AppLayoutProps {
language: string;
menus: MenuItem[];
children: React.ReactChild;
}

const AppLayout: FC<AppLayoutProps> = ({ menus, children }) => {
const AppLayout: FC<AppLayoutProps> = ({ language, menus, children }) => {
const [collapsed, toggle] = useState<boolean>(false);
const [openKeys, setOpenKeys] = useState<string[]>([]);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
const { t } = useTranslation();

const history = useHistory();
const { pathname } = useLocation();

useEffect(() => {
Expand Down Expand Up @@ -85,7 +90,7 @@ const AppLayout: FC<AppLayoutProps> = ({ menus, children }) => {
<Layout.Sider theme="light" className={styles.sider} trigger={null} collapsible collapsed={collapsed}>
<div className={styles.logo}>
<strong>XConf</strong>
{!collapsed && <small>分布式配置中心</small>}
{!collapsed && <small>{t('layout.title')}</small>}
</div>
<Menu theme="light" mode="inline" className={styles.menus} openKeys={openKeys} selectedKeys={selectedKeys}>
{menus.map(renderMenuItem)}
Expand All @@ -97,14 +102,30 @@ const AppLayout: FC<AppLayoutProps> = ({ menus, children }) => {
className: styles.trigger,
onClick: () => toggle((prev) => !prev),
})}
<div className={styles.rightLayout}>
<Select
value={language}
style={{ width: 100 }}
onChange={(lang) => {
setComputerLanguage(lang);
replaceSearch(history, { lang });
}}
>
{languages.map((l) => (
<Select.Option key={l.lng} value={l.lng}>
{l.label}
</Select.Option>
))}
</Select>
</div>
</Layout.Header>
<Layout.Content>{children}</Layout.Content>
<Layout.Footer style={{ textAlign: 'center' }}>
<div>
<span>XConf 分布式配置中心</span>
<span>XConf {t('layout.title')}</span>
<GithubOutlined style={{ marginLeft: 36 }} />
</div>
<div>Copyright © 2020 Micro China开源技术出品</div>
<div>Copyright © 2020 {t('layout.ownership')}</div>
</Layout.Footer>
</Layout>
</Layout>
Expand Down
19 changes: 11 additions & 8 deletions dashboard/src/components/Editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { ForwardRefRenderFunction, forwardRef } from 'react';
import { TFunction } from 'i18next';
import { Button, Input, message } from 'antd';
import CodeMirror from 'codemirror';
import { CodeEditorProps } from '@src/components/Editor/Editor';
Expand All @@ -9,15 +10,16 @@ import YamlEditor from './YamlEditor';
import TomlEditor from './TomlEditor';

import styles from './index.module.scss';
import { useTranslation } from 'react-i18next';

window.CodeMirror = CodeMirror;

export { default as JsonEditor } from './JsonEditor';
export { default as YamlEditor } from './YamlEditor';
export { default as TomlEditor } from './TomlEditor';

export const validateFormat = (value: string, format: NamespaceFormat): [boolean, string?] => {
if (!value) return [false, '配置不能为空'];
export const validateFormat = (value: string, format: NamespaceFormat, t: TFunction): [boolean, string?] => {
if (!value) return [false, t('form.creation.configuration.validation')];
if (format === NamespaceFormat.CUSTOM) return [true];
let validate;
if (format === NamespaceFormat.JSON) validate = window.jsonlint.parse;
Expand Down Expand Up @@ -46,6 +48,7 @@ const Editor: ForwardRefRenderFunction<any, EditorProps> = (
{ canControl, format, value, released, onSave, onRelease, ...props },
ref,
) => {
const { t } = useTranslation();
const renderEditor = () => {
if (format === NamespaceFormat.CUSTOM)
return (
Expand Down Expand Up @@ -74,24 +77,24 @@ const Editor: ForwardRefRenderFunction<any, EditorProps> = (
type="primary"
onClick={() => {
const v = value || '';
const [result, msg] = validateFormat(v, format);
const [result, msg] = validateFormat(v, format, t);
if (result) onSave && onSave(v, format);
else message.error(`格式错误: ${msg}`);
else message.error(t('form.creation.format.validation.failure') + `: ${msg}`);
}}
>
保存
{t('form.creation.button.save')}
</Button>
<Button
danger
disabled={released || props.initialValue !== value}
onClick={() => {
const v = value || '';
const [result, msg] = validateFormat(v, format);
const [result, msg] = validateFormat(v, format, t);
if (result) onRelease && onRelease(v, format);
else message.error(`格式错误: ${msg}`);
else message.error(t('form.creation.format.validation.failure') + `: ${msg}`);
}}
>
发布
{t('form.creation.button.release')}
</Button>
</div>
)}
Expand Down
10 changes: 6 additions & 4 deletions dashboard/src/components/ITable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import { TableProps } from 'antd/lib/table';
import { useTranslation } from 'react-i18next';
import { Button, Input, Table } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { TableProps } from 'antd/lib/table';

import { usePropsValue } from '@src/hooks/usePropsValue';

Expand All @@ -22,6 +23,7 @@ export interface ITableProps<T = any> extends TableProps<T> {
}

const ITable = <T extends object = any>({ showCreate, showSearch, ...props }: ITableProps<T>): JSX.Element => {
const { t } = useTranslation();
const [key, setKey] = usePropsValue<string>({ value: showSearch?.value, onChange: showSearch?.onChange });
const [data, setData] = useState<T[]>([]);

Expand All @@ -41,15 +43,15 @@ const ITable = <T extends object = any>({ showCreate, showSearch, ...props }: IT
<div className="clear-float">
{!!showSearch && (
<div style={{ float: 'left', marginBottom: 16, display: 'flex', alignItems: 'center' }}>
<label>关键字过滤: </label>
<label>{t('table.filter')}: </label>
<Input
value={key}
onChange={(e) => setKey(e.target.value)}
style={{ marginLeft: 12, width: 300 }}
placeholder="输入关键字过滤"
placeholder={t('table.filter.placeholder')}
addonAfter={
<span style={{ cursor: 'pointer' }} onClick={() => setKey('')}>
清除
{t('table.filter_clear_button')}
</span>
}
/>
Expand Down
4 changes: 3 additions & 1 deletion dashboard/src/components/Loading/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Spin } from 'antd';

export interface LoadingProps {}

const Loading: FC<LoadingProps> = () => {
const { t } = useTranslation();
return (
<div style={{ minHeight: 300, lineHeight: 300, width: '100%', textAlign: 'center' }}>
<Spin spinning tip="努力加载中捏. 💪" />
<Spin spinning tip={t('label.loading') + ' 💪'} />
</div>
);
};
Expand Down
30 changes: 30 additions & 0 deletions dashboard/src/i18n.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { Locale } from 'antd/lib/locale-provider';

import { AnyObject, Language } from '@src/typings';
import { LocaleValue } from '@src/locales/interface';

import enUS from './locales/en_us';
import zhCN from './locales/zh_cn';

const resources: AnyObject<LocaleValue> = {};
const locales = [enUS, zhCN];
export const languages: Language[] = [];
export const antLocales: AnyObject<Locale> = {};
locales.forEach((locale) => {
resources[locale.lng] = { translation: locale.value };
antLocales[locale.lng] = locale.antLocale;
languages.push({ lng: locale.lng, label: locale.languageLabel, langs: locale.langs });
});

i18n
.use(initReactI18next) // * passes i18n down to react-i18next
.init({
resources,
lng: 'en',
keySeparator: false, // * we do not use keys in form messages.welcome
interpolation: { escapeValue: false }, // * react already safes from xss
});

export default i18n;
3 changes: 2 additions & 1 deletion dashboard/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import './i18n';
import App from './App';
import './index.scss';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
Expand Down
Loading

0 comments on commit d2eb57b

Please sign in to comment.