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

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


The following commit(s) were added to refs/heads/main by this push:
     new 190b1ea  [UI] Encapsulate http request (#29)
190b1ea is described below

commit 190b1ea93afb8f526b58d90bbc08c1e829a73098
Author: s7monk <[email protected]>
AuthorDate: Wed Aug 16 11:44:48 2023 +0800

    [UI] Encapsulate http request (#29)
---
 README.md                                          |   2 +-
 paimon-web-api/pom.xml                             |   1 +
 paimon-web-server/pom.xml                          |  17 +++
 .../paimon/web/server/configrue/CorsConfig.java    |  45 +++++++
 .../paimon/web/server/configrue/JacksonConfig.java |  55 ++++++++
 .../web/server/configrue/SaTokenConfigure.java     |   5 +-
 .../web/server/controller/CatalogController.java   | 100 +++++++++++++++
 .../paimon/web/server/data/model/CatalogInfo.java  |  49 ++++++++
 .../web/server/data/result/enums/Status.java       |   4 +
 .../paimon/web/server/mapper/CatalogMapper.java    |  28 +++++
 .../paimon/web/server/service/CatalogService.java  |  35 ++++++
 .../server/service/impl/CatalogServiceImpl.java    |  40 ++++++
 .../src/main/resources/application-dev.yml         |   3 +
 .../src/main/resources/application.yml             |   2 +-
 paimon-web-server/src/main/resources/db/ddl-h2.sql |  13 ++
 paimon-web-server/src/main/resources/db/dml-h2.sql |   7 +-
 .../src/main/resources/i18n/messages.properties    |   2 +
 paimon-web-ui/index.html                           |   2 +-
 paimon-web-ui/package.json                         |   6 +-
 paimon-web-ui/src/api/api.ts                       |  54 ++++++++
 .../LeftContent/index.tsx => api/data.d.ts}        |  39 +++---
 .../catalog-tree.module.less => api/endpoints.ts}  |  11 +-
 paimon-web-ui/src/api/http.ts                      |  89 +++++++++++++
 paimon-web-ui/src/app/App.less                     |   7 +-
 .../src/components/Dropdown/CatalogDropdown.tsx    |   2 +-
 .../CatalogModalForm/CatalogForm/index.tsx         | 104 +++++++++++++++
 .../components/CatalogModalForm/index.tsx          |  58 +++++++++
 .../CatalogTree/catalog-tree.module.less           |   2 +-
 .../LeftContent/components/CatalogTree/index.tsx   | 139 +++++----------------
 .../Metadata/components/LeftContent/index.tsx      |  61 ++++++++-
 .../LeftContent/left-content.module.less           |   8 ++
 .../RightContent/components/TopPanel/Tab/index.tsx |   2 +-
 .../TreeComponent/tree-component.module.less       |   4 +
 paimon-web-ui/src/store/catalogStore.ts            |  94 ++++++++++++++
 .../Catalog/data.d.ts}                             |  23 ++--
 paimon-web-ui/tsconfig.json                        |   3 +-
 paimon-web-ui/vite.config.ts                       |  15 ++-
 pom.xml                                            |   3 +
 38 files changed, 976 insertions(+), 158 deletions(-)

