You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2020/05/21 06:47:56 UTC

[incubator-apisix-dashboard] branch next updated: Feat layout (#204)

This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-apisix-dashboard.git


The following commit(s) were added to refs/heads/next by this push:
     new 62f0658  Feat layout (#204)
62f0658 is described below

commit 62f065857ddaf6d32bf069b45fa3aa54be0a2c45
Author: 琚致远 <ju...@apache.org>
AuthorDate: Thu May 21 14:47:46 2020 +0800

    Feat layout (#204)
    
    * feat: remove unused files
    
    * feat: added initial state
    
    * feat: update global
    
    * feat: update layout
    
    * feat: update typings
    
    * fix: routes
    
    * fix: ssl create
    
    * feat: remove unused files
    
    * feat: update login
    
    * codes clean
---
 README.md                                          |   2 +-
 config/config.ts                                   |  74 ++++-----
 config/defaultSettings.ts                          |  22 +--
 src/app.tsx                                        |  44 ++++-
 src/components/Authorized/Authorized.tsx           |  35 ----
 src/components/Authorized/AuthorizedRoute.tsx      |  33 ----
 src/components/Authorized/CheckPermissions.tsx     |  83 ----------
 src/components/Authorized/PromiseRender.tsx        |  93 -----------
 src/components/Authorized/Secured.tsx              |  66 --------
 src/components/Authorized/index.tsx                |  11 --
 src/components/Authorized/renderAuthorize.ts       |  30 ----
 src/components/Footer/index.tsx                    |  17 ++
 src/components/GlobalHeader/AvatarDropdown.tsx     |  86 ----------
 src/components/GlobalHeader/NoticeIconView.tsx     | 171 -------------------
 src/components/GlobalHeader/RightContent.tsx       |  56 -------
 src/components/HeaderSearch/index.less             |  30 ----
 src/components/HeaderSearch/index.tsx              | 105 ------------
 src/components/RightContent/AvatarDropdown.tsx     |  99 +++++++++++
 .../{GlobalHeader => RightContent}/index.less      |  35 +---
 src/components/RightContent/index.tsx              |  51 ++++++
 src/components/SelectLang/index.less               |  24 ---
 src/components/SelectLang/index.tsx                |  50 ------
 src/global.tsx                                     |  24 +--
 src/layouts/BasicLayout.tsx                        | 155 ------------------
 src/layouts/BlankLayout.tsx                        |   5 -
 src/layouts/SecurityLayout.tsx                     |  57 -------
 src/layouts/UserLayout.less                        |  71 --------
 src/layouts/UserLayout.tsx                         |  77 ---------
 src/models/connect.d.ts                            |  31 ----
 src/models/global.ts                               | 137 ----------------
 src/models/login.ts                                |  87 ----------
 src/models/setting.ts                              |  37 -----
 src/models/user.ts                                 |  85 ----------
 src/pages/Authorized.tsx                           |  36 ----
 src/pages/document.ejs                             |   2 +-
 src/pages/ssl/components/Step3/index.tsx           |   3 +-
 .../user/login/components/Login/LoginItem.tsx      |  72 +-------
 src/pages/user/login/components/Login/LoginTab.tsx |   6 +-
 src/pages/user/login/components/Login/index.tsx    |  21 +--
 src/pages/user/login/index.tsx                     | 182 +++++++++++++--------
 src/pages/user/login/style.less                    |  80 +++++++--
 src/services/API.d.ts                              |   7 -
 src/services/login.ts                              |  21 ++-
 src/services/user.ts                               |   3 +
 src/utils/Authorized.ts                            |  19 ---
 src/utils/authority.ts                             |  32 ----
 46 files changed, 484 insertions(+), 1983 deletions(-)

diff --git a/README.md b/README.md
index 4e0151a..7495abf 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# APISIX Dashboard
+# Apache APISIX Dashboard
 
 This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
 
diff --git a/config/config.ts b/config/config.ts
index 7524365..8247273 100644
--- a/config/config.ts
+++ b/config/config.ts
@@ -21,12 +21,17 @@ export default defineConfig({
   targets: {
     ie: 11,
   },
+  layout: {
+    name: 'APISIX Dashboard',
+    locale: true,
+    logo: '/favicon.png',
+  },
   base: '/dashboard/',
   publicPath: '/',
   routes: [
     {
       path: '/user',
-      component: '../layouts/UserLayout',
+      layout: false,
       routes: [
         {
           name: 'login',
@@ -37,53 +42,34 @@ export default defineConfig({
     },
     {
       path: '/',
-      component: '../layouts/SecurityLayout',
+      redirect: '/settings',
+    },
+    {
+      name: 'settings',
+      path: '/settings',
+      icon: 'setting',
+      component: './Settings',
+    },
+    {
+      name: 'ssl',
+      path: '/ssl',
+      icon: 'BarsOutlined',
       routes: [
         {
-          path: '/',
-          component: '../layouts/BasicLayout',
-          authority: ['admin', 'user'],
-          routes: [
-            {
-              path: '/',
-              redirect: '/settings',
-            },
-            {
-              name: 'settings',
-              path: '/settings',
-              icon: 'setting',
-              component: './Settings',
-            },
-            {
-              name: 'ssl',
-              path: '/ssl',
-              icon: 'BarsOutlined',
-              routes: [
-                {
-                  path: '/ssl',
-                  redirect: '/ssl/list',
-                },
-                {
-                  path: '/ssl/list',
-                  name: 'list',
-                  component: './ssl/List',
-                  hideInMenu: true,
-                },
-                {
-                  name: 'create',
-                  path: '/ssl/create',
-                  component: './ssl/Create',
-                  hideInMenu: true,
-                },
-              ],
-            },
-            {
-              component: './404',
-            },
-          ],
+          path: '/ssl',
+          redirect: '/ssl/list',
+        },
+        {
+          path: '/ssl/list',
+          name: 'list',
+          component: './ssl/List',
+          hideInMenu: true,
         },
         {
-          component: './404',
+          name: 'create',
+          path: '/ssl/create',
+          component: './ssl/Create',
+          hideInMenu: true,
         },
       ],
     },
diff --git a/config/defaultSettings.ts b/config/defaultSettings.ts
index a98401c..0245a76 100644
--- a/config/defaultSettings.ts
+++ b/config/defaultSettings.ts
@@ -1,26 +1,20 @@
-import { Settings as ProSettings } from '@ant-design/pro-layout';
+import { Settings as LayoutSettings } from '@ant-design/pro-layout';
 
-type DefaultSettings = ProSettings & {
-  pwa: boolean;
-};
-
-const defaultSettings: DefaultSettings = {
-  navTheme: 'dark',
-  // 拂晓蓝
+export default {
+  navTheme: 'light',
   primaryColor: '#1890ff',
-  layout: 'side',
+  layout: 'mix',
   contentWidth: 'Fluid',
   fixedHeader: false,
+  autoHideHeader: false,
   fixSiderbar: false,
   colorWeak: false,
   menu: {
     locale: true,
   },
-  title: 'APISIX',
+  title: 'APISIX Dashboard',
   pwa: false,
   iconfontUrl: '',
+} as LayoutSettings & {
+  pwa: boolean;
 };
-
-export { DefaultSettings };
-
-export default defaultSettings;
diff --git a/src/app.tsx b/src/app.tsx
index 1d66ae6..e94290a 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,6 +1,48 @@
+import React from 'react';
 import { notification } from 'antd';
-import { RequestConfig } from 'umi';
+import { RequestConfig, history } from 'umi';
+import { BasicLayoutProps, Settings as LayoutSettings } from '@ant-design/pro-layout';
+
 import { getAdminAPIConfig } from '@/utils/setting';
+import RightContent from '@/components/RightContent';
+import Footer from '@/components/Footer';
+import { queryCurrent } from '@/services/user';
+import defaultSettings from '../config/defaultSettings';
+
+export async function getInitialState(): Promise<{
+  currentUser?: API.CurrentUser;
+  settings?: LayoutSettings;
+}> {
+  // 如果是登录页面,不执行
+  if (history.location.pathname !== '/user/login') {
+    try {
+      const currentUser = await queryCurrent();
+      return {
+        currentUser,
+        settings: defaultSettings,
+      };
+    } catch (error) {
+      history.push('/user/login');
+    }
+  }
+  return {
+    settings: defaultSettings,
+  };
+}
+
+export const layout = ({
+  initialState,
+}: {
+  initialState: { settings?: LayoutSettings };
+}): BasicLayoutProps => {
+  return {
+    rightContentRender: () => <RightContent />,
+    disableContentMargin: false,
+    footerRender: () => <Footer />,
+    menuHeaderRender: undefined,
+    ...initialState?.settings,
+  };
+};
 
 const codeMessage = {
   200: '服务器成功返回请求的数据。',
diff --git a/src/components/Authorized/Authorized.tsx b/src/components/Authorized/Authorized.tsx
deleted file mode 100644
index 36af4c6..0000000
--- a/src/components/Authorized/Authorized.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { Result } from 'antd';
-import check, { IAuthorityType } from './CheckPermissions';
-
-import AuthorizedRoute from './AuthorizedRoute';
-import Secured from './Secured';
-
-interface AuthorizedProps {
-  authority: IAuthorityType;
-  noMatch?: React.ReactNode;
-}
-
-type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & {
-  Secured: typeof Secured;
-  check: typeof check;
-  AuthorizedRoute: typeof AuthorizedRoute;
-};
-
-const Authorized: React.FunctionComponent<AuthorizedProps> = ({
-  children,
-  authority,
-  noMatch = (
-    <Result
-      status={403}
-      title="403"
-      subTitle="Sorry, you are not authorized to access this page."
-    />
-  ),
-}) => {
-  const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
-  const dom = check(authority, childrenRender, noMatch);
-  return <>{dom}</>;
-};
-
-export default Authorized as IAuthorizedType;
diff --git a/src/components/Authorized/AuthorizedRoute.tsx b/src/components/Authorized/AuthorizedRoute.tsx
deleted file mode 100644
index 7743eae..0000000
--- a/src/components/Authorized/AuthorizedRoute.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Redirect, Route } from 'umi';
-
-import React from 'react';
-import Authorized from './Authorized';
-import { IAuthorityType } from './CheckPermissions';
-
-interface AuthorizedRoutePops {
-  currentAuthority: string;
-  component: React.ComponentClass<any, any>;
-  render: (props: any) => React.ReactNode;
-  redirectPath: string;
-  authority: IAuthorityType;
-}
-
-const AuthorizedRoute: React.SFC<AuthorizedRoutePops> = ({
-  component: Component,
-  render,
-  authority,
-  redirectPath,
-  ...rest
-}) => (
-  <Authorized
-    authority={authority}
-    noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
-  >
-    <Route
-      {...rest}
-      render={(props: any) => (Component ? <Component {...props} /> : render(props))}
-    />
-  </Authorized>
-);
-
-export default AuthorizedRoute;
diff --git a/src/components/Authorized/CheckPermissions.tsx b/src/components/Authorized/CheckPermissions.tsx
deleted file mode 100644
index caa15a3..0000000
--- a/src/components/Authorized/CheckPermissions.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from 'react';
-import { CURRENT } from './renderAuthorize';
-// eslint-disable-next-line import/no-cycle
-import PromiseRender from './PromiseRender';
-
-export type IAuthorityType =
-  | undefined
-  | string
-  | string[]
-  | Promise<boolean>
-  | ((currentAuthority: string | string[]) => IAuthorityType);
-
-/**
- * 通用权限检查方法
- * Common check permissions method
- * @param { 权限判定 | Permission judgment } authority
- * @param { 你的权限 | Your permission description } currentAuthority
- * @param { 通过的组件 | Passing components } target
- * @param { 未通过的组件 | no pass components } Exception
- */
-const checkPermissions = <T, K>(
-  authority: IAuthorityType,
-  currentAuthority: string | string[],
-  target: T,
-  Exception: K,
-): T | K | React.ReactNode => {
-  // 没有判定权限.默认查看所有
-  // Retirement authority, return target;
-  if (!authority) {
-    return target;
-  }
-  // 数组处理
-  if (Array.isArray(authority)) {
-    if (Array.isArray(currentAuthority)) {
-      if (currentAuthority.some(item => authority.includes(item))) {
-        return target;
-      }
-    } else if (authority.includes(currentAuthority)) {
-      return target;
-    }
-    return Exception;
-  }
-  // string 处理
-  if (typeof authority === 'string') {
-    if (Array.isArray(currentAuthority)) {
-      if (currentAuthority.some(item => authority === item)) {
-        return target;
-      }
-    } else if (authority === currentAuthority) {
-      return target;
-    }
-    return Exception;
-  }
-  // Promise 处理
-  if (authority instanceof Promise) {
-    return <PromiseRender<T, K> ok={target} error={Exception} promise={authority} />;
-  }
-  // Function 处理
-  if (typeof authority === 'function') {
-    try {
-      const bool = authority(currentAuthority);
-      // 函数执行后返回值是 Promise
-      if (bool instanceof Promise) {
-        return <PromiseRender<T, K> ok={target} error={Exception} promise={bool} />;
-      }
-      if (bool) {
-        return target;
-      }
-      return Exception;
-    } catch (error) {
-      throw error;
-    }
-  }
-  throw new Error('unsupported parameters');
-};
-
-export { checkPermissions };
-
-function check<T, K>(authority: IAuthorityType, target: T, Exception: K): T | K | React.ReactNode {
-  return checkPermissions<T, K>(authority, CURRENT, target, Exception);
-}
-
-export default check;
diff --git a/src/components/Authorized/PromiseRender.tsx b/src/components/Authorized/PromiseRender.tsx
deleted file mode 100644
index 25f2597..0000000
--- a/src/components/Authorized/PromiseRender.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React from 'react';
-import { Spin } from 'antd';
-import isEqual from 'lodash/isEqual';
-import { isComponentClass } from './Secured';
-// eslint-disable-next-line import/no-cycle
-
-interface PromiseRenderProps<T, K> {
-  ok: T;
-  error: K;
-  promise: Promise<boolean>;
-}
-
-interface PromiseRenderState {
-  component: React.ComponentClass | React.FunctionComponent;
-}
-
-export default class PromiseRender<T, K> extends React.Component<
-  PromiseRenderProps<T, K>,
-  PromiseRenderState
-> {
-  state: PromiseRenderState = {
-    component: () => null,
-  };
-
-  componentDidMount() {
-    this.setRenderComponent(this.props);
-  }
-
-  shouldComponentUpdate = (nextProps: PromiseRenderProps<T, K>, nextState: PromiseRenderState) => {
-    const { component } = this.state;
-    if (!isEqual(nextProps, this.props)) {
-      this.setRenderComponent(nextProps);
-    }
-    if (nextState.component !== component) return true;
-    return false;
-  };
-
-  // set render Component : ok or error
-  setRenderComponent(props: PromiseRenderProps<T, K>) {
-    const ok = this.checkIsInstantiation(props.ok);
-    const error = this.checkIsInstantiation(props.error);
-    props.promise
-      .then(() => {
-        this.setState({
-          component: ok,
-        });
-        return true;
-      })
-      .catch(() => {
-        this.setState({
-          component: error,
-        });
-      });
-  }
-
-  // Determine whether the incoming component has been instantiated
-  // AuthorizedRoute is already instantiated
-  // Authorized  render is already instantiated, children is no instantiated
-  // Secured is not instantiated
-  checkIsInstantiation = (
-    target: React.ReactNode | React.ComponentClass,
-  ): React.FunctionComponent => {
-    if (isComponentClass(target)) {
-      const Target = target as React.ComponentClass;
-      return (props: any) => <Target {...props} />;
-    }
-    if (React.isValidElement(target)) {
-      return (props: any) => React.cloneElement(target, props);
-    }
-    return () => target as React.ReactNode & null;
-  };
-
-  render() {
-    const { component: Component } = this.state;
-    const { ok, error, promise, ...rest } = this.props;
-
-    return Component ? (
-      <Component {...rest} />
-    ) : (
-      <div
-        style={{
-          width: '100%',
-          height: '100%',
-          margin: 'auto',
-          paddingTop: 50,
-          textAlign: 'center',
-        }}
-      >
-        <Spin size="large" />
-      </div>
-    );
-  }
-}
diff --git a/src/components/Authorized/Secured.tsx b/src/components/Authorized/Secured.tsx
deleted file mode 100644
index 0bdbbe4..0000000
--- a/src/components/Authorized/Secured.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from 'react';
-import CheckPermissions from './CheckPermissions';
-
-/**
- * 默认不能访问任何页面
- * default is "NULL"
- */
-const Exception403 = () => 403;
-
-export const isComponentClass = (component: React.ComponentClass | React.ReactNode): boolean => {
-  if (!component) return false;
-  const proto = Object.getPrototypeOf(component);
-  if (proto === React.Component || proto === Function.prototype) return true;
-  return isComponentClass(proto);
-};
-
-// Determine whether the incoming component has been instantiated
-// AuthorizedRoute is already instantiated
-// Authorized  render is already instantiated, children is no instantiated
-// Secured is not instantiated
-const checkIsInstantiation = (target: React.ComponentClass | React.ReactNode) => {
-  if (isComponentClass(target)) {
-    const Target = target as React.ComponentClass;
-    return (props: any) => <Target {...props} />;
-  }
-  if (React.isValidElement(target)) {
-    return (props: any) => React.cloneElement(target, props);
-  }
-  return () => target;
-};
-
-/**
- * 用于判断是否拥有权限访问此 view 权限
- * authority 支持传入 string, () => boolean | Promise
- * e.g. 'user' 只有 user 用户能访问
- * e.g. 'user,admin' user 和 admin 都能访问
- * e.g. ()=>boolean 返回true能访问,返回false不能访问
- * e.g. Promise  then 能访问   catch不能访问
- * e.g. authority support incoming string, () => boolean | Promise
- * e.g. 'user' only user user can access
- * e.g. 'user, admin' user and admin can access
- * e.g. () => boolean true to be able to visit, return false can not be accessed
- * e.g. Promise then can not access the visit to catch
- * @param {string | function | Promise} authority
- * @param {ReactNode} error 非必需参数
- */
-const authorize = (authority: string, error?: React.ReactNode) => {
-  /**
-   * conversion into a class
-   * 防止传入字符串时找不到staticContext造成报错
-   * String parameters can cause staticContext not found error
-   */
-  let classError: boolean | React.FunctionComponent = false;
-  if (error) {
-    classError = (() => error) as React.FunctionComponent;
-  }
-  if (!authority) {
-    throw new Error('authority is required');
-  }
-  return function decideAuthority(target: React.ComponentClass | React.ReactNode) {
-    const component = CheckPermissions(authority, target, classError || Exception403);
-    return checkIsInstantiation(component);
-  };
-};
-
-export default authorize;
diff --git a/src/components/Authorized/index.tsx b/src/components/Authorized/index.tsx
deleted file mode 100644
index 6703a46..0000000
--- a/src/components/Authorized/index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import Authorized from './Authorized';
-import Secured from './Secured';
-import check from './CheckPermissions';
-import renderAuthorize from './renderAuthorize';
-
-Authorized.Secured = Secured;
-Authorized.check = check;
-
-const RenderAuthorize = renderAuthorize(Authorized);
-
-export default RenderAuthorize;
diff --git a/src/components/Authorized/renderAuthorize.ts b/src/components/Authorized/renderAuthorize.ts
deleted file mode 100644
index df00875..0000000
--- a/src/components/Authorized/renderAuthorize.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/* eslint-disable eslint-comments/disable-enable-pair */
-/* eslint-disable import/no-mutable-exports */
-let CURRENT: string | string[] = 'NULL';
-
-type CurrentAuthorityType = string | string[] | (() => typeof CURRENT);
-/**
- * use  authority or getAuthority
- * @param {string|()=>String} currentAuthority
- */
-const renderAuthorize = <T>(Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => (
-  currentAuthority: CurrentAuthorityType,
-): T => {
-  if (currentAuthority) {
-    if (typeof currentAuthority === 'function') {
-      CURRENT = currentAuthority();
-    }
-    if (
-      Object.prototype.toString.call(currentAuthority) === '[object String]' ||
-      Array.isArray(currentAuthority)
-    ) {
-      CURRENT = currentAuthority as string[];
-    }
-  } else {
-    CURRENT = 'NULL';
-  }
-  return Authorized;
-};
-
-export { CURRENT };
-export default <T>(Authorized: T) => renderAuthorize<T>(Authorized);
diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
new file mode 100644
index 0000000..0f6648b
--- /dev/null
+++ b/src/components/Footer/index.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-layout';
+
+export default () => (
+  <DefaultFooter
+    copyright="2020 Apache APISIX"
+    links={[
+      {
+        key: 'GitHub',
+        title: <GithubOutlined />,
+        href: 'https://github.com/apache/incubator-apisix',
+        blankTarget: true,
+      },
+    ]}
+  />
+);
diff --git a/src/components/GlobalHeader/AvatarDropdown.tsx b/src/components/GlobalHeader/AvatarDropdown.tsx
deleted file mode 100644
index 4cf5340..0000000
--- a/src/components/GlobalHeader/AvatarDropdown.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
-import { Avatar, Menu, Spin } from 'antd';
-import { ClickParam } from 'antd/es/menu';
-import React from 'react';
-import { history, connect } from 'umi';
-import { ConnectProps, ConnectState } from '@/models/connect';
-import { CurrentUser } from '@/models/user';
-import HeaderDropdown from '../HeaderDropdown';
-import styles from './index.less';
-
-export interface GlobalHeaderRightProps extends ConnectProps {
-  currentUser?: CurrentUser;
-  menu?: boolean;
-}
-
-class AvatarDropdown extends React.Component<GlobalHeaderRightProps> {
-  onMenuClick = (event: ClickParam) => {
-    const { key } = event;
-
-    if (key === 'logout') {
-      const { dispatch } = this.props;
-
-      if (dispatch) {
-        dispatch({
-          type: 'login/logout',
-        });
-      }
-
-      return;
-    }
-
-    history.push(`/account/${key}`);
-  };
-
-  render(): React.ReactNode {
-    const {
-      currentUser = {
-        avatar: '',
-        name: '',
-      },
-      menu,
-    } = this.props;
-    const menuHeaderDropdown = (
-      <Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
-        {menu && (
-          <Menu.Item key="center">
-            <UserOutlined />
-            个人中心
-          </Menu.Item>
-        )}
-        {menu && (
-          <Menu.Item key="settings">
-            <SettingOutlined />
-            个人设置
-          </Menu.Item>
-        )}
-        {menu && <Menu.Divider />}
-
-        <Menu.Item key="logout">
-          <LogoutOutlined />
-          退出登录
-        </Menu.Item>
-      </Menu>
-    );
-    return currentUser && currentUser.name ? (
-      <HeaderDropdown overlay={menuHeaderDropdown}>
-        <span className={`${styles.action} ${styles.account}`}>
-          <Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
-          <span className={styles.name}>{currentUser.name}</span>
-        </span>
-      </HeaderDropdown>
-    ) : (
-      <Spin
-        size="small"
-        style={{
-          marginLeft: 8,
-          marginRight: 8,
-        }}
-      />
-    );
-  }
-}
-
-export default connect(({ user }: ConnectState) => ({
-  currentUser: user.currentUser,
-}))(AvatarDropdown);
diff --git a/src/components/GlobalHeader/NoticeIconView.tsx b/src/components/GlobalHeader/NoticeIconView.tsx
deleted file mode 100644
index f450087..0000000
--- a/src/components/GlobalHeader/NoticeIconView.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import React, { Component } from 'react';
-import { Tag, message } from 'antd';
-import { connect } from 'umi';
-import groupBy from 'lodash/groupBy';
-import moment from 'moment';
-import { NoticeItem } from '@/models/global';
-import { CurrentUser } from '@/models/user';
-import { ConnectProps, ConnectState } from '@/models/connect';
-import NoticeIcon from '../NoticeIcon';
-import styles from './index.less';
-
-export interface GlobalHeaderRightProps extends ConnectProps {
-  notices?: NoticeItem[];
-  currentUser?: CurrentUser;
-  fetchingNotices?: boolean;
-  onNoticeVisibleChange?: (visible: boolean) => void;
-  onNoticeClear?: (tabName?: string) => void;
-}
-
-class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
-  componentDidMount() {
-    const { dispatch } = this.props;
-
-    if (dispatch) {
-      dispatch({
-        type: 'global/fetchNotices',
-      });
-    }
-  }
-
-  changeReadState = (clickedItem: NoticeItem): void => {
-    const { id } = clickedItem;
-    const { dispatch } = this.props;
-
-    if (dispatch) {
-      dispatch({
-        type: 'global/changeNoticeReadState',
-        payload: id,
-      });
-    }
-  };
-
-  handleNoticeClear = (title: string, key: string) => {
-    const { dispatch } = this.props;
-    message.success(`${'清空了'} ${title}`);
-
-    if (dispatch) {
-      dispatch({
-        type: 'global/clearNotices',
-        payload: key,
-      });
-    }
-  };
-
-  getNoticeData = (): {
-    [key: string]: NoticeItem[];
-  } => {
-    const { notices = [] } = this.props;
-
-    if (notices.length === 0) {
-      return {};
-    }
-
-    const newNotices = notices.map(notice => {
-      const newNotice = { ...notice };
-
-      if (newNotice.datetime) {
-        newNotice.datetime = moment(notice.datetime as string).fromNow();
-      }
-
-      if (newNotice.id) {
-        newNotice.key = newNotice.id;
-      }
-
-      if (newNotice.extra && newNotice.status) {
-        const color = {
-          todo: '',
-          processing: 'blue',
-          urgent: 'red',
-          doing: 'gold',
-        }[newNotice.status];
-        newNotice.extra = (
-          <Tag
-            color={color}
-            style={{
-              marginRight: 0,
-            }}
-          >
-            {newNotice.extra}
-          </Tag>
-        );
-      }
-
-      return newNotice;
-    });
-    return groupBy(newNotices, 'type');
-  };
-
-  getUnreadData = (noticeData: { [key: string]: NoticeItem[] }) => {
-    const unreadMsg: {
-      [key: string]: number;
-    } = {};
-    Object.keys(noticeData).forEach(key => {
-      const value = noticeData[key];
-
-      if (!unreadMsg[key]) {
-        unreadMsg[key] = 0;
-      }
-
-      if (Array.isArray(value)) {
-        unreadMsg[key] = value.filter(item => !item.read).length;
-      }
-    });
-    return unreadMsg;
-  };
-
-  render() {
-    const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
-    const noticeData = this.getNoticeData();
-    const unreadMsg = this.getUnreadData(noticeData);
-    return (
-      <NoticeIcon
-        className={styles.action}
-        count={currentUser && currentUser.unreadCount}
-        onItemClick={item => {
-          this.changeReadState(item as NoticeItem);
-        }}
-        loading={fetchingNotices}
-        clearText="清空"
-        viewMoreText="查看更多"
-        onClear={this.handleNoticeClear}
-        onPopupVisibleChange={onNoticeVisibleChange}
-        onViewMore={() => message.info('Click on view more')}
-        clearClose
-      >
-        <NoticeIcon.Tab
-          tabKey="notification"
-          count={unreadMsg.notification}
-          list={noticeData.notification}
-          title="通知"
-          emptyText="你已查看所有通知"
-          showViewMore
-        />
-        <NoticeIcon.Tab
-          tabKey="message"
-          count={unreadMsg.message}
-          list={noticeData.message}
-          title="消息"
-          emptyText="您已读完所有消息"
-          showViewMore
-        />
-        <NoticeIcon.Tab
-          tabKey="event"
-          title="待办"
-          emptyText="你已完成所有待办"
-          count={unreadMsg.event}
-          list={noticeData.event}
-          showViewMore
-        />
-      </NoticeIcon>
-    );
-  }
-}
-
-export default connect(({ user, global, loading }: ConnectState) => ({
-  currentUser: user.currentUser,
-  collapsed: global.collapsed,
-  fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
-  fetchingNotices: loading.effects['global/fetchNotices'],
-  notices: global.notices,
-}))(GlobalHeaderRight);
diff --git a/src/components/GlobalHeader/RightContent.tsx b/src/components/GlobalHeader/RightContent.tsx
deleted file mode 100644
index 9841729..0000000
--- a/src/components/GlobalHeader/RightContent.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Tooltip, Tag } from 'antd';
-import { QuestionCircleOutlined } from '@ant-design/icons';
-import React from 'react';
-import { connect } from 'umi';
-import { ConnectProps, ConnectState } from '@/models/connect';
-import Avatar from './AvatarDropdown';
-import SelectLang from '../SelectLang';
-import styles from './index.less';
-
-export type SiderTheme = 'light' | 'dark';
-export interface GlobalHeaderRightProps extends ConnectProps {
-  theme?: SiderTheme;
-  layout: 'sidemenu' | 'topmenu';
-}
-
-const ENVTagColor = {
-  dev: 'orange',
-  test: 'green',
-  pre: '#87d068',
-};
-
-const GlobalHeaderRight: React.SFC<GlobalHeaderRightProps> = props => {
-  const { theme, layout } = props;
-  let className = styles.right;
-
-  if (theme === 'dark' && layout === 'topmenu') {
-    className = `${styles.right}  ${styles.dark}`;
-  }
-
-  return (
-    <div className={className}>
-      <Tooltip title="Documentation">
-        <a
-          target="_blank"
-          href="https://github.com/apache/incubator-apisix"
-          rel="noopener noreferrer"
-          className={styles.action}
-        >
-          <QuestionCircleOutlined />
-        </a>
-      </Tooltip>
-      <Avatar />
-      {REACT_APP_ENV && (
-        <span>
-          <Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
-        </span>
-      )}
-      <SelectLang className={styles.action} />
-    </div>
-  );
-};
-
-export default connect(({ settings }: ConnectState) => ({
-  theme: settings.navTheme,
-  layout: settings.layout,
-}))(GlobalHeaderRight);
diff --git a/src/components/HeaderSearch/index.less b/src/components/HeaderSearch/index.less
deleted file mode 100644
index 9af69d5..0000000
--- a/src/components/HeaderSearch/index.less
+++ /dev/null
@@ -1,30 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.headerSearch {
-  .input {
-    width: 0;
-    min-width: 0;
-    overflow: hidden;
-    background: transparent;
-    border-radius: 0;
-    transition: width 0.3s, margin-left 0.3s;
-    :global(.ant-select-selection) {
-      background: transparent;
-    }
-    input {
-      padding-right: 0;
-      padding-left: 0;
-      border: 0;
-      box-shadow: none !important;
-    }
-    &,
-    &:hover,
-    &:focus {
-      border-bottom: 1px solid @border-color-base;
-    }
-    &.show {
-      width: 210px;
-      margin-left: 8px;
-    }
-  }
-}
diff --git a/src/components/HeaderSearch/index.tsx b/src/components/HeaderSearch/index.tsx
deleted file mode 100644
index e09cf5b..0000000
--- a/src/components/HeaderSearch/index.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { SearchOutlined } from '@ant-design/icons';
-import { AutoComplete, Input } from 'antd';
-import useMergeValue from 'use-merge-value';
-import { AutoCompleteProps } from 'antd/es/auto-complete';
-import React, { useRef } from 'react';
-
-import classNames from 'classnames';
-import styles from './index.less';
-
-export interface HeaderSearchProps {
-  onSearch?: (value?: string) => void;
-  onChange?: (value?: string) => void;
-  onVisibleChange?: (b: boolean) => void;
-  className?: string;
-  placeholder?: string;
-  options: AutoCompleteProps['options'];
-  defaultOpen?: boolean;
-  open?: boolean;
-  defaultValue?: string;
-  value?: string;
-}
-
-const HeaderSearch: React.FC<HeaderSearchProps> = props => {
-  const {
-    className,
-    defaultValue,
-    onVisibleChange,
-    placeholder,
-    open,
-    defaultOpen,
-    ...restProps
-  } = props;
-
-  const inputRef = useRef<Input | null>(null);
-
-  const [value, setValue] = useMergeValue<string | undefined>(defaultValue, {
-    value: props.value,
-    onChange: props.onChange,
-  });
-
-  const [searchMode, setSearchMode] = useMergeValue(defaultOpen || false, {
-    value: props.open,
-    onChange: onVisibleChange,
-  });
-
-  const inputClass = classNames(styles.input, {
-    [styles.show]: searchMode,
-  });
-
-  return (
-    <div
-      className={classNames(className, styles.headerSearch)}
-      onClick={() => {
-        setSearchMode(true);
-        if (searchMode && inputRef.current) {
-          inputRef.current.focus();
-        }
-      }}
-      onTransitionEnd={({ propertyName }) => {
-        if (propertyName === 'width' && !searchMode) {
-          if (onVisibleChange) {
-            onVisibleChange(searchMode);
-          }
-        }
-      }}
-    >
-      <SearchOutlined
-        key="Icon"
-        style={{
-          cursor: 'pointer',
-        }}
-      />
-      <AutoComplete
-        key="AutoComplete"
-        className={inputClass}
-        value={value}
-        style={{
-          height: 28,
-          marginTop: -6,
-        }}
-        options={restProps.options}
-        onChange={setValue}
-      >
-        <Input
-          ref={inputRef}
-          defaultValue={defaultValue}
-          aria-label={placeholder}
-          placeholder={placeholder}
-          onKeyDown={e => {
-            if (e.key === 'Enter') {
-              if (restProps.onSearch) {
-                restProps.onSearch(value);
-              }
-            }
-          }}
-          onBlur={() => {
-            setSearchMode(false);
-          }}
-        />
-      </AutoComplete>
-    </div>
-  );
-};
-
-export default HeaderSearch;
diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..5555355
--- /dev/null
+++ b/src/components/RightContent/AvatarDropdown.tsx
@@ -0,0 +1,99 @@
+import React, { useCallback } from 'react';
+import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
+import { Avatar, Menu, Spin } from 'antd';
+import { ClickParam } from 'antd/es/menu';
+import { history, useModel } from 'umi';
+import { getPageQuery } from '@/utils/utils';
+
+import { stringify } from 'querystring';
+import HeaderDropdown from '../HeaderDropdown';
+import styles from './index.less';
+
+export interface GlobalHeaderRightProps {
+  menu?: boolean;
+}
+
+/**
+ * 退出登录,并且将当前的 url 保存
+ */
+const loginOut = async () => {
+  const { redirect } = getPageQuery();
+  // Note: There may be security issues, please note
+  if (window.location.pathname !== '/user/login' && !redirect) {
+    history.replace({
+      pathname: '/user/login',
+      search: stringify({
+        redirect: window.location.href,
+      }),
+    });
+  }
+};
+
+const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
+  const { initialState, setInitialState } = useModel('@@initialState');
+
+  const onMenuClick = useCallback((event: ClickParam) => {
+    const { key } = event;
+    if (key === 'logout') {
+      setInitialState({ ...initialState, currentUser: undefined });
+      loginOut();
+      return;
+    }
+    history.push(`/account/${key}`);
+  }, []);
+
+  const loading = (
+    <span className={`${styles.action} ${styles.account}`}>
+      <Spin
+        size="small"
+        style={{
+          marginLeft: 8,
+          marginRight: 8,
+        }}
+      />
+    </span>
+  );
+
+  if (!initialState) {
+    return loading;
+  }
+
+  const { currentUser } = initialState;
+
+  if (!currentUser || !currentUser.name) {
+    return loading;
+  }
+
+  const menuHeaderDropdown = (
+    <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
+      {menu && (
+        <Menu.Item key="center">
+          <UserOutlined />
+          个人中心
+        </Menu.Item>
+      )}
+      {menu && (
+        <Menu.Item key="settings">
+          <SettingOutlined />
+          个人设置
+        </Menu.Item>
+      )}
+      {menu && <Menu.Divider />}
+
+      <Menu.Item key="logout">
+        <LogoutOutlined />
+        退出登录
+      </Menu.Item>
+    </Menu>
+  );
+  return (
+    <HeaderDropdown overlay={menuHeaderDropdown}>
+      <span className={`${styles.action} ${styles.account}`}>
+        <Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
+        <span className={`${styles.name} anticon`}>{currentUser.name}</span>
+      </span>
+    </HeaderDropdown>
+  );
+};
+
+export default AvatarDropdown;
diff --git a/src/components/GlobalHeader/index.less b/src/components/RightContent/index.less
similarity index 66%
rename from src/components/GlobalHeader/index.less
rename to src/components/RightContent/index.less
index 6a156db..ef2549e 100644
--- a/src/components/GlobalHeader/index.less
+++ b/src/components/RightContent/index.less
@@ -14,20 +14,17 @@
 .right {
   display: flex;
   float: right;
-  height: @layout-header-height;
+  height: 48px;
   margin-left: auto;
   overflow: hidden;
   .action {
     display: flex;
     align-items: center;
-    height: 100%;
+    height: 48px;
     padding: 0 12px;
     cursor: pointer;
     transition: all 0.3s;
-    > span {
-      color: @text-color;
-      vertical-align: middle;
-    }
+
     &:hover {
       background: @pro-header-hover-bg;
     }
@@ -43,7 +40,6 @@
   }
   .account {
     .avatar {
-      margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
       margin-right: 8px;
       color: @primary-color;
       vertical-align: top;
@@ -54,30 +50,11 @@
 
 .dark {
   .action {
-    color: rgba(255, 255, 255, 0.85);
-    > span {
-      color: rgba(255, 255, 255, 0.85);
+    &:hover {
+      background: #252a3d;
     }
-    &:hover,
     &:global(.opened) {
-      background: @primary-color;
-    }
-  }
-}
-
-:global(.ant-pro-global-header) {
-  .dark {
-    .action {
-      color: @text-color;
-      > span {
-        color: @text-color;
-      }
-      &:hover {
-        color: rgba(255, 255, 255, 0.85);
-        > span {
-          color: rgba(255, 255, 255, 0.85);
-        }
-      }
+      background: #252a3d;
     }
   }
 }
diff --git a/src/components/RightContent/index.tsx b/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..f54e5b7
--- /dev/null
+++ b/src/components/RightContent/index.tsx
@@ -0,0 +1,51 @@
+import { Tooltip, Tag, Space } from 'antd';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import React from 'react';
+import { useModel, SelectLang } from 'umi';
+import Avatar from './AvatarDropdown';
+import styles from './index.less';
+
+export type SiderTheme = 'light' | 'dark';
+
+const ENVTagColor = {
+  dev: 'orange',
+  test: 'green',
+  pre: '#87d068',
+};
+
+const GlobalHeaderRight: React.FC<{}> = () => {
+  const { initialState } = useModel('@@initialState');
+
+  if (!initialState || !initialState.settings) {
+    return null;
+  }
+
+  const { navTheme, layout } = initialState.settings;
+  let className = styles.right;
+
+  if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') {
+    className = `${styles.right}  ${styles.dark}`;
+  }
+  return (
+    <Space className={className}>
+      <Tooltip title="Documentation">
+        <span
+          className={styles.action}
+          onClick={() => {
+            window.location.href = 'https://github.com/apache/incubator-apisix';
+          }}
+        >
+          <QuestionCircleOutlined />
+        </span>
+      </Tooltip>
+      <Avatar />
+      {REACT_APP_ENV && (
+        <span>
+          <Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
+        </span>
+      )}
+      <SelectLang className={styles.action} />
+    </Space>
+  );
+};
+export default GlobalHeaderRight;
diff --git a/src/components/SelectLang/index.less b/src/components/SelectLang/index.less
deleted file mode 100644
index c0da9b4..0000000
--- a/src/components/SelectLang/index.less
+++ /dev/null
@@ -1,24 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.menu {
-  :global(.anticon) {
-    margin-right: 8px;
-  }
-  :global(.ant-dropdown-menu-item) {
-    min-width: 160px;
-  }
-}
-
-.dropDown {
-  line-height: @layout-header-height;
-  vertical-align: top;
-  cursor: pointer;
-  > span {
-    font-size: 16px !important;
-    transform: none !important;
-    svg {
-      position: relative;
-      top: -1px;
-    }
-  }
-}
diff --git a/src/components/SelectLang/index.tsx b/src/components/SelectLang/index.tsx
deleted file mode 100644
index fbb6658..0000000
--- a/src/components/SelectLang/index.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { GlobalOutlined } from '@ant-design/icons';
-import { Menu } from 'antd';
-import { getLocale, setLocale } from 'umi';
-import { ClickParam } from 'antd/es/menu';
-import React from 'react';
-import classNames from 'classnames';
-import HeaderDropdown from '../HeaderDropdown';
-import styles from './index.less';
-
-interface SelectLangProps {
-  className?: string;
-}
-
-const SelectLang: React.FC<SelectLangProps> = props => {
-  const { className } = props;
-  const selectedLang = getLocale();
-
-  const changeLang = ({ key }: ClickParam): void => setLocale(key);
-
-  const locales = ['zh-CN', 'en-US'];
-  const languageLabels = {
-    'zh-CN': '简体中文',
-    'en-US': 'English',
-  };
-  const languageIcons = {
-    'zh-CN': '🇨🇳',
-    'en-US': '🇺🇸',
-  };
-  const langMenu = (
-    <Menu className={styles.menu} selectedKeys={[selectedLang]} onClick={changeLang}>
-      {locales.map(locale => (
-        <Menu.Item key={locale}>
-          <span role="img" aria-label={languageLabels[locale]}>
-            {languageIcons[locale]}
-          </span>{' '}
-          {languageLabels[locale]}
-        </Menu.Item>
-      ))}
-    </Menu>
-  );
-  return (
-    <HeaderDropdown overlay={langMenu} placement="bottomRight">
-      <span className={classNames(styles.dropDown, className)}>
-        <GlobalOutlined title="语言" />
-      </span>
-    </HeaderDropdown>
-  );
-};
-
-export default SelectLang;
diff --git a/src/global.tsx b/src/global.tsx
index 4c9014c..f7620c4 100644
--- a/src/global.tsx
+++ b/src/global.tsx
@@ -1,15 +1,15 @@
 import { Button, message, notification } from 'antd';
+
 import React from 'react';
-import { useIntl } from 'umi';
+import { formatMessage } from 'umi';
 import defaultSettings from '../config/defaultSettings';
 
 const { pwa } = defaultSettings;
-
 // if pwa is true
 if (pwa) {
   // Notify user if offline now
   window.addEventListener('sw.offline', () => {
-    message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
+    message.warning(formatMessage({ id: 'app.pwa.offline' }));
   });
 
   // Pop up a prompt on the page asking the user if they want to use the latest version
@@ -25,7 +25,7 @@ if (pwa) {
       // Send skip-waiting event to waiting SW with MessageChannel
       await new Promise((resolve, reject) => {
         const channel = new MessageChannel();
-        channel.port1.onmessage = msgEvent => {
+        channel.port1.onmessage = (msgEvent) => {
           if (msgEvent.data.error) {
             reject(msgEvent.data.error);
           } else {
@@ -47,12 +47,12 @@ if (pwa) {
           reloadSW();
         }}
       >
-        {useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
+        {formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
       </Button>
     );
     notification.open({
-      message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
-      description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+      message: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+      description: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
       btn,
       key,
       onClose: async () => {},
@@ -62,20 +62,20 @@ if (pwa) {
   // unregister service worker
   const { serviceWorker } = navigator;
   if (serviceWorker.getRegistrations) {
-    serviceWorker.getRegistrations().then(sws => {
-      sws.forEach(sw => {
+    serviceWorker.getRegistrations().then((sws) => {
+      sws.forEach((sw) => {
         sw.unregister();
       });
     });
   }
-  serviceWorker.getRegistration().then(sw => {
+  serviceWorker.getRegistration().then((sw) => {
     if (sw) sw.unregister();
   });
 
   // remove all caches
   if (window.caches && window.caches.keys) {
-    caches.keys().then(keys => {
-      keys.forEach(key => {
+    caches.keys().then((keys) => {
+      keys.forEach((key) => {
         caches.delete(key);
       });
     });
diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx
deleted file mode 100644
index 6f68a6a..0000000
--- a/src/layouts/BasicLayout.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import ProLayout, {
-  MenuDataItem,
-  BasicLayoutProps as ProLayoutProps,
-  Settings,
-  DefaultFooter,
-} from '@ant-design/pro-layout';
-import React, { useEffect } from 'react';
-import { Link, useIntl, connect, Dispatch } from 'umi';
-import { GithubOutlined } from '@ant-design/icons';
-import { Result, Button } from 'antd';
-import Authorized from '@/utils/Authorized';
-import RightContent from '@/components/GlobalHeader/RightContent';
-import { ConnectState } from '@/models/connect';
-import { getAuthorityFromRouter } from '@/utils/utils';
-import logo from '../assets/logo.svg';
-
-const noMatch = (
-  <Result
-    status={403}
-    title="403"
-    subTitle="Sorry, you are not authorized to access this page."
-    extra={
-      <Button type="primary">
-        <Link to="/user/login">Go Login</Link>
-      </Button>
-    }
-  />
-);
-export interface BasicLayoutProps extends ProLayoutProps {
-  breadcrumbNameMap: {
-    [path: string]: MenuDataItem;
-  };
-  route: ProLayoutProps['route'] & {
-    authority: string[];
-  };
-  settings: Settings;
-  dispatch: Dispatch;
-}
-export type BasicLayoutContext = { [K in 'location']: BasicLayoutProps[K] } & {
-  breadcrumbNameMap: {
-    [path: string]: MenuDataItem;
-  };
-};
-/**
- * use Authorized check all menu item
- */
-
-const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
-  menuList.map(item => {
-    const localItem = { ...item, children: item.children ? menuDataRender(item.children) : [] };
-    return Authorized.check(item.authority, localItem, null) as MenuDataItem;
-  });
-
-const defaultFooterDom = (
-  <DefaultFooter
-    copyright="2020 Apache APISIX"
-    links={[
-      {
-        key: 'GitHub',
-        title: <GithubOutlined />,
-        href: 'https://github.com/apache/incubator-apisix',
-        blankTarget: true,
-      },
-    ]}
-  />
-);
-
-const BasicLayout: React.FC<BasicLayoutProps> = props => {
-  const {
-    dispatch,
-    children,
-    settings,
-    location = {
-      pathname: '/',
-    },
-  } = props;
-  /**
-   * constructor
-   */
-
-  useEffect(() => {
-    if (dispatch) {
-      dispatch({
-        type: 'user/fetchCurrent',
-      });
-    }
-  }, []);
-  /**
-   * init variables
-   */
-
-  const handleMenuCollapse = (payload: boolean): void => {
-    if (dispatch) {
-      dispatch({
-        type: 'global/changeLayoutCollapsed',
-        payload,
-      });
-    }
-  }; // get children authority
-
-  const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || {
-    authority: undefined,
-  };
-  const { formatMessage } = useIntl();
-
-  return (
-    <ProLayout
-      logo={logo}
-      formatMessage={formatMessage}
-      menuHeaderRender={(logoDom, titleDom) => (
-        <Link to="/">
-          {logoDom}
-          {titleDom}
-        </Link>
-      )}
-      onCollapse={handleMenuCollapse}
-      menuItemRender={(menuItemProps, defaultDom) => {
-        if (menuItemProps.isUrl || menuItemProps.children || !menuItemProps.path) {
-          return defaultDom;
-        }
-
-        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
-      }}
-      breadcrumbRender={(routers = []) => [
-        {
-          path: '/',
-          breadcrumbName: formatMessage({ id: 'menu.home' }),
-        },
-        ...routers,
-      ]}
-      itemRender={(route, params, routes, paths) => {
-        const first = routes.indexOf(route) === 0;
-        return first ? (
-          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
-        ) : (
-          <span>{route.breadcrumbName}</span>
-        );
-      }}
-      footerRender={() => defaultFooterDom}
-      menuDataRender={menuDataRender}
-      rightContentRender={() => <RightContent />}
-      {...props}
-      {...settings}
-    >
-      <Authorized authority={authorized!.authority} noMatch={noMatch}>
-        {children}
-      </Authorized>
-    </ProLayout>
-  );
-};
-
-export default connect(({ global, settings }: ConnectState) => ({
-  collapsed: global.collapsed,
-  settings,
-}))(BasicLayout);
diff --git a/src/layouts/BlankLayout.tsx b/src/layouts/BlankLayout.tsx
deleted file mode 100644
index cdc55b0..0000000
--- a/src/layouts/BlankLayout.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-
-const Layout: React.FC = ({ children }) => <>{children}</>;
-
-export default Layout;
diff --git a/src/layouts/SecurityLayout.tsx b/src/layouts/SecurityLayout.tsx
deleted file mode 100644
index 6024b65..0000000
--- a/src/layouts/SecurityLayout.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import React from 'react';
-import { PageLoading } from '@ant-design/pro-layout';
-import { Redirect, connect } from 'umi';
-import { stringify } from 'querystring';
-import { ConnectState, ConnectProps } from '@/models/connect';
-import { CurrentUser } from '@/models/user';
-
-interface SecurityLayoutProps extends ConnectProps {
-  loading?: boolean;
-  currentUser?: CurrentUser;
-}
-
-interface SecurityLayoutState {
-  isReady: boolean;
-}
-
-class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> {
-  state: SecurityLayoutState = {
-    isReady: false,
-  };
-
-  componentDidMount() {
-    this.setState({
-      isReady: true,
-    });
-    const { dispatch } = this.props;
-    if (dispatch) {
-      dispatch({
-        type: 'user/fetchCurrent',
-      });
-    }
-  }
-
-  render() {
-    const { isReady } = this.state;
-    const { children, loading, currentUser } = this.props;
-    // You can replace it to your authentication rule (such as check token exists)
-    // 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在)
-    const isLogin = currentUser && currentUser.userid;
-    const queryString = stringify({
-      redirect: window.location.href,
-    });
-
-    if ((!isLogin && loading) || !isReady) {
-      return <PageLoading />;
-    }
-    if (!isLogin && window.location.pathname !== '/user/login') {
-      return <Redirect to={`/user/login?${queryString}`} />;
-    }
-    return children;
-  }
-}
-
-export default connect(({ user, loading }: ConnectState) => ({
-  currentUser: user.currentUser,
-  loading: loading.models.user,
-}))(SecurityLayout);
diff --git a/src/layouts/UserLayout.less b/src/layouts/UserLayout.less
deleted file mode 100755
index cdc207e..0000000
--- a/src/layouts/UserLayout.less
+++ /dev/null
@@ -1,71 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.container {
-  display: flex;
-  flex-direction: column;
-  height: 100vh;
-  overflow: auto;
-  background: @layout-body-background;
-}
-
-.lang {
-  width: 100%;
-  height: 40px;
-  line-height: 44px;
-  text-align: right;
-  :global(.ant-dropdown-trigger) {
-    margin-right: 24px;
-  }
-}
-
-.content {
-  flex: 1;
-  padding: 32px 0;
-}
-
-@media (min-width: @screen-md-min) {
-  .container {
-    background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
-    background-repeat: no-repeat;
-    background-position: center 110px;
-    background-size: 100%;
-  }
-
-  .content {
-    padding: 32px 0 24px;
-  }
-}
-
-.top {
-  text-align: center;
-}
-
-.header {
-  height: 44px;
-  line-height: 44px;
-  a {
-    text-decoration: none;
-  }
-}
-
-.logo {
-  height: 44px;
-  margin-right: 16px;
-  vertical-align: top;
-}
-
-.title {
-  position: relative;
-  top: 2px;
-  color: @heading-color;
-  font-weight: 600;
-  font-size: 33px;
-  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
-}
-
-.desc {
-  margin-top: 12px;
-  margin-bottom: 40px;
-  color: @text-color-secondary;
-  font-size: @font-size-base;
-}
diff --git a/src/layouts/UserLayout.tsx b/src/layouts/UserLayout.tsx
deleted file mode 100644
index c759ab3..0000000
--- a/src/layouts/UserLayout.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { DefaultFooter, MenuDataItem, getMenuData, getPageTitle } from '@ant-design/pro-layout';
-import { Helmet, HelmetProvider } from 'react-helmet-async';
-import { Link, useIntl, ConnectProps, connect } from 'umi';
-import React from 'react';
-import { GithubOutlined } from '@ant-design/icons';
-import SelectLang from '@/components/SelectLang';
-import { ConnectState } from '@/models/connect';
-import logo from '../assets/logo.svg';
-import styles from './UserLayout.less';
-
-export interface UserLayoutProps extends Partial<ConnectProps> {
-  breadcrumbNameMap: {
-    [path: string]: MenuDataItem;
-  };
-}
-
-const UserLayout: React.FC<UserLayoutProps> = props => {
-  const {
-    route = {
-      routes: [],
-    },
-  } = props;
-  const { routes = [] } = route;
-  const {
-    children,
-    location = {
-      pathname: '',
-    },
-  } = props;
-  const { formatMessage } = useIntl();
-  const { breadcrumb } = getMenuData(routes);
-  const title = getPageTitle({
-    pathname: location.pathname,
-    formatMessage,
-    breadcrumb,
-    ...props,
-  });
-  return (
-    <HelmetProvider>
-      <Helmet>
-        <title>{title}</title>
-        <meta name="description" content={title} />
-      </Helmet>
-
-      <div className={styles.container}>
-        <div className={styles.lang}>
-          <SelectLang />
-        </div>
-        <div className={styles.content}>
-          <div className={styles.top}>
-            <div className={styles.header}>
-              <Link to="/">
-                <img alt="logo" className={styles.logo} src={logo} />
-                <span className={styles.title}>APISIX Dashboard</span>
-              </Link>
-            </div>
-            <div className={styles.desc}>Cloud-Native Microservices API Gateway</div>
-          </div>
-          {children}
-        </div>
-        <DefaultFooter
-          copyright="2020 Apache APISIX"
-          links={[
-            {
-              key: 'GitHub',
-              title: <GithubOutlined />,
-              href: 'https://github.com/apache/incubator-apisix',
-              blankTarget: true,
-            },
-          ]}
-        />
-      </div>
-    </HelmetProvider>
-  );
-};
-
-export default connect(({ settings }: ConnectState) => ({ ...settings }))(UserLayout);
diff --git a/src/models/connect.d.ts b/src/models/connect.d.ts
deleted file mode 100644
index a0696dc..0000000
--- a/src/models/connect.d.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { MenuDataItem } from '@ant-design/pro-layout';
-import { GlobalModelState } from './global';
-import { DefaultSettings as SettingModelState } from '../../config/defaultSettings';
-import { UserModelState } from './user';
-import { StateType } from './login';
-
-export { GlobalModelState, SettingModelState, UserModelState };
-
-export interface Loading {
-  global: boolean;
-  effects: { [key: string]: boolean | undefined };
-  models: {
-    global?: boolean;
-    menu?: boolean;
-    setting?: boolean;
-    user?: boolean;
-    login?: boolean;
-  };
-}
-
-export interface ConnectState {
-  global: GlobalModelState;
-  loading: Loading;
-  settings: SettingModelState;
-  user: UserModelState;
-  login: StateType;
-}
-
-export interface Route extends MenuDataItem {
-  routes?: Route[];
-}
diff --git a/src/models/global.ts b/src/models/global.ts
deleted file mode 100644
index a9432f6..0000000
--- a/src/models/global.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import { Subscription, Effect, Reducer } from 'umi';
-
-import { queryNotices } from '@/services/user';
-import { ConnectState } from './connect.d';
-
-export interface NoticeItem extends API.NoticeIconData {
-  id: string;
-  type: string;
-  status: string;
-}
-
-export interface GlobalModelState {
-  collapsed: boolean;
-  notices: NoticeItem[];
-}
-
-export interface GlobalModelType {
-  namespace: 'global';
-  state: GlobalModelState;
-  effects: {
-    fetchNotices: Effect;
-    clearNotices: Effect;
-    changeNoticeReadState: Effect;
-  };
-  reducers: {
-    changeLayoutCollapsed: Reducer<GlobalModelState>;
-    saveNotices: Reducer<GlobalModelState>;
-    saveClearedNotices: Reducer<GlobalModelState>;
-  };
-  subscriptions: { setup: Subscription };
-}
-
-const GlobalModel: GlobalModelType = {
-  namespace: 'global',
-
-  state: {
-    collapsed: false,
-    notices: [],
-  },
-
-  effects: {
-    *fetchNotices(_, { call, put, select }) {
-      const data = yield call(queryNotices);
-      yield put({
-        type: 'saveNotices',
-        payload: data,
-      });
-      const unreadCount: number = yield select(
-        (state: ConnectState) => state.global.notices.filter((item) => !item.read).length,
-      );
-      yield put({
-        type: 'user/changeNotifyCount',
-        payload: {
-          totalCount: data.length,
-          unreadCount,
-        },
-      });
-    },
-    *clearNotices({ payload }, { put, select }) {
-      yield put({
-        type: 'saveClearedNotices',
-        payload,
-      });
-      const count: number = yield select((state: ConnectState) => state.global.notices.length);
-      const unreadCount: number = yield select(
-        (state: ConnectState) => state.global.notices.filter((item) => !item.read).length,
-      );
-      yield put({
-        type: 'user/changeNotifyCount',
-        payload: {
-          totalCount: count,
-          unreadCount,
-        },
-      });
-    },
-    *changeNoticeReadState({ payload }, { put, select }) {
-      const notices: NoticeItem[] = yield select((state: ConnectState) =>
-        state.global.notices.map((item) => {
-          const notice = { ...item };
-          if (notice.id === payload) {
-            notice.read = true;
-          }
-          return notice;
-        }),
-      );
-
-      yield put({
-        type: 'saveNotices',
-        payload: notices,
-      });
-
-      yield put({
-        type: 'user/changeNotifyCount',
-        payload: {
-          totalCount: notices.length,
-          unreadCount: notices.filter((item) => !item.read).length,
-        },
-      });
-    },
-  },
-
-  reducers: {
-    changeLayoutCollapsed(state = { notices: [], collapsed: true }, { payload }): GlobalModelState {
-      return {
-        ...state,
-        collapsed: payload,
-      };
-    },
-    saveNotices(state, { payload }): GlobalModelState {
-      return {
-        collapsed: false,
-        ...state,
-        notices: payload,
-      };
-    },
-    saveClearedNotices(state = { notices: [], collapsed: true }, { payload }): GlobalModelState {
-      return {
-        collapsed: false,
-        ...state,
-        notices: state.notices.filter((item): boolean => item.type !== payload),
-      };
-    },
-  },
-
-  subscriptions: {
-    setup({ history }): void {
-      // Subscribe history(url) change, trigger `load` action if pathname is `/`
-      history.listen(({ pathname, search }): void => {
-        if (typeof window.ga !== 'undefined') {
-          window.ga('send', 'pageview', pathname + search);
-        }
-      });
-    },
-  },
-};
-
-export default GlobalModel;
diff --git a/src/models/login.ts b/src/models/login.ts
deleted file mode 100644
index 446f5ac..0000000
--- a/src/models/login.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { stringify } from 'querystring';
-import { history, Effect, Reducer } from 'umi';
-
-import { fakeAccountLogin } from '@/services/login';
-import { setAuthority } from '@/utils/authority';
-import { getPageQuery } from '@/utils/utils';
-
-export interface StateType {
-  status?: 'ok' | 'error';
-  type?: string;
-  currentAuthority?: 'user' | 'guest' | 'admin';
-}
-
-export interface LoginModelType {
-  namespace: string;
-  state: StateType;
-  effects: {
-    login: Effect;
-    logout: Effect;
-  };
-  reducers: {
-    changeLoginStatus: Reducer<StateType>;
-  };
-}
-
-const Model: LoginModelType = {
-  namespace: 'login',
-
-  state: {
-    status: undefined,
-  },
-
-  effects: {
-    *login({ payload }, { call, put }) {
-      const response = yield call(fakeAccountLogin, payload);
-      yield put({
-        type: 'changeLoginStatus',
-        payload: response,
-      });
-      // Login successfully
-      if (response.status === 'ok') {
-        const urlParams = new URL(window.location.href);
-        const params = getPageQuery();
-        let { redirect } = params as { redirect: string };
-        if (redirect) {
-          const redirectUrlParams = new URL(redirect);
-          if (redirectUrlParams.origin === urlParams.origin) {
-            redirect = redirect.substr(urlParams.origin.length);
-            if (redirect.match(/^\/.*#/)) {
-              redirect = redirect.substr(redirect.indexOf('#') + 1);
-            }
-          } else {
-            window.location.href = '/';
-            return;
-          }
-        }
-        history.replace(redirect || '/');
-      }
-    },
-
-    logout() {
-      const { redirect } = getPageQuery();
-      // Note: There may be security issues, please note
-      if (window.location.pathname !== '/user/login' && !redirect) {
-        history.replace({
-          pathname: '/user/login',
-          search: stringify({
-            redirect: window.location.href,
-          }),
-        });
-      }
-    },
-  },
-
-  reducers: {
-    changeLoginStatus(state, { payload }) {
-      setAuthority(payload.currentAuthority);
-      return {
-        ...state,
-        status: payload.status,
-        type: payload.type,
-      };
-    },
-  },
-};
-
-export default Model;
diff --git a/src/models/setting.ts b/src/models/setting.ts
deleted file mode 100644
index 59fa70a..0000000
--- a/src/models/setting.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Reducer } from 'umi';
-import defaultSettings, { DefaultSettings } from '../../config/defaultSettings';
-
-export interface SettingModelType {
-  namespace: 'settings';
-  state: DefaultSettings;
-  reducers: {
-    changeSetting: Reducer<DefaultSettings>;
-  };
-}
-
-const updateColorWeak: (colorWeak: boolean) => void = colorWeak => {
-  const root = document.getElementById('root');
-  if (root) {
-    root.className = colorWeak ? 'colorWeak' : '';
-  }
-};
-
-const SettingModel: SettingModelType = {
-  namespace: 'settings',
-  state: defaultSettings,
-  reducers: {
-    changeSetting(state = defaultSettings, { payload }) {
-      const { colorWeak, contentWidth } = payload;
-
-      if (state.contentWidth !== contentWidth && window.dispatchEvent) {
-        window.dispatchEvent(new Event('resize'));
-      }
-      updateColorWeak(!!colorWeak);
-      return {
-        ...state,
-        ...payload,
-      };
-    },
-  },
-};
-export default SettingModel;
diff --git a/src/models/user.ts b/src/models/user.ts
deleted file mode 100644
index ff5160d..0000000
--- a/src/models/user.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Effect, Reducer } from 'umi';
-
-import { queryCurrent, query as queryUsers } from '@/services/user';
-
-export interface CurrentUser {
-  avatar?: string;
-  name?: string;
-  title?: string;
-  group?: string;
-  signature?: string;
-  tags?: {
-    key: string;
-    label: string;
-  }[];
-  userid?: string;
-  unreadCount?: number;
-}
-
-export interface UserModelState {
-  currentUser?: CurrentUser;
-}
-
-export interface UserModelType {
-  namespace: 'user';
-  state: UserModelState;
-  effects: {
-    fetch: Effect;
-    fetchCurrent: Effect;
-  };
-  reducers: {
-    saveCurrentUser: Reducer<UserModelState>;
-    changeNotifyCount: Reducer<UserModelState>;
-  };
-}
-
-const UserModel: UserModelType = {
-  namespace: 'user',
-
-  state: {
-    currentUser: {},
-  },
-
-  effects: {
-    *fetch(_, { call, put }) {
-      const response = yield call(queryUsers);
-      yield put({
-        type: 'save',
-        payload: response,
-      });
-    },
-    *fetchCurrent(_, { call, put }) {
-      const response = yield call(queryCurrent);
-      yield put({
-        type: 'saveCurrentUser',
-        payload: response,
-      });
-    },
-  },
-
-  reducers: {
-    saveCurrentUser(state, action) {
-      return {
-        ...state,
-        currentUser: action.payload || {},
-      };
-    },
-    changeNotifyCount(
-      state = {
-        currentUser: {},
-      },
-      action,
-    ) {
-      return {
-        ...state,
-        currentUser: {
-          ...state.currentUser,
-          notifyCount: action.payload.totalCount,
-          unreadCount: action.payload.unreadCount,
-        },
-      };
-    },
-  },
-};
-
-export default UserModel;
diff --git a/src/pages/Authorized.tsx b/src/pages/Authorized.tsx
deleted file mode 100644
index 468543d..0000000
--- a/src/pages/Authorized.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import { Redirect, connect, ConnectProps } from 'umi';
-import Authorized from '@/utils/Authorized';
-import { getRouteAuthority } from '@/utils/utils';
-import { ConnectState, UserModelState } from '@/models/connect';
-
-interface AuthComponentProps extends ConnectProps {
-  user: UserModelState;
-}
-
-const AuthComponent: React.FC<AuthComponentProps> = ({
-  children,
-  route = {
-    routes: [],
-  },
-  location = {
-    pathname: '',
-  },
-  user,
-}) => {
-  const { currentUser } = user;
-  const { routes = [] } = route;
-  const isLogin = currentUser && currentUser.name;
-  return (
-    <Authorized
-      authority={getRouteAuthority(location.pathname, routes) || ''}
-      noMatch={isLogin ? <Redirect to="/exception/403" /> : <Redirect to="/user/login" />}
-    >
-      {children}
-    </Authorized>
-  );
-};
-
-export default connect(({ user }: ConnectState) => ({
-  user,
-}))(AuthComponent);
diff --git a/src/pages/document.ejs b/src/pages/document.ejs
index 8c4db94..387a509 100644
--- a/src/pages/document.ejs
+++ b/src/pages/document.ejs
@@ -181,7 +181,7 @@
         </div>
         <div style="display: flex;justify-content: center;align-items: center;">
           <img
-            src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
+            src="/favicon.png"
             width="32"
             style="margin-right: 8px;"
           />
diff --git a/src/pages/ssl/components/Step3/index.tsx b/src/pages/ssl/components/Step3/index.tsx
index 10e5ab4..e1a921c 100644
--- a/src/pages/ssl/components/Step3/index.tsx
+++ b/src/pages/ssl/components/Step3/index.tsx
@@ -8,8 +8,7 @@ const Step3: React.FC<StepProps> = ({ data, onStepChange }) => {
   const [form] = Form.useForm();
   const submit = () => {
     createSSL({
-      // sni: data.sni!.split(';'),
-      sni: 'www.baidu.com',
+      sni: data.sni!.split(';'),
       cert: data.cert!,
       key: data.key!,
     }).then(() => {
diff --git a/src/pages/user/login/components/Login/LoginItem.tsx b/src/pages/user/login/components/Login/LoginItem.tsx
index bd027b0..9473240 100644
--- a/src/pages/user/login/components/Login/LoginItem.tsx
+++ b/src/pages/user/login/components/Login/LoginItem.tsx
@@ -1,12 +1,9 @@
-import { Button, Col, Input, Row, Form, message } from 'antd';
-import React, { useState, useCallback, useEffect } from 'react';
-import omit from 'omit.js';
+import { Input, Form } from 'antd';
+import React from 'react';
 import { FormItemProps } from 'antd/es/form/FormItem';
-import { getFakeCaptcha } from '@/services/login';
 
 import ItemMap from './map';
 import LoginContext, { LoginContextProps } from './LoginContext';
-import styles from './index.less';
 
 export type WrappedLoginItemProps = LoginItemProps;
 export type LoginItemKeyType = keyof typeof ItemMap;
@@ -57,9 +54,7 @@ const getFormItemOptions = ({
   return options;
 };
 
-const LoginItem: React.FC<LoginItemProps> = props => {
-  const [count, setCount] = useState<number>(props.countDown || 0);
-  const [timing, setTiming] = useState(false);
+const LoginItem: React.FC<LoginItemProps> = (props) => {
   // 这么写是为了防止restProps中 带入 onChange, defaultValue, rules props tabUtil
   const {
     onChange,
@@ -75,33 +70,6 @@ const LoginItem: React.FC<LoginItemProps> = props => {
     ...restProps
   } = props;
 
-  const onGetCaptcha = useCallback(async (mobile: string) => {
-    const result = await getFakeCaptcha(mobile);
-    if (result === false) {
-      return;
-    }
-    message.success('获取验证码成功!验证码为:1234');
-    setTiming(true);
-  }, []);
-
-  useEffect(() => {
-    let interval: number = 0;
-    const { countDown } = props;
-    if (timing) {
-      interval = window.setInterval(() => {
-        setCount(preSecond => {
-          if (preSecond <= 1) {
-            setTiming(false);
-            clearInterval(interval);
-            // 重置秒数
-            return countDown || 60;
-          }
-          return preSecond - 1;
-        });
-      }, 1000);
-    }
-    return () => clearInterval(interval);
-  }, [timing]);
   if (!name) {
     return null;
   }
@@ -109,36 +77,6 @@ const LoginItem: React.FC<LoginItemProps> = props => {
   const options = getFormItemOptions(props);
   const otherProps = restProps || {};
 
-  if (type === 'Captcha') {
-    const inputProps = omit(otherProps, ['onGetCaptcha', 'countDown']);
-
-    return (
-      <FormItem shouldUpdate>
-        {({ getFieldValue }) => (
-          <Row gutter={8}>
-            <Col span={16}>
-              <FormItem name={name} {...options}>
-                <Input {...customProps} {...inputProps} />
-              </FormItem>
-            </Col>
-            <Col span={8}>
-              <Button
-                disabled={timing}
-                className={styles.getCaptcha}
-                size="large"
-                onClick={() => {
-                  const value = getFieldValue('mobile');
-                  onGetCaptcha(value);
-                }}
-              >
-                {timing ? `${count} 秒` : '获取验证码'}
-              </Button>
-            </Col>
-          </Row>
-        )}
-      </FormItem>
-    );
-  }
   return (
     <FormItem name={name} {...options}>
       <Input {...customProps} {...otherProps} />
@@ -148,11 +86,11 @@ const LoginItem: React.FC<LoginItemProps> = props => {
 
 const LoginItems: Partial<LoginItemType> = {};
 
-Object.keys(ItemMap).forEach(key => {
+Object.keys(ItemMap).forEach((key) => {
   const item = ItemMap[key];
   LoginItems[key] = (props: LoginItemProps) => (
     <LoginContext.Consumer>
-      {context => (
+      {(context) => (
         <LoginItem
           customProps={item.props}
           rules={item.rules}
diff --git a/src/pages/user/login/components/Login/LoginTab.tsx b/src/pages/user/login/components/Login/LoginTab.tsx
index 935f2fd..adbed2a 100644
--- a/src/pages/user/login/components/Login/LoginTab.tsx
+++ b/src/pages/user/login/components/Login/LoginTab.tsx
@@ -18,7 +18,7 @@ interface LoginTabProps extends TabPaneProps {
   active?: boolean;
 }
 
-const LoginTab: React.FC<LoginTabProps> = props => {
+const LoginTab: React.FC<LoginTabProps> = (props) => {
   useEffect(() => {
     const uniqueId = generateId('login-tab-');
     const { tabUtil } = props;
@@ -32,9 +32,9 @@ const LoginTab: React.FC<LoginTabProps> = props => {
 
 const WrapContext: React.FC<TabPaneProps> & {
   typeName: string;
-} = props => (
+} = (props) => (
   <LoginContext.Consumer>
-    {value => <LoginTab tabUtil={value.tabUtil} {...props} />}
+    {(value) => <LoginTab tabUtil={value.tabUtil} {...props} />}
   </LoginContext.Consumer>
 );
 
diff --git a/src/pages/user/login/components/Login/index.tsx b/src/pages/user/login/components/Login/index.tsx
index 4148c25..8c550e3 100644
--- a/src/pages/user/login/components/Login/index.tsx
+++ b/src/pages/user/login/components/Login/index.tsx
@@ -26,14 +26,12 @@ interface LoginType extends React.FC<LoginProps> {
   Submit: typeof LoginSubmit;
   UserName: React.FunctionComponent<LoginItemProps>;
   Password: React.FunctionComponent<LoginItemProps>;
-  Mobile: React.FunctionComponent<LoginItemProps>;
-  Captcha: React.FunctionComponent<LoginItemProps>;
 }
 
-const Login: LoginType = props => {
+const Login: LoginType = (props) => {
   const { className } = props;
   const [tabs, setTabs] = useState<string[]>([]);
-  const [active, setActive] = useState();
+  const [active, setActive] = useState({});
   const [type, setType] = useMergeValue('', {
     value: props.activeKey,
     onChange: props.onTabChange,
@@ -57,14 +55,15 @@ const Login: LoginType = props => {
     <LoginContext.Provider
       value={{
         tabUtil: {
-          addTab: id => {
+          addTab: (id) => {
             setTabs([...tabs, id]);
           },
-          removeTab: id => {
-            setTabs(tabs.filter(currentId => currentId !== id));
+          removeTab: (id) => {
+            setTabs(tabs.filter((currentId) => currentId !== id));
           },
         },
-        updateActive: activeItem => {
+        updateActive: (activeItem) => {
+          if (!active) return;
           if (active[type]) {
             active[type].push(activeItem);
           } else {
@@ -77,7 +76,7 @@ const Login: LoginType = props => {
       <div className={classNames(className, styles.login)}>
         <Form
           form={props.from}
-          onFinish={values => {
+          onFinish={(values) => {
             if (props.onSubmit) {
               props.onSubmit(values as LoginParamsType);
             }
@@ -89,7 +88,7 @@ const Login: LoginType = props => {
                 animated={false}
                 className={styles.tabs}
                 activeKey={type}
-                onChange={activeKey => {
+                onChange={(activeKey) => {
                   setType(activeKey);
                 }}
               >
@@ -111,7 +110,5 @@ Login.Submit = LoginSubmit;
 
 Login.UserName = LoginItem.UserName;
 Login.Password = LoginItem.Password;
-Login.Mobile = LoginItem.Mobile;
-Login.Captcha = LoginItem.Captcha;
 
 export default Login;
diff --git a/src/pages/user/login/index.tsx b/src/pages/user/login/index.tsx
index 0690290..238dee8 100644
--- a/src/pages/user/login/index.tsx
+++ b/src/pages/user/login/index.tsx
@@ -1,19 +1,13 @@
-import { Alert, Checkbox } from 'antd';
+import { Alert, Checkbox, message } from 'antd';
 import React, { useState } from 'react';
-import { connect, Dispatch, useIntl, FormattedMessage } from 'umi';
-import { StateType } from '@/models/login';
-import { LoginParamsType } from '@/services/login';
-import { ConnectState } from '@/models/connect';
-import LoginForm from './components/Login';
-
+import { Link, SelectLang, history, useModel } from 'umi';
+import { getPageQuery } from '@/utils/utils';
+import logo from '@/assets/logo.svg';
+import { fakeAccountLogin } from '@/services/login';
+import LoginFrom from './components/Login';
 import styles from './style.less';
 
-const { Tab, UserName, Password, Submit } = LoginForm;
-interface LoginProps {
-  dispatch: Dispatch;
-  userLogin: StateType;
-  submitting?: boolean;
-}
+const { Tab, UserName, Password, Submit } = LoginFrom;
 
 const LoginMessage: React.FC<{
   content: string;
@@ -28,67 +22,121 @@ const LoginMessage: React.FC<{
   />
 );
 
-const Login: React.FC<LoginProps> = (props) => {
-  const { userLogin = {}, submitting } = props;
-  const { status, type: loginType } = userLogin;
+/**
+ * 此方法会跳转到 redirect 参数所在的位置
+ */
+const replaceGoto = () => {
+  const urlParams = new URL(window.location.href);
+  const params = getPageQuery();
+  let { redirect } = params as { redirect: string };
+  if (redirect) {
+    const redirectUrlParams = new URL(redirect);
+    if (redirectUrlParams.origin === urlParams.origin) {
+      redirect = redirect.substr(urlParams.origin.length);
+      if (redirect.match(/^\/.*#/)) {
+        redirect = redirect.substr(redirect.indexOf('#') + 1);
+      }
+    } else {
+      window.location.href = '/';
+      return;
+    }
+  }
+  history.replace(redirect || '/');
+};
+
+const Login: React.FC<{}> = () => {
+  const [userLoginState, setUserLoginState] = useState<API.LoginStateType>({});
+  const [submitting, setSubmitting] = useState(false);
+
+  const { refresh } = useModel('@@initialState');
   const [autoLogin, setAutoLogin] = useState(true);
-  const [type, setType] = useState('account');
-  const { formatMessage } = useIntl();
+  const [type, setType] = useState<string>('account');
 
-  const handleSubmit = (values: LoginParamsType) => {
-    const { dispatch } = props;
-    dispatch({
-      type: 'login/login',
-      payload: { ...values, type },
-    });
+  const handleSubmit = async () => {
+    setSubmitting(true);
+    try {
+      // 登录
+      const msg = await fakeAccountLogin();
+      if (msg.status === 'ok') {
+        message.success('登陆成功!');
+        replaceGoto();
+        setTimeout(() => {
+          refresh();
+        }, 0);
+        return;
+      }
+      // 如果失败去设置用户错误信息
+      setUserLoginState(msg);
+    } catch (error) {
+      message.error('登陆失败,请重试!');
+    }
+    setSubmitting(false);
   };
+
+  const { status, type: loginType } = userLoginState;
+
   return (
-    <div className={styles.main}>
-      <LoginForm activeKey={type} onTabChange={setType} onSubmit={handleSubmit}>
-        <Tab key="account" tab={formatMessage({ id: 'component.user.loginByPassword' })}>
-          {status === 'error' && loginType === 'account' && !submitting && (
-            <LoginMessage
-              content={formatMessage({ id: 'component.user.wrongUsernameOrPassword' })}
-            />
-          )}
+    <div className={styles.container}>
+      <div className={styles.lang}>
+        <SelectLang />
+      </div>
+      <div className={styles.content}>
+        <div className={styles.top}>
+          <div className={styles.header}>
+            <Link to="/">
+              <img alt="logo" className={styles.logo} src={logo} />
+              <span className={styles.title}>APISIX Dashboard</span>
+            </Link>
+          </div>
+          <div className={styles.desc}>Cloud-Native Microservices API Gateway</div>
+        </div>
+
+        <div className={styles.main}>
+          <LoginFrom activeKey={type} onTabChange={setType} onSubmit={handleSubmit}>
+            <Tab key="account" tab="账户密码登录">
+              {status === 'error' && loginType === 'account' && !submitting && (
+                <LoginMessage content="账户或密码错误" />
+              )}
 
-          <UserName
-            name="userName"
-            placeholder={formatMessage({ id: 'component.user.username' })}
-            defaultValue="admin"
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'component.user.inputUsername' }),
-              },
-            ]}
-          />
-          <Password
-            name="password"
-            placeholder={formatMessage({ id: 'component.user.password' })}
-            defaultValue="123456"
-            rules={[
-              {
-                required: true,
-                message: formatMessage({ id: 'component.user.inputPassword' }),
-              },
-            ]}
-          />
-        </Tab>
-        <div>
-          <Checkbox checked={autoLogin} onChange={(e) => setAutoLogin(e.target.checked)}>
-            <FormattedMessage id="component.user.rememberMe" />
-          </Checkbox>
+              <UserName
+                name="userName"
+                placeholder="请输入用户名"
+                rules={[
+                  {
+                    required: true,
+                    message: '请输入用户名!',
+                  },
+                ]}
+              />
+              <Password
+                name="password"
+                placeholder="请输入密码"
+                rules={[
+                  {
+                    required: true,
+                    message: '请输入密码!',
+                  },
+                ]}
+              />
+            </Tab>
+            <div>
+              <Checkbox checked={autoLogin} onChange={(e) => setAutoLogin(e.target.checked)}>
+                自动登录
+              </Checkbox>
+              <a
+                style={{
+                  float: 'right',
+                }}
+              >
+                忘记密码
+              </a>
+            </div>
+            <Submit loading={submitting}>登录</Submit>
+          </LoginFrom>
         </div>
-        <Submit loading={submitting}>
-          <FormattedMessage id="component.user.login" />
-        </Submit>
-      </LoginForm>
+      </div>
     </div>
   );
 };
 
-export default connect(({ login, loading }: ConnectState) => ({
-  userLogin: login,
-  submitting: loading.effects['login/login'],
-}))(Login);
+export default Login;
diff --git a/src/pages/user/login/style.less b/src/pages/user/login/style.less
index d9bbbf3..c195315 100644
--- a/src/pages/user/login/style.less
+++ b/src/pages/user/login/style.less
@@ -1,5 +1,75 @@
 @import '~antd/es/style/themes/default.less';
 
+.container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: auto;
+  background: @layout-body-background;
+}
+
+.lang {
+  width: 100%;
+  height: 40px;
+  line-height: 44px;
+  text-align: right;
+  :global(.ant-dropdown-trigger) {
+    margin-right: 24px;
+  }
+}
+
+.content {
+  flex: 1;
+  padding: 32px 0;
+}
+
+@media (min-width: @screen-md-min) {
+  .container {
+    background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
+    background-repeat: no-repeat;
+    background-position: center 110px;
+    background-size: 100%;
+  }
+
+  .content {
+    padding: 32px 0 24px;
+  }
+}
+
+.top {
+  text-align: center;
+}
+
+.header {
+  height: 44px;
+  line-height: 44px;
+  a {
+    text-decoration: none;
+  }
+}
+
+.logo {
+  height: 44px;
+  margin-right: 16px;
+  vertical-align: top;
+}
+
+.title {
+  position: relative;
+  top: 2px;
+  color: @heading-color;
+  font-weight: 600;
+  font-size: 33px;
+  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+}
+
+.desc {
+  margin-top: 12px;
+  margin-bottom: 40px;
+  color: @text-color-secondary;
+  font-size: @font-size-base;
+}
+
 .main {
   width: 368px;
   margin: 0 auto;
@@ -20,16 +90,6 @@
     }
   }
 
-  .other {
-    margin-top: 24px;
-    line-height: 22px;
-    text-align: left;
-
-    .register {
-      float: right;
-    }
-  }
-
   :global {
     .antd-pro-login-submit {
       width: 100%;
diff --git a/src/services/API.d.ts b/src/services/API.d.ts
index dccbbd9..d31fb9e 100644
--- a/src/services/API.d.ts
+++ b/src/services/API.d.ts
@@ -2,13 +2,6 @@ declare namespace API {
   export interface CurrentUser {
     avatar?: string;
     name?: string;
-    title?: string;
-    group?: string;
-    signature?: string;
-    tags?: {
-      key: string;
-      label: string;
-    }[];
     userid?: string;
     access?: 'user' | 'guest' | 'admin';
     unreadCount?: number;
diff --git a/src/services/login.ts b/src/services/login.ts
index a4e081e..94c781b 100644
--- a/src/services/login.ts
+++ b/src/services/login.ts
@@ -1,5 +1,3 @@
-import { request } from 'umi';
-
 export interface LoginParamsType {
   userName: string;
   password: string;
@@ -7,15 +5,16 @@ export interface LoginParamsType {
   captcha: string;
 }
 
-export async function fakeAccountLogin() {
+export async function fakeAccountLogin(): Promise<API.LoginStateType> {
   // NOTE: APISIX doesn‘t support user login currently, we return fake data directly.
-  return {
+  return Promise.resolve({
+    name: 'APISIX User',
+    avatar:
+      'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxzdmcgdmlld0JveD0iMCAwIDUwMCA1MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogIDxkZWZzPg0KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iaWQwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjI1MTE5LjgiIHkxPSIxMTA1Mi41IiB4Mj0iMjE3MjUuNyIgeTI9IjIxNTUxLjciIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4wMjgwOTUsIDAsIDAsIDAuMDI4MDk1LCAtNDkyLjg2NzA5NiwgLTE0NC43Njk4MjEpIj4NCiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29 [...]
+    userid: '00000001',
+    notifyCount: 12,
+    unreadCount: 11,
+    access: 'admin',
     status: 'ok',
-    type: 'account',
-    currentAuthority: 'admin',
-  };
-}
-
-export async function getFakeCaptcha(mobile: string) {
-  return request(`/api/login/captcha?mobile=${mobile}`);
+  });
 }
diff --git a/src/services/user.ts b/src/services/user.ts
index 870c4c7..3189a54 100644
--- a/src/services/user.ts
+++ b/src/services/user.ts
@@ -11,6 +11,9 @@ export async function queryCurrent() {
     avatar:
       'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxzdmcgdmlld0JveD0iMCAwIDUwMCA1MDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogIDxkZWZzPg0KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iaWQwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjI1MTE5LjgiIHkxPSIxMTA1Mi41IiB4Mj0iMjE3MjUuNyIgeTI9IjIxNTUxLjciIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4wMjgwOTUsIDAsIDAsIDAuMDI4MDk1LCAtNDkyLjg2NzA5NiwgLTE0NC43Njk4MjEpIj4NCiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29 [...]
     userid: '00000001',
+    notifyCount: 12,
+    unreadCount: 11,
+    access: 'admin',
   });
 }
 
diff --git a/src/utils/Authorized.ts b/src/utils/Authorized.ts
deleted file mode 100644
index e3b813a..0000000
--- a/src/utils/Authorized.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import RenderAuthorize from '../components/Authorized';
-import { getAuthority } from './authority';
-/* eslint-disable eslint-comments/disable-enable-pair */
-/* eslint-disable import/no-mutable-exports */
-let Authorized = RenderAuthorize(getAuthority());
-
-// Reload the rights component
-const reloadAuthorized = (): void => {
-  Authorized = RenderAuthorize(getAuthority());
-};
-
-/**
- * hard code
- * block need it。
- */
-window.reloadAuthorized = reloadAuthorized;
-
-export { reloadAuthorized };
-export default Authorized;
diff --git a/src/utils/authority.ts b/src/utils/authority.ts
deleted file mode 100644
index d99659d..0000000
--- a/src/utils/authority.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { reloadAuthorized } from './Authorized';
-
-// use localStorage to store the authority info, which might be sent from server in actual project.
-export function getAuthority(str?: string): string | string[] {
-  const authorityString =
-    typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str;
-  // authorityString could be admin, "admin", ["admin"]
-  let authority;
-  try {
-    if (authorityString) {
-      authority = JSON.parse(authorityString);
-    }
-  } catch (e) {
-    authority = authorityString;
-  }
-  if (typeof authority === 'string') {
-    return [authority];
-  }
-  // preview.pro.ant.design only do not use in your production.
-  // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
-  if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
-    return ['admin'];
-  }
-  return authority;
-}
-
-export function setAuthority(authority: string | string[]): void {
-  const proAuthority = typeof authority === 'string' ? [authority] : authority;
-  localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
-  // auto reload
-  reloadAuthorized();
-}