You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by mi...@apache.org on 2023/05/26 01:09:05 UTC

[incubator-devlake] branch main updated: feat(config-ui): use new create bp and project design (#5284)

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

mintsweet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 6813da56e feat(config-ui): use new create bp and project design (#5284)
6813da56e is described below

commit 6813da56e116438f49984de863989cb0da67f31f
Author: 青湛 <0x...@gmail.com>
AuthorDate: Fri May 26 09:09:00 2023 +0800

    feat(config-ui): use new create bp and project design (#5284)
    
    * feat(config-ui): export history in utils
    
    * feat(config-ui): export login page in pages
    
    * refactor(config-ui): don't load custom hook when table is loading
    
    * feat(config-ui): use new create bp logic to replace old
    
    * feat(config-ui): use new create project logic to replace old
    
    * refactor(config-ui): remove the bp create page
---
 config-ui/src/App.tsx                              |  16 +-
 .../table/{table.tsx => components/content.tsx}    |  32 +-
 config-ui/src/components/table/components/index.ts |   1 +
 config-ui/src/components/table/table.tsx           |  78 +----
 config-ui/src/pages/blueprint/create/api.ts        |  30 --
 .../pages/blueprint/create/components/step-1.tsx   | 166 -----------
 .../pages/blueprint/create/components/step-2.tsx   |  40 ---
 .../pages/blueprint/create/components/step-3.tsx   |  33 ---
 .../pages/blueprint/create/components/step-4.tsx   |  72 -----
 .../pages/blueprint/create/components/styled.ts    | 101 -------
 config-ui/src/pages/blueprint/create/context.tsx   | 183 ------------
 config-ui/src/pages/blueprint/create/index.tsx     |  76 -----
 config-ui/src/pages/blueprint/create/types.ts      |  47 ---
 config-ui/src/pages/blueprint/home/api.ts          |  16 +-
 config-ui/src/pages/blueprint/home/index.tsx       | 323 ++++++++++++++-------
 config-ui/src/pages/blueprint/home/styled.ts       |  20 ++
 config-ui/src/pages/blueprint/home/use-home.ts     |  79 -----
 config-ui/src/pages/blueprint/index.ts             |   1 -
 config-ui/src/pages/index.ts                       |   9 +-
 .../create/components => login}/index.ts           |   5 +-
 config-ui/src/pages/project/home/api.ts            |  16 +-
 config-ui/src/pages/project/home/index.tsx         | 196 ++++++++-----
 config-ui/src/pages/project/home/styled.ts         |  33 +--
 config-ui/src/pages/project/home/use-project.ts    |  92 ------
 .../components/index.ts => project/utils.ts}       |   7 +-
 config-ui/src/utils/index.ts                       |   3 +-
 26 files changed, 430 insertions(+), 1245 deletions(-)

diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index 51b14cb60..3998e95c1 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -17,11 +17,9 @@
  */
 
 import { Switch, Route, Redirect, Router } from 'react-router-dom';
-import { LoginPage } from './pages/login/login';
-import { history } from './utils/history';
 import { ErrorLayout, BaseLayout } from '@/layouts';
-import { FromEnum } from '@/pages';
 import {
+  LoginPage,
   OfflinePage,
   DBMigratePage,
   ConnectionHomePage,
@@ -29,11 +27,11 @@ import {
   ProjectHomePage,
   ProjectDetailPage,
   BlueprintHomePage,
-  BlueprintCreatePage,
   BlueprintDetailPage,
   BlueprintConnectioAddPage,
   BlueprintConnectionDetailPage,
 } from '@/pages';
+import { history } from '@/utils';
 
 function App() {
   return (
@@ -77,17 +75,7 @@ function App() {
                   component={() => <BlueprintConnectioAddPage />}
                 />
                 <Route exact path="/projects/:pname/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
-                <Route
-                  exact
-                  path="/projects/:pname/create-blueprint"
-                  component={() => <BlueprintCreatePage from={FromEnum.project} />}
-                />
                 <Route exact path="/blueprints" component={() => <BlueprintHomePage />} />
-                <Route
-                  exact
-                  path="/blueprints/create"
-                  component={() => <BlueprintCreatePage from={FromEnum.blueprint} />}
-                />
                 <Route exact path="/blueprints/:id" component={() => <BlueprintDetailPage />} />
                 <Route exact path="/blueprints/:bid/connection-add" component={() => <BlueprintConnectioAddPage />} />
                 <Route exact path="/blueprints/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
diff --git a/config-ui/src/components/table/table.tsx b/config-ui/src/components/table/components/content.tsx
similarity index 82%
copy from config-ui/src/components/table/table.tsx
copy to config-ui/src/components/table/components/content.tsx
index bace2e740..9b288827d 100644
--- a/config-ui/src/components/table/table.tsx
+++ b/config-ui/src/components/table/components/content.tsx
@@ -16,49 +16,31 @@
  *
  */
 
-import React from 'react';
 import { Checkbox, Radio } from '@blueprintjs/core';
 
 import { TextTooltip } from '@/components';
 
-import { ColumnType } from './types';
-import { TableLoading, TableNoData } from './components';
-import { useRowSelection, UseRowSelectionProps } from './hooks';
-import * as S from './styled';
+import { ColumnType } from '../types';
+import { useRowSelection, UseRowSelectionProps } from '../hooks';
+import * as S from '../styled';
 
-interface Props<T> extends UseRowSelectionProps<T> {
-  loading?: boolean;
+export interface TableContentProps<T> extends UseRowSelectionProps<T> {
   columns: ColumnType<T>;
   dataSource: T[];
-  noData?: {
-    text?: React.ReactNode;
-    btnText?: string;
-    onCreate?: () => void;
-  };
   noShadow?: boolean;
 }
 
-export const Table = <T extends Record<string, any>>({
-  loading,
+export const TableContent = <T extends Record<string, any>>({
   columns,
   dataSource,
-  noData = {},
+  noShadow,
   rowSelection,
-  noShadow = false,
-}: Props<T>) => {
+}: TableContentProps<T>) => {
   const { canSelection, selectionType, getCheckedAll, onCheckedAll, getChecked, onChecked } = useRowSelection<T>({
     dataSource,
     rowSelection,
   });
 
-  if (loading) {
-    return <TableLoading />;
-  }
-
-  if (!dataSource.length) {
-    return <TableNoData {...noData} />;
-  }
-
   return (
     <S.Table
       style={{
diff --git a/config-ui/src/components/table/components/index.ts b/config-ui/src/components/table/components/index.ts
index ef68e2564..df5950d7f 100644
--- a/config-ui/src/components/table/components/index.ts
+++ b/config-ui/src/components/table/components/index.ts
@@ -18,3 +18,4 @@
 
 export * from './loading';
 export * from './no-data';
+export * from './content';
diff --git a/config-ui/src/components/table/table.tsx b/config-ui/src/components/table/table.tsx
index bace2e740..f4188d5a3 100644
--- a/config-ui/src/components/table/table.tsx
+++ b/config-ui/src/components/table/table.tsx
@@ -17,40 +17,20 @@
  */
 
 import React from 'react';
-import { Checkbox, Radio } from '@blueprintjs/core';
 
-import { TextTooltip } from '@/components';
+import type { TableContentProps } from './components';
+import { TableLoading, TableNoData, TableContent } from './components';
 
-import { ColumnType } from './types';
-import { TableLoading, TableNoData } from './components';
-import { useRowSelection, UseRowSelectionProps } from './hooks';
-import * as S from './styled';
-
-interface Props<T> extends UseRowSelectionProps<T> {
+interface Props<T> extends TableContentProps<T> {
   loading?: boolean;
-  columns: ColumnType<T>;
-  dataSource: T[];
   noData?: {
     text?: React.ReactNode;
     btnText?: string;
     onCreate?: () => void;
   };
-  noShadow?: boolean;
 }
 
-export const Table = <T extends Record<string, any>>({
-  loading,
-  columns,
-  dataSource,
-  noData = {},
-  rowSelection,
-  noShadow = false,
-}: Props<T>) => {
-  const { canSelection, selectionType, getCheckedAll, onCheckedAll, getChecked, onChecked } = useRowSelection<T>({
-    dataSource,
-    rowSelection,
-  });
-
+export const Table = <T extends Record<string, any>>({ loading, dataSource, noData = {}, ...props }: Props<T>) => {
   if (loading) {
     return <TableLoading />;
   }
@@ -59,53 +39,5 @@ export const Table = <T extends Record<string, any>>({
     return <TableNoData {...noData} />;
   }
 
-  return (
-    <S.Table
-      style={{
-        boxShadow: noShadow ? 'none' : '0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07)',
-      }}
-    >
-      <S.THeader>
-        <S.TR>
-          {canSelection && (
-            <S.TH style={{ width: 40, textAlign: 'center' }}>
-              {selectionType === 'checkbox' && <Checkbox checked={getCheckedAll()} onChange={() => onCheckedAll()} />}
-            </S.TH>
-          )}
-          {columns.map(({ key, width, align = 'left', title }) => (
-            <S.TH key={key} style={{ width, textAlign: align }}>
-              {title}
-            </S.TH>
-          ))}
-        </S.TR>
-      </S.THeader>
-      <S.TBody>
-        {dataSource.map((data, i) => (
-          <S.TR key={i}>
-            {canSelection && (
-              <S.TD style={{ width: 40, textAlign: 'center' }}>
-                {selectionType === 'checkbox' && (
-                  <Checkbox checked={getChecked(data)} onChange={() => onChecked(data)} />
-                )}
-                {selectionType === 'radio' && <Radio checked={getChecked(data)} onChange={() => onChecked(data)} />}
-              </S.TD>
-            )}
-            {columns.map(({ key, width, align = 'left', ellipsis, dataIndex, render }) => {
-              const value = Array.isArray(dataIndex)
-                ? dataIndex.reduce((acc, cur) => {
-                    acc[cur] = data[cur];
-                    return acc;
-                  }, {} as any)
-                : data[dataIndex];
-              return (
-                <S.TD key={key} style={{ width, textAlign: align }}>
-                  {render ? render(value, data) : ellipsis ? <TextTooltip content={value}>{value}</TextTooltip> : value}
-                </S.TD>
-              );
-            })}
-          </S.TR>
-        ))}
-      </S.TBody>
-    </S.Table>
-  );
+  return <TableContent dataSource={dataSource} {...props} />;
 };
diff --git a/config-ui/src/pages/blueprint/create/api.ts b/config-ui/src/pages/blueprint/create/api.ts
deleted file mode 100644
index c82ccaef8..000000000
--- a/config-ui/src/pages/blueprint/create/api.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { request } from '@/utils';
-
-export const createBlueprint = (payload: any) =>
-  request('/blueprints', {
-    method: 'post',
-    data: payload,
-  });
-
-export const runBlueprint = (id: ID) =>
-  request(`/blueprints/${id}/trigger`, {
-    method: 'post',
-  });
diff --git a/config-ui/src/pages/blueprint/create/components/step-1.tsx b/config-ui/src/pages/blueprint/create/components/step-1.tsx
deleted file mode 100644
index a546dd44b..000000000
--- a/config-ui/src/pages/blueprint/create/components/step-1.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useMemo } from 'react';
-import { Link } from 'react-router-dom';
-import { InputGroup, Icon, Button, Intent } from '@blueprintjs/core';
-
-import { Card, Divider, MultiSelector, Loading } from '@/components';
-import { useConnections } from '@/hooks';
-import { getPluginConfig } from '@/plugins';
-import { ConnectionStatusEnum } from '@/store';
-
-import { ModeEnum, FromEnum } from '../../types';
-import { AdvancedEditor } from '../../components';
-import { validRawPlan } from '../../utils';
-
-import { useCreate } from '../context';
-
-import * as S from './styled';
-
-interface Props {
-  from: FromEnum;
-}
-
-export const Step1 = ({ from }: Props) => {
-  const { connections, onTest } = useConnections({ filterBeta: true, filterPlugin: ['webhook'] });
-  const { mode, name, rawPlan, onChangeMode, onChangeName, onChangeConnections, onChangeRawPlan, onNext, ...props } =
-    useCreate();
-
-  const fromProject = useMemo(() => from === FromEnum.project, [from]);
-  const uniqueList = useMemo(() => props.connections.map((sc) => sc.unique), [props.connections]);
-
-  const error = useMemo(() => {
-    switch (true) {
-      case !name:
-        return true;
-      case name.length < 3:
-        return true;
-      case mode === ModeEnum.advanced && validRawPlan(rawPlan):
-        return true;
-      case mode === ModeEnum.normal && !uniqueList.length:
-        return true;
-      case mode === ModeEnum.normal &&
-        !connections
-          .filter((cs) => uniqueList.includes(cs.unique))
-          .every((cs) => cs.status === ConnectionStatusEnum.ONLINE):
-        return true;
-      default:
-        return false;
-    }
-  }, [mode, name, connections, props.connections, rawPlan]);
-
-  return (
-    <S.Wrapper>
-      <Card className="card">
-        <h2>Blueprint Name</h2>
-        <Divider />
-        <p>Give your Blueprint a unique name to help you identify it in the future.</p>
-        <InputGroup placeholder="Enter Blueprint Name" value={name} onChange={(e) => onChangeName(e.target.value)} />
-      </Card>
-
-      {mode === ModeEnum.normal && (
-        <>
-          <Card className="card">
-            <h2>Add Data Connections</h2>
-            <Divider />
-            <p>
-              If you have not created any connections yet, please <Link to="/connections">create connections</Link>{' '}
-              first.
-            </p>
-            <MultiSelector
-              placeholder="Select Connections..."
-              items={connections}
-              getKey={(it) => it.unique}
-              getName={(it) => it.name}
-              getIcon={(it) => it.icon}
-              selectedItems={connections.filter((cs) => uniqueList.includes(cs.unique))}
-              onChangeItems={(selectedItems) => {
-                const lastItem = selectedItems[selectedItems.length - 1];
-                if (lastItem) {
-                  onTest(lastItem.unique);
-                }
-                onChangeConnections(
-                  selectedItems.map((sc) => {
-                    const config = getPluginConfig(sc.plugin);
-                    return {
-                      unique: sc.unique,
-                      plugin: sc.plugin,
-                      connectionId: sc.id,
-                      name: sc.name,
-                      icon: sc.icon,
-                      scope: [],
-                      origin: [],
-                      transformationType: config.transformationType,
-                    };
-                  }),
-                );
-              }}
-            />
-            <S.ConnectionList>
-              {connections
-                .filter((cs) => uniqueList.includes(cs.unique))
-                .map((cs) => (
-                  <li key={cs.unique}>
-                    <span className="name">{cs.name}</span>
-                    <span className={`status ${cs.status}`}>
-                      {cs.status === ConnectionStatusEnum.TESTING && <Loading size={14} style={{ marginRight: 4 }} />}
-                      {cs.status === ConnectionStatusEnum.OFFLINE && (
-                        <Icon
-                          size={14}
-                          icon="repeat"
-                          style={{ marginRight: 4, cursor: 'pointer' }}
-                          onClick={() => onTest(cs.unique)}
-                        />
-                      )}
-                      {cs.status}
-                    </span>
-                  </li>
-                ))}
-            </S.ConnectionList>
-          </Card>
-          {!fromProject && (
-            <S.Tips>
-              <span>To customize how tasks are executed in the blueprint, please use </span>
-              <span onClick={() => onChangeMode(ModeEnum.advanced)}>Advanced Mode.</span>
-            </S.Tips>
-          )}
-        </>
-      )}
-
-      {mode === ModeEnum.advanced && !fromProject && (
-        <>
-          <Card className="card">
-            <h2>JSON Configuration</h2>
-            <Divider />
-            <AdvancedEditor value={rawPlan} onChange={onChangeRawPlan} />
-          </Card>
-          <S.Tips>
-            <span>To visually define blueprint tasks, please use </span>
-            <span onClick={() => onChangeMode(ModeEnum.normal)}>Normal Mode.</span>
-          </S.Tips>
-        </>
-      )}
-
-      <S.Btns>
-        <span></span>
-        <Button intent={Intent.PRIMARY} disabled={error} text="Next Step" onClick={onNext} />
-      </S.Btns>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/create/components/step-2.tsx b/config-ui/src/pages/blueprint/create/components/step-2.tsx
deleted file mode 100644
index e3dbff814..000000000
--- a/config-ui/src/pages/blueprint/create/components/step-2.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { DataScope } from '@/plugins';
-
-import { useCreate } from '../context';
-
-import * as S from './styled';
-
-export const Step2 = () => {
-  const { connections, onChangeConnections, onPrev, onNext } = useCreate();
-
-  return (
-    <S.Wrapper>
-      <DataScope
-        connections={connections}
-        cancelBtnProps={{ text: 'Previous Step' }}
-        submitBtnProps={{ text: 'Next Step' }}
-        onCancel={onPrev}
-        onSubmit={onChangeConnections}
-        onNext={onNext}
-      />
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/create/components/step-3.tsx b/config-ui/src/pages/blueprint/create/components/step-3.tsx
deleted file mode 100644
index be178feb2..000000000
--- a/config-ui/src/pages/blueprint/create/components/step-3.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { Transformation } from '@/plugins';
-
-import { useCreate } from '../context';
-
-import * as S from './styled';
-
-export const Step3 = () => {
-  const { connections, onChangeConnections, onPrev, onNext } = useCreate();
-
-  return (
-    <S.Wrapper>
-      <Transformation connections={connections} onCancel={onPrev} onSubmit={onChangeConnections} onNext={onNext} />
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/create/components/step-4.tsx b/config-ui/src/pages/blueprint/create/components/step-4.tsx
deleted file mode 100644
index 7108c9216..000000000
--- a/config-ui/src/pages/blueprint/create/components/step-4.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Card, Divider } from '@/components';
-
-import { ModeEnum } from '../../types';
-import { SyncPolicy } from '../../components';
-
-import { useCreate } from '../context';
-
-import * as S from './styled';
-
-export const Step4 = () => {
-  const {
-    mode,
-    isManual,
-    cronConfig,
-    skipOnFail,
-    timeAfter,
-    onChangeIsManual,
-    onChangeCronConfig,
-    onChangeSkipOnFail,
-    onChangeTimeAfter,
-    onPrev,
-    onSave,
-    onSaveAndRun,
-  } = useCreate();
-
-  return (
-    <S.Wrapper>
-      <Card>
-        <h2>Set Sync Policy</h2>
-        <Divider />
-        <SyncPolicy
-          isManual={isManual}
-          cronConfig={cronConfig}
-          skipOnFail={skipOnFail}
-          showTimeFilter={mode === ModeEnum.normal}
-          timeAfter={timeAfter}
-          onChangeIsManual={onChangeIsManual}
-          onChangeCronConfig={onChangeCronConfig}
-          onChangeSkipOnFail={onChangeSkipOnFail}
-          onChangeTimeAfter={onChangeTimeAfter}
-        />
-      </Card>
-      <S.Btns>
-        <Button intent={Intent.PRIMARY} outlined text="Previous Step" onClick={onPrev} />
-        <div>
-          <Button intent={Intent.PRIMARY} outlined text="Save Blueprint" onClick={onSave} />
-          <Button intent={Intent.PRIMARY} text="Save and Run Now" onClick={onSaveAndRun} />
-        </div>
-      </S.Btns>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/create/components/styled.ts b/config-ui/src/pages/blueprint/create/components/styled.ts
deleted file mode 100644
index 67066091d..000000000
--- a/config-ui/src/pages/blueprint/create/components/styled.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import styled from 'styled-components';
-import { Colors } from '@blueprintjs/core';
-
-export const Wrapper = styled.div`
-  margin-top: 36px;
-
-  .card + .card {
-    margin-top: 16px;
-  }
-`;
-
-export const ConnectionList = styled.ul`
-  padding: 12px;
-
-  li {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 4px 0;
-    border-bottom: 1px solid #f0f0f0;
-
-    .name {
-      font-weight: 600;
-    }
-
-    .status {
-      display: flex;
-      align-items: center;
-
-      &.online {
-        color: ${Colors.GREEN3};
-      }
-
-      &.offline {
-        color: ${Colors.RED3};
-      }
-    }
-  }
-`;
-
-export const Tips = styled.p`
-  margin: 24px 0 0;
-
-  span:last-child {
-    color: #7497f7;
-    cursor: pointer;
-  }
-`;
-
-export const ConnectionColumn = styled.div`
-  display: flex;
-  align-items: center;
-
-  img {
-    margin-right: 4px;
-    width: 20px;
-  }
-`;
-
-export const ScopeColumn = styled.ul``;
-
-export const ScopeItem = styled.li`
-  margin-bottom: 4px;
-
-  &:last-child {
-    margin-bottom: 0;
-  }
-
-  .bp4-button {
-    margin-left: 6px;
-  }
-`;
-
-export const Btns = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-top: 36px;
-
-  .bp4-button + .bp4-button {
-    margin-left: 8px;
-  }
-`;
diff --git a/config-ui/src/pages/blueprint/create/context.tsx b/config-ui/src/pages/blueprint/create/context.tsx
deleted file mode 100644
index f2af24ded..000000000
--- a/config-ui/src/pages/blueprint/create/context.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import React, { useState, useMemo, useContext } from 'react';
-import { useHistory } from 'react-router-dom';
-import dayjs from 'dayjs';
-
-import { operator, formatTime } from '@/utils';
-
-import { ModeEnum, FromEnum } from '../types';
-import { validRawPlan } from '../utils';
-
-import type { ContextType } from './types';
-import * as API from './api';
-
-export const Context = React.createContext<ContextType>({
-  step: 1,
-
-  name: 'MY BLUEPRINT',
-  mode: ModeEnum.normal,
-  connections: [],
-  rawPlan: JSON.stringify([[]], null, '  '),
-  cronConfig: '0 0 * * *',
-  isManual: false,
-  skipOnFail: false,
-  timeAfter: null,
-
-  onPrev: () => {},
-  onNext: () => {},
-  onSave: () => {},
-  onSaveAndRun: () => {},
-
-  onChangeMode: () => {},
-  onChangeName: () => {},
-  onChangeConnections: () => {},
-  onChangeRawPlan: () => {},
-  onChangeCronConfig: () => {},
-  onChangeIsManual: () => {},
-  onChangeSkipOnFail: () => {},
-  onChangeTimeAfter: () => {},
-});
-
-interface Props {
-  from: FromEnum;
-  projectName: string;
-  children: React.ReactNode;
-}
-
-export const ContextProvider = ({ from, projectName, children }: Props) => {
-  const [step, setStep] = useState(1);
-
-  const [name, setName] = useState(
-    from === FromEnum.project ? `${window.decodeURIComponent(projectName)}-BLUEPRINT` : 'MY BLUEPRINT',
-  );
-  const [mode, setMode] = useState<ModeEnum>(ModeEnum.normal);
-  const [connections, setConnections] = useState<MixConnection[]>([]);
-  const [rawPlan, setRawPlan] = useState(JSON.stringify([[]], null, '  '));
-  const [cronConfig, setCronConfig] = useState('0 0 * * *');
-  const [isManual, setIsManual] = useState(false);
-  const [skipOnFail, setSkipOnFail] = useState(true);
-  const [timeAfter, setTimeAfter] = useState<string | null>(
-    formatTime(dayjs().subtract(6, 'month').startOf('day').toDate(), 'YYYY-MM-DD[T]HH:mm:ssZ'),
-  );
-
-  const history = useHistory();
-
-  const payload = useMemo(() => {
-    const params: any = {
-      name,
-      projectName: projectName ? window.decodeURIComponent(projectName) : '',
-      mode,
-      enable: true,
-      cronConfig,
-      isManual,
-      skipOnFail,
-    };
-
-    if (mode === ModeEnum.normal) {
-      params.settings = {
-        version: '2.0.0',
-        timeAfter,
-        connections: connections.map((cs) => {
-          return {
-            plugin: cs.plugin,
-            connectionId: cs.connectionId,
-            scopes: cs.scope.map((sc) => ({
-              id: `${sc.id}`,
-              entities: sc.entities,
-            })),
-          };
-        }),
-      };
-    }
-
-    if (mode === ModeEnum.advanced) {
-      params.plan = !validRawPlan(rawPlan) ? JSON.parse(rawPlan) : JSON.stringify([[]], null, '  ');
-      params.settings = null;
-    }
-
-    return params;
-  }, [projectName, name, mode, connections, rawPlan, cronConfig, isManual, skipOnFail, timeAfter]);
-
-  const handleSaveAfter = (id: ID) => {
-    const path =
-      from === FromEnum.blueprint ? `/blueprints/${id}` : `/projects/${window.encodeURIComponent(projectName)}`;
-
-    history.push(path);
-  };
-
-  const handleSave = async () => {
-    const [success, res] = await operator(() => API.createBlueprint(payload));
-
-    if (success) {
-      handleSaveAfter(res.id);
-    }
-  };
-
-  const hanldeSaveAndRun = async () => {
-    const [success, res] = await operator(async () => {
-      const res = await API.createBlueprint(payload);
-      return await API.runBlueprint(res.id);
-    });
-
-    if (success) {
-      handleSaveAfter(res.blueprintId);
-    }
-  };
-
-  const handlePrev = () => setStep(step - 1);
-  const handleNext = () => setStep(step + 1);
-
-  return (
-    <Context.Provider
-      value={{
-        step,
-
-        mode,
-        name,
-        connections,
-        rawPlan,
-        cronConfig,
-        isManual,
-        skipOnFail,
-        timeAfter,
-
-        onPrev: handlePrev,
-        onNext: handleNext,
-        onSave: handleSave,
-        onSaveAndRun: hanldeSaveAndRun,
-
-        onChangeMode: setMode,
-        onChangeName: setName,
-        onChangeConnections: setConnections,
-        onChangeRawPlan: setRawPlan,
-        onChangeCronConfig: setCronConfig,
-        onChangeIsManual: setIsManual,
-        onChangeSkipOnFail: setSkipOnFail,
-        onChangeTimeAfter: setTimeAfter,
-      }}
-    >
-      {children}
-    </Context.Provider>
-  );
-};
-
-export const useCreate = () => {
-  return useContext(Context);
-};
diff --git a/config-ui/src/pages/blueprint/create/index.tsx b/config-ui/src/pages/blueprint/create/index.tsx
deleted file mode 100644
index f9f788e31..000000000
--- a/config-ui/src/pages/blueprint/create/index.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useMemo } from 'react';
-import { useParams } from 'react-router-dom';
-
-import { PageHeader, Workflow } from '@/components';
-
-import { ModeEnum, FromEnum } from '../types';
-
-import { ContextProvider, Context } from './context';
-import { Step1, Step2, Step3, Step4 } from './components';
-
-interface Props {
-  from: FromEnum;
-}
-
-export const BlueprintCreatePage = ({ from }: Props) => {
-  const { pname } = useParams<{ pname: string }>();
-
-  const breadcrumbs = useMemo(
-    () =>
-      from === FromEnum.project
-        ? [
-            { name: 'Projects', path: '/projects' },
-            { name: window.decodeURIComponent(pname), path: `/projects/${pname}` },
-            {
-              name: 'Create a Blueprint',
-              path: `/projects/${pname}/create-blueprint`,
-            },
-          ]
-        : [
-            { name: 'Blueprints', path: '/blueprints' },
-            { name: 'Create a Blueprint', path: '/blueprints/create' },
-          ],
-    [from, pname],
-  );
-
-  return (
-    <ContextProvider from={from} projectName={pname}>
-      <Context.Consumer>
-        {({ step, mode }) => (
-          <PageHeader breadcrumbs={breadcrumbs}>
-            <Workflow
-              steps={
-                mode === ModeEnum.normal
-                  ? ['Add Data Connections', 'Set Data Scope', 'Add Transformation (Optional)', 'Set Sync Policy']
-                  : ['Create Advanced Configuration', 'Set Sync Policy']
-              }
-              activeStep={step}
-            />
-            {step === 1 && <Step1 from={from} />}
-            {mode === ModeEnum.normal && step === 2 && <Step2 />}
-            {step === 3 && <Step3 />}
-            {((mode === ModeEnum.normal && step === 4) || (mode === ModeEnum.advanced && step === 2)) && <Step4 />}
-          </PageHeader>
-        )}
-      </Context.Consumer>
-    </ContextProvider>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/create/types.ts b/config-ui/src/pages/blueprint/create/types.ts
deleted file mode 100644
index 9717371d3..000000000
--- a/config-ui/src/pages/blueprint/create/types.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { ModeEnum } from '../types';
-
-export type ContextType = {
-  step: number;
-
-  mode: ModeEnum;
-  name: string;
-  connections: MixConnection[];
-  rawPlan: string;
-  cronConfig: string;
-  isManual: boolean;
-  skipOnFail: boolean;
-  timeAfter: string | null;
-
-  onPrev: () => void;
-  onNext: () => void;
-
-  onSave: () => void;
-  onSaveAndRun: () => void;
-
-  onChangeMode: (mode: ModeEnum) => void;
-  onChangeName: React.Dispatch<React.SetStateAction<string>>;
-  onChangeConnections: React.Dispatch<React.SetStateAction<MixConnection[]>>;
-  onChangeRawPlan: React.Dispatch<React.SetStateAction<string>>;
-  onChangeCronConfig: React.Dispatch<React.SetStateAction<string>>;
-  onChangeIsManual: React.Dispatch<React.SetStateAction<boolean>>;
-  onChangeSkipOnFail: React.Dispatch<React.SetStateAction<boolean>>;
-  onChangeTimeAfter: React.Dispatch<React.SetStateAction<string | null>>;
-};
diff --git a/config-ui/src/pages/blueprint/home/api.ts b/config-ui/src/pages/blueprint/home/api.ts
index b31204b0e..62e7c80ed 100644
--- a/config-ui/src/pages/blueprint/home/api.ts
+++ b/config-ui/src/pages/blueprint/home/api.ts
@@ -18,9 +18,23 @@
 
 import { request } from '@/utils';
 
+import { BlueprintType } from '../types';
+
 type GetBlueprintsParams = {
   page: number;
   pageSize: number;
 };
 
-export const getBlueprints = (params: GetBlueprintsParams) => request('/blueprints', { data: params });
+type GetBlueprintResponse = {
+  blueprints: Array<BlueprintType>;
+  count: number;
+};
+
+export const getBlueprints = (params: GetBlueprintsParams): Promise<GetBlueprintResponse> =>
+  request('/blueprints', { data: params });
+
+export const createBlueprint = (payload: any) =>
+  request('/blueprints', {
+    method: 'post',
+    data: payload,
+  });
diff --git a/config-ui/src/pages/blueprint/home/index.tsx b/config-ui/src/pages/blueprint/home/index.tsx
index c70d1c4dc..80815526d 100644
--- a/config-ui/src/pages/blueprint/home/index.tsx
+++ b/config-ui/src/pages/blueprint/home/index.tsx
@@ -16,144 +16,261 @@
  *
  */
 
-import React, { useMemo } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import { ButtonGroup, Button, Tag, Intent } from '@blueprintjs/core';
+import { useState, useMemo } from 'react';
+import { Link } from 'react-router-dom';
+import { ButtonGroup, Button, Tag, Intent, FormGroup, InputGroup, RadioGroup, Radio } from '@blueprintjs/core';
+import dayjs from 'dayjs';
 
-import { PageLoading, PageHeader, Table, ColumnType, IconButton, TextTooltip } from '@/components';
-import { getCron, getCronOptions } from '@/config';
-import { formatTime } from '@/utils';
+import { PageHeader, Table, IconButton, TextTooltip, Dialog } from '@/components';
+import { getCronOptions, cronPresets, getCron } from '@/config';
+import { useConnections, useRefreshData } from '@/hooks';
+import { formatTime, operator } from '@/utils';
 
-import type { BlueprintType } from '../types';
 import { ModeEnum } from '../types';
 
-import { useHome } from './use-home';
+import * as API from './api';
 import * as S from './styled';
 
 export const BlueprintHomePage = () => {
-  const history = useHistory();
+  const [type, setType] = useState('all');
+  const [version, setVersion] = useState(1);
+  const [isOpen, setIsOpen] = useState(false);
+  const [name, setName] = useState('');
+  const [mode, setMode] = useState(ModeEnum.normal);
+  const [saving, setSaving] = useState(false);
 
-  const { loading, dataSource, type, onChangeType } = useHome();
+  const { onGet } = useConnections();
+  const { ready, data } = useRefreshData(() => API.getBlueprints({ page: 1, pageSize: 200 }), [version]);
 
-  const options = useMemo(() => getCronOptions(), []);
-
-  const columns = useMemo(
+  const [options, presets] = useMemo(() => [getCronOptions(), cronPresets.map((preset) => preset.config)], []);
+  const dataSource = useMemo(
     () =>
-      [
-        {
-          title: 'Blueprint Name',
-          dataIndex: 'name',
-          key: 'name',
-          ellipsis: true,
-        },
-        {
-          title: 'Data Connections',
-          key: 'connections',
-          align: 'center',
-          render: (_, row) => {
-            if (row.mode === ModeEnum.advanced) {
-              return 'Advanced Mode';
-            }
-            return row.settings.connections.map((cs) => cs.plugin).join(',');
-          },
-        },
-        {
-          title: 'Frequency',
-          key: 'frequency',
-          width: 100,
-          align: 'center',
-          render: (_, row) => {
-            const cron = getCron(row.isManual, row.cronConfig);
-            return cron.label;
-          },
-        },
-        {
-          title: 'Next Run Time',
-          key: 'nextRunTime',
-          width: 200,
-          align: 'center',
-          render: (_, row) => {
-            const cron = getCron(row.isManual, row.cronConfig);
-            return formatTime(cron.nextTime);
-          },
-        },
-        {
-          title: 'Project',
-          dataIndex: 'projectName',
-          key: 'project',
-          align: 'center',
-          render: (val) =>
-            val ? (
-              <Link to={`/projects/${window.encodeURIComponent(val)}`}>
-                <TextTooltip content={val}>{val}</TextTooltip>
-              </Link>
-            ) : (
-              val
-            ),
-        },
-        {
-          title: 'Status',
-          dataIndex: 'enable',
-          key: 'enable',
-          align: 'center',
-          width: 100,
-          render: (val) => (
-            <Tag minimal intent={val ? Intent.SUCCESS : Intent.DANGER}>
-              {val ? 'Enabled' : 'Disabled'}
-            </Tag>
-          ),
-        },
-        {
-          title: '',
-          dataIndex: 'id',
-          key: 'action',
-          width: 100,
-          align: 'center',
-          render: (val) => (
-            <IconButton icon="cog" tooltip="Detail" onClick={() => history.push(`/blueprints/${val}`)} />
-          ),
-        },
-      ] as ColumnType<BlueprintType>,
-    [],
+      (data?.blueprints ?? [])
+        .filter((it) => {
+          switch (type) {
+            case 'all':
+              return true;
+            case 'manual':
+              return it.isManual;
+            case 'custom':
+              return !presets.includes(it.cronConfig);
+            default:
+              return !it.isManual && it.cronConfig === type;
+          }
+        })
+        .map((it) => {
+          const connections = it.settings.connections
+            .filter((cs) => cs.plugin !== 'webhook')
+            .map((cs) => onGet(`${cs.plugin}-${cs.connectionId}`));
+          return {
+            ...it,
+            connections: connections.map((cs) => cs.name),
+          };
+        }),
+    [data, type],
   );
 
-  const handleCreate = () => history.push('/blueprints/create');
+  const handleShowDialog = () => setIsOpen(true);
+  const handleHideDialog = () => {
+    setName('');
+    setMode(ModeEnum.normal);
+    setIsOpen(false);
+  };
+
+  const handleCreate = async () => {
+    const payload: any = {
+      name,
+      mode,
+      enable: true,
+      cronConfig: presets[0],
+      isManual: false,
+      skipOnFail: true,
+    };
+
+    if (mode === ModeEnum.normal) {
+      payload.settings = {
+        version: '2.0.0',
+        timeAfter: formatTime(dayjs().subtract(6, 'month').startOf('day').toDate(), 'YYYY-MM-DD[T]HH:mm:ssZ'),
+        connections: [],
+      };
+    }
+
+    if (mode === ModeEnum.advanced) {
+      payload.settings = null;
+      payload.plan = [[]];
+    }
 
-  if (loading) {
-    return <PageLoading />;
-  }
+    const [success] = await operator(() => API.createBlueprint(payload), {
+      setOperating: setSaving,
+    });
+
+    if (success) {
+      handleHideDialog();
+      setVersion((v) => v + 1);
+    }
+  };
 
   return (
-    <PageHeader breadcrumbs={[{ name: 'Blueprints', path: '/blueprints' }]}>
+    <PageHeader
+      breadcrumbs={[
+        { name: 'Advanced', path: '/blueprints' },
+        { name: 'Blueprints', path: '/blueprints' },
+      ]}
+    >
       <S.Wrapper>
+        <p>This is a complete list of all Blueprints you have created, whether they belong to Projects or not.</p>
         <div className="action">
           <ButtonGroup>
-            <Button
-              intent={type === 'all' ? Intent.PRIMARY : Intent.NONE}
-              text="All"
-              onClick={() => onChangeType('all')}
-            />
+            <Button intent={type === 'all' ? Intent.PRIMARY : Intent.NONE} text="All" onClick={() => setType('all')} />
             {options.map(({ label, value }) => (
               <Button
                 key={value}
                 intent={type === value ? Intent.PRIMARY : Intent.NONE}
                 text={label}
-                onClick={() => onChangeType(value)}
+                onClick={() => setType(value)}
               />
             ))}
           </ButtonGroup>
-          <Button icon="plus" intent={Intent.PRIMARY} text="New Blueprint" onClick={handleCreate} />
+          <Button icon="plus" intent={Intent.PRIMARY} text="New Blueprint" onClick={handleShowDialog} />
         </div>
         <Table
-          columns={columns}
+          loading={!ready}
+          columns={[
+            {
+              title: 'Blueprint Name',
+              dataIndex: 'name',
+              key: 'name',
+              ellipsis: true,
+            },
+            {
+              title: 'Data Connections',
+              dataIndex: ['mode', 'connections'],
+              key: 'connections',
+              align: 'center',
+              render: ({ mode, connections }) => {
+                if (mode === ModeEnum.advanced) {
+                  return 'Advanced Mode';
+                }
+                return connections.join(',');
+              },
+            },
+            {
+              title: 'Frequency',
+              dataIndex: ['isManual', 'cronConfig'],
+              key: 'frequency',
+              width: 100,
+              align: 'center',
+              render: ({ isManual, cronConfig }) => {
+                const cron = getCron(isManual, cronConfig);
+                return cron.label;
+              },
+            },
+            {
+              title: 'Next Run Time',
+              dataIndex: ['isManual', 'cronConfig'],
+              key: 'nextRunTime',
+              width: 200,
+              align: 'center',
+              render: ({ isManual, cronConfig }) => {
+                const cron = getCron(isManual, cronConfig);
+                return formatTime(cron.nextTime);
+              },
+            },
+            {
+              title: 'Project',
+              dataIndex: 'projectName',
+              key: 'project',
+              align: 'center',
+              render: (val) =>
+                val ? (
+                  <Link to={`/projects/${window.encodeURIComponent(val)}`}>
+                    <TextTooltip content={val}>{val}</TextTooltip>
+                  </Link>
+                ) : (
+                  val
+                ),
+            },
+            {
+              title: 'Status',
+              dataIndex: 'enable',
+              key: 'enable',
+              align: 'center',
+              width: 100,
+              render: (val) => (
+                <Tag minimal intent={val ? Intent.SUCCESS : Intent.DANGER}>
+                  {val ? 'Enabled' : 'Disabled'}
+                </Tag>
+              ),
+            },
+            {
+              title: '',
+              dataIndex: 'id',
+              key: 'action',
+              width: 100,
+              align: 'center',
+              render: (val) => (
+                <Link to={`/blueprints/${val}`}>
+                  <IconButton icon="cog" tooltip="Detail" />
+                </Link>
+              ),
+            },
+          ]}
           dataSource={dataSource}
           noData={{
             text: 'There is no Blueprint yet. Please add a new Blueprint here or from a Project.',
             btnText: 'New Blueprint',
-            onCreate: handleCreate,
+            onCreate: handleShowDialog,
           }}
         />
       </S.Wrapper>
+      <Dialog
+        style={{ width: 820 }}
+        isOpen={isOpen}
+        title="Create a New Blueprint"
+        okText="Save"
+        okDisabled={!name}
+        okLoading={saving}
+        onOk={handleCreate}
+        onCancel={handleHideDialog}
+      >
+        <S.DialogWrapper>
+          <FormGroup
+            label={<S.Label>Blueprint Name</S.Label>}
+            subLabel={
+              <S.LabelDescription>
+                Give your Blueprint a unique name to help you identify it in the future.
+              </S.LabelDescription>
+            }
+            labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+          >
+            <InputGroup
+              style={{ width: 386 }}
+              placeholder="Your Blueprint Name"
+              value={name}
+              onChange={(e) => setName(e.target.value)}
+            />
+          </FormGroup>
+          <FormGroup
+            label={<S.Label>Blueprint Mode</S.Label>}
+            subLabel={
+              <S.LabelDescription>
+                Normal Mode is usually adequate for most usages. But if you need to customize how tasks are executed in
+                the Blueprint, please use Advanced Mode to create a Blueprint.
+              </S.LabelDescription>
+            }
+            labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+          >
+            <RadioGroup
+              inline
+              selectedValue={mode}
+              onChange={(e) => setMode((e.target as HTMLInputElement).value as ModeEnum)}
+            >
+              <Radio value={ModeEnum.normal}>Normal Mode</Radio>
+              <Radio value={ModeEnum.advanced}>Advanced Mode</Radio>
+            </RadioGroup>
+          </FormGroup>
+        </S.DialogWrapper>
+      </Dialog>
     </PageHeader>
   );
 };
diff --git a/config-ui/src/pages/blueprint/home/styled.ts b/config-ui/src/pages/blueprint/home/styled.ts
index 7406e280d..1cf236862 100644
--- a/config-ui/src/pages/blueprint/home/styled.ts
+++ b/config-ui/src/pages/blueprint/home/styled.ts
@@ -26,3 +26,23 @@ export const Wrapper = styled.div`
     margin-bottom: 16px;
   }
 `;
+
+export const DialogWrapper = styled.div`
+  .bp4-form-group + .bp4-form-group {
+    margin-top: 40px;
+    margin-bottom: 0;
+  }
+`;
+
+export const Label = styled.label`
+  font-size: 16px;
+  font-weight: 600;
+`;
+
+export const LabelInfo = styled.i`
+  color: #ff8b8b;
+`;
+
+export const LabelDescription = styled.p`
+  margin: 0;
+`;
diff --git a/config-ui/src/pages/blueprint/home/use-home.ts b/config-ui/src/pages/blueprint/home/use-home.ts
deleted file mode 100644
index 0295666f8..000000000
--- a/config-ui/src/pages/blueprint/home/use-home.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useState, useEffect, useMemo } from 'react';
-
-import { cronPresets } from '@/config';
-
-import type { BlueprintType } from '../types';
-
-import * as API from './api';
-
-export const useHome = () => {
-  const [loading, setLoading] = useState(false);
-  const [blueprints, setBlueprints] = useState<BlueprintType[]>([]);
-  const [dataSource, setDataSource] = useState<BlueprintType[]>([]);
-  const [type, setType] = useState('all');
-
-  const presets = cronPresets.map((preset) => preset.config);
-
-  const getBlueprints = async () => {
-    setLoading(true);
-    try {
-      const res = await API.getBlueprints({
-        page: 1,
-        pageSize: 200,
-      });
-      setBlueprints(res.blueprints);
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  useEffect(() => {
-    getBlueprints();
-  }, []);
-
-  useEffect(() => {
-    setDataSource(
-      blueprints.filter((bp) => {
-        switch (type) {
-          case 'all':
-            return true;
-          case 'manual':
-            return bp.isManual;
-          case 'custom':
-            return !presets.includes(bp.cronConfig);
-          default:
-            return !bp.isManual && bp.cronConfig === type;
-        }
-      }),
-    );
-  }, [blueprints, type]);
-
-  return useMemo(
-    () => ({
-      loading,
-      blueprints,
-      dataSource,
-      type,
-      onChangeType: setType,
-    }),
-    [loading, blueprints, dataSource, type],
-  );
-};
diff --git a/config-ui/src/pages/blueprint/index.ts b/config-ui/src/pages/blueprint/index.ts
index cd8a30e1f..68aec9419 100644
--- a/config-ui/src/pages/blueprint/index.ts
+++ b/config-ui/src/pages/blueprint/index.ts
@@ -18,7 +18,6 @@
 
 export * from './types';
 export * from './home';
-export * from './create';
 export * from './detail';
 export * from './connection-add';
 export * from './connection-detail';
diff --git a/config-ui/src/pages/index.ts b/config-ui/src/pages/index.ts
index bcf789c04..557b2860e 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/pages/index.ts
@@ -16,9 +16,10 @@
  *
  */
 
-export * from './project';
-export * from './connection';
 export * from './blueprint';
-export * from './pipeline';
-export * from './offline';
+export * from './connection';
 export * from './db-migrate';
+export * from './login';
+export * from './offline';
+export * from './pipeline';
+export * from './project';
diff --git a/config-ui/src/pages/blueprint/create/components/index.ts b/config-ui/src/pages/login/index.ts
similarity index 88%
copy from config-ui/src/pages/blueprint/create/components/index.ts
copy to config-ui/src/pages/login/index.ts
index 6e5963478..a365b1ed2 100644
--- a/config-ui/src/pages/blueprint/create/components/index.ts
+++ b/config-ui/src/pages/login/index.ts
@@ -16,7 +16,4 @@
  *
  */
 
-export * from './step-1';
-export * from './step-2';
-export * from './step-3';
-export * from './step-4';
+export * from './login';
diff --git a/config-ui/src/pages/project/home/api.ts b/config-ui/src/pages/project/home/api.ts
index 0cb21da63..2ac30a8a0 100644
--- a/config-ui/src/pages/project/home/api.ts
+++ b/config-ui/src/pages/project/home/api.ts
@@ -23,7 +23,15 @@ type GetProjectsParams = {
   pageSize: number;
 };
 
-export const getProjects = (params: GetProjectsParams) => request('/projects', { data: params });
+type GetProjectsResponse = {
+  projects: Array<{
+    name: string;
+  }>;
+  counts: number;
+};
+
+export const getProjects = (params: GetProjectsParams): Promise<GetProjectsResponse> =>
+  request('/projects', { data: params });
 
 type CreateProjectPayload = {
   name: string;
@@ -40,3 +48,9 @@ export const createProject = (payload: CreateProjectPayload) =>
     method: 'post',
     data: payload,
   });
+
+export const createBlueprint = (payload: any) =>
+  request('/blueprints', {
+    method: 'post',
+    data: payload,
+  });
diff --git a/config-ui/src/pages/project/home/index.tsx b/config-ui/src/pages/project/home/index.tsx
index 844a05662..ffc287fd4 100644
--- a/config-ui/src/pages/project/home/index.tsx
+++ b/config-ui/src/pages/project/home/index.tsx
@@ -16,82 +16,118 @@
  *
  */
 
-import React, { useMemo, useState } from 'react';
+import { useState, useMemo } from 'react';
 import { Link, useHistory } from 'react-router-dom';
-import { Button, InputGroup, Checkbox, Intent } from '@blueprintjs/core';
+import { Button, InputGroup, Checkbox, Intent, FormGroup } from '@blueprintjs/core';
+import dayjs from 'dayjs';
 
-import { PageHeader, Table, ColumnType, Dialog, IconButton } from '@/components';
+import { PageHeader, Table, Dialog, IconButton, toast } from '@/components';
+import { cronPresets } from '@/config';
+import { useRefreshData } from '@/hooks';
+import { formatTime, operator } from '@/utils';
 
-import { useProject } from './use-project';
-import * as S from './styled';
+import { encodeName } from '../utils';
+import { ModeEnum } from '../../blueprint';
 
-type ProjectItem = {
-  name: string;
-};
+import * as API from './api';
+import * as S from './styled';
 
 export const ProjectHomePage = () => {
+  const [version, setVersion] = useState(1);
   const [isOpen, setIsOpen] = useState(false);
   const [name, setName] = useState('');
   const [enableDora, setEnableDora] = useState(true);
+  const [saving, setSaving] = useState(false);
+
+  const { ready, data } = useRefreshData(() => API.getProjects({ page: 1, pageSize: 200 }), [version]);
 
   const history = useHistory();
 
+  const dataSource = useMemo(() => data?.projects ?? [], [data]);
+  const presets = useMemo(() => cronPresets.map((preset) => preset.config), []);
+
   const handleShowDialog = () => setIsOpen(true);
   const handleHideDialog = () => {
     setIsOpen(false);
     setName('');
+    setEnableDora(true);
   };
 
-  const { loading, operating, projects, onSave } = useProject<ProjectItem>({
-    name,
-    enableDora,
-    onHideDialog: handleHideDialog,
-  });
+  const handleCreate = async () => {
+    if (!/^(\w|-|\/)+$/.test(name)) {
+      toast.error('Please enter alphanumeric or underscore');
+      return;
+    }
 
-  const columns = useMemo(
-    () =>
-      [
-        {
-          title: 'Project Name',
-          dataIndex: 'name',
-          key: 'name',
-          render: (name: string) => (
-            <Link to={`/projects/${window.encodeURIComponent(name)}`} style={{ color: '#292b3f' }}>
-              {name}
-            </Link>
-          ),
-        },
-        {
-          title: '',
-          dataIndex: 'name',
-          key: 'action',
-          width: 100,
-          align: 'center',
-          render: (name: any) => (
-            <IconButton
-              icon="cog"
-              tooltip="Detail"
-              onClick={() => history.push(`/projects/${window.encodeURIComponent(name)}`)}
-            />
-          ),
-        },
-      ] as ColumnType<ProjectItem>,
-    [],
-  );
+    const [success] = await operator(
+      async () => {
+        await API.createProject({
+          name: encodeName(name),
+          description: '',
+          metrics: [
+            {
+              pluginName: 'dora',
+              pluginOption: '',
+              enable: enableDora,
+            },
+          ],
+        });
+        return API.createBlueprint({
+          name: `${name}-Blueprint`,
+          projectName: name,
+          mode: ModeEnum.normal,
+          enable: true,
+          cronConfig: presets[0],
+          isManual: false,
+          skipOnFail: true,
+          settings: {
+            version: '2.0.0',
+            timeAfter: formatTime(dayjs().subtract(6, 'month').startOf('day').toDate(), 'YYYY-MM-DD[T]HH:mm:ssZ'),
+            connections: [],
+          },
+        });
+      },
+      {
+        setOperating: setSaving,
+      },
+    );
+
+    if (success) {
+      handleHideDialog();
+      setVersion((v) => v + 1);
+    }
+  };
 
   return (
     <PageHeader
       breadcrumbs={[{ name: 'Projects', path: '/projects' }]}
-      extra={
-        projects.length ? (
-          <Button intent={Intent.PRIMARY} icon="plus" text="New Project" onClick={handleShowDialog} />
-        ) : null
-      }
+      extra={<Button intent={Intent.PRIMARY} icon="plus" text="New Project" onClick={handleShowDialog} />}
     >
       <Table
-        loading={loading}
-        columns={columns}
-        dataSource={projects}
+        loading={!ready}
+        columns={[
+          {
+            title: 'Project Name',
+            dataIndex: 'name',
+            key: 'name',
+            render: (name: string) => (
+              <Link to={`/projects/${encodeName(name)}`} style={{ color: '#292b3f' }}>
+                {name}
+              </Link>
+            ),
+          },
+          {
+            title: '',
+            dataIndex: 'name',
+            key: 'action',
+            width: 100,
+            align: 'center',
+            render: (name: any) => (
+              <IconButton icon="cog" tooltip="Detail" onClick={() => history.push(`/projects/${encodeName(name)}`)} />
+            ),
+          },
+        ]}
+        dataSource={dataSource}
         noData={{
           text: 'Add new projects to see engineering metrics based on projects.',
           btnText: 'New Project',
@@ -101,41 +137,49 @@ export const ProjectHomePage = () => {
       <Dialog
         isOpen={isOpen}
         title="Create a New Project"
-        style={{
-          top: -100,
-          width: 820,
-        }}
+        style={{ width: 820 }}
         okText="Save"
         okDisabled={!name}
-        okLoading={operating}
+        okLoading={saving}
+        onOk={handleCreate}
         onCancel={handleHideDialog}
-        onOk={onSave}
       >
-        <S.DialogInner>
-          <div className="block">
-            <h3>Project Name *</h3>
-            <p>Give your project a unique name with letters, numbers, -, _ or /</p>
-            <InputGroup placeholder="Your Project Name" value={name} onChange={(e) => setName(e.target.value)} />
-          </div>
-          <div className="block">
-            <h3>Project Settings</h3>
-            <div className="checkbox">
-              <Checkbox
-                label="Enable DORA Metrics"
-                checked={enableDora}
-                onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
-              />
-              <p>
+        <S.DialogWrapper>
+          <FormGroup
+            label={<S.Label>Project Name</S.Label>}
+            subLabel={
+              <S.LabelDescription>Give your project a unique name with letters, numbers, -, _ or /</S.LabelDescription>
+            }
+            labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+          >
+            <InputGroup
+              style={{ width: 386 }}
+              placeholder="Your Project Name"
+              value={name}
+              onChange={(e) => setName(e.target.value)}
+            />
+          </FormGroup>
+
+          <FormGroup
+            label={<S.Label>Project Settings</S.Label>}
+            subLabel={
+              <S.LabelDescription>
                 <a href="https://devlake.apache.org/docs/DORA/" rel="noreferrer" target="_blank">
                   DORA metrics
                 </a>
                 <span style={{ marginLeft: 4 }}>
                   are four widely-adopted metrics for measuring software delivery performance.
                 </span>
-              </p>
-            </div>
-          </div>
-        </S.DialogInner>
+              </S.LabelDescription>
+            }
+          >
+            <Checkbox
+              label="Enable DORA Metrics"
+              checked={enableDora}
+              onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
+            />
+          </FormGroup>
+        </S.DialogWrapper>
       </Dialog>
     </PageHeader>
   );
diff --git a/config-ui/src/pages/project/home/styled.ts b/config-ui/src/pages/project/home/styled.ts
index 92f71638e..1449bc5ca 100644
--- a/config-ui/src/pages/project/home/styled.ts
+++ b/config-ui/src/pages/project/home/styled.ts
@@ -18,27 +18,22 @@
 
 import styled from 'styled-components';
 
-export const DialogInner = styled.div`
-  .block + .block {
-    margin-top: 24px;
-  }
-
-  .bp4-input-group {
-    width: 386px;
+export const DialogWrapper = styled.div`
+  .bp4-form-group + .bp4-form-group {
+    margin-top: 40px;
+    margin-bottom: 0;
   }
+`;
 
-  .checkbox {
-    display: flex;
-    margin-top: 8px;
+export const Label = styled.label`
+  font-size: 16px;
+  font-weight: 600;
+`;
 
-    & > .bp4-control {
-      margin: 0;
-    }
+export const LabelInfo = styled.i`
+  color: #ff8b8b;
+`;
 
-    & > p {
-      display: flex;
-      align-items: center;
-      margin: 0 0 0 16px;
-    }
-  }
+export const LabelDescription = styled.p`
+  margin: 0;
 `;
diff --git a/config-ui/src/pages/project/home/use-project.ts b/config-ui/src/pages/project/home/use-project.ts
deleted file mode 100644
index b241de813..000000000
--- a/config-ui/src/pages/project/home/use-project.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useState, useEffect, useMemo } from 'react';
-
-import { toast } from '@/components';
-import { operator } from '@/utils';
-
-import * as API from './api';
-
-interface Props {
-  name: string;
-  enableDora: boolean;
-  onHideDialog: () => void;
-}
-
-export const useProject = <T>({ name, enableDora, onHideDialog }: Props) => {
-  const [loading, setLoading] = useState(false);
-  const [operating, setOperating] = useState(false);
-  const [projects, setProjects] = useState<T[]>([]);
-
-  const getProjects = async () => {
-    setLoading(true);
-    try {
-      const res = await API.getProjects({ page: 1, pageSize: 200 });
-      setProjects(
-        res.projects.map((it: any) => ({
-          name: it.name,
-        })),
-      );
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  useEffect(() => {
-    getProjects();
-  }, []);
-
-  const handleSave = async () => {
-    if (!/^(\w|-|\/)+$/.test(name)) {
-      toast.error('Please enter alphanumeric or underscore');
-      return;
-    }
-
-    const payload = {
-      name,
-      description: '',
-      metrics: [
-        {
-          pluginName: 'dora',
-          pluginOption: '',
-          enable: enableDora,
-        },
-      ],
-    };
-
-    const [success] = await operator(() => API.createProject(payload), {
-      setOperating,
-    });
-
-    if (success) {
-      onHideDialog();
-      getProjects();
-    }
-  };
-
-  return useMemo(
-    () => ({
-      loading,
-      operating,
-      projects,
-      onSave: handleSave,
-    }),
-    [loading, operating, projects, name, enableDora],
-  );
-};
diff --git a/config-ui/src/pages/blueprint/create/components/index.ts b/config-ui/src/pages/project/utils.ts
similarity index 83%
rename from config-ui/src/pages/blueprint/create/components/index.ts
rename to config-ui/src/pages/project/utils.ts
index 6e5963478..0dd911bd3 100644
--- a/config-ui/src/pages/blueprint/create/components/index.ts
+++ b/config-ui/src/pages/project/utils.ts
@@ -16,7 +16,6 @@
  *
  */
 
-export * from './step-1';
-export * from './step-2';
-export * from './step-3';
-export * from './step-4';
+export const decodeName = (name: string) => window.decodeURIComponent(name);
+
+export const encodeName = (name: string) => window.encodeURIComponent(name);
diff --git a/config-ui/src/utils/index.ts b/config-ui/src/utils/index.ts
index 472050b64..21c45e9f0 100644
--- a/config-ui/src/utils/index.ts
+++ b/config-ui/src/utils/index.ts
@@ -16,6 +16,7 @@
  *
  */
 
-export * from './request';
+export * from './history';
 export * from './operator';
+export * from './request';
 export * from './time';