diff --git a/README.md b/README.md
index 9249cd8..3744ba3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Apache Paimon (incubating) Manager
+# Apache Paimon (incubating) Web UI
 
 This repository is web ui for the [Apache Paimon](https://paimon.apache.org/) 
project.
 
diff --git a/paimon-web-api/pom.xml b/paimon-web-api/pom.xml
index 6d6886d..24f8c1c 100644
--- a/paimon-web-api/pom.xml
+++ b/paimon-web-api/pom.xml
@@ -49,6 +49,7 @@ under the License.
             <artifactId>paimon-web-common</artifactId>
             <version>${project.version}</version>
         </dependency>
+
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
diff --git a/paimon-web-server/pom.xml b/paimon-web-server/pom.xml
index 78d43f4..993f48e 100644
--- a/paimon-web-server/pom.xml
+++ b/paimon-web-server/pom.xml
@@ -34,6 +34,10 @@ under the License.
         Paimon Web Server is a server for the [Apache 
Paimon](https://paimon.apache.org/) project.
     </description>
 
+    <properties>
+        <hadoop.version>2.8.5</hadoop.version>
+    </properties>
+
     <dependencies>
         <dependency>
             <groupId>org.apache.paimon</groupId>
@@ -41,6 +45,12 @@ under the License.
             <version>${project.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.paimon</groupId>
+            <artifactId>paimon-web-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-configuration-processor</artifactId>
@@ -136,6 +146,13 @@ under the License.
             <artifactId>h2</artifactId>
             <scope>runtime</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.hadoop</groupId>
+            <artifactId>hadoop-client</artifactId>
+            <version>${hadoop.version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/CorsConfig.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/CorsConfig.java
new file mode 100644
index 0000000..7fff0b2
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/CorsConfig.java
@@ -0,0 +1,45 @@
+/*
+ *
+ *  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.
+ *
+ */
+
+package org.apache.paimon.web.server.configrue;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+/** CorsConfig. */
+@Slf4j
+// @Configuration
+public class CorsConfig {
+
+    @Bean
+    public CorsFilter corsFilter() {
+        CorsConfiguration corsConfiguration = new CorsConfiguration();
+        corsConfiguration.setAllowCredentials(true);
+        corsConfiguration.addAllowedHeader("*");
+        corsConfiguration.addAllowedMethod("*");
+        corsConfiguration.addExposedHeader("*");
+        corsConfiguration.addAllowedOrigin("http://127.0.0.1:5173";);
+        UrlBasedCorsConfigurationSource source = new 
UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", corsConfiguration);
+        return new CorsFilter(source);
+    }
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/JacksonConfig.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/JacksonConfig.java
new file mode 100644
index 0000000..2f7b2fb
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/JacksonConfig.java
@@ -0,0 +1,55 @@
+/*
+ *
+ *  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.
+ *
+ */
+
+package org.apache.paimon.web.server.configrue;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+import java.text.SimpleDateFormat;
+
+/** JacksonConfig. */
+@Configuration
+public class JacksonConfig {
+    @Bean
+    public ObjectMapper getJacksonObjectMapper(Jackson2ObjectMapperBuilder 
builder) {
+        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
+        // When the sequence is changed to json, all longs will be changed to 
strings.
+        // Because the numeric type in js cannot contain all java long values, 
precision will be
+        // lost after more than 16 digits.
+        SimpleModule simpleModule = new SimpleModule();
+        simpleModule.addSerializer(
+                Long.class, 
com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance);
+        simpleModule.addSerializer(
+                Long.TYPE, 
com.fasterxml.jackson.databind.ser.std.ToStringSerializer.instance);
+        objectMapper.registerModule(simpleModule);
+        // If there are more attributes during deserialization, no exception 
will be thrown.
+        
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
false);
+        // Date format handling.
+        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd 
HH:mm:ss"));
+        objectMapper.setVisibility(PropertyAccessor.FIELD, 
JsonAutoDetect.Visibility.ANY);
+        return objectMapper;
+    }
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/SaTokenConfigure.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/SaTokenConfigure.java
index 11bf305..2497037 100644
--- 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/SaTokenConfigure.java
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/configrue/SaTokenConfigure.java
@@ -21,17 +21,16 @@ package org.apache.paimon.web.server.configrue;
 
 import cn.dev33.satoken.interceptor.SaInterceptor;
 import cn.dev33.satoken.stp.StpUtil;
-import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 /** Sa-Token path config. */
-@Configuration
+// @Configuration
 public class SaTokenConfigure implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new SaInterceptor(handle -> 
StpUtil.checkLogin()))
                 .addPathPatterns("/**")
-                .excludePathPatterns("/api/login");
+                .excludePathPatterns("/***");
     }
 }
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java
new file mode 100644
index 0000000..ef954b4
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+package org.apache.paimon.web.server.controller;
+
+import org.apache.paimon.web.api.catalog.CatalogCreator;
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.data.result.enums.Status;
+import org.apache.paimon.web.server.service.CatalogService;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/** Catalog api controller. */
+@Slf4j
+@RestController
+@RequestMapping("/api/catalog")
+public class CatalogController {
+
+    @Autowired private CatalogService catalogService;
+
+    /**
+     * Create a filesystem catalog.
+     *
+     * @param catalogInfo The catalogInfo for the filesystem catalog.
+     * @return The created catalog.
+     */
+    @PostMapping("/createFilesystemCatalog")
+    public R<Void> createFilesystemCatalog(@RequestBody CatalogInfo 
catalogInfo) {
+        if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
+            return R.failed(Status.CATALOG_NAME_IS_EXIST, 
catalogInfo.getCatalogName());
+        }
+
+        try {
+            CatalogCreator.createFilesystemCatalog(catalogInfo.getWarehouse());
+            return catalogService.save(catalogInfo) ? R.succeed() : R.failed();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.failed(Status.CATALOG_CREATE_ERROR);
+        }
+    }
+
+    /**
+     * Create a hive catalog.
+     *
+     * @param catalogInfo The information for the hive catalog.
+     * @return The created catalog.
+     */
+    @PostMapping("/createHiveCatalog")
+    public R<Void> createHiveCatalog(@RequestBody CatalogInfo catalogInfo) {
+        if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
+            return R.failed(Status.CATALOG_NAME_IS_EXIST, 
catalogInfo.getCatalogName());
+        }
+
+        try {
+            CatalogCreator.createHiveCatalog(
+                    catalogInfo.getWarehouse(),
+                    catalogInfo.getHiveUri(),
+                    catalogInfo.getHiveConfDir());
+            return catalogService.save(catalogInfo) ? R.succeed() : R.failed();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.failed(Status.CATALOG_CREATE_ERROR);
+        }
+    }
+
+    /**
+     * Get all catalog information.
+     *
+     * @return The list of all catalogs.
+     */
+    @GetMapping("/getAllCatalogs")
+    public R<List<CatalogInfo>> getCatalog() {
+        List<CatalogInfo> catalogs = catalogService.list();
+        return R.succeed(catalogs);
+    }
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/CatalogInfo.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/CatalogInfo.java
new file mode 100644
index 0000000..a90885a
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/model/CatalogInfo.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package org.apache.paimon.web.server.data.model;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/** Catalog table model. */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("catalog")
+public class CatalogInfo extends BaseModel {
+
+    private String catalogType;
+
+    private String catalogName;
+
+    private String warehouse;
+
+    private String hiveUri;
+
+    private String hiveConfDir;
+
+    @TableLogic private boolean isDelete;
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
index e20edca..c8dc0d3 100644
--- 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java
@@ -49,6 +49,10 @@ public enum Status {
     MENU_IN_USED(10201, "menu.in.used"),
     MENU_NAME_IS_EXIST(10202, "menu.name.exist"),
     MENU_PATH_INVALID(10203, "menu.path.invalid"),
+
+    /** ------------catalog-----------------. */
+    CATALOG_NAME_IS_EXIST(10301, "catalog.name.exist"),
+    CATALOG_CREATE_ERROR(10302, "catalog.create.error"),
     ;
 
     private final int code;
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/mapper/CatalogMapper.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/mapper/CatalogMapper.java
new file mode 100644
index 0000000..6d4c922
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/mapper/CatalogMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package org.apache.paimon.web.server.mapper;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/** Catalog table mapper. */
+@Mapper
+public interface CatalogMapper extends BaseMapper<CatalogInfo> {}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/CatalogService.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/CatalogService.java
new file mode 100644
index 0000000..ecba5a1
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/CatalogService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package org.apache.paimon.web.server.service;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/** Catalog Service. */
+public interface CatalogService extends IService<CatalogInfo> {
+
+    /**
+     * Verify if the catalog name is unique.
+     *
+     * @param catalog catalog info
+     * @return result
+     */
+    boolean checkCatalogNameUnique(CatalogInfo catalog);
+}
diff --git 
a/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/CatalogServiceImpl.java
 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/CatalogServiceImpl.java
new file mode 100644
index 0000000..316459a
--- /dev/null
+++ 
b/paimon-web-server/src/main/java/org/apache/paimon/web/server/service/impl/CatalogServiceImpl.java
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package org.apache.paimon.web.server.service.impl;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.mapper.CatalogMapper;
+import org.apache.paimon.web.server.service.CatalogService;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/** CatalogServiceImpl. */
+@Service
+public class CatalogServiceImpl extends ServiceImpl<CatalogMapper, CatalogInfo>
+        implements CatalogService {
+
+    @Override
+    public boolean checkCatalogNameUnique(CatalogInfo catalog) {
+        int catalogId = catalog.getId() == null ? -1 : catalog.getId();
+        CatalogInfo info =
+                this.lambdaQuery().eq(CatalogInfo::getCatalogName, 
catalog.getCatalogName()).one();
+        return info == null || info.getId() == catalogId;
+    }
+}
diff --git a/paimon-web-server/src/main/resources/application-dev.yml 
b/paimon-web-server/src/main/resources/application-dev.yml
index 252158b..70756dd 100644
--- a/paimon-web-server/src/main/resources/application-dev.yml
+++ b/paimon-web-server/src/main/resources/application-dev.yml
@@ -14,6 +14,9 @@
 # limitations under the License.
 
 spring:
+  h2:  #http://localhost:10088/h2-console
+    console:
+      enabled: true
   datasource:
     driver-class-name: org.h2.Driver
     url: 
jdbc:h2:mem:paimon;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;;NON_KEYWORDS=USER;INIT=runscript
 from 'classpath:db/ddl-h2.sql'
diff --git a/paimon-web-server/src/main/resources/application.yml 
b/paimon-web-server/src/main/resources/application.yml
index 4e702ad..8bd64dc 100644
--- a/paimon-web-server/src/main/resources/application.yml
+++ b/paimon-web-server/src/main/resources/application.yml
@@ -20,7 +20,7 @@ server:
 spring:
   # Paimon Web Service Name
   application:
-    name: PaimonManager
+    name: Paimon Web UI
   profiles:
     active:  dev
   messages:
diff --git a/paimon-web-server/src/main/resources/db/ddl-h2.sql 
b/paimon-web-server/src/main/resources/db/ddl-h2.sql
index 4f406e1..fc6936f 100644
--- a/paimon-web-server/src/main/resources/db/ddl-h2.sql
+++ b/paimon-web-server/src/main/resources/db/ddl-h2.sql
@@ -101,4 +101,17 @@ CREATE TABLE if not exists `role_menu`
     `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create 
time',
     `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update 
time',
     unique key `idx_role_menu` (`role_id`, `menu_id`)
+) engine = innodb;
+
+CREATE TABLE if not exists `catalog`
+(
+    `id`          int(11)     not null auto_increment primary key comment 'id',
+    `catalog_type`     varchar(50)  not null comment 'catalog type',
+    `catalog_name`     varchar(100)     not null comment 'catalog name',
+    `warehouse`     varchar(200)     not null comment 'warehouse',
+    `hive_uri`     varchar(200)     comment 'hive uri',
+    `hive_conf_dir`     varchar(100)   comment 'catalog name',
+    `is_delete`   tinyint(1)   NOT NULL DEFAULT 0 COMMENT 'is delete',
+    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create 
time',
+    `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update 
time'
 ) engine = innodb;
\ No newline at end of file
diff --git a/paimon-web-server/src/main/resources/db/dml-h2.sql 
b/paimon-web-server/src/main/resources/db/dml-h2.sql
index a8b0c54..6b0cfa6 100644
--- a/paimon-web-server/src/main/resources/db/dml-h2.sql
+++ b/paimon-web-server/src/main/resources/db/dml-h2.sql
@@ -59,4 +59,9 @@ values (1, 1),
        (1, 1001),
        (1, 1002),
        (1, 1003),
-       (1, 1004);
\ No newline at end of file
+       (1, 1004);
+
+insert into `catalog` (catalog_type, catalog_name, warehouse)
+values ('filesystem', 'paimon', 'file:///D:/path/'),
+       ('filesystem', 'fts', 'file:///D:/path/'),
+       ('filesystem', 'streaming_warehouse', 'file:///D:/path/')
diff --git a/paimon-web-server/src/main/resources/i18n/messages.properties 
b/paimon-web-server/src/main/resources/i18n/messages.properties
index ed08361..a96af9f 100644
--- a/paimon-web-server/src/main/resources/i18n/messages.properties
+++ b/paimon-web-server/src/main/resources/i18n/messages.properties
@@ -30,3 +30,5 @@ role.key.exist=This role key {0} is exist
 menu.in.used=This menu is in used
 menu.name.exist=This menu name is exist:{0}
 menu.path.invalid=This menu path is invalid:{0}
+catalog.name.exist=This catalog name {0} is exist
+catalog.create.error=An exception is returned when calling the Paimon API to 
create a Catalog.
diff --git a/paimon-web-ui/index.html b/paimon-web-ui/index.html
index 85d1141..6d0b55b 100644
--- a/paimon-web-ui/index.html
+++ b/paimon-web-ui/index.html
@@ -23,7 +23,7 @@ under the License.
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/favicon_blue.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Paimon Manager</title>
+    <title>Apache Paimon</title>
   </head>
   <body>
     <div id="root"></div>
diff --git a/paimon-web-ui/package.json b/paimon-web-ui/package.json
index e9ef617..7fe6537 100644
--- a/paimon-web-ui/package.json
+++ b/paimon-web-ui/package.json
@@ -13,14 +13,18 @@
     "@douyinfe/semi-icons": "^2.38.1",
     "@douyinfe/semi-ui": "^2.38.1",
     "@monaco-editor/react": "^4.5.1",
+    "@reduxjs/toolkit": "^1.9.5",
     "@semi-bot/semi-theme-doucreator": "^1.0.18",
     "@semi-bot/semi-theme-figma": "^0.1.6",
+    "axios": "^1.4.0",
+    "http-proxy-middleware": "^2.0.6",
     "less": "^4.1.3",
     "monaco-editor": "^0.40.0",
     "react": "^18.2.0",
     "react-bootstrap": "^2.8.0",
     "react-dom": "^18.2.0",
-    "react-icons": "^4.10.1"
+    "react-icons": "^4.10.1",
+    "zustand": "^4.4.1"
   },
   "devDependencies": {
     "@types/node": "^20.3.2",
diff --git a/paimon-web-ui/src/api/api.ts b/paimon-web-ui/src/api/api.ts
new file mode 100644
index 0000000..679728c
--- /dev/null
+++ b/paimon-web-ui/src/api/api.ts
@@ -0,0 +1,54 @@
+/* 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 http from '@api/http'
+import {API_ENDPOINTS} from '@api/endpoints';
+import Result = API.Result;
+import {CatalogItemList} from "@src/types/Catalog/data";
+
+export const createFileSystemCatalog = async (catalogProp: Prop.CatalogProp) 
=> {
+    try {
+        return await http.httpPost<Result<any>, 
Prop.CatalogProp>(API_ENDPOINTS.CREATE_FILE_SYSTEM_CATALOG, catalogProp);
+    } catch (error) {
+        console.error('Failed to create catalog:', error);
+    }
+};
+
+export const createHiveCatalog = async (catalogProp: Prop.CatalogProp) => {
+    try {
+        return await http.httpPost<Result<any>, 
Prop.CatalogProp>(API_ENDPOINTS.CREATE_HIVE_CATALOG, catalogProp);
+    } catch (error) {
+        console.error('Failed to create catalog:', error);
+    }
+};
+
+export const getAllCatalogs = async () => {
+    try {
+        return await http.httpGet<Result<CatalogItemList>, 
null>(API_ENDPOINTS.GET_ALL_CATALOGS)
+    } catch (error: any) {
+        console.error('Failed to get catalogs:', error);
+    }
+}
+
+const Api = {
+    createFileSystemCatalog,
+    createHiveCatalog,
+    getAllCatalogs
+}
+
+export default Api;
+
diff --git a/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx 
b/paimon-web-ui/src/api/data.d.ts
similarity index 58%
copy from paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
copy to paimon-web-ui/src/api/data.d.ts
index b2a016c..2381a78 100644
--- a/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
+++ b/paimon-web-ui/src/api/data.d.ts
@@ -15,20 +15,31 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import { IconPlus } from "@douyinfe/semi-icons";
-import CatalogTree from 
"@pages/Metadata/components/LeftContent/components/CatalogTree";
-import styles from "./left-content.module.less";
+declare namespace API {
+    export interface Result<T> {
+        code: number;
+        msg: string;
+        data: T;
+    }
 
-const MetadataSidebar = () => {
-    return(
-        <div className={styles.container}>
-            <div className={styles['add-catalog-container']}>
-                <span>Catalog</span>
-                <IconPlus/>
-            </div>
-            <CatalogTree/>
-        </div>
-    )
+    export interface PageData<T> {
+        records: T[];
+        page: number;
+        size: number;
+        total: number;
+    }
+
+    export type PageResult<T> = Result<PageData<T>>
 }
 
-export default MetadataSidebar;
+declare namespace Prop {
+    // Catalog
+    export interface CatalogProp {
+        catalogName: string,
+        catalogType: string,
+        warehouse: string,
+        hiveUri: string,
+        hiveConfDir: string,
+        isDelete: boolean
+    }
+}
\ No newline at end of file
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
 b/paimon-web-ui/src/api/endpoints.ts
similarity index 77%
copy from 
paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
copy to paimon-web-ui/src/api/endpoints.ts
index 9f178b6..dc7a79f 100644
--- 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
+++ b/paimon-web-ui/src/api/endpoints.ts
@@ -15,8 +15,9 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-.catalog-tree-input {
-  background-color: var(--semi-color-bg-0);
-  width: 347px;
-  margin-left: -12px;
-}
\ No newline at end of file
+export const API_ENDPOINTS = {
+    // catalog
+    CREATE_FILE_SYSTEM_CATALOG: '/catalog/createFilesystemCatalog',
+    CREATE_HIVE_CATALOG: '/catalog/createHiveCatalog',
+    GET_ALL_CATALOGS: '/catalog/getAllCatalogs',
+};
\ No newline at end of file
diff --git a/paimon-web-ui/src/api/http.ts b/paimon-web-ui/src/api/http.ts
new file mode 100644
index 0000000..e1364c6
--- /dev/null
+++ b/paimon-web-ui/src/api/http.ts
@@ -0,0 +1,89 @@
+/* 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 axios, { AxiosResponse, AxiosError } from 'axios';
+
+const httpClient = axios.create({
+    baseURL: '/api',
+    timeout:  30 * 1000,
+    withCredentials: false,
+    headers: {
+        'Content-Type': 'application/json'
+    }
+});
+
+// request interceptor.
+httpClient.interceptors.request.use(
+    (config: any) => {
+        // Here you can set request headers like:config.headers['Token'] = 
localStorage.getItem('Token').
+       /* config.headers = {
+            "Authorization": store.getState().user.token
+        }*/
+        return config;
+    },
+    (error: AxiosError) => {
+        // Handle request errors
+        console.log('request:', error);
+        return Promise.reject(error);
+    }
+);
+
+// response interceptor.
+httpClient.interceptors.response.use(
+    (response: AxiosResponse) => {
+        // Here you can process the response data.
+        return response;
+    },
+    (error: AxiosError) => {
+        // Handle response errors.
+        console.log('response:', error);
+        return Promise.reject(error);
+    }
+);
+
+const httpGet = async <T, E>(url: string, param?: E): Promise<T> => {
+    const {data} = await httpClient.get<T>(url, {params: param})
+    return data
+}
+
+const httpPost = async <T, E>(url: string, param: E): Promise<T> => {
+    const {data} = await httpClient.post<T>(url, param)
+    return data
+}
+
+const httpDelete = async <T, E>(url: string, param: E): Promise<T> => {
+    const {data} = await httpClient.delete(url, {params: param})
+    return data
+}
+
+const httpFormPost = async <T, E>(url: string, params?: E): Promise<T> => {
+    const {data} = await httpClient.post(url, params, {
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        }
+    })
+    return data
+}
+
+const http = {
+    httpGet,
+    httpPost,
+    httpDelete,
+    httpFormPost
+}
+
+export default http;
\ No newline at end of file
diff --git a/paimon-web-ui/src/app/App.less b/paimon-web-ui/src/app/App.less
index aa5443c..e14bbbd 100644
--- a/paimon-web-ui/src/app/App.less
+++ b/paimon-web-ui/src/app/App.less
@@ -24,7 +24,10 @@ body,
   padding: 0;
   color: rgba(0, 0, 0, 0.65) !important;
   //--semi-color-primary: rgba(var(--semi-violet-6), 1);
-  --semi-color-primary: rgba(var(--semi-grey-6), 1);
-  --semi-color-primary-hover: rgba(var(--semi-violet-3), 1);
+  //--semi-color-primary: rgba(var(--semi-grey-6), 1);
+  //--semi-color-primary: rgba(var(--semi-blue-5), 1);
+  //--semi-color-primary-hover: rgba(var(--semi-violet-3), 1);
+  //--semi-color-primary-hover: rgba(var(--semi-blue-6), 1);
+  //--semi-color-primary-active: rgba(var(--semi-blue-7), 1);
   --semi-color-primary-light-default: rgba(var(--semi-color-bg-0), 1);
 }
diff --git a/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx 
b/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
index 722c206..f533505 100644
--- a/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
+++ b/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
@@ -65,7 +65,7 @@ const CatalogDropdown: React.FC<CatalogDropdownProps> = 
(props) => {
             placeholder="Select Catalog"
             optionList={renderCatalogList()}
             showClear
-            style={{display: "flex", flexGrow: 1, backgroundColor: 
"var(--semi-color-bg-0)"}}
+            style={{display: "flex", flexGrow: 1}}
         />
     </>
 }
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogModalForm/CatalogForm/index.tsx
 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogModalForm/CatalogForm/index.tsx
new file mode 100644
index 0000000..55cd745
--- /dev/null
+++ 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogModalForm/CatalogForm/index.tsx
@@ -0,0 +1,104 @@
+/* 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 { Form } from '@douyinfe/semi-ui';
+
+// @ts-ignore
+const CatalogForm = ({ getFormApi }) => {
+    let message = 'This item is required.';
+    return(
+       <>
+           <Form
+               wrapperCol={{ span: 20 }}
+               labelCol={{ span: 4 }}
+               labelPosition='left'
+               labelAlign='left'
+               getFormApi={getFormApi}
+           >
+               {
+                   ({ formState }) => (
+                       <>
+                           <Form.Input
+                               field="catalogName"
+                               label='Catalog Name'
+                               trigger='blur'
+                               rules={[
+                                   { required: true, message },
+                               ]}
+                               style={{ width: "100%" }}/>
+
+                           <Form.Select
+                               field="catalogType"
+                               label='Catalog Type'
+                               placeholder={"please select catalog"}
+                               rules={[
+                                   { required: true, message },
+                               ]}
+                               style={{ width: "100%" }}>
+                               <Form.Select.Option 
value="filesystem">FileSystem</Form.Select.Option>
+                               <Form.Select.Option 
value="hive">Hive</Form.Select.Option>
+                           </Form.Select>
+
+                           {formState.values.catalogType === 'filesystem' ? (
+                               <Form.Input
+                                   field="warehouse"
+                                   label='Warehouse'
+                                   trigger='blur'
+                                   rules={[
+                                       { required: true, message },
+                                   ]}
+                                   style={{ width: "100%" }}/>
+                           )
+                               :
+                               <>
+                                   <Form.Input
+                                       field="warehouse"
+                                       label='Warehouse'
+                                       trigger='blur'
+                                       rules={[
+                                           { required: true, message },
+                                       ]}
+                                       style={{ width: "100%" }}/>
+
+                                   <Form.Input
+                                       field="uri"
+                                       label='Hive Uri'
+                                       trigger='blur'
+                                       rules={[
+                                           { required: true, message },
+                                       ]}
+                                       style={{ width: "100%" }}/>
+
+                                   <Form.Input
+                                       field="hiveConfDir"
+                                       label='Hive Conf Dir'
+                                       trigger='blur'
+                                       rules={[
+                                           { required: true, message },
+                                       ]}
+                                       style={{ width: "100%" }}/>
+                               </>
+                           }
+                       </>
+                   )
+               }
+           </Form>
+       </>
+    );
+}
+
+export default CatalogForm;
\ No newline at end of file
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogModalForm/index.tsx
 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogModalForm/index.tsx
new file mode 100644
index 0000000..05780b1
--- /dev/null
+++ 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogModalForm/index.tsx
@@ -0,0 +1,58 @@
+/* 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} from "react";
+import { Modal } from '@douyinfe/semi-ui';
+import CatalogForm from 
"@pages/Metadata/components/LeftContent/components/CatalogModalForm/CatalogForm";
+
+type CatalogModalFormProps = {
+    visible: boolean;
+    onClose: () => void;
+    onOk: (formApi: any) => void;
+};
+
+
+const CatalogModalForm: React.FC<CatalogModalFormProps> = ({ visible , 
onClose, onOk }) => {
+    const [formApi, setFormApi] = useState(null);
+
+    const getFormApi = (api: any) => {
+        setFormApi(api);
+    };
+
+    const handleOkClick = async () => {
+        await onOk(formApi);
+        onClose();
+    };
+
+    return(
+        <Modal
+            title="Create Catalog"
+            visible = {visible}
+            onCancel= {onClose}
+            maskClosable={false}
+            okText={'Submit'}
+            cancelText={'Cancel'}
+            width={'750px'}
+            height={'550px'}
+            onOk={() => handleOkClick()}
+        >
+            <CatalogForm getFormApi={getFormApi}/>
+        </Modal>
+    );
+}
+
+export default CatalogModalForm;
\ No newline at end of file
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
index 9f178b6..4f3f847 100644
--- 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
+++ 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/catalog-tree.module.less
@@ -16,7 +16,7 @@ specific language governing permissions and limitations
 under the License. */
 
 .catalog-tree-input {
-  background-color: var(--semi-color-bg-0);
+  //background-color: var(--semi-color-bg-0);
   width: 347px;
   margin-left: -12px;
 }
\ No newline at end of file
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
index cc61ee8..e3fdd71 100644
--- 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
+++ 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
@@ -16,116 +16,38 @@ specific language governing permissions and limitations
 under the License. */
 
 import {Input, Tree} from '@douyinfe/semi-ui';
-import { IconSearch } from "@douyinfe/semi-icons";
+import { IconSearch, IconFile } from "@douyinfe/semi-icons";
+import {useEffect, useState} from "react";
 import styles from "./catalog-tree.module.less"
+import { useCatalogStore } from "@src/store/catalogStore.ts";
 
 const CatalogTree = () => {
-    const treeData = [
-        {
-            label: 'paimon_catalog_01',
-            value: 'catalog01',
-            key: '0',
-            children: [
-                {
-                    label: 'paimon_db_01',
-                    value: 'db01',
-                    key: '0-0',
-                    children: [
-                        {
-                            label: 'paimon_table_01',
-                            value: 'paimon_table_01',
-                            key: '0-0-0',
-                        },
-                        {
-                            label: 'paimon_table_02',
-                            value: 'paimon_table_02',
-                            key: '0-1-0',
-                        },
-                        {
-                            label: 'paimon_table_03',
-                            value: 'paimon_table_03',
-                            key: '0-2-0',
-                        },
-                        {
-                            label: 'paimon_table_04',
-                            value: 'paimon_table_04',
-                            key: '0-3-0',
-                        },
-                        {
-                            label: 'paimon_table_05',
-                            value: 'paimon_table_05',
-                            key: '0-4-0',
-                        },
-                    ],
-                },
-                {
-                    label: 'paimon_db_02',
-                    value: 'paimon_db_02',
-                    key: '0-1',
-                    children: [
-                        {
-                            label: 'Osaka',
-                            value: 'Osaka',
-                            key: '0-1-0'
-                        }
-                    ]
-                },
-                {
-                    label: 'paimon_db_03',
-                    value: 'paimon_db_03',
-                    key: '0-2',
-                    children: [
-                        {
-                            label: 'Beijing',
-                            value: 'Beijing',
-                            key: '0-2-0',
-                        },
-                    ],
-                },
-                {
-                    label: 'paimon_db_04',
-                    value: 'paimon_db_04',
-                    key: '0-3',
-                    children: [
-                        {
-                            label: 'Beijing',
-                            value: 'Beijing',
-                            key: '0-3-0',
-                        },
-                    ],
-                },
-                {
-                    label: 'paimon_db_05',
-                    value: 'paimon_db_05',
-                    key: '0-4',
-                    children: [
-                        {
-                            label: 'Beijing',
-                            value: 'Beijing',
-                            key: '0-4-0',
-                        },
-                    ],
-                },
-            ],
-        },
-        {
-            label: 'paimon_catalog_02',
-            value: 'paimon_catalog_02',
-            key: '1',
-            children: [
-                {
-                    label: 'United States',
-                    value: 'United States',
-                    key: '1-0'
-                },
-                {
-                    label: 'Canada',
-                    value: 'Canada',
-                    key: '1-1'
-                }
-            ]
-        }
-    ];
+
+    type TreeDataItem = {
+        label: string;
+        value: string;
+        key: string;
+        children?: TreeDataItem[];
+    };
+
+    const [treeData, setTreeData] = useState<TreeDataItem[]>([]);
+    const fetchCatalogData = useCatalogStore(state => state.fetchCatalogData);
+    const catalogItemList = useCatalogStore(state => state.catalogItemList);
+
+    useEffect(() => {
+        // Fetch the catalog data when the component mounts
+        fetchCatalogData();
+    }, [fetchCatalogData]);
+
+    useEffect(() => {
+        // Update treeData when catalogItemList changes
+        const transformedData = catalogItemList.map(item => ({
+            label: item.catalogName,
+            value: item.catalogName,
+            key: item.id.toString(),
+        }));
+        setTreeData(transformedData);
+    }, [catalogItemList]);
 
     const renderLabel = (x: any) => {
         const className = x.className;
@@ -141,13 +63,12 @@ const CatalogTree = () => {
                 role="treeitem"
                 onClick={isLeaf ? onClick : onExpand}
             >
-                {isLeaf ? null : expandIcon}
+                {isLeaf ? <IconFile style={{marginRight: "8px", color: 
"var(--semi-color-text-2)"}}/> : expandIcon}
                 <span>{label}</span>
             </li>
         );
     };
 
-
     return(
         <Tree
             filterTreeNode
diff --git a/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
index b2a016c..b03a999 100644
--- a/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
+++ b/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
@@ -17,16 +17,75 @@ under the License. */
 
 import { IconPlus } from "@douyinfe/semi-icons";
 import CatalogTree from 
"@pages/Metadata/components/LeftContent/components/CatalogTree";
+import { useState } from "react";
+import CatalogModalForm from 
"@pages/Metadata/components/LeftContent/components/CatalogModalForm";
+import {useCatalogStore} from "@src/store/catalogStore.ts";
 import styles from "./left-content.module.less";
 
 const MetadataSidebar = () => {
+
+    const [showModal, setShowModal] = useState(false);
+    const createFilesystemCatalog = useCatalogStore(state => 
state.createFileSystemCatalog);
+    const createHiveCatalog = useCatalogStore(state => 
state.createHiveCatalog);
+    const fetchCatalogData = useCatalogStore(state => state.fetchCatalogData);
+
+    const handleOpenModal = () => {
+        setShowModal(true);
+    };
+
+    const handleCloseModal = () => {
+        setShowModal(false);
+    };
+
+    const handleOk = (formApi: any) => {
+        return new Promise<void>((resolve, reject) => {
+            formApi
+                .validate()
+                .then((values: any) => {
+                    // Assuming values contains the form data
+                    const formData = values;
+
+                    const catalogProp: Prop.CatalogProp = {
+                        catalogName: formData.catalogName,
+                        catalogType: formData.catalogType,
+                        warehouse: formData.warehouse,
+                        hiveUri: formData.hiveUri,
+                        hiveConfDir: formData.hiveConfDir,
+                        isDelete: false
+                    };
+
+                    if (formData.catalogType === 'filesystem') {
+                        createFilesystemCatalog(catalogProp)
+                            .then(() => {
+                                fetchCatalogData();
+                                resolve();
+                            })
+                    } else {
+                        createHiveCatalog(catalogProp)
+                            .then(() => {
+                                fetchCatalogData();
+                                resolve();
+                            })
+                    }
+                    resolve();
+                })
+                .catch((errors: any) => {
+                    console.log(errors);
+                    reject(errors);
+                });
+        });
+    };
+
     return(
         <div className={styles.container}>
             <div className={styles['add-catalog-container']}>
                 <span>Catalog</span>
-                <IconPlus/>
+                <IconPlus className={styles.iconPlus}  
onClick={handleOpenModal}/>
             </div>
             <CatalogTree/>
+            {showModal && (
+                <CatalogModalForm visible={showModal} 
onClose={handleCloseModal} onOk={handleOk}/>
+            )}
         </div>
     )
 }
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
index bcee825..862b5c5 100644
--- 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
+++ 
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
@@ -28,4 +28,12 @@ under the License. */
     flex-direction: row;
     justify-content: space-between;
     align-items: center;
+}
+
+.iconPlus {
+    transition: box-shadow 0.1s ease-in-out;
+}
+
+.iconPlus:hover {
+    box-shadow: 0px 0px 30px rgba(var(--semi-light-blue-5), 1);
 }
\ No newline at end of file
diff --git 
a/paimon-web-ui/src/pages/Playground/components/RightContent/components/TopPanel/Tab/index.tsx
 
b/paimon-web-ui/src/pages/Playground/components/RightContent/components/TopPanel/Tab/index.tsx
index 0eb8710..3397ad3 100644
--- 
a/paimon-web-ui/src/pages/Playground/components/RightContent/components/TopPanel/Tab/index.tsx
+++ 
b/paimon-web-ui/src/pages/Playground/components/RightContent/components/TopPanel/Tab/index.tsx
@@ -40,7 +40,7 @@ const Index = () => {
     return (
         <Tabs type="card" defaultActiveKey="1" onTabClose={close}>
             {tabList.map((t) => (
-                <TabPane closable={t.closable} tab={<span><IconCode 
style={{color: "rgba(var(--semi-violet-6), 1)"}}/>{t.tab}</span>} 
itemKey={t.itemKey} key={t.itemKey}>
+                <TabPane closable={t.closable} tab={<span><IconCode 
style={{color: "rgba(var(--semi-blue-5), 1)"}}/>{t.tab}</span>} 
itemKey={t.itemKey} key={t.itemKey}>
                     {t.content}
                 </TabPane>
             ))}
diff --git 
a/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/tree-component.module.less
 
b/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/tree-component.module.less
index 9c77a32..892457f 100644
--- 
a/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/tree-component.module.less
+++ 
b/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/tree-component.module.less
@@ -22,3 +22,7 @@ under the License. */
 .catalog-tree-input {
   background-color: var(--semi-color-bg-0);
 }
+
+.selected-node {
+  background-color: #ff0000;
+}
diff --git a/paimon-web-ui/src/store/catalogStore.ts 
b/paimon-web-ui/src/store/catalogStore.ts
new file mode 100644
index 0000000..a1efcf8
--- /dev/null
+++ b/paimon-web-ui/src/store/catalogStore.ts
@@ -0,0 +1,94 @@
+/* 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 {create} from 'zustand';
+import {persist} from "zustand/middleware";
+import {CatalogItemList} from "@src/types/Catalog/data";
+import Api from "@api/api.ts";
+import {Toast} from "@douyinfe/semi-ui";
+
+type Store = {
+    catalogItemList: CatalogItemList;
+    createFileSystemCatalog: (catalogProp: Prop.CatalogProp) => Promise<void>;
+    createHiveCatalog: (catalogProp: Prop.CatalogProp) => Promise<void>;
+    fetchCatalogData: () => Promise<void>;
+};
+
+export const useCatalogStore = create<Store>()(persist(
+    (set) => ({
+        catalogItemList: [],
+        createFileSystemCatalog: async (catalogProp) => {
+            try {
+                const response = await 
Api.createFileSystemCatalog(catalogProp);
+                if (!response) {
+                    throw new Error('No response from 
createFileSystemCatalog');
+                }
+                if (response.code === 200) {
+                    Toast.success('Catalog created successfully!');
+                } else {
+                    console.error('Failed to create catalog:', response.msg);
+                    Toast.error('Failed to create catalog:' +  response.msg);
+                }
+            } catch (error) {
+                console.error('Failed to create catalog:', error);
+                Toast.error('Failed to create catalog:' + error);
+            }
+        },
+        createHiveCatalog: async (catalogProp) => {
+            try {
+                const response = await Api.createHiveCatalog(catalogProp);
+                if (!response) {
+                    throw new Error('No response from 
createFileSystemCatalog');
+                }
+                if (response.code === 200) {
+                    Toast.success('Catalog created successfully!');
+                } else {
+                    console.error('Failed to create catalog:', response.msg);
+                    Toast.error('Failed to create catalog:' +  response.msg);
+                }
+            } catch (error) {
+                console.error('Failed to create catalog:', error);
+                Toast.error('Failed to create catalog:' + error);
+            }
+        },
+        fetchCatalogData: async () => {
+            try {
+                const result = await Api.getAllCatalogs();
+                if (result && result.data) {
+                    const newCatalogItemList = result.data.map((item) => {
+                        return {
+                            id: item.id,
+                            catalogName: item.catalogName,
+                            catalogType: item.catalogType,
+                            warehouse: item.warehouse,
+                            hiveUri: item.hiveUri,
+                            hiveConfDir: item.hiveConfDir,
+                            isDelete: item.isDelete,
+                            createTime: item.createTime,
+                            updateTime: item.updateTime
+                        };
+                    });
+                    set((state) => ({ ...state, catalogItemList: 
newCatalogItemList }));
+                }
+            } catch (error) {
+                console.error('Failed to get catalogs:', error);
+            }
+        },
+    }),{
+        name: 'catalog-storage'
+    }
+))
\ No newline at end of file
diff --git 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
 b/paimon-web-ui/src/types/Catalog/data.d.ts
similarity index 73%
copy from 
paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
copy to paimon-web-ui/src/types/Catalog/data.d.ts
index bcee825..2bf4e51 100644
--- 
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/left-content.module.less
+++ b/paimon-web-ui/src/types/Catalog/data.d.ts
@@ -15,17 +15,16 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-.container {
-    width: 347px;
-    height: 100%;
-    padding: 10px 24px;
+export type CatalogItem = {
+    id: number,
+    catalogName: string,
+    catalogType: string,
+    warehouse: string,
+    hiveUri: string,
+    hiveConfDir: string,
+    isDelete: boolean,
+    createTime: Date,
+    updateTime: Date
 }
 
-.add-catalog-container {
-    color: var(--semi-color-text-0);
-    margin: 6px 0 16px;
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-    align-items: center;
-}
\ No newline at end of file
+export type CatalogItemList = CatalogItem[];
\ No newline at end of file
diff --git a/paimon-web-ui/tsconfig.json b/paimon-web-ui/tsconfig.json
index dc41217..eb79bd4 100644
--- a/paimon-web-ui/tsconfig.json
+++ b/paimon-web-ui/tsconfig.json
@@ -27,9 +27,10 @@
       "@config/*": ["src/config/*"],
       "@utils/*": ["src/utils/*"],
       "@pages/*": ["src/pages/*"],
+      "@api/*": ["src/api/*"],
       "@mock/*": ["mock/*"]
     }
   },
-  "include": ["src"],
+  "include": ["src","./**/*.d.ts", "./**/*.ts", "./**/*.tsx"],
   "references": [{ "path": "./tsconfig.node.json" }]
 }
diff --git a/paimon-web-ui/vite.config.ts b/paimon-web-ui/vite.config.ts
index 812d23e..1a9f901 100644
--- a/paimon-web-ui/vite.config.ts
+++ b/paimon-web-ui/vite.config.ts
@@ -22,9 +22,9 @@ import { resolve } from 'path'
 
 export default defineConfig({
   plugins: [
-    SemiPlugin({
+    /*SemiPlugin({
       theme: "@semi-bot/semi-theme-figma"
-    }),
+    }),*/
     react()
   ],
   resolve: {
@@ -35,7 +35,16 @@ export default defineConfig({
       '@pages': resolve(__dirname, './src/pages'),
       '@utils': resolve(__dirname, './src/utils'),
       '@config': resolve(__dirname, './src/config'),
-      '@mock': resolve(__dirname, './mock')
+      '@mock': resolve(__dirname, './mock'),
+      '@api': resolve(__dirname, './src/api')
+    }
+  },
+  server: {
+    proxy: {
+      '/api': {
+        target: 'http://localhost:10088',
+        changeOrigin: true
+      }
     }
   }
 })
diff --git a/pom.xml b/pom.xml
index 1194f24..028091e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -435,6 +435,9 @@ under the License.
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-surefire-plugin</artifactId>
                     <version>${maven-surefire-plugin.version}</version>
+                    <configuration>
+                        <skip>true</skip>
+                    </configuration>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>

Reply via email to