You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2023/02/27 03:09:24 UTC

[kyuubi] 01/01: Kyuubi UI draft

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

chengpan pushed a commit to branch PR_4002-rebase
in repository https://gitbox.apache.org/repos/asf/kyuubi.git

commit 367682af9b671d4a3e3edc728f8eda70daf31ed0
Author: hezhao2 <he...@cisco.com>
AuthorDate: Mon Nov 14 14:36:08 2022 +0800

    Kyuubi UI draft
---
 .rat-excludes                                      |    1 +
 build/Dockerfile                                   |    2 +-
 docs/deployment/settings.md                        |    6 +
 .../engine/spark/SparkTBinaryFrontendService.scala |   27 +-
 .../spark/ZookeeperSparkEngineRegisterSuite.scala  |   44 +
 .../org/apache/kyuubi/config/KyuubiConf.scala      |    7 +
 .../apache/kyuubi/config/KyuubiReservedKeys.scala  |    5 +
 .../org/apache/kyuubi/server/log/ServerLog.scala   |  167 ++++
 .../apache/kyuubi/session/AbstractSession.scala    |    4 +
 .../scala/org/apache/kyuubi/session/Session.scala  |    4 +-
 .../apache/kyuubi/ha/client/DiscoveryClient.scala  |    4 +-
 .../zookeeper/ZookeeperDiscoveryClient.scala       |   57 +-
 .../apache/kyuubi/client/api/v1/dto/Engine.java    |  117 ++-
 .../kyuubi/client/api/v1/dto/HadoopConfData.java   |   59 ++
 .../apache/kyuubi/client/api/v1/dto/SQLDetail.java |  168 ++++
 .../apache/kyuubi/client/api/v1/dto/Server.java    |  148 +++
 .../apache/kyuubi/client/api/v1/dto/ServerLog.java |   74 ++
 kyuubi-server/pom.xml                              |   11 +
 .../kyuubi/events/KyuubiOperationEvent.scala       |   15 +-
 .../apache/kyuubi/events/KyuubiSessionEvent.scala  |    8 +-
 .../kyuubi/server/KyuubiRestFrontendService.scala  |   19 +-
 .../org/apache/kyuubi/server/KyuubiServer.scala    |   12 +
 .../server/KyuubiTBinaryFrontendService.scala      |    4 +
 .../kyuubi/server/api/WebUIProxyServlet.scala      |   81 ++
 .../kyuubi/server/api/v1/AdminResource.scala       |  192 +++-
 .../kyuubi/server/api/v1/ApiRootResource.scala     |   28 +-
 .../kyuubi/server/api/v1/OperationsResource.scala  |  114 ++-
 .../kyuubi/server/api/v1/SessionsResource.scala    |  119 ++-
 .../apache/kyuubi/session/KyuubiSessionImpl.scala  |   14 +-
 .../scala/org/apache/kyuubi/util/OSUtils.scala}    |   25 +-
 kyuubi-server/web-ui/package.json                  |    1 +
 kyuubi-server/web-ui/pnpm-lock.yaml                | 1045 ++++++++++----------
 .../src/{locales/en_US => api/server}/index.ts     |   33 +-
 .../en_US/index.ts => api/server/types.ts}         |   12 +-
 kyuubi-server/web-ui/src/api/session/index.ts      |   76 ++
 .../en_US/index.ts => api/session/types.ts}        |   13 +-
 kyuubi-server/web-ui/src/components/menu/index.vue |   32 +-
 .../web-ui/src/components/modal/index.vue          |  112 +++
 kyuubi-server/web-ui/src/locales/en_US/index.ts    |   41 +-
 kyuubi-server/web-ui/src/locales/zh_CN/index.ts    |   43 +-
 kyuubi-server/web-ui/src/router/index.ts           |   15 +-
 .../src/router/{contact => run-sql}/index.ts       |    6 +-
 .../src/router/{operation => server}/index.ts      |   12 +-
 .../src/router/{workload => session}/index.ts      |   23 +-
 kyuubi-server/web-ui/src/styles/element/index.scss |    9 +-
 kyuubi-server/web-ui/src/styles/index.scss         |   10 +
 kyuubi-server/web-ui/src/utils/index.ts            |   24 +
 kyuubi-server/web-ui/src/utils/request.ts          |    2 +-
 .../views/common/use-table.ts}                     |   43 +-
 .../src/views/layout/components/aside/index.vue    |   62 +-
 .../src/views/layout/components/aside/types.ts     |   71 --
 kyuubi-server/web-ui/src/views/layout/index.vue    |    7 +-
 .../src/views/operation/completedJobs/index.vue    |   29 -
 .../src/views/operation/runningJobs/index.vue      |   29 -
 .../src/views/{contact => run-sql}/index.vue       |    8 +-
 .../web-ui/src/views/server/engine/index.vue       |  122 +++
 .../src/views/server/kyuubi-service/index.vue      |   79 ++
 .../web-ui/src/views/session/operation/index.vue   |  261 +++++
 .../src/views/session/session-statistics/index.vue |  172 ++++
 .../src/views/session/sql-statistics/index.vue     |  203 ++++
 .../web-ui/src/views/workload/analysis/index.vue   |   29 -
 .../web-ui/src/views/workload/query/index.vue      |   29 -
 .../web-ui/src/views/workload/queue/index.vue      |   29 -
 .../web-ui/src/views/workload/session/index.vue    |   29 -
 kyuubi-server/web-ui/vite.config.ts                |    9 +
 pom.xml                                            |    6 +
 66 files changed, 3314 insertions(+), 948 deletions(-)

diff --git a/.rat-excludes b/.rat-excludes
index 7a841cf9c..fa47484e8 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -52,6 +52,7 @@ build/scala-*/**
 **/*.derby.sql
 **/*.mysql.sql
 **/node/**
+**/web-ui/**
 **/web-ui/dist/**
 **/pnpm-lock.yaml
 **/node_modules/**
diff --git a/build/Dockerfile b/build/Dockerfile
index 8ecc6c8b7..8a99465ad 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -76,7 +76,7 @@ RUN set -ex && \
     ln -snf /bin/bash /bin/sh && \
     useradd -u ${kyuubi_uid} -g root kyuubi && \
     mkdir -p ${KYUUBI_HOME} ${KYUUBI_LOG_DIR} ${KYUUBI_PID_DIR} ${KYUUBI_WORK_DIR_ROOT} && \
-    chmod ug+rw -R ${KYUUBI_HOME} && \
+    chmod ug+rwx -R ${KYUUBI_HOME} && \
     chmod a+rwx -R ${KYUUBI_WORK_DIR_ROOT} && \
     rm -rf /var/cache/apt/*
 
diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md
index dac72825e..a066dcd64 100644
--- a/docs/deployment/settings.md
+++ b/docs/deployment/settings.md
@@ -447,6 +447,12 @@ You can configure the Kyuubi properties in `$KYUUBI_HOME/conf/kyuubi-defaults.co
 | kyuubi.operation.spark.listener.enabled         | true                                                                            | When set to true, Spark engine registers an SQLOperationListener before executing the statement, logging a few summary statistics when each stage completes.                                                                                                                                                                                                           [...]
 | kyuubi.operation.status.polling.timeout         | PT5S                                                                            | Timeout(ms) for long polling asynchronous running sql query's status                                                                                                                                                                                                                                                                                                   [...]
 
+### Proxy
+
+|              Key              | Default |                    Meaning                    |  Type   | Since |
+|-------------------------------|---------|-----------------------------------------------|---------|-------|
+| kyuubi.proxy.spark.ui.enabled | false   | Whether to enabled kyuubi help proxy spark ui | boolean | 1.7.0 |
+
 ### Server
 
 |                           Key                            |      Default      |                                                                                                                    Meaning                                                                                                                     |   Type   | Since |
diff --git a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/SparkTBinaryFrontendService.scala b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/SparkTBinaryFrontendService.scala
index d4eaf3454..b36cac071 100644
--- a/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/SparkTBinaryFrontendService.scala
+++ b/externals/kyuubi-spark-sql-engine/src/main/scala/org/apache/kyuubi/engine/spark/SparkTBinaryFrontendService.scala
@@ -18,6 +18,7 @@
 package org.apache.kyuubi.engine.spark
 
 import scala.collection.JavaConverters._
+import scala.collection.mutable
 
 import org.apache.hadoop.io.Text
 import org.apache.hadoop.security.{Credentials, UserGroupInformation}
@@ -94,13 +95,37 @@ class SparkTBinaryFrontendService(
   }
 
   override def attributes: Map[String, String] = {
-    Map(KYUUBI_ENGINE_ID -> KyuubiSparkUtil.engineId)
+    val settings = sc.getConf.getAll.toMap
+    val memory = sc.getExecutorMemoryStatus.size + "*" +
+      settings.get(SPARK_ENGINE_EXECUTOR_MEMORY).orNull
+
+    val cores = sc.getExecutorMemoryStatus.size + "*" +
+      settings.get(SPARK_ENGINE_EXECUTOR_CORES).orNull
+    val address = java.net.InetAddress.getLocalHost.getHostAddress
+
+    val attr = new mutable.HashMap[String, String]
+    attr.put(KYUUBI_ENGINE_ID, KyuubiSparkUtil.engineId)
+    attr.put(KYUUBI_ENGINE_SUBMIT_TIME_KEY, sc.startTime.toString)
+    attr.put(KYUUBI_ENGINE_MEMORY, memory)
+    attr.put(KYUUBI_ENGINE_CPU, cores)
+    attr.put(KYUUBI_ENGINE_UI_PORT, sc.getConf.get("spark.ui.port"))
+    attr.put(KYUUBI_ENGINE_DRIVER_IP, address)
+    sc.uiWebUrl.foreach { ui => attr.put(KYUUBI_ENGINE_URL, ui.replace("//", "")) }
+    attr.toMap
   }
 }
 
 object SparkTBinaryFrontendService extends Logging {
 
   val HIVE_DELEGATION_TOKEN = new Text("HIVE_DELEGATION_TOKEN")
+  final val SPARK_ENGINE_DRIVER_MEMORY = "spark.driver.memory"
+  final val SPARK_ENGINE_EXECUTOR_MEMORY = "spark.executor.memory"
+
+  final val SPARK_ENGINE_EXECUTOR_CORES = "spark.executor.cores"
+  final val SPARK_ENGINE_DRIVER_CORES = "spark.driver.cores"
+
+  final val SPARK_ENGINE_EXECUTOR_INSTANCE = "spark.executor.instances"
+  final val SPARK_ENGINE_EXECUTOR_MAX_INSTANCE = "spark.dynamicAllocation.maxExecutors"
 
   private[spark] def renewDelegationToken(sc: SparkContext, delegationToken: String): Unit = {
     val newCreds = KyuubiHadoopUtils.decodeCredentials(delegationToken)
diff --git a/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/ZookeeperSparkEngineRegisterSuite.scala b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/ZookeeperSparkEngineRegisterSuite.scala
new file mode 100644
index 000000000..35c2c31b9
--- /dev/null
+++ b/externals/kyuubi-spark-sql-engine/src/test/scala/org/apache/kyuubi/engine/spark/ZookeeperSparkEngineRegisterSuite.scala
@@ -0,0 +1,44 @@
+/*
+ * 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.kyuubi.engine.spark
+
+import java.nio.charset.StandardCharsets
+import java.util.UUID
+
+import org.apache.kyuubi.config.KyuubiConf.PROXY_SPARK_UI_ENABLED
+import org.apache.kyuubi.ha.client.zookeeper.ZookeeperClientProvider
+
+class ZookeeperSparkEngineRegisterSuite extends WithDiscoverySparkSQLEngine
+  with WithEmbeddedZookeeper {
+  override def withKyuubiConf: Map[String, String] =
+    super.withKyuubiConf ++ zookeeperConf ++ Map(
+      PROXY_SPARK_UI_ENABLED.key -> "true",
+      "spark.ui.enabled" -> "true")
+
+  override val namespace: String = s"/kyuubi/deregister_test/${UUID.randomUUID}"
+
+  test("Spark Engine Register Zookeeper with spark ui info") {
+    zookeeperConf.foreach(entry => kyuubiConf.set(entry._1, entry._2))
+    val client = ZookeeperClientProvider.buildZookeeperClient(kyuubiConf)
+    client.start()
+    val bytes =
+      client.getData.forPath(namespace + "/" + client.getChildren.forPath(namespace).get(0))
+    val data = new String(bytes, StandardCharsets.UTF_8)
+    assert(data.contains("spark.ui"))
+  }
+}
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
index 517d92db3..04a9bc498 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
@@ -365,6 +365,13 @@ object KyuubiConf {
       .booleanConf
       .createWithDefault(true)
 
+  val PROXY_SPARK_UI_ENABLED: ConfigEntry[Boolean] =
+    buildConf("kyuubi.proxy.spark.ui.enabled")
+      .doc("Whether to enabled kyuubi help proxy spark ui")
+      .version("1.7.0")
+      .booleanConf
+      .createWithDefault(false)
+
   // ///////////////////////////////////////////////////////////////////////////////////////////////
   //                              Frontend Service Configuration                                 //
   // ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala
index 8b42e659f..ef2db7067 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiReservedKeys.scala
@@ -32,6 +32,7 @@ object KyuubiReservedKeys {
   final val KYUUBI_ENGINE_NAME = "kyuubi.engine.name"
   final val KYUUBI_ENGINE_URL = "kyuubi.engine.url"
   final val KYUUBI_ENGINE_SUBMIT_TIME_KEY = "kyuubi.engine.submit.time"
+  final val KYUUBI_SERVER_SUBMIT_TIME = "kyuubi.server.submit.time"
   final val KYUUBI_ENGINE_CREDENTIALS_KEY = "kyuubi.engine.credentials"
   final val KYUUBI_SESSION_HANDLE_KEY = "kyuubi.session.handle"
   final val KYUUBI_SESSION_ENGINE_LAUNCH_HANDLE_GUID =
@@ -43,4 +44,8 @@ object KyuubiReservedKeys {
   final val KYUUBI_OPERATION_SET_CURRENT_DATABASE = "kyuubi.operation.set.current.database"
   final val KYUUBI_OPERATION_GET_CURRENT_DATABASE = "kyuubi.operation.get.current.database"
   final val KYUUBI_OPERATION_HANDLE_KEY = "kyuubi.operation.handle"
+  final val KYUUBI_ENGINE_MEMORY = "kyuubi.engine.memory"
+  final val KYUUBI_ENGINE_CPU = "kyuubi.engine.cpu"
+  final val KYUUBI_ENGINE_DRIVER_IP = "kyuubi.engine.driver.ip"
+  final val KYUUBI_ENGINE_UI_PORT = "kyuubi.engine.ui.port"
 }
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/server/log/ServerLog.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/server/log/ServerLog.scala
new file mode 100644
index 000000000..3b555bbf6
--- /dev/null
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/server/log/ServerLog.scala
@@ -0,0 +1,167 @@
+/*
+ * 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.kyuubi.server.log
+
+import java.io.{BufferedReader, IOException}
+import java.nio.charset.StandardCharsets
+import java.nio.file.{Files, Path, Paths}
+import java.util.{ArrayList => JArrayList}
+
+import scala.collection.mutable.ListBuffer
+
+import org.apache.kyuubi.{KyuubiException, Logging}
+import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf.SERVER_OPERATION_LOG_DIR_ROOT
+import org.apache.kyuubi.operation.log.SeekableBufferedReader
+
+object ServerLog extends Logging {
+  final private val SERVER_LOG: InheritableThreadLocal[ServerLog] = {
+    new InheritableThreadLocal[ServerLog] {
+      override def initialValue(): ServerLog = null
+    }
+  }
+
+  def setCurrentServerLog(serverLog: ServerLog): Unit = {
+    SERVER_LOG.set(serverLog)
+  }
+
+  def getCurrentServerLog: ServerLog = SERVER_LOG.get()
+
+  def removeCurrentServerLog(): Unit = SERVER_LOG.remove()
+
+  def createServerLog(conf: KyuubiConf): ServerLog = {
+    try {
+      val logPath = conf.get(SERVER_OPERATION_LOG_DIR_ROOT)
+      val logFile = Paths.get(logPath)
+      info(s"Creating server log $logFile")
+      new ServerLog(logFile)
+    } catch {
+      case e: IOException =>
+        error(s"Failed to create server log", e)
+        null
+    }
+  }
+
+}
+
+class ServerLog(path: Path) {
+
+  private lazy val writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)
+  private lazy val reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)
+
+  private lazy val extraPaths: ListBuffer[Path] = ListBuffer()
+  private lazy val extraReaders: ListBuffer[BufferedReader] = ListBuffer()
+  private var lastSeekReadPos = 0
+  private var seekableReader: SeekableBufferedReader = _
+
+  def addExtraLog(path: Path): Unit = synchronized {
+    try {
+      extraReaders += Files.newBufferedReader(path, StandardCharsets.UTF_8)
+      extraPaths += path
+      Option(seekableReader).foreach(_.close)
+      seekableReader = null
+    } catch {
+      case _: IOException =>
+    }
+  }
+
+  private def readLogs(
+      reader: BufferedReader,
+      lastRows: Int,
+      maxRows: Int): (JArrayList[String], Int) = {
+    val logs = new JArrayList[String]
+    var i = 0
+    try {
+      var line: String = reader.readLine()
+      while ((i < lastRows || maxRows <= 0) && line != null) {
+        logs.add(line)
+        line = reader.readLine()
+        i += 1
+      }
+      (logs, i)
+    } catch {
+      case e: IOException =>
+        val absPath = path.toAbsolutePath
+        val opHandle = absPath.getFileName
+        throw new KyuubiException(s"Operation[$opHandle] log file $absPath is not found", e)
+    }
+  }
+
+  /**
+   * Read to log file line by line
+   *
+   * @param maxRows maximum result number can reach
+   */
+  def read(maxRows: Int): JArrayList[String] = synchronized {
+    val (logs, lines) = readLogs(reader, maxRows, maxRows)
+    var lastRows = maxRows - lines
+    for (extraReader <- extraReaders if lastRows > 0 || maxRows <= 0) {
+      val (extraLogs, extraRows) = readLogs(extraReader, lastRows, maxRows)
+      lastRows = lastRows - extraRows
+      logs.addAll(extraLogs)
+    }
+    logs
+  }
+
+  def close(): Unit = synchronized {
+    closeExtraReaders()
+
+    trySafely {
+      reader.close()
+    }
+    trySafely {
+      writer.close()
+    }
+
+    if (seekableReader != null) {
+      lastSeekReadPos = 0
+      trySafely {
+        seekableReader.close()
+      }
+    }
+
+    trySafely {
+      Files.delete(path)
+    }
+  }
+
+  private def trySafely(f: => Unit): Unit = {
+    try {
+      f
+    } catch {
+      case e: IOException =>
+        // Printing log here may cause a deadlock. The lock order of OperationLog.write
+        // is RootLogger -> LogDivertAppender -> OperationLog. If printing log here, the
+        // lock order is OperationLog -> RootLogger. So the exception is thrown and
+        // processing at the invocations
+        throw new IOException(
+          s"Failed to remove corresponding log file of operation: ${path.toAbsolutePath}",
+          e)
+    }
+  }
+
+  private def closeExtraReaders(): Unit = {
+    extraReaders.foreach { extraReader =>
+      try {
+        extraReader.close()
+      } catch {
+        case _: IOException => // for the outside log file reader, ignore it
+      }
+    }
+  }
+}
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/AbstractSession.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/AbstractSession.scala
index 1a8c51ccd..25254e2fb 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/AbstractSession.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/AbstractSession.scala
@@ -61,6 +61,10 @@ abstract class AbstractSession(
 
   final private val opHandleSet = new java.util.HashSet[OperationHandle]
 
+  override def allOperations(): Iterable[OperationHandle] = opHandleSet.asScala
+
+  override def getOperationCount: Long = opHandleSet.size()
+
   private def acquire(userAccess: Boolean): Unit = synchronized {
     if (userAccess) {
       _lastAccessTime = System.currentTimeMillis
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/Session.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/Session.scala
index bc9f9a8f6..6c37af69a 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/session/Session.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/session/Session.scala
@@ -17,7 +17,7 @@
 
 package org.apache.kyuubi.session
 
-import org.apache.hive.service.rpc.thrift.{TGetInfoType, TGetInfoValue, TGetResultSetMetadataResp, TProtocolVersion, TRowSet}
+import org.apache.hive.service.rpc.thrift._
 
 import org.apache.kyuubi.operation.FetchOrientation.FetchOrientation
 import org.apache.kyuubi.operation.OperationHandle
@@ -39,6 +39,8 @@ trait Session {
   def lastIdleTime: Long
   def getNoOperationTime: Long
   def sessionIdleTimeoutThreshold: Long
+  def allOperations(): Iterable[OperationHandle]
+  def getOperationCount(): Long
 
   def sessionManager: SessionManager
 
diff --git a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/DiscoveryClient.scala b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/DiscoveryClient.scala
index 77588180e..86ddd678d 100644
--- a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/DiscoveryClient.scala
+++ b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/DiscoveryClient.scala
@@ -197,7 +197,9 @@ object DiscoveryClient {
         maybeInfos("hive.server2.thrift.port").toInt)
     } else {
       val strings = instance.split(":")
-      (strings(0), strings(1).toInt)
+      val host: String = strings(0)
+      val port: Int = if (strings.length == 1) 0 else strings(1).toInt
+      (host, port)
     }
   }
 }
diff --git a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala
index daa27047e..53639525e 100644
--- a/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala
+++ b/kyuubi-ha/src/main/scala/org/apache/kyuubi/ha/client/zookeeper/ZookeeperDiscoveryClient.scala
@@ -29,34 +29,21 @@ import org.apache.curator.framework.CuratorFramework
 import org.apache.curator.framework.recipes.atomic.{AtomicValue, DistributedAtomicInteger}
 import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex
 import org.apache.curator.framework.recipes.nodes.PersistentNode
-import org.apache.curator.framework.state.ConnectionState
-import org.apache.curator.framework.state.ConnectionState.CONNECTED
-import org.apache.curator.framework.state.ConnectionState.LOST
-import org.apache.curator.framework.state.ConnectionState.RECONNECTED
-import org.apache.curator.framework.state.ConnectionStateListener
+import org.apache.curator.framework.state.{ConnectionState, ConnectionStateListener}
+import org.apache.curator.framework.state.ConnectionState.{CONNECTED, LOST, RECONNECTED}
 import org.apache.curator.retry.RetryForever
 import org.apache.curator.utils.ZKPaths
-import org.apache.zookeeper.CreateMode
+import org.apache.zookeeper.{CreateMode, KeeperException, WatchedEvent, Watcher}
 import org.apache.zookeeper.CreateMode.PERSISTENT
-import org.apache.zookeeper.KeeperException
 import org.apache.zookeeper.KeeperException.NodeExistsException
-import org.apache.zookeeper.WatchedEvent
-import org.apache.zookeeper.Watcher
 
-import org.apache.kyuubi.KYUUBI_VERSION
-import org.apache.kyuubi.KyuubiException
-import org.apache.kyuubi.KyuubiSQLException
-import org.apache.kyuubi.Logging
+import org.apache.kyuubi.{KYUUBI_VERSION, KyuubiException, KyuubiSQLException, Logging}
 import org.apache.kyuubi.config.KyuubiConf
+import org.apache.kyuubi.config.KyuubiConf.PROXY_SPARK_UI_ENABLED
 import org.apache.kyuubi.config.KyuubiReservedKeys.KYUUBI_ENGINE_ID
-import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ENGINE_REF_ID
-import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ZK_NODE_TIMEOUT
-import org.apache.kyuubi.ha.HighAvailabilityConf.HA_ZK_PUBLISH_CONFIGS
-import org.apache.kyuubi.ha.client.DiscoveryClient
-import org.apache.kyuubi.ha.client.ServiceDiscovery
-import org.apache.kyuubi.ha.client.ServiceNodeInfo
-import org.apache.kyuubi.ha.client.zookeeper.ZookeeperClientProvider.buildZookeeperClient
-import org.apache.kyuubi.ha.client.zookeeper.ZookeeperClientProvider.getGracefulStopThreadDelay
+import org.apache.kyuubi.ha.HighAvailabilityConf.{HA_ENGINE_REF_ID, HA_ZK_NODE_TIMEOUT, HA_ZK_PUBLISH_CONFIGS}
+import org.apache.kyuubi.ha.client.{DiscoveryClient, ServiceDiscovery, ServiceNodeInfo}
+import org.apache.kyuubi.ha.client.zookeeper.ZookeeperClientProvider.{buildZookeeperClient, getGracefulStopThreadDelay}
 import org.apache.kyuubi.ha.client.zookeeper.ZookeeperDiscoveryClient.connectionChecker
 import org.apache.kyuubi.util.ThreadUtils
 
@@ -215,16 +202,23 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient {
       val size = sizeOpt.getOrElse(hosts.size())
       hosts.asScala.takeRight(size).map { p =>
         val path = ZKPaths.makePath(namespace, p)
-        val instance = new String(zkClient.getData.forPath(path), StandardCharsets.UTF_8)
+        // zookeeper node data may contains public conf or engine ui info
+        val znodeData = new String(zkClient.getData.forPath(path), StandardCharsets.UTF_8)
+        val instance = znodeData.split(";")(0)
+        val ui =
+          znodeData.split(";").find(_.startsWith("spark.ui=")).map(_.stripPrefix("spark.ui="))
         val (host, port) = DiscoveryClient.parseInstanceHostPort(instance)
-        val attributes =
-          p.split(";").map(_.split("=", 2)).filter(_.length == 2).map(kv =>
-            (kv.head, kv.last)).toMap
-        val version = attributes.get("version")
-        val engineRefId = attributes.get("refId")
+        var attributes =
+          p.split(";").map(_.split("=", 2)).filter(_.size == 2).map(kv => (kv.head, kv.last)).toMap
+        val version = p.split(";").find(_.startsWith("version=")).map(_.stripPrefix("version="))
+        val engineRefId = p.split(";").find(_.startsWith("refId=")).map(_.stripPrefix("refId="))
         val engineIdStr = attributes.get(KYUUBI_ENGINE_ID).map(" engine id:" + _).getOrElse("")
         info(s"Get service instance:$instance$engineIdStr and version:${version.getOrElse("")} " +
           s"under $namespace")
+        attributes = ui match {
+          case Some(_ui) => attributes + ("ui" -> _ui)
+          case None => attributes
+        }
         ServiceNodeInfo(namespace, p, host, port, version, engineRefId, attributes)
       }
     } catch {
@@ -377,12 +371,19 @@ class ZookeeperDiscoveryClient(conf: KyuubiConf) extends DiscoveryClient {
     val createMode =
       if (external) CreateMode.PERSISTENT_SEQUENTIAL
       else CreateMode.EPHEMERAL_SEQUENTIAL
-    val znodeData =
+    var znodeData =
       if (conf.get(HA_ZK_PUBLISH_CONFIGS) && session.isEmpty) {
         addConfsToPublish(conf, instance)
       } else {
         instance
       }
+    val ui = attributes.get("ui")
+    znodeData =
+      if (conf.get(PROXY_SPARK_UI_ENABLED) && ui.nonEmpty) {
+        znodeData + ";spark.ui=" + ui.get
+      } else {
+        znodeData
+      }
     try {
       localServiceNode = new PersistentNode(
         zkClient,
diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
index 133897dd6..6b6141868 100644
--- a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
+++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Engine.java
@@ -17,7 +17,6 @@
 
 package org.apache.kyuubi.client.api.v1.dto;
 
-import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@@ -34,7 +33,17 @@ public class Engine {
   private String namespace;
   private Map<String, String> attributes;
 
-  public Engine() {}
+  private String createTime;
+  private String url;
+  private String host;
+  private int port;
+
+  private String id;
+  private String status;
+
+  private String memoryTotal;
+
+  private String cpuTotal;
 
   public Engine(
       String version,
@@ -44,7 +53,15 @@ public class Engine {
       String subdomain,
       String instance,
       String namespace,
-      Map<String, String> attributes) {
+      Map<String, String> attributes,
+      String createTime,
+      String url,
+      String host,
+      int port,
+      String id,
+      String status,
+      String memoryTotal,
+      String cpuTotal) {
     this.version = version;
     this.user = user;
     this.engineType = engineType;
@@ -53,6 +70,14 @@ public class Engine {
     this.instance = instance;
     this.namespace = namespace;
     this.attributes = attributes;
+    this.createTime = createTime;
+    this.url = url;
+    this.host = host;
+    this.port = port;
+    this.id = id;
+    this.status = status;
+    this.memoryTotal = memoryTotal;
+    this.cpuTotal = cpuTotal;
   }
 
   public String getVersion() {
@@ -112,9 +137,6 @@ public class Engine {
   }
 
   public Map<String, String> getAttributes() {
-    if (null == attributes) {
-      return Collections.emptyMap();
-    }
     return attributes;
   }
 
@@ -122,6 +144,70 @@ public class Engine {
     this.attributes = attributes;
   }
 
+  public String getCreateTime() {
+    return createTime;
+  }
+
+  public void setCreateTime(String createTime) {
+    this.createTime = createTime;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public void setUrl(String url) {
+    this.url = url;
+  }
+
+  public String getHost() {
+    return host;
+  }
+
+  public void setHost(String host) {
+    this.host = host;
+  }
+
+  public int getPort() {
+    return port;
+  }
+
+  public void setPort(int port) {
+    this.port = port;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public String getMemoryTotal() {
+    return memoryTotal;
+  }
+
+  public void setMemoryTotal(String memoryTotal) {
+    this.memoryTotal = memoryTotal;
+  }
+
+  public String getCpuTotal() {
+    return cpuTotal;
+  }
+
+  public void setCpuTotal(String cpuTotal) {
+    this.cpuTotal = cpuTotal;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
@@ -134,7 +220,14 @@ public class Engine {
         && Objects.equals(getSubdomain(), that.getSubdomain())
         && Objects.equals(getInstance(), that.getInstance())
         && Objects.equals(getNamespace(), that.getNamespace())
-        && Objects.equals(getAttributes(), that.getAttributes());
+        && Objects.equals(getAttributes(), that.getAttributes())
+        && Objects.equals(getCreateTime(), that.getCreateTime())
+        && Objects.equals(getUrl(), that.getUrl())
+        && Objects.equals(getHost(), that.getHost())
+        && Objects.equals(getPort(), that.getPort())
+        && Objects.equals(getStatus(), that.getStatus())
+        && Objects.equals(getCpuTotal(), that.getCpuTotal())
+        && Objects.equals(getMemoryTotal(), that.getMemoryTotal());
   }
 
   @Override
@@ -147,7 +240,15 @@ public class Engine {
         getSubdomain(),
         getInstance(),
         getNamespace(),
-        getAttributes());
+        getAttributes(),
+        getCreateTime(),
+        getUrl(),
+        getHost(),
+        getPort(),
+        getId(),
+        getStatus(),
+        getCpuTotal(),
+        getMemoryTotal());
   }
 
   @Override
diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/HadoopConfData.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/HadoopConfData.java
new file mode 100644
index 000000000..bd9db81b3
--- /dev/null
+++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/HadoopConfData.java
@@ -0,0 +1,59 @@
+/*
+ * 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.kyuubi.client.api.v1.dto;
+
+import java.util.Objects;
+
+public class HadoopConfData {
+  private String key;
+  private String value;
+
+  public HadoopConfData(String key, String value) {
+    this.key = key;
+    this.value = value;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public void setKey(String key) {
+    this.key = key;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    HadoopConfData that = (HadoopConfData) o;
+    return Objects.equals(key, that.key) && Objects.equals(value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(key, value);
+  }
+}
diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/SQLDetail.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/SQLDetail.java
new file mode 100644
index 000000000..f436a3509
--- /dev/null
+++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/SQLDetail.java
@@ -0,0 +1,168 @@
+/*
+ * 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.kyuubi.client.api.v1.dto;
+
+import java.util.Objects;
+
+public class SQLDetail {
+  private String sessionId;
+  private String sessionUser;
+  private String statementId;
+  private Long createTime;
+  private Long completeTime;
+  private String statement;
+  private String engineId;
+  private String engineType;
+  private String engineShareLevel;
+  private String exception;
+
+  public SQLDetail(
+      String sessionId,
+      String sessionUser,
+      String statementId,
+      Long createTime,
+      Long completeTime,
+      String statement,
+      String engineId,
+      String engineType,
+      String engineShareLevel,
+      String exception) {
+    this.sessionId = sessionId;
+    this.sessionUser = sessionUser;
+    this.statementId = statementId;
+    this.createTime = createTime;
+    this.completeTime = completeTime;
+    this.statement = statement;
+    this.engineId = engineId;
+    this.engineType = engineType;
+    this.engineShareLevel = engineShareLevel;
+    this.exception = exception;
+  }
+
+  public String getSessionId() {
+    return sessionId;
+  }
+
+  public void setSessionId(String sessionId) {
+    this.sessionId = sessionId;
+  }
+
+  public String getSessionUser() {
+    return sessionUser;
+  }
+
+  public void setSessionUser(String sessionUser) {
+    this.sessionUser = sessionUser;
+  }
+
+  public String getStatementId() {
+    return statementId;
+  }
+
+  public void setStatementId(String statementId) {
+    this.statementId = statementId;
+  }
+
+  public Long getCreateTime() {
+    return createTime;
+  }
+
+  public void setCreateTime(Long createTime) {
+    this.createTime = createTime;
+  }
+
+  public Long getCompleteTime() {
+    return completeTime;
+  }
+
+  public void setCompleteTime(Long completeTime) {
+    this.completeTime = completeTime;
+  }
+
+  public String getStatement() {
+    return statement;
+  }
+
+  public void setStatement(String statement) {
+    this.statement = statement;
+  }
+
+  public String getEngineId() {
+    return engineId;
+  }
+
+  public void setEngineId(String engineName) {
+    this.engineId = engineName;
+  }
+
+  public String getEngineType() {
+    return engineType;
+  }
+
+  public void setEngineType(String engineType) {
+    this.engineType = engineType;
+  }
+
+  public String getEngineShareLevel() {
+    return engineShareLevel;
+  }
+
+  public void setEngineShareLevel(String engineShareLevel) {
+    this.engineShareLevel = engineShareLevel;
+  }
+
+  public String getException() {
+    return exception;
+  }
+
+  public void setException(String exception) {
+    this.exception = exception;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    SQLDetail that = (SQLDetail) o;
+    return Objects.equals(sessionId, that.sessionId)
+        && Objects.equals(sessionUser, that.sessionUser)
+        && Objects.equals(statementId, that.statementId)
+        && Objects.equals(createTime, that.createTime)
+        && Objects.equals(completeTime, that.completeTime)
+        && Objects.equals(statement, that.statement)
+        && Objects.equals(engineId, that.engineId)
+        && Objects.equals(engineType, that.engineType)
+        && Objects.equals(engineShareLevel, that.engineShareLevel)
+        && Objects.equals(exception, that.exception);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        sessionId,
+        sessionUser,
+        statementId,
+        createTime,
+        completeTime,
+        statement,
+        engineId,
+        engineType,
+        engineShareLevel,
+        exception);
+  }
+}
diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Server.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Server.java
new file mode 100644
index 000000000..dbd718651
--- /dev/null
+++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/Server.java
@@ -0,0 +1,148 @@
+/*
+ * 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.kyuubi.client.api.v1.dto;
+
+import java.util.Objects;
+
+public class Server {
+
+  private String nodeName;
+  private String namespace;
+  private String instance;
+  private String host;
+  private int port;
+  private Long createTime;
+  private Long memoryTotal;
+  private int cpuTotal;
+  private String status;
+
+  public Server(
+      String nodeName,
+      String namespace,
+      String instance,
+      String host,
+      int port,
+      Long createTime,
+      Long memoryTotal,
+      int cpuTotal,
+      String status) {
+    this.nodeName = nodeName;
+    this.namespace = namespace;
+    this.instance = instance;
+    this.host = host;
+    this.port = port;
+    this.createTime = createTime;
+    this.memoryTotal = memoryTotal;
+    this.cpuTotal = cpuTotal;
+    this.status = status;
+  }
+
+  public String getNodeName() {
+    return nodeName;
+  }
+
+  public void setNodeName(String nodeName) {
+    this.nodeName = nodeName;
+  }
+
+  public String getNamespace() {
+    return namespace;
+  }
+
+  public void setNamespace(String namespace) {
+    this.namespace = namespace;
+  }
+
+  public String getInstance() {
+    return instance;
+  }
+
+  public void setInstance(String instance) {
+    this.instance = instance;
+  }
+
+  public String getHost() {
+    return host;
+  }
+
+  public void setHost(String host) {
+    this.host = host;
+  }
+
+  public int getPort() {
+    return port;
+  }
+
+  public void setPort(int port) {
+    this.port = port;
+  }
+
+  public Long getCreateTime() {
+    return createTime;
+  }
+
+  public void setCreateTime(Long createTime) {
+    this.createTime = createTime;
+  }
+
+  public Long getMemoryTotal() {
+    return memoryTotal;
+  }
+
+  public void setMemoryTotal(Long memoryTotal) {
+    this.memoryTotal = memoryTotal;
+  }
+
+  public int getCpuTotal() {
+    return cpuTotal;
+  }
+
+  public void setCpuTotal(int cpuTotal) {
+    this.cpuTotal = cpuTotal;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    Server server = (Server) o;
+    return port == server.port
+        && cpuTotal == server.cpuTotal
+        && Objects.equals(nodeName, server.nodeName)
+        && Objects.equals(namespace, server.namespace)
+        && Objects.equals(instance, server.instance)
+        && Objects.equals(host, server.host)
+        && Objects.equals(createTime, server.createTime)
+        && Objects.equals(memoryTotal, server.memoryTotal)
+        && Objects.equals(status, server.status);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        nodeName, namespace, instance, host, port, createTime, memoryTotal, cpuTotal, status);
+  }
+}
diff --git a/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ServerLog.java b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ServerLog.java
new file mode 100644
index 000000000..2c3f748f3
--- /dev/null
+++ b/kyuubi-rest-client/src/main/java/org/apache/kyuubi/client/api/v1/dto/ServerLog.java
@@ -0,0 +1,74 @@
+/*
+ * 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.kyuubi.client.api.v1.dto;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public class ServerLog {
+  private List<String> logRowSet;
+  private int rowCount;
+
+  public ServerLog() {}
+
+  public ServerLog(List<String> logRowSet, int rowCount) {
+    this.logRowSet = logRowSet;
+    this.rowCount = rowCount;
+  }
+
+  public List<String> getLogRowSet() {
+    if (null == logRowSet) {
+      return Collections.emptyList();
+    }
+    return logRowSet;
+  }
+
+  public void setLogRowSet(List<String> logRowSet) {
+    this.logRowSet = logRowSet;
+  }
+
+  public int getRowCount() {
+    return rowCount;
+  }
+
+  public void setRowCount(int rowCount) {
+    this.rowCount = rowCount;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ServerLog that = (ServerLog) o;
+    return getRowCount() == that.getRowCount()
+        && Objects.equals(getLogRowSet(), that.getLogRowSet());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getLogRowSet(), getRowCount());
+  }
+
+  @Override
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE);
+  }
+}
diff --git a/kyuubi-server/pom.xml b/kyuubi-server/pom.xml
index aebce5dda..08c1b7fd1 100644
--- a/kyuubi-server/pom.xml
+++ b/kyuubi-server/pom.xml
@@ -196,6 +196,11 @@
             <artifactId>jakarta.servlet-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-proxy</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.glassfish.jersey.core</groupId>
             <artifactId>jersey-server</artifactId>
@@ -511,6 +516,12 @@
             <artifactId>flexmark-all</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>6.3.2</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiOperationEvent.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiOperationEvent.scala
index 74a3a3fad..de939b8fa 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiOperationEvent.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiOperationEvent.scala
@@ -18,8 +18,9 @@
 package org.apache.kyuubi.events
 
 import org.apache.kyuubi.Utils
+import org.apache.kyuubi.config.KyuubiConf.{ENGINE_SHARE_LEVEL, ENGINE_TYPE}
 import org.apache.kyuubi.operation.{KyuubiOperation, OperationHandle}
-import org.apache.kyuubi.session.KyuubiSession
+import org.apache.kyuubi.session.{KyuubiSession, KyuubiSessionImpl}
 
 /**
  * A [[KyuubiOperationEvent]] used to tracker the lifecycle of an operation at server side.
@@ -48,6 +49,7 @@ case class KyuubiOperationEvent private (
     remoteId: String,
     statement: String,
     shouldRunAsync: Boolean,
+    operationType: String,
     state: String,
     eventTime: Long,
     createTime: Long,
@@ -56,7 +58,10 @@ case class KyuubiOperationEvent private (
     exception: Option[Throwable],
     sessionId: String,
     sessionUser: String,
-    sessionType: String) extends KyuubiEvent {
+    sessionType: String,
+    engineId: String,
+    engineType: String,
+    engineShareLevel: String) extends KyuubiEvent {
 
   // operation events are partitioned by the date when the corresponding operations are
   // created.
@@ -77,6 +82,7 @@ object KyuubiOperationEvent {
       Option(operation.remoteOpHandle()).map(OperationHandle(_).identifier.toString).orNull,
       operation.statement,
       operation.shouldRunAsync,
+      Option(operation.remoteOpHandle()).map(_.getOperationType.name()).orNull,
       status.state.name(),
       status.lastModified,
       status.create,
@@ -85,6 +91,9 @@ object KyuubiOperationEvent {
       status.exception,
       session.handle.identifier.toString,
       session.user,
-      session.sessionType.toString)
+      session.sessionType.toString,
+      session.asInstanceOf[KyuubiSessionImpl].client.engineId.orNull,
+      Option(session.sessionManager.getConf).map(_.get(ENGINE_TYPE)).orNull,
+      Option(session.sessionManager.getConf).map(_.get(ENGINE_SHARE_LEVEL)).orNull)
   }
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiSessionEvent.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiSessionEvent.scala
index 821017b29..499cfc756 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiSessionEvent.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/events/KyuubiSessionEvent.scala
@@ -37,7 +37,10 @@ import org.apache.kyuubi.session.KyuubiSession
  * @param openedTime session opened time
  * @param endTime session end time
  * @param totalOperations how many queries and meta calls
+ * @param finishedOperations how many queries and meta calls
  * @param exception the session exception, such as the exception that occur when opening session
+ * @param runningOperations how many queries running
+ * @param errorOperations how many queries failed
  */
 case class KyuubiSessionEvent(
     sessionId: String,
@@ -55,7 +58,10 @@ case class KyuubiSessionEvent(
     var openedTime: Long = -1L,
     var endTime: Long = -1L,
     var totalOperations: Int = 0,
-    var exception: Option[Throwable] = None) extends KyuubiEvent {
+    var finishedOperations: Int = 0,
+    var exception: Option[Throwable] = None,
+    var runningOperations: Int = 0,
+    var errorOperations: Int = 0) extends KyuubiEvent {
   override def partitions: Seq[(String, String)] =
     ("day", Utils.getDateFromTimestamp(startTime)) :: Nil
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala
index 7019d8a6a..b299d6f17 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiRestFrontendService.scala
@@ -26,7 +26,7 @@ import javax.ws.rs.core.Response.Status
 
 import com.google.common.annotations.VisibleForTesting
 import org.apache.hadoop.conf.Configuration
-import org.eclipse.jetty.servlet.{ErrorPageErrorHandler, FilterHolder}
+import org.eclipse.jetty.servlet.{ErrorPageErrorHandler, FilterHolder, ServletContextHandler}
 
 import org.apache.kyuubi.{KyuubiException, Utils}
 import org.apache.kyuubi.config.KyuubiConf
@@ -81,14 +81,21 @@ class KyuubiRestFrontendService(override val serverable: Serverable)
   }
 
   private def startInternal(): Unit = {
-    val contextHandler = ApiRootResource.getServletHandler(this)
+    // val contextHandler = ApiRootResource.getServletHandler(this)
+    val contextHandlerCollection = ApiRootResource.getServletHandlerCollection(this)
     val holder = new FilterHolder(new AuthenticationFilter(conf))
-    contextHandler.addFilter(holder, "/v1/*", EnumSet.allOf(classOf[DispatcherType]))
+    contextHandlerCollection.getChildHandlers.map(handler => {
+      if (handler.isInstanceOf[ServletContextHandler]) {
+        handler.asInstanceOf[ServletContextHandler]
+          .addFilter(holder, "/v1/*", EnumSet.allOf(classOf[DispatcherType]))
+      }
+    })
+    // contextHandler.addFilter(holder, "/v1/*", EnumSet.allOf(classOf[DispatcherType]))
     val authenticationFactory = new KyuubiHttpAuthenticationFactory(conf)
-    server.addHandler(authenticationFactory.httpHandlerWrapperFactory.wrapHandler(contextHandler))
-
+    server.addHandler(
+      authenticationFactory.httpHandlerWrapperFactory.wrapHandler(contextHandlerCollection))
     server.addStaticHandler("org/apache/kyuubi/ui/static", "/static/")
-    server.addRedirectHandler("/", "/static/")
+    server.addRedirectHandler("/", "/static")
     server.addRedirectHandler("/static", "/static/")
     server.addStaticHandler("META-INF/resources/webjars/swagger-ui/4.9.1/", "/swagger-static/")
     server.addStaticHandler("org/apache/kyuubi/ui/swagger", "/swagger/")
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
index e81240a96..e25f4ddf2 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiServer.scala
@@ -17,6 +17,8 @@
 
 package org.apache.kyuubi.server
 
+import java.util
+
 import scala.util.Properties
 
 import org.apache.commons.lang3.StringUtils
@@ -31,6 +33,7 @@ import org.apache.kyuubi.events.{EventBus, KyuubiServerInfoEvent, ServerEventHan
 import org.apache.kyuubi.ha.HighAvailabilityConf._
 import org.apache.kyuubi.ha.client.{AuthTypes, ServiceDiscovery}
 import org.apache.kyuubi.metrics.{MetricsConf, MetricsSystem}
+import org.apache.kyuubi.server.log.ServerLog
 import org.apache.kyuubi.server.metadata.jdbc.JDBCMetadataStoreConf
 import org.apache.kyuubi.service.{AbstractBackendService, AbstractFrontendService, Serverable, ServiceState}
 import org.apache.kyuubi.session.KyuubiSessionManager
@@ -137,6 +140,13 @@ object KyuubiServer extends Logging {
     val refreshedUnlimitedUsers = sessionMgr.getUnlimitedUsers()
     info(s"Refreshed unlimited users from $existingUnlimitedUsers to $refreshedUnlimitedUsers")
   }
+
+  private[kyuubi] def getServerLogRowSet(maxRows: Int): util.ArrayList[String] = {
+    val serverLog = ServerLog.getCurrentServerLog
+    Option(serverLog).map(_.read(maxRows)).getOrElse {
+      throw KyuubiSQLException("failed to generate server log")
+    }
+  }
 }
 
 class KyuubiServer(name: String) extends Serverable(name) {
@@ -165,6 +175,8 @@ class KyuubiServer(name: String) extends Serverable(name) {
 
   override def initialize(conf: KyuubiConf): Unit = synchronized {
     initLoggerEventHandler(conf)
+    val serverLog = ServerLog.createServerLog(conf)
+    ServerLog.setCurrentServerLog(serverLog)
 
     val kinit = new KinitAuxiliaryService()
     addService(kinit)
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTBinaryFrontendService.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTBinaryFrontendService.scala
index 069bc63e2..72d365671 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTBinaryFrontendService.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/KyuubiTBinaryFrontendService.scala
@@ -39,6 +39,10 @@ final class KyuubiTBinaryFrontendService(
     override val serverable: Serverable)
   extends TBinaryFrontendService("KyuubiTBinaryFrontend") {
 
+  override def attributes: Map[String, String] = {
+    Map(KYUUBI_SERVER_SUBMIT_TIME -> System.currentTimeMillis().toString)
+  }
+
   override protected def hadoopConf: Configuration = KyuubiServer.getHadoopConf()
 
   override lazy val discoveryService: Option[Service] = {
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/WebUIProxyServlet.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/WebUIProxyServlet.scala
new file mode 100644
index 000000000..d0d530c78
--- /dev/null
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/WebUIProxyServlet.scala
@@ -0,0 +1,81 @@
+/*
+ * 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.kyuubi.server.api
+
+import java.net.URLDecoder
+import javax.servlet.http.HttpServletRequest
+
+import org.eclipse.jetty.client.api.Request
+import org.eclipse.jetty.proxy.ProxyServlet
+
+import org.apache.kyuubi.Logging
+import org.apache.kyuubi.config.KyuubiConf
+
+private[api] class WebUIProxyServlet(conf: KyuubiConf) extends ProxyServlet with Logging {
+  var ipAddress: String = _
+  var port: Int = _
+  val CONTEXT_HEADER_KEY = "X-Forwarded-Context"
+  var CONTEXT_HEADER_VALUE = ""
+
+  override def rewriteTarget(request: HttpServletRequest): String = {
+    var targetUrl = "/no-ui-error"
+    val requestUrl = request.getRequestURI
+    logger.info("requestUrl is {}", requestUrl)
+    val url = requestUrl.split("/")
+    logger.info("url is {}", url)
+    if (url != null && url.length > 0) {
+      ipAddress = url(2).split(":")(0)
+      port = url(2).split(":")(1).toInt
+      var targetPath = "jobs/"
+      val subPath = String.format(
+        "/%s/%s/",
+        url(1),
+        url(2))
+      if (requestUrl.substring(subPath.length).nonEmpty) {
+        targetPath = requestUrl.substring(subPath.length)
+      }
+      targetUrl = String.format(
+        "http://%s:%s/%s%s",
+        ipAddress,
+        port.toString,
+        targetPath,
+        getQueryString(request))
+      CONTEXT_HEADER_VALUE = s"/proxy/$ipAddress:$port"
+      logger.info("ui -> {}", targetUrl)
+    }
+    targetUrl
+  }
+
+  override def addXForwardedHeaders(
+      clientRequest: HttpServletRequest,
+      proxyRequest: Request): Unit = {
+    proxyRequest.header(CONTEXT_HEADER_KEY, CONTEXT_HEADER_VALUE)
+    super.addXForwardedHeaders(clientRequest, proxyRequest)
+  }
+
+  def getQueryString(servletRequest: HttpServletRequest): String = {
+    val result = new StringBuilder()
+    val queryString = servletRequest.getQueryString()
+    if (queryString != null && queryString.length() > 0) {
+      result.append('?')
+      result.append(URLDecoder.decode(queryString, "UTF-8"))
+    }
+    result.toString()
+  }
+
+}
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
index 104dd1045..1533d8d54 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/AdminResource.scala
@@ -23,20 +23,25 @@ import javax.ws.rs.core.{MediaType, Response}
 
 import scala.collection.JavaConverters._
 import scala.collection.mutable.ListBuffer
+import scala.util.control.NonFatal
 
-import io.swagger.v3.oas.annotations.media.Content
+import io.swagger.v3.oas.annotations.media.{Content, Schema}
 import io.swagger.v3.oas.annotations.responses.ApiResponse
 import io.swagger.v3.oas.annotations.tags.Tag
+import org.apache.commons.lang3.StringUtils
 
 import org.apache.kyuubi.{KYUUBI_VERSION, Logging, Utils}
-import org.apache.kyuubi.client.api.v1.dto.Engine
+import org.apache.kyuubi.client.api.v1.dto.{Engine, HadoopConfData, Server, ServerLog}
 import org.apache.kyuubi.config.KyuubiConf
 import org.apache.kyuubi.config.KyuubiConf._
+import org.apache.kyuubi.config.KyuubiReservedKeys._
 import org.apache.kyuubi.ha.HighAvailabilityConf.HA_NAMESPACE
 import org.apache.kyuubi.ha.client.{DiscoveryPaths, ServiceNodeInfo}
 import org.apache.kyuubi.ha.client.DiscoveryClientProvider.withDiscoveryClient
 import org.apache.kyuubi.server.KyuubiServer
+import org.apache.kyuubi.server.KyuubiServer.getServerLogRowSet
 import org.apache.kyuubi.server.api.ApiRequestContext
+import org.apache.kyuubi.util.OSUtils
 
 @Tag(name = "Admin")
 @Produces(Array(MediaType.APPLICATION_JSON))
@@ -109,14 +114,13 @@ private[v1] class AdminResource extends ApiRequestContext with Logging {
   @DELETE
   @Path("engine")
   def deleteEngine(
-      @QueryParam("type") engineType: String,
-      @QueryParam("sharelevel") shareLevel: String,
+      @QueryParam("engineType") engineType: String,
+      @QueryParam("shareLevel") shareLevel: String,
       @QueryParam("subdomain") subdomain: String,
       @QueryParam("hive.server2.proxy.user") hs2ProxyUser: String): Response = {
     val userName = fe.getSessionUser(hs2ProxyUser)
     val engine = getEngine(userName, engineType, shareLevel, subdomain, "default")
     val engineSpace = getEngineSpace(engine)
-
     withDiscoveryClient(fe.getConf) { discoveryClient =>
       val engineNodes = discoveryClient.getChildren(engineSpace)
       engineNodes.foreach { node =>
@@ -138,44 +142,152 @@ private[v1] class AdminResource extends ApiRequestContext with Logging {
 
   @ApiResponse(
     responseCode = "200",
-    content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)),
-    description = "list kyuubi engines")
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[Engine]))),
+    description = "list alive kyuubi engines")
   @GET
   @Path("engine")
   def listEngines(
-      @QueryParam("type") engineType: String,
-      @QueryParam("sharelevel") shareLevel: String,
+      @QueryParam("engineType") engineType: String,
+      @QueryParam("shareLevel") shareLevel: String,
       @QueryParam("subdomain") subdomain: String,
       @QueryParam("hive.server2.proxy.user") hs2ProxyUser: String): Seq[Engine] = {
-    val userName = fe.getSessionUser(hs2ProxyUser)
-    val engine = getEngine(userName, engineType, shareLevel, subdomain, "")
-    val engineSpace = getEngineSpace(engine)
-
-    var engineNodes = ListBuffer[ServiceNodeInfo]()
-    Option(subdomain).filter(_.nonEmpty) match {
-      case Some(_) =>
-        withDiscoveryClient(fe.getConf) { discoveryClient =>
-          info(s"Listing engine nodes for $engineSpace")
-          engineNodes ++= discoveryClient.getServiceNodesInfo(engineSpace)
-        }
-      case None =>
-        withDiscoveryClient(fe.getConf) { discoveryClient =>
-          discoveryClient.getChildren(engineSpace).map { child =>
-            info(s"Listing engine nodes for $engineSpace/$child")
-            engineNodes ++= discoveryClient.getServiceNodesInfo(s"$engineSpace/$child")
+    val engines = ListBuffer[Engine]()
+    try {
+      val userName = if (StringUtils.isEmpty(hs2ProxyUser)) "" else fe.getSessionUser(hs2ProxyUser)
+      val engine = getEngine(userName, engineType, shareLevel, subdomain, "")
+      val engineSpace = getEngineSpace(engine)
+      val engineNodes = ListBuffer[ServiceNodeInfo]()
+      Option(subdomain).filter(_.nonEmpty) match {
+        case Some(_) =>
+          withDiscoveryClient(fe.getConf) { discoveryClient =>
+            info(s"Listing engine nodes for $engineSpace")
+            engineNodes ++= discoveryClient.getServiceNodesInfo(engineSpace)
+          }
+        case None =>
+          withDiscoveryClient(fe.getConf) { discoveryClient =>
+            discoveryClient.getChildren(engineSpace).map { child =>
+              {
+                if (StringUtils.isNotEmpty(hs2ProxyUser)) {
+                  info(s"Listing engine nodes for $engineSpace/$child")
+                  engineNodes ++= discoveryClient.getServiceNodesInfo(s"$engineSpace/$child")
+                } else {
+                  discoveryClient.getChildren(s"$engineSpace/$child").map(restChild => {
+                    info(s"Listing engine nodes for $engineSpace/$child/$restChild")
+                    engineNodes ++= discoveryClient
+                      .getServiceNodesInfo(s"$engineSpace/$child/$restChild")
+                  })
+                }
+              }
+            }
           }
+      }
+      engineNodes.map(node => {
+        engines += new Engine(
+          engine.getVersion,
+          engine.getUser,
+          engine.getEngineType,
+          engine.getSharelevel,
+          node.namespace.split("/").last,
+          node.instance,
+          node.namespace,
+          node.attributes.asJava,
+          node.attributes.get(KYUUBI_ENGINE_SUBMIT_TIME_KEY).orNull,
+          node.attributes.get(KYUUBI_ENGINE_URL).orNull,
+          node.host,
+          node.port,
+          node.engineRefId.orNull,
+          "Running",
+          node.attributes.get(KYUUBI_ENGINE_MEMORY).orNull,
+          node.attributes.get(KYUUBI_ENGINE_CPU).orNull)
+      })
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = s"Error getting engines"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+    engines
+  }
+
+  @deprecated
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)),
+    description = "list  all live kyuubi servers")
+  @GET
+  @Path("servers")
+  def listServers(@QueryParam("host") @DefaultValue("") host: String): Seq[Server] = {
+    val Servers = ListBuffer[Server]()
+    val serverSpace = DiscoveryPaths.makePath(null, fe.getConf.get(HA_NAMESPACE))
+    val serverNodes = ListBuffer[ServiceNodeInfo]()
+    withDiscoveryClient(fe.getConf) { discoveryClient =>
+      info(s"Listing server nodes for $serverSpace")
+      serverNodes ++= discoveryClient.getServiceNodesInfo(serverSpace)
+      serverNodes.map(node => {
+        if (host.equalsIgnoreCase("") || node.host.equalsIgnoreCase(host)) {
+          Servers += new Server(
+            node.nodeName,
+            node.namespace,
+            node.instance,
+            node.host,
+            node.port,
+            node.attributes.get(KYUUBI_SERVER_SUBMIT_TIME).orNull.toLong,
+            OSUtils.memoryTotal(),
+            OSUtils.cpuTotal(),
+            "Running")
         }
+      })
     }
-    engineNodes.map(node =>
-      new Engine(
-        engine.getVersion,
-        engine.getUser,
-        engine.getEngineType,
-        engine.getSharelevel,
-        node.namespace.split("/").last,
-        node.instance,
-        node.namespace,
-        node.attributes.asJava))
+    Servers
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[ServerLog]))),
+    description =
+      "get server log")
+  @GET
+  @Path("server/log")
+  def getServerLog(
+      @QueryParam("maxrows") maxRows: Int): ServerLog = {
+    try {
+      val rowSet = getServerLogRowSet(maxRows)
+      new ServerLog(rowSet, rowSet.size)
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = s"Error getting server log"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(mediaType = MediaType.APPLICATION_JSON)),
+    description = "get the Kyuubi server hadoop conf")
+  @GET
+  @Path("get/hadoop_conf")
+  def getFrontendHadoopConf(): Seq[HadoopConfData] = {
+    val userName = fe.getSessionUser(Map.empty[String, String])
+    val ipAddress = fe.getIpAddress
+    info(s"Receive get Kyuubi server hadoop conf request from $userName/$ipAddress")
+    info(s"the admin is $administrator")
+    if (!userName.equals(administrator)) {
+      throw new NotAllowedException(
+        s"$userName is not allowed to get the Kyuubi server hadoop conf")
+    }
+    info(s"Getting the Kyuubi server hadoop conf")
+    val hadoopConf = ListBuffer[HadoopConfData]()
+    val iterator = KyuubiServer.getHadoopConf().iterator()
+    while (iterator.hasNext()) {
+      val element = iterator.next();
+      hadoopConf += new HadoopConfData(element.getKey, element.getValue)
+    }
+    hadoopConf
   }
 
   private def getEngine(
@@ -203,7 +315,15 @@ private[v1] class AdminResource extends ApiRequestContext with Logging {
       engineSubdomain,
       null,
       null,
-      Collections.emptyMap())
+      Collections.emptyMap(),
+      null,
+      null,
+      null,
+      0,
+      null,
+      null,
+      null,
+      null)
   }
 
   private def getEngineSpace(engine: Engine): String = {
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala
index d8b997e86..1dcdc8d4a 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/ApiRootResource.scala
@@ -23,14 +23,16 @@ import javax.ws.rs.core.{MediaType, Response}
 import com.google.common.annotations.VisibleForTesting
 import io.swagger.v3.oas.annotations.media.Content
 import io.swagger.v3.oas.annotations.responses.ApiResponse
+import org.eclipse.jetty.server.handler.ContextHandlerCollection
 import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
 import org.glassfish.jersey.server.ResourceConfig
 import org.glassfish.jersey.servlet.ServletContainer
 
 import org.apache.kyuubi.KYUUBI_VERSION
 import org.apache.kyuubi.client.api.v1.dto._
+import org.apache.kyuubi.config.KyuubiConf
 import org.apache.kyuubi.server.KyuubiRestFrontendService
-import org.apache.kyuubi.server.api.{ApiRequestContext, FrontendServiceContext, OpenAPIConfig}
+import org.apache.kyuubi.server.api._
 
 @Path("/v1")
 private[v1] class ApiRootResource extends ApiRequestContext {
@@ -82,4 +84,28 @@ private[server] object ApiRootResource {
     handler.addServlet(holder, "/*")
     handler
   }
+
+  def getServletHandlerCollection(fe: KyuubiRestFrontendService): ContextHandlerCollection = {
+    val contextHandlerCollection = new ContextHandlerCollection();
+    val openapiConf: ResourceConfig = new OpenAPIConfig
+    val holder = new ServletHolder(new ServletContainer(openapiConf))
+    val handler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
+    handler.setContextPath("/api")
+    FrontendServiceContext.set(handler, fe)
+    handler.addServlet(holder, "/*")
+    contextHandlerCollection.addHandler(handler)
+    addProxyHandler(contextHandlerCollection, fe.getConf)
+    contextHandlerCollection
+  }
+
+  def addProxyHandler(
+      contextHandlerCollection: ContextHandlerCollection,
+      conf: KyuubiConf): Unit = {
+    val proxyServlet = new WebUIProxyServlet(conf)
+    val holder = new ServletHolder(proxyServlet)
+    val contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+    contextHandler.setContextPath("/proxy");
+    contextHandler.addServlet(holder, "/*")
+    contextHandlerCollection.addHandler(contextHandler)
+  }
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala
index 70a6d3a28..f67964ab0 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/OperationsResource.scala
@@ -21,6 +21,7 @@ import javax.ws.rs._
 import javax.ws.rs.core.{MediaType, Response}
 
 import scala.collection.JavaConverters._
+import scala.collection.mutable.ListBuffer
 import scala.util.control.NonFatal
 
 import io.swagger.v3.oas.annotations.media.{Content, Schema}
@@ -31,7 +32,7 @@ import org.apache.hive.service.rpc.thrift._
 import org.apache.kyuubi.{KyuubiSQLException, Logging}
 import org.apache.kyuubi.client.api.v1.dto._
 import org.apache.kyuubi.events.KyuubiOperationEvent
-import org.apache.kyuubi.operation.{FetchOrientation, KyuubiOperation, OperationHandle}
+import org.apache.kyuubi.operation.{FetchOrientation, KyuubiOperation, OperationHandle, OperationStatus}
 import org.apache.kyuubi.server.api.ApiRequestContext
 
 @Tag(name = "Operation")
@@ -79,7 +80,7 @@ private[v1] class OperationsResource extends ApiRequestContext with Logging {
         case "close" => fe.be.closeOperation(operationHandle)
         case _ => throw KyuubiSQLException(s"Invalid action ${request.getAction}")
       }
-      Response.ok().build()
+      Response.ok("Apply an action for an operation successfully.").build()
     } catch {
       case NonFatal(e) =>
         val errorMsg =
@@ -237,4 +238,113 @@ private[v1] class OperationsResource extends ApiRequestContext with Logging {
         throw new NotFoundException(errorMsg)
     }
   }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[InfoDetail]))),
+    description =
+      "all the enum values of OperationType")
+  @GET
+  @Path("/operations/types")
+  def getSupportedInfoType(): Seq[InfoDetail] = {
+    try {
+      val infoTypes = TOperationType.values()
+      infoTypes.map(infoType => {
+        new InfoDetail(infoType.toString, infoType.getValue.toString)
+      })
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = "Error getting all the enum values of OperationType"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[OperationStatus]))),
+    description =
+      "Get the operation status")
+  @POST
+  @Path("{operationHandle}/status")
+  def getOperationStatus(
+      @PathParam("operationHandle") operationHandleStr: String): OperationStatus = {
+    try {
+      val opHandle = OperationHandle(operationHandleStr)
+      val operation = fe.be.sessionManager.operationManager.getOperation(opHandle)
+      operation.getStatus
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = "Error getting an operation status"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[String]))),
+    description =
+      "Get the query id of the given operation identifier")
+  @GET
+  @Path("{operationHandle}/queryId")
+  def getQueryId(
+      @PathParam("operationHandle") operationHandleStr: String): String = {
+    try {
+      val opHandle = OperationHandle(operationHandleStr)
+      val operation = fe.be.sessionManager.operationManager.getOperation(opHandle)
+      val queryId = fe.be.sessionManager.operationManager.getQueryId(operation)
+      queryId
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = "Error getting an the query id of operation"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[KyuubiOperationEvent]))),
+    description =
+      "query operation event list")
+  @GET
+  @Path("listOperation")
+  def listOperation(
+      @QueryParam("sessionHandle") @DefaultValue("") sessionHandleStr: String,
+      @QueryParam("operationType") @DefaultValue("") operationType: String,
+      @QueryParam("state") @DefaultValue("") stateStr: String): Seq[KyuubiOperationEvent] = {
+    try {
+      val KyuubiOperationEvents = ListBuffer[KyuubiOperationEvent]()
+      fe.be.sessionManager.allSessions()
+        .map { session =>
+          session.allOperations().map { operationHandle =>
+            val operation = fe.be.sessionManager.operationManager.getOperation(operationHandle)
+            val kyuubiOperationEvent = KyuubiOperationEvent(operation.asInstanceOf[KyuubiOperation])
+            KyuubiOperationEvents += kyuubiOperationEvent
+          }
+        }
+      (KyuubiOperationEvents
+        .filter(
+          sessionHandleStr.equalsIgnoreCase("") || _.sessionId.equalsIgnoreCase(sessionHandleStr))
+        .filter(
+          operationType.equalsIgnoreCase("") || _.operationType.equalsIgnoreCase(operationType))
+        .filter(
+          stateStr.equalsIgnoreCase("") || _.state.equalsIgnoreCase(stateStr)))
+
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = "Error querying operation event list"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+  }
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
index 84b19eb00..7ac2d53dc 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
@@ -21,6 +21,7 @@ import javax.ws.rs._
 import javax.ws.rs.core.{MediaType, Response}
 
 import scala.collection.JavaConverters._
+import scala.collection.mutable.ListBuffer
 import scala.language.implicitConversions
 import scala.util.control.NonFatal
 
@@ -33,11 +34,10 @@ import org.apache.kyuubi.Logging
 import org.apache.kyuubi.client.api.v1.dto
 import org.apache.kyuubi.client.api.v1.dto._
 import org.apache.kyuubi.config.KyuubiReservedKeys._
-import org.apache.kyuubi.events.KyuubiEvent
-import org.apache.kyuubi.operation.OperationHandle
+import org.apache.kyuubi.events.{KyuubiEvent, KyuubiOperationEvent, KyuubiSessionEvent}
+import org.apache.kyuubi.operation.{KyuubiOperation, OperationHandle}
 import org.apache.kyuubi.server.api.ApiRequestContext
-import org.apache.kyuubi.session.KyuubiSession
-import org.apache.kyuubi.session.SessionHandle
+import org.apache.kyuubi.session.{KyuubiSession, SessionHandle}
 
 @Tag(name = "Session")
 @Produces(Array(MediaType.APPLICATION_JSON))
@@ -85,6 +85,34 @@ private[v1] class SessionsResource extends ApiRequestContext with Logging {
     }
   }
 
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[KyuubiEvent]))),
+    description = "get session info")
+  @GET
+  @Path("listSessionInfo")
+  def listSessionInfo(
+      @QueryParam("user") @DefaultValue("") user: String,
+      @QueryParam("serverIP") @DefaultValue("") serverIP: String): Seq[KyuubiSessionEvent] = {
+    try {
+      val kyuubiSessionEvents = ListBuffer[KyuubiSessionEvent]()
+      sessionManager.allSessions().map { session =>
+        kyuubiSessionEvents += sessionManager.getSession(session.handle.identifier.toString)
+          .asInstanceOf[KyuubiSession].getSessionEvent.get
+      }
+      (kyuubiSessionEvents
+        .filter(serverIP.equalsIgnoreCase("") || _.serverIP.equalsIgnoreCase(serverIP))
+        .filter(user.equalsIgnoreCase("") || _.user.equalsIgnoreCase(user)))
+    } catch {
+      case NonFatal(e) =>
+        val errorMsg = "Error getting all session info"
+        error(errorMsg, e)
+        throw new NotFoundException(errorMsg)
+    }
+  }
+
   @ApiResponse(
     responseCode = "200",
     content = Array(new Content(
@@ -404,4 +432,87 @@ private[v1] class SessionsResource extends ApiRequestContext with Logging {
         throw new NotFoundException(errorMsg)
     }
   }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[SQLDetail]))),
+    description =
+      "get sql detail list hosted a specific session binding via an identifier")
+  @GET
+  @Path("{sessionHandle}/sqlDetails")
+  def getOperations(
+      @PathParam("sessionHandle") sessionHandleStr: String): Seq[SQLDetail] = {
+    try {
+      sessionManager.getSession(sessionHandleStr)
+        .allOperations().map { operationHandle =>
+          val operation = fe.be.sessionManager.operationManager.getOperation(operationHandle)
+          val kyuubiSessionEvent = KyuubiOperationEvent(operation.asInstanceOf[KyuubiOperation])
+          new SQLDetail(
+            sessionHandleStr,
+            kyuubiSessionEvent.sessionUser,
+            kyuubiSessionEvent.statementId,
+            kyuubiSessionEvent.createTime,
+            kyuubiSessionEvent.completeTime,
+            kyuubiSessionEvent.statement,
+            kyuubiSessionEvent.engineId,
+            kyuubiSessionEvent.engineType,
+            kyuubiSessionEvent.engineShareLevel,
+            kyuubiSessionEvent.exception.map(_.toString).orNull)
+        }.toSeq
+    } catch {
+      case NonFatal(e) =>
+        error(s"Invalid $sessionHandleStr", e)
+        throw new NotFoundException(s"Invalid $sessionHandleStr")
+    }
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[InfoDetail]))),
+    description =
+      "get all supported info types by Kyuubi session")
+  @GET
+  @Path("{sessionHandle}/infoTypes")
+  def getSupportedInfoType(
+      @PathParam("sessionHandle") sessionHandleStr: String): Seq[InfoDetail] = {
+    try {
+      val infoTypes = TGetInfoType.values()
+      infoTypes.map(infoType => {
+        new InfoDetail(infoType.toString, infoType.getValue.toString)
+      }).toSeq
+    } catch {
+      case NonFatal(e) =>
+        error(s"Invalid $sessionHandleStr", e)
+        throw new NotFoundException(s"Invalid $sessionHandleStr")
+    }
+  }
+
+  @ApiResponse(
+    responseCode = "200",
+    content = Array(new Content(
+      mediaType = MediaType.APPLICATION_JSON,
+      schema = new Schema(implementation = classOf[KyuubiOperationEvent]))),
+    description =
+      "get all the operation event hosted a specific session binding via an identifier")
+  @GET
+  @Path("{sessionHandle}/operations")
+  def getAllOperationEvent(
+      @PathParam("sessionHandle") sessionHandleStr: String): Seq[KyuubiOperationEvent] = {
+    try {
+      sessionManager.getSession(sessionHandleStr)
+        .allOperations().map { operationHandle =>
+          val operation = fe.be.sessionManager.operationManager.getOperation(operationHandle)
+          KyuubiOperationEvent(operation.asInstanceOf[KyuubiOperation])
+        }.toSeq
+    } catch {
+      case NonFatal(e) =>
+        error(s"Invalid $sessionHandleStr", e)
+        throw new NotFoundException(s"Invalid $sessionHandleStr")
+    }
+  }
+
 }
diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala
index e4203b301..93381cd1c 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/session/KyuubiSessionImpl.scala
@@ -206,9 +206,19 @@ class KyuubiSessionImpl(
           operation.close()
           throw t
       }
-      sessionEvent.totalOperations += 1
     }
-    super.runOperation(operation)
+    try {
+      sessionEvent.runningOperations += 1
+      val OperationHandle = super.runOperation(operation)
+      sessionEvent.runningOperations -= 1
+      sessionEvent.finishedOperations += 1
+      OperationHandle
+    } catch {
+      case e: Exception =>
+        sessionEvent.errorOperations += 1
+        throw e
+    }
+
   }
 
   @volatile private var engineLaunched: Boolean = false
diff --git a/kyuubi-server/web-ui/src/router/contact/index.ts b/kyuubi-server/src/main/scala/org/apache/kyuubi/util/OSUtils.scala
similarity index 68%
copy from kyuubi-server/web-ui/src/router/contact/index.ts
copy to kyuubi-server/src/main/scala/org/apache/kyuubi/util/OSUtils.scala
index a83c653ec..5754747bd 100644
--- a/kyuubi-server/web-ui/src/router/contact/index.ts
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/util/OSUtils.scala
@@ -15,12 +15,23 @@
  * limitations under the License.
  */
 
-const routes = [
-  {
-    path: '/contact',
-    name: 'contact',
-    component: () => import('@/views/contact/index.vue')
+package org.apache.kyuubi.util
+
+import oshi.SystemInfo
+
+object OSUtils {
+  private val SI = new SystemInfo
+
+  private val hal = SI.getHardware
+
+  def memoryTotal(): Long = {
+    val memory = hal.getMemory()
+    val memoryTotal = memory.getTotal
+    memoryTotal
   }
-]
 
-export default routes
+  def cpuTotal(): Int = {
+    val processor = hal.getProcessor()
+    processor.getLogicalProcessorCount
+  }
+}
diff --git a/kyuubi-server/web-ui/package.json b/kyuubi-server/web-ui/package.json
index 63fdc7221..131e69b7f 100644
--- a/kyuubi-server/web-ui/package.json
+++ b/kyuubi-server/web-ui/package.json
@@ -17,6 +17,7 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.0.9",
     "axios": "^0.27.2",
+    "date-fns": "^2.29.3",
     "element-plus": "^2.2.12",
     "pinia": "^2.0.18",
     "pinia-plugin-persistedstate": "^2.1.1",
diff --git a/kyuubi-server/web-ui/pnpm-lock.yaml b/kyuubi-server/web-ui/pnpm-lock.yaml
index 61fc5124d..c44c785bc 100644
--- a/kyuubi-server/web-ui/pnpm-lock.yaml
+++ b/kyuubi-server/web-ui/pnpm-lock.yaml
@@ -12,6 +12,7 @@ specifiers:
   '@vue/eslint-config-typescript': ^11.0.0
   '@vue/test-utils': ^2.0.2
   axios: ^0.27.2
+  date-fns: ^2.29.3
   element-plus: ^2.2.12
   eslint: ^8.21.0
   eslint-plugin-prettier: ^4.2.1
@@ -30,80 +31,90 @@ specifiers:
   vue-tsc: ^0.38.4
 
 dependencies:
-  '@element-plus/icons-vue': 2.0.9_vue@3.2.37
+  '@element-plus/icons-vue': 2.0.10_vue@3.2.45
   axios: 0.27.2
-  element-plus: 2.2.13_vue@3.2.37
-  pinia: 2.0.18_j6bzmzd4ujpabbp5objtwxyjp4
-  pinia-plugin-persistedstate: 2.1.1_pinia@2.0.18
-  vue: 3.2.37
-  vue-i18n: 9.2.2_vue@3.2.37
-  vue-router: 4.1.3_vue@3.2.37
+  date-fns: 2.29.3
+  element-plus: 2.2.26_vue@3.2.45
+  pinia: 2.0.27_mgnvym7yiazkylwwogi5r767ue
+  pinia-plugin-persistedstate: 2.4.0_pinia@2.0.27
+  vue: 3.2.45
+  vue-i18n: 9.2.2_vue@3.2.45
+  vue-router: 4.1.6_vue@3.2.45
 
 devDependencies:
-  '@iconify-json/ep': 1.1.7
-  '@types/node': 18.7.6
-  '@typescript-eslint/eslint-plugin': 5.33.1_vsoshirnpb7xw6mr7xomgfas2i
-  '@typescript-eslint/parser': 5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq
-  '@vitejs/plugin-vue': 3.0.3_vite@3.0.8+vue@3.2.37
-  '@vitest/coverage-c8': 0.22.0_jsdom@20.0.0+sass@1.54.4
-  '@vue/eslint-config-prettier': 7.0.0_2xd4q2tc5cqa5as7uugqhp6oue
-  '@vue/eslint-config-typescript': 11.0.0_4py5zxx5ck6utobkmozwvrmyiy
-  '@vue/test-utils': 2.0.2_vue@3.2.37
-  eslint: 8.22.0
-  eslint-plugin-prettier: 4.2.1_2xd4q2tc5cqa5as7uugqhp6oue
-  eslint-plugin-vue: 9.3.0_eslint@8.22.0
-  jsdom: 20.0.0
-  prettier: 2.7.1
-  sass: 1.54.4
-  typescript: 4.7.4
-  vite: 3.0.8_sass@1.54.4
-  vitest: 0.22.0_jsdom@20.0.0+sass@1.54.4
-  vue-tsc: 0.38.9_typescript@4.7.4
+  '@iconify-json/ep': 1.1.8
+  '@types/node': 18.11.11
+  '@typescript-eslint/eslint-plugin': 5.45.1_tdm6ms4ntwhlpozn7kjqrhum74
+  '@typescript-eslint/parser': 5.45.1_s5ps7njkmjlaqajutnox5ntcla
+  '@vitejs/plugin-vue': 3.2.0_vite@3.2.5+vue@3.2.45
+  '@vitest/coverage-c8': 0.22.1_jsdom@20.0.3+sass@1.56.1
+  '@vue/eslint-config-prettier': 7.0.0_yz7jvh5l5mgl5xhpwlgtkgcccy
+  '@vue/eslint-config-typescript': 11.0.2_sb3redb42nwkq4q6vnqovjecl4
+  '@vue/test-utils': 2.2.6_vue@3.2.45
+  eslint: 8.29.0
+  eslint-plugin-prettier: 4.2.1_yz7jvh5l5mgl5xhpwlgtkgcccy
+  eslint-plugin-vue: 9.8.0_eslint@8.29.0
+  jsdom: 20.0.3
+  prettier: 2.8.0
+  sass: 1.56.1
+  typescript: 4.9.3
+  vite: 3.2.5_tr7neyh64qpc4cjqb6wcsya4ri
+  vitest: 0.22.1_jsdom@20.0.3+sass@1.56.1
+  vue-tsc: 0.38.9_typescript@4.9.3
 
 packages:
 
-  /@babel/helper-string-parser/7.18.10:
-    resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
+  /@babel/helper-string-parser/7.19.4:
+    resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
     engines: {node: '>=6.9.0'}
 
-  /@babel/helper-validator-identifier/7.18.6:
-    resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
+  /@babel/helper-validator-identifier/7.19.1:
+    resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
     engines: {node: '>=6.9.0'}
 
-  /@babel/parser/7.18.11:
-    resolution: {integrity: sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==}
+  /@babel/parser/7.20.5:
+    resolution: {integrity: sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==}
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.18.10
+      '@babel/types': 7.20.5
 
-  /@babel/types/7.18.10:
-    resolution: {integrity: sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==}
+  /@babel/types/7.20.5:
+    resolution: {integrity: sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-string-parser': 7.18.10
-      '@babel/helper-validator-identifier': 7.18.6
+      '@babel/helper-string-parser': 7.19.4
+      '@babel/helper-validator-identifier': 7.19.1
       to-fast-properties: 2.0.0
 
   /@bcoe/v8-coverage/0.2.3:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
 
-  /@ctrl/tinycolor/3.4.1:
-    resolution: {integrity: sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==}
+  /@ctrl/tinycolor/3.5.0:
+    resolution: {integrity: sha512-tlJpwF40DEQcfR/QF+wNMVyGMaO9FQp6Z1Wahj4Gk3CJQYHwA2xVG7iKDFdW6zuxZY9XWOpGcfNCTsX4McOsOg==}
     engines: {node: '>=10'}
     dev: false
 
-  /@element-plus/icons-vue/2.0.9_vue@3.2.37:
-    resolution: {integrity: sha512-okdrwiVeKBmW41Hkl0eMrXDjzJwhQMuKiBOu17rOszqM+LS/yBYpNQNV5Jvoh06Wc+89fMmb/uhzf8NZuDuUaQ==}
+  /@element-plus/icons-vue/2.0.10_vue@3.2.45:
+    resolution: {integrity: sha512-ygEZ1mwPjcPo/OulhzLE7mtDrQBWI8vZzEWSNB2W/RNCRjoQGwbaK4N8lV4rid7Ts4qvySU3njMN7YCiSlSaTQ==}
     peerDependencies:
       vue: ^3.2.0
     dependencies:
-      vue: 3.2.37
+      vue: 3.2.45
     dev: false
 
-  /@esbuild/linux-loong64/0.14.54:
-    resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+  /@esbuild/android-arm/0.15.18:
+    resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /@esbuild/linux-loong64/0.15.18:
+    resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==}
     engines: {node: '>=12'}
     cpu: [loong64]
     os: [linux]
@@ -111,15 +122,15 @@ packages:
     dev: true
     optional: true
 
-  /@eslint/eslintrc/1.3.0:
-    resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==}
+  /@eslint/eslintrc/1.3.3:
+    resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
       debug: 4.3.4
-      espree: 9.3.3
-      globals: 13.17.0
-      ignore: 5.2.0
+      espree: 9.4.1
+      globals: 13.18.0
+      ignore: 5.2.1
       import-fresh: 3.3.0
       js-yaml: 4.1.0
       minimatch: 3.1.2
@@ -128,18 +139,18 @@ packages:
       - supports-color
     dev: true
 
-  /@floating-ui/core/0.7.3:
-    resolution: {integrity: sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==}
+  /@floating-ui/core/1.0.3:
+    resolution: {integrity: sha512-27FDAEVHrAEQI1UV+7FIjZrFK862gBoAG0USoPMU7RoBCmaTDt6bnKVW/J2uPnOPI6TWqiWGtS7RFN+tN/k+vQ==}
     dev: false
 
-  /@floating-ui/dom/0.5.4:
-    resolution: {integrity: sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==}
+  /@floating-ui/dom/1.0.8:
+    resolution: {integrity: sha512-uUm1xYQ0xdmFLhtetKgjK9AtF4INwDVwuJZvpK19ENHg1wYn7T0w9phYyCL5+HEpgJtX4ZYG6HJkDbtd2yP6cg==}
     dependencies:
-      '@floating-ui/core': 0.7.3
+      '@floating-ui/core': 1.0.3
     dev: false
 
-  /@humanwhocodes/config-array/0.10.4:
-    resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==}
+  /@humanwhocodes/config-array/0.11.7:
+    resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==}
     engines: {node: '>=10.10.0'}
     dependencies:
       '@humanwhocodes/object-schema': 1.2.1
@@ -149,22 +160,23 @@ packages:
       - supports-color
     dev: true
 
-  /@humanwhocodes/gitignore-to-minimatch/1.0.2:
-    resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==}
+  /@humanwhocodes/module-importer/1.0.1:
+    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+    engines: {node: '>=12.22'}
     dev: true
 
   /@humanwhocodes/object-schema/1.2.1:
     resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
     dev: true
 
-  /@iconify-json/ep/1.1.7:
-    resolution: {integrity: sha512-GhXWVKalXFlrGgfrCXAgqBre5hv3pPAknuxyywmjamcrL5gl5Mq9WOZtuhb4cB6cJ5pMiKOMtegt73FheqWscA==}
+  /@iconify-json/ep/1.1.8:
+    resolution: {integrity: sha512-pHCrsWU1R9/pTDU+Fps4+mjqOQFLtpGdXWegkhQ1P1DlgQAlCPyICtl6E1s8b7VwJMeZXaK84HA02UF6WD0o/Q==}
     dependencies:
-      '@iconify/types': 1.1.0
+      '@iconify/types': 2.0.0
     dev: true
 
-  /@iconify/types/1.1.0:
-    resolution: {integrity: sha512-Jh0llaK2LRXQoYsorIH8maClebsnzTcve+7U3rQUSnC11X4jtPnFuyatqFLvMxZ8MLG8dB4zfHsbPfuvxluONw==}
+  /@iconify/types/2.0.0:
+    resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
     dev: true
 
   /@intlify/core-base/9.2.2:
@@ -219,8 +231,8 @@ packages:
     resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
     dev: true
 
-  /@jridgewell/trace-mapping/0.3.15:
-    resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==}
+  /@jridgewell/trace-mapping/0.3.17:
+    resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
     dependencies:
       '@jridgewell/resolve-uri': 3.1.0
       '@jridgewell/sourcemap-codec': 1.4.14
@@ -244,7 +256,7 @@ packages:
     engines: {node: '>= 8'}
     dependencies:
       '@nodelib/fs.scandir': 2.1.5
-      fastq: 1.13.0
+      fastq: 1.14.0
     dev: true
 
   /@sxzz/popperjs-es/2.11.7:
@@ -259,11 +271,11 @@ packages:
   /@types/chai-subset/1.3.3:
     resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
     dependencies:
-      '@types/chai': 4.3.3
+      '@types/chai': 4.3.4
     dev: true
 
-  /@types/chai/4.3.3:
-    resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==}
+  /@types/chai/4.3.4:
+    resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
     dev: true
 
   /@types/istanbul-lib-coverage/2.0.4:
@@ -277,23 +289,27 @@ packages:
   /@types/lodash-es/4.17.6:
     resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
     dependencies:
-      '@types/lodash': 4.14.183
+      '@types/lodash': 4.14.191
     dev: false
 
-  /@types/lodash/4.14.183:
-    resolution: {integrity: sha512-UXavyuxzXKMqJPEpFPri6Ku5F9af6ZJXUneHhvQJxavrEjuHkFp2YnDWHcxJiG7hk8ZkWqjcyNeW1s/smZv5cw==}
+  /@types/lodash/4.14.191:
+    resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
     dev: false
 
-  /@types/node/18.7.6:
-    resolution: {integrity: sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A==}
+  /@types/node/18.11.11:
+    resolution: {integrity: sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==}
+    dev: true
+
+  /@types/semver/7.3.13:
+    resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
     dev: true
 
-  /@types/web-bluetooth/0.0.14:
-    resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
+  /@types/web-bluetooth/0.0.16:
+    resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
     dev: false
 
-  /@typescript-eslint/eslint-plugin/5.33.1_vsoshirnpb7xw6mr7xomgfas2i:
-    resolution: {integrity: sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==}
+  /@typescript-eslint/eslint-plugin/5.45.1_tdm6ms4ntwhlpozn7kjqrhum74:
+    resolution: {integrity: sha512-cOizjPlKEh0bXdFrBLTrI/J6B/QMlhwE9auOov53tgB+qMukH6/h8YAK/qw+QJGct/PTbdh2lytGyipxCcEtAw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       '@typescript-eslint/parser': ^5.0.0
@@ -303,24 +319,24 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq
-      '@typescript-eslint/scope-manager': 5.33.1
-      '@typescript-eslint/type-utils': 5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq
-      '@typescript-eslint/utils': 5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq
+      '@typescript-eslint/parser': 5.45.1_s5ps7njkmjlaqajutnox5ntcla
+      '@typescript-eslint/scope-manager': 5.45.1
+      '@typescript-eslint/type-utils': 5.45.1_s5ps7njkmjlaqajutnox5ntcla
+      '@typescript-eslint/utils': 5.45.1_s5ps7njkmjlaqajutnox5ntcla
       debug: 4.3.4
-      eslint: 8.22.0
-      functional-red-black-tree: 1.0.1
-      ignore: 5.2.0
+      eslint: 8.29.0
+      ignore: 5.2.1
+      natural-compare-lite: 1.4.0
       regexpp: 3.2.0
-      semver: 7.3.7
-      tsutils: 3.21.0_typescript@4.7.4
-      typescript: 4.7.4
+      semver: 7.3.8
+      tsutils: 3.21.0_typescript@4.9.3
+      typescript: 4.9.3
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser/5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq:
-    resolution: {integrity: sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==}
+  /@typescript-eslint/parser/5.45.1_s5ps7njkmjlaqajutnox5ntcla:
+    resolution: {integrity: sha512-JQ3Ep8bEOXu16q0ztsatp/iQfDCtvap7sp/DKo7DWltUquj5AfCOpX2zSzJ8YkAVnrQNqQ5R62PBz2UtrfmCkA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -329,26 +345,26 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 5.33.1
-      '@typescript-eslint/types': 5.33.1
-      '@typescript-eslint/typescript-estree': 5.33.1_typescript@4.7.4
+      '@typescript-eslint/scope-manager': 5.45.1
+      '@typescript-eslint/types': 5.45.1
+      '@typescript-eslint/typescript-estree': 5.45.1_typescript@4.9.3
       debug: 4.3.4
-      eslint: 8.22.0
-      typescript: 4.7.4
+      eslint: 8.29.0
+      typescript: 4.9.3
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/scope-manager/5.33.1:
-    resolution: {integrity: sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==}
+  /@typescript-eslint/scope-manager/5.45.1:
+    resolution: {integrity: sha512-D6fCileR6Iai7E35Eb4Kp+k0iW7F1wxXYrOhX/3dywsOJpJAQ20Fwgcf+P/TDtvQ7zcsWsrJaglaQWDhOMsspQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      '@typescript-eslint/types': 5.33.1
-      '@typescript-eslint/visitor-keys': 5.33.1
+      '@typescript-eslint/types': 5.45.1
+      '@typescript-eslint/visitor-keys': 5.45.1
     dev: true
 
-  /@typescript-eslint/type-utils/5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq:
-    resolution: {integrity: sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==}
+  /@typescript-eslint/type-utils/5.45.1_s5ps7njkmjlaqajutnox5ntcla:
+    resolution: {integrity: sha512-aosxFa+0CoYgYEl3aptLe1svP910DJq68nwEJzyQcrtRhC4BN0tJAvZGAe+D0tzjJmFXe+h4leSsiZhwBa2vrA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: '*'
@@ -357,22 +373,23 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/utils': 5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq
+      '@typescript-eslint/typescript-estree': 5.45.1_typescript@4.9.3
+      '@typescript-eslint/utils': 5.45.1_s5ps7njkmjlaqajutnox5ntcla
       debug: 4.3.4
-      eslint: 8.22.0
-      tsutils: 3.21.0_typescript@4.7.4
-      typescript: 4.7.4
+      eslint: 8.29.0
+      tsutils: 3.21.0_typescript@4.9.3
+      typescript: 4.9.3
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/types/5.33.1:
-    resolution: {integrity: sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==}
+  /@typescript-eslint/types/5.45.1:
+    resolution: {integrity: sha512-HEW3U0E5dLjUT+nk7b4lLbOherS1U4ap+b9pfu2oGsW3oPu7genRaY9dDv3nMczC1rbnRY2W/D7SN05wYoGImg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /@typescript-eslint/typescript-estree/5.33.1_typescript@4.7.4:
-    resolution: {integrity: sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==}
+  /@typescript-eslint/typescript-estree/5.45.1_typescript@4.9.3:
+    resolution: {integrity: sha512-76NZpmpCzWVrrb0XmYEpbwOz/FENBi+5W7ipVXAsG3OoFrQKJMiaqsBMbvGRyLtPotGqUfcY7Ur8j0dksDJDng==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       typescript: '*'
@@ -380,60 +397,62 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 5.33.1
-      '@typescript-eslint/visitor-keys': 5.33.1
+      '@typescript-eslint/types': 5.45.1
+      '@typescript-eslint/visitor-keys': 5.45.1
       debug: 4.3.4
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.3.7
-      tsutils: 3.21.0_typescript@4.7.4
-      typescript: 4.7.4
+      semver: 7.3.8
+      tsutils: 3.21.0_typescript@4.9.3
+      typescript: 4.9.3
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/utils/5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq:
-    resolution: {integrity: sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==}
+  /@typescript-eslint/utils/5.45.1_s5ps7njkmjlaqajutnox5ntcla:
+    resolution: {integrity: sha512-rlbC5VZz68+yjAzQBc4I7KDYVzWG2X/OrqoZrMahYq3u8FFtmQYc+9rovo/7wlJH5kugJ+jQXV5pJMnofGmPRw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
     dependencies:
       '@types/json-schema': 7.0.11
-      '@typescript-eslint/scope-manager': 5.33.1
-      '@typescript-eslint/types': 5.33.1
-      '@typescript-eslint/typescript-estree': 5.33.1_typescript@4.7.4
-      eslint: 8.22.0
+      '@types/semver': 7.3.13
+      '@typescript-eslint/scope-manager': 5.45.1
+      '@typescript-eslint/types': 5.45.1
+      '@typescript-eslint/typescript-estree': 5.45.1_typescript@4.9.3
+      eslint: 8.29.0
       eslint-scope: 5.1.1
-      eslint-utils: 3.0.0_eslint@8.22.0
+      eslint-utils: 3.0.0_eslint@8.29.0
+      semver: 7.3.8
     transitivePeerDependencies:
       - supports-color
       - typescript
     dev: true
 
-  /@typescript-eslint/visitor-keys/5.33.1:
-    resolution: {integrity: sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==}
+  /@typescript-eslint/visitor-keys/5.45.1:
+    resolution: {integrity: sha512-cy9ln+6rmthYWjH9fmx+5FU/JDpjQb586++x2FZlveq7GdGuLLW9a2Jcst2TGekH82bXpfmRNSwP9tyEs6RjvQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      '@typescript-eslint/types': 5.33.1
+      '@typescript-eslint/types': 5.45.1
       eslint-visitor-keys: 3.3.0
     dev: true
 
-  /@vitejs/plugin-vue/3.0.3_vite@3.0.8+vue@3.2.37:
-    resolution: {integrity: sha512-U4zNBlz9mg+TA+i+5QPc3N5lQvdUXENZLO2h0Wdzp56gI1MWhqJOv+6R+d4kOzoaSSq6TnGPBdZAXKOe4lXy6g==}
+  /@vitejs/plugin-vue/3.2.0_vite@3.2.5+vue@3.2.45:
+    resolution: {integrity: sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^3.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 3.0.8_sass@1.54.4
-      vue: 3.2.37
+      vite: 3.2.5_tr7neyh64qpc4cjqb6wcsya4ri
+      vue: 3.2.45
     dev: true
 
-  /@vitest/coverage-c8/0.22.0_jsdom@20.0.0+sass@1.54.4:
-    resolution: {integrity: sha512-jwW6b8U+h9nbzQfKoRmpf2xjDg+mcAjLIdVUrZGhjTnIdekGfvoqFoeiXzsLv2HwYBeFi4943lYUftuj8qD1FQ==}
+  /@vitest/coverage-c8/0.22.1_jsdom@20.0.3+sass@1.56.1:
+    resolution: {integrity: sha512-KOOYpO7EGpaF+nD8GD+Y05D0JtZp12NUu6DdLXvBPqSOPo2HkZ7KNBtfR0rb6gOy3NLtGiWTYTzCwhajgb2HlA==}
     dependencies:
       c8: 7.12.0
-      vitest: 0.22.0_jsdom@20.0.0+sass@1.54.4
+      vitest: 0.22.1_jsdom@20.0.3+sass@1.56.1
     transitivePeerDependencies:
       - '@edge-runtime/vm'
       - '@vitest/browser'
@@ -443,6 +462,7 @@ packages:
       - less
       - sass
       - stylus
+      - sugarss
       - supports-color
       - terser
     dev: true
@@ -462,9 +482,9 @@ packages:
     dependencies:
       '@volar/code-gen': 0.38.9
       '@volar/source-map': 0.38.9
-      '@vue/compiler-core': 3.2.37
-      '@vue/compiler-dom': 3.2.37
-      '@vue/shared': 3.2.37
+      '@vue/compiler-core': 3.2.45
+      '@vue/compiler-dom': 3.2.45
+      '@vue/shared': 3.2.45
     dev: true
 
   /@volar/vue-typescript/0.38.9:
@@ -473,62 +493,62 @@ packages:
       '@volar/code-gen': 0.38.9
       '@volar/source-map': 0.38.9
       '@volar/vue-code-gen': 0.38.9
-      '@vue/compiler-sfc': 3.2.37
-      '@vue/reactivity': 3.2.37
+      '@vue/compiler-sfc': 3.2.45
+      '@vue/reactivity': 3.2.45
     dev: true
 
-  /@vue/compiler-core/3.2.37:
-    resolution: {integrity: sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==}
+  /@vue/compiler-core/3.2.45:
+    resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==}
     dependencies:
-      '@babel/parser': 7.18.11
-      '@vue/shared': 3.2.37
+      '@babel/parser': 7.20.5
+      '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       source-map: 0.6.1
 
-  /@vue/compiler-dom/3.2.37:
-    resolution: {integrity: sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==}
+  /@vue/compiler-dom/3.2.45:
+    resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==}
     dependencies:
-      '@vue/compiler-core': 3.2.37
-      '@vue/shared': 3.2.37
+      '@vue/compiler-core': 3.2.45
+      '@vue/shared': 3.2.45
 
-  /@vue/compiler-sfc/3.2.37:
-    resolution: {integrity: sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==}
+  /@vue/compiler-sfc/3.2.45:
+    resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==}
     dependencies:
-      '@babel/parser': 7.18.11
-      '@vue/compiler-core': 3.2.37
-      '@vue/compiler-dom': 3.2.37
-      '@vue/compiler-ssr': 3.2.37
-      '@vue/reactivity-transform': 3.2.37
-      '@vue/shared': 3.2.37
+      '@babel/parser': 7.20.5
+      '@vue/compiler-core': 3.2.45
+      '@vue/compiler-dom': 3.2.45
+      '@vue/compiler-ssr': 3.2.45
+      '@vue/reactivity-transform': 3.2.45
+      '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       magic-string: 0.25.9
-      postcss: 8.4.16
+      postcss: 8.4.19
       source-map: 0.6.1
 
-  /@vue/compiler-ssr/3.2.37:
-    resolution: {integrity: sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==}
+  /@vue/compiler-ssr/3.2.45:
+    resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==}
     dependencies:
-      '@vue/compiler-dom': 3.2.37
-      '@vue/shared': 3.2.37
+      '@vue/compiler-dom': 3.2.45
+      '@vue/shared': 3.2.45
 
-  /@vue/devtools-api/6.2.1:
-    resolution: {integrity: sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==}
+  /@vue/devtools-api/6.4.5:
+    resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==}
     dev: false
 
-  /@vue/eslint-config-prettier/7.0.0_2xd4q2tc5cqa5as7uugqhp6oue:
+  /@vue/eslint-config-prettier/7.0.0_yz7jvh5l5mgl5xhpwlgtkgcccy:
     resolution: {integrity: sha512-/CTc6ML3Wta1tCe1gUeO0EYnVXfo3nJXsIhZ8WJr3sov+cGASr6yuiibJTL6lmIBm7GobopToOuB3B6AWyV0Iw==}
     peerDependencies:
       eslint: '>= 7.28.0'
       prettier: '>= 2.0.0'
     dependencies:
-      eslint: 8.22.0
-      eslint-config-prettier: 8.5.0_eslint@8.22.0
-      eslint-plugin-prettier: 4.2.1_i2cojdczqdiurzgttlwdgf764e
-      prettier: 2.7.1
+      eslint: 8.29.0
+      eslint-config-prettier: 8.5.0_eslint@8.29.0
+      eslint-plugin-prettier: 4.2.1_nrhoyyjffvfyk4vtlt5destxgm
+      prettier: 2.8.0
     dev: true
 
-  /@vue/eslint-config-typescript/11.0.0_4py5zxx5ck6utobkmozwvrmyiy:
-    resolution: {integrity: sha512-txuRzxnQVmtUvvy9UyWUy9sHWXNeRPGmSPqP53hRtaiUeCTAondI9Ho9GQYI/8/eWljYOST7iA4Aa8sANBkWaA==}
+  /@vue/eslint-config-typescript/11.0.2_sb3redb42nwkq4q6vnqovjecl4:
+    resolution: {integrity: sha512-EiKud1NqlWmSapBFkeSrE994qpKx7/27uCGnhdqzllYDpQZroyX/O6bwjEpeuyKamvLbsGdO6PMR2faIf+zFnw==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
@@ -538,132 +558,114 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/eslint-plugin': 5.33.1_vsoshirnpb7xw6mr7xomgfas2i
-      '@typescript-eslint/parser': 5.33.1_4rv7y5c6xz3vfxwhbrcxxi73bq
-      eslint: 8.22.0
-      eslint-plugin-vue: 9.3.0_eslint@8.22.0
-      typescript: 4.7.4
-      vue-eslint-parser: 9.0.3_eslint@8.22.0
+      '@typescript-eslint/eslint-plugin': 5.45.1_tdm6ms4ntwhlpozn7kjqrhum74
+      '@typescript-eslint/parser': 5.45.1_s5ps7njkmjlaqajutnox5ntcla
+      eslint: 8.29.0
+      eslint-plugin-vue: 9.8.0_eslint@8.29.0
+      typescript: 4.9.3
+      vue-eslint-parser: 9.1.0_eslint@8.29.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vue/reactivity-transform/3.2.37:
-    resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
+  /@vue/reactivity-transform/3.2.45:
+    resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==}
     dependencies:
-      '@babel/parser': 7.18.11
-      '@vue/compiler-core': 3.2.37
-      '@vue/shared': 3.2.37
+      '@babel/parser': 7.20.5
+      '@vue/compiler-core': 3.2.45
+      '@vue/shared': 3.2.45
       estree-walker: 2.0.2
       magic-string: 0.25.9
 
-  /@vue/reactivity/3.2.37:
-    resolution: {integrity: sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==}
+  /@vue/reactivity/3.2.45:
+    resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==}
     dependencies:
-      '@vue/shared': 3.2.37
+      '@vue/shared': 3.2.45
 
-  /@vue/runtime-core/3.2.37:
-    resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
+  /@vue/runtime-core/3.2.45:
+    resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==}
     dependencies:
-      '@vue/reactivity': 3.2.37
-      '@vue/shared': 3.2.37
+      '@vue/reactivity': 3.2.45
+      '@vue/shared': 3.2.45
 
-  /@vue/runtime-dom/3.2.37:
-    resolution: {integrity: sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==}
+  /@vue/runtime-dom/3.2.45:
+    resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==}
     dependencies:
-      '@vue/runtime-core': 3.2.37
-      '@vue/shared': 3.2.37
-      csstype: 2.6.20
+      '@vue/runtime-core': 3.2.45
+      '@vue/shared': 3.2.45
+      csstype: 2.6.21
 
-  /@vue/server-renderer/3.2.37_vue@3.2.37:
-    resolution: {integrity: sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==}
+  /@vue/server-renderer/3.2.45_vue@3.2.45:
+    resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
     peerDependencies:
-      vue: 3.2.37
+      vue: 3.2.45
     dependencies:
-      '@vue/compiler-ssr': 3.2.37
-      '@vue/shared': 3.2.37
-      vue: 3.2.37
+      '@vue/compiler-ssr': 3.2.45
+      '@vue/shared': 3.2.45
+      vue: 3.2.45
 
-  /@vue/shared/3.2.37:
-    resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==}
+  /@vue/shared/3.2.45:
+    resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
 
-  /@vue/test-utils/2.0.2_vue@3.2.37:
-    resolution: {integrity: sha512-E2P4oXSaWDqTZNbmKZFVLrNN/siVN78YkEqs7pHryWerrlZR9bBFLWdJwRoguX45Ru6HxIflzKl4vQvwRMwm5g==}
+  /@vue/test-utils/2.2.6_vue@3.2.45:
+    resolution: {integrity: sha512-64zHtJZdG7V/U2L0j/z3Pt5bSygccI3xs+Kl7LB73AZK4MQ8WONJhqDQPK8leUFFA9CmmoJygeky7zcl2hX10A==}
     peerDependencies:
       vue: ^3.0.1
     dependencies:
-      vue: 3.2.37
+      vue: 3.2.45
     dev: true
 
-  /@vueuse/core/8.9.4_vue@3.2.37:
-    resolution: {integrity: sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q==}
-    peerDependencies:
-      '@vue/composition-api': ^1.1.0
-      vue: ^2.6.0 || ^3.2.0
-    peerDependenciesMeta:
-      '@vue/composition-api':
-        optional: true
-      vue:
-        optional: true
+  /@vueuse/core/9.6.0_vue@3.2.45:
+    resolution: {integrity: sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==}
     dependencies:
-      '@types/web-bluetooth': 0.0.14
-      '@vueuse/metadata': 8.9.4
-      '@vueuse/shared': 8.9.4_vue@3.2.37
-      vue: 3.2.37
-      vue-demi: 0.13.8_vue@3.2.37
+      '@types/web-bluetooth': 0.0.16
+      '@vueuse/metadata': 9.6.0
+      '@vueuse/shared': 9.6.0_vue@3.2.45
+      vue-demi: 0.13.11_vue@3.2.45
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
     dev: false
 
-  /@vueuse/metadata/8.9.4:
-    resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==}
+  /@vueuse/metadata/9.6.0:
+    resolution: {integrity: sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w==}
     dev: false
 
-  /@vueuse/shared/8.9.4_vue@3.2.37:
-    resolution: {integrity: sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==}
-    peerDependencies:
-      '@vue/composition-api': ^1.1.0
-      vue: ^2.6.0 || ^3.2.0
-    peerDependenciesMeta:
-      '@vue/composition-api':
-        optional: true
-      vue:
-        optional: true
+  /@vueuse/shared/9.6.0_vue@3.2.45:
+    resolution: {integrity: sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==}
     dependencies:
-      vue: 3.2.37
-      vue-demi: 0.13.8_vue@3.2.37
+      vue-demi: 0.13.11_vue@3.2.45
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
     dev: false
 
   /abab/2.0.6:
     resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
     dev: true
 
-  /acorn-globals/6.0.0:
-    resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==}
+  /acorn-globals/7.0.1:
+    resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
     dependencies:
-      acorn: 7.4.1
-      acorn-walk: 7.2.0
+      acorn: 8.8.1
+      acorn-walk: 8.2.0
     dev: true
 
-  /acorn-jsx/5.3.2_acorn@8.8.0:
+  /acorn-jsx/5.3.2_acorn@8.8.1:
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
       acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      acorn: 8.8.0
-    dev: true
-
-  /acorn-walk/7.2.0:
-    resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
-    engines: {node: '>=0.4.0'}
+      acorn: 8.8.1
     dev: true
 
-  /acorn/7.4.1:
-    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+  /acorn-walk/8.2.0:
+    resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
     engines: {node: '>=0.4.0'}
-    hasBin: true
     dev: true
 
-  /acorn/8.8.0:
-    resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
+  /acorn/8.8.1:
+    resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
     engines: {node: '>=0.4.0'}
     hasBin: true
     dev: true
@@ -698,8 +700,8 @@ packages:
       color-convert: 2.0.1
     dev: true
 
-  /anymatch/3.1.2:
-    resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
+  /anymatch/3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
     dependencies:
       normalize-path: 3.0.0
@@ -729,7 +731,7 @@ packages:
   /axios/0.27.2:
     resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
     dependencies:
-      follow-redirects: 1.15.1
+      follow-redirects: 1.15.2
       form-data: 4.0.0
     transitivePeerDependencies:
       - debug
@@ -762,10 +764,6 @@ packages:
       fill-range: 7.0.1
     dev: true
 
-  /browser-process-hrtime/1.0.0:
-    resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
-    dev: true
-
   /c8/7.12.0:
     resolution: {integrity: sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==}
     engines: {node: '>=10.12.0'}
@@ -790,15 +788,15 @@ packages:
     engines: {node: '>=6'}
     dev: true
 
-  /chai/4.3.6:
-    resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==}
+  /chai/4.3.7:
+    resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==}
     engines: {node: '>=4'}
     dependencies:
       assertion-error: 1.1.0
       check-error: 1.0.2
-      deep-eql: 3.0.1
+      deep-eql: 4.1.3
       get-func-name: 2.0.0
-      loupe: 2.3.4
+      loupe: 2.3.6
       pathval: 1.1.1
       type-detect: 4.0.8
     dev: true
@@ -819,7 +817,7 @@ packages:
     resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
     engines: {node: '>= 8.10.0'}
     dependencies:
-      anymatch: 3.1.2
+      anymatch: 3.1.3
       braces: 3.0.2
       glob-parent: 5.1.2
       is-binary-path: 2.1.0
@@ -859,10 +857,8 @@ packages:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
     dev: true
 
-  /convert-source-map/1.8.0:
-    resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
-    dependencies:
-      safe-buffer: 5.1.2
+  /convert-source-map/1.9.0:
+    resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
     dev: true
 
   /cross-spawn/7.0.3:
@@ -895,8 +891,8 @@ packages:
       cssom: 0.3.8
     dev: true
 
-  /csstype/2.6.20:
-    resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==}
+  /csstype/2.6.21:
+    resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
 
   /data-urls/3.0.2:
     resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
@@ -907,8 +903,13 @@ packages:
       whatwg-url: 11.0.0
     dev: true
 
-  /dayjs/1.11.5:
-    resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==}
+  /date-fns/2.29.3:
+    resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
+    engines: {node: '>=0.11'}
+    dev: false
+
+  /dayjs/1.11.7:
+    resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
     dev: false
 
   /debug/4.3.4:
@@ -923,13 +924,13 @@ packages:
       ms: 2.1.2
     dev: true
 
-  /decimal.js/10.4.0:
-    resolution: {integrity: sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==}
+  /decimal.js/10.4.3:
+    resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
     dev: true
 
-  /deep-eql/3.0.1:
-    resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}
-    engines: {node: '>=0.12'}
+  /deep-eql/4.1.3:
+    resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
+    engines: {node: '>=6'}
     dependencies:
       type-detect: 4.0.8
     dev: true
@@ -963,27 +964,27 @@ packages:
       webidl-conversions: 7.0.0
     dev: true
 
-  /element-plus/2.2.13_vue@3.2.37:
-    resolution: {integrity: sha512-dKQ7BPZC8deUPhv+6s4GgOL0GyGj3KpUarywxm6s1nWnHjH6FqeZlUcxPqBvJd7W/d81POayx3B13GP+rfkG9g==}
+  /element-plus/2.2.26_vue@3.2.45:
+    resolution: {integrity: sha512-O/rdY5m9DkclpVg8r3GynyqCunm7MxSR142xSsjrZA77bi7bcwA3SIy6SPEDqHi5R4KqgkGYgKSp4Q4e3irbYg==}
     peerDependencies:
       vue: ^3.2.0
     dependencies:
-      '@ctrl/tinycolor': 3.4.1
-      '@element-plus/icons-vue': 2.0.9_vue@3.2.37
-      '@floating-ui/dom': 0.5.4
+      '@ctrl/tinycolor': 3.5.0
+      '@element-plus/icons-vue': 2.0.10_vue@3.2.45
+      '@floating-ui/dom': 1.0.8
       '@popperjs/core': /@sxzz/popperjs-es/2.11.7
-      '@types/lodash': 4.14.183
+      '@types/lodash': 4.14.191
       '@types/lodash-es': 4.17.6
-      '@vueuse/core': 8.9.4_vue@3.2.37
+      '@vueuse/core': 9.6.0_vue@3.2.45
       async-validator: 4.2.5
-      dayjs: 1.11.5
+      dayjs: 1.11.7
       escape-html: 1.0.3
       lodash: 4.17.21
       lodash-es: 4.17.21
-      lodash-unified: 1.0.2_3ib2ivapxullxkx3xftsimdk7u
+      lodash-unified: 1.0.3_3ib2ivapxullxkx3xftsimdk7u
       memoize-one: 6.0.0
       normalize-wheel-es: 1.2.0
-      vue: 3.2.37
+      vue: 3.2.45
     transitivePeerDependencies:
       - '@vue/composition-api'
     dev: false
@@ -992,13 +993,13 @@ packages:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
     dev: true
 
-  /entities/4.3.1:
-    resolution: {integrity: sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==}
+  /entities/4.4.0:
+    resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
     engines: {node: '>=0.12'}
     dev: true
 
-  /esbuild-android-64/0.14.54:
-    resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
+  /esbuild-android-64/0.15.18:
+    resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [android]
@@ -1006,8 +1007,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-android-arm64/0.14.54:
-    resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
+  /esbuild-android-arm64/0.15.18:
+    resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [android]
@@ -1015,8 +1016,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-darwin-64/0.14.54:
-    resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
+  /esbuild-darwin-64/0.15.18:
+    resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [darwin]
@@ -1024,8 +1025,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-darwin-arm64/0.14.54:
-    resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
+  /esbuild-darwin-arm64/0.15.18:
+    resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [darwin]
@@ -1033,8 +1034,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-freebsd-64/0.14.54:
-    resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
+  /esbuild-freebsd-64/0.15.18:
+    resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [freebsd]
@@ -1042,8 +1043,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-freebsd-arm64/0.14.54:
-    resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
+  /esbuild-freebsd-arm64/0.15.18:
+    resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [freebsd]
@@ -1051,8 +1052,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-32/0.14.54:
-    resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
+  /esbuild-linux-32/0.15.18:
+    resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [linux]
@@ -1060,8 +1061,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-64/0.14.54:
-    resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
+  /esbuild-linux-64/0.15.18:
+    resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [linux]
@@ -1069,8 +1070,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-arm/0.14.54:
-    resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
+  /esbuild-linux-arm/0.15.18:
+    resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [linux]
@@ -1078,8 +1079,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-arm64/0.14.54:
-    resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
+  /esbuild-linux-arm64/0.15.18:
+    resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [linux]
@@ -1087,8 +1088,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-mips64le/0.14.54:
-    resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
+  /esbuild-linux-mips64le/0.15.18:
+    resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==}
     engines: {node: '>=12'}
     cpu: [mips64el]
     os: [linux]
@@ -1096,8 +1097,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-ppc64le/0.14.54:
-    resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
+  /esbuild-linux-ppc64le/0.15.18:
+    resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==}
     engines: {node: '>=12'}
     cpu: [ppc64]
     os: [linux]
@@ -1105,8 +1106,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-riscv64/0.14.54:
-    resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
+  /esbuild-linux-riscv64/0.15.18:
+    resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==}
     engines: {node: '>=12'}
     cpu: [riscv64]
     os: [linux]
@@ -1114,8 +1115,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-linux-s390x/0.14.54:
-    resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
+  /esbuild-linux-s390x/0.15.18:
+    resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==}
     engines: {node: '>=12'}
     cpu: [s390x]
     os: [linux]
@@ -1123,8 +1124,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-netbsd-64/0.14.54:
-    resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
+  /esbuild-netbsd-64/0.15.18:
+    resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [netbsd]
@@ -1132,8 +1133,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-openbsd-64/0.14.54:
-    resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
+  /esbuild-openbsd-64/0.15.18:
+    resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [openbsd]
@@ -1141,8 +1142,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-sunos-64/0.14.54:
-    resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
+  /esbuild-sunos-64/0.15.18:
+    resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [sunos]
@@ -1150,8 +1151,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-windows-32/0.14.54:
-    resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
+  /esbuild-windows-32/0.15.18:
+    resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [win32]
@@ -1159,8 +1160,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-windows-64/0.14.54:
-    resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
+  /esbuild-windows-64/0.15.18:
+    resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [win32]
@@ -1168,8 +1169,8 @@ packages:
     dev: true
     optional: true
 
-  /esbuild-windows-arm64/0.14.54:
-    resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
+  /esbuild-windows-arm64/0.15.18:
+    resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [win32]
@@ -1177,33 +1178,34 @@ packages:
     dev: true
     optional: true
 
-  /esbuild/0.14.54:
-    resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
+  /esbuild/0.15.18:
+    resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
     optionalDependencies:
-      '@esbuild/linux-loong64': 0.14.54
-      esbuild-android-64: 0.14.54
-      esbuild-android-arm64: 0.14.54
-      esbuild-darwin-64: 0.14.54
-      esbuild-darwin-arm64: 0.14.54
-      esbuild-freebsd-64: 0.14.54
-      esbuild-freebsd-arm64: 0.14.54
-      esbuild-linux-32: 0.14.54
-      esbuild-linux-64: 0.14.54
-      esbuild-linux-arm: 0.14.54
-      esbuild-linux-arm64: 0.14.54
-      esbuild-linux-mips64le: 0.14.54
-      esbuild-linux-ppc64le: 0.14.54
-      esbuild-linux-riscv64: 0.14.54
-      esbuild-linux-s390x: 0.14.54
-      esbuild-netbsd-64: 0.14.54
-      esbuild-openbsd-64: 0.14.54
-      esbuild-sunos-64: 0.14.54
-      esbuild-windows-32: 0.14.54
-      esbuild-windows-64: 0.14.54
-      esbuild-windows-arm64: 0.14.54
+      '@esbuild/android-arm': 0.15.18
+      '@esbuild/linux-loong64': 0.15.18
+      esbuild-android-64: 0.15.18
+      esbuild-android-arm64: 0.15.18
+      esbuild-darwin-64: 0.15.18
+      esbuild-darwin-arm64: 0.15.18
+      esbuild-freebsd-64: 0.15.18
+      esbuild-freebsd-arm64: 0.15.18
+      esbuild-linux-32: 0.15.18
+      esbuild-linux-64: 0.15.18
+      esbuild-linux-arm: 0.15.18
+      esbuild-linux-arm64: 0.15.18
+      esbuild-linux-mips64le: 0.15.18
+      esbuild-linux-ppc64le: 0.15.18
+      esbuild-linux-riscv64: 0.15.18
+      esbuild-linux-s390x: 0.15.18
+      esbuild-netbsd-64: 0.15.18
+      esbuild-openbsd-64: 0.15.18
+      esbuild-sunos-64: 0.15.18
+      esbuild-windows-32: 0.15.18
+      esbuild-windows-64: 0.15.18
+      esbuild-windows-arm64: 0.15.18
     dev: true
 
   /escalade/3.1.1:
@@ -1233,16 +1235,16 @@ packages:
       source-map: 0.6.1
     dev: true
 
-  /eslint-config-prettier/8.5.0_eslint@8.22.0:
+  /eslint-config-prettier/8.5.0_eslint@8.29.0:
     resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==}
     hasBin: true
     peerDependencies:
       eslint: '>=7.0.0'
     dependencies:
-      eslint: 8.22.0
+      eslint: 8.29.0
     dev: true
 
-  /eslint-plugin-prettier/4.2.1_2xd4q2tc5cqa5as7uugqhp6oue:
+  /eslint-plugin-prettier/4.2.1_nrhoyyjffvfyk4vtlt5destxgm:
     resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
     engines: {node: '>=12.0.0'}
     peerDependencies:
@@ -1253,12 +1255,13 @@ packages:
       eslint-config-prettier:
         optional: true
     dependencies:
-      eslint: 8.22.0
-      prettier: 2.7.1
+      eslint: 8.29.0
+      eslint-config-prettier: 8.5.0_eslint@8.29.0
+      prettier: 2.8.0
       prettier-linter-helpers: 1.0.0
     dev: true
 
-  /eslint-plugin-prettier/4.2.1_i2cojdczqdiurzgttlwdgf764e:
+  /eslint-plugin-prettier/4.2.1_yz7jvh5l5mgl5xhpwlgtkgcccy:
     resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
     engines: {node: '>=12.0.0'}
     peerDependencies:
@@ -1269,25 +1272,24 @@ packages:
       eslint-config-prettier:
         optional: true
     dependencies:
-      eslint: 8.22.0
-      eslint-config-prettier: 8.5.0_eslint@8.22.0
-      prettier: 2.7.1
+      eslint: 8.29.0
+      prettier: 2.8.0
       prettier-linter-helpers: 1.0.0
     dev: true
 
-  /eslint-plugin-vue/9.3.0_eslint@8.22.0:
-    resolution: {integrity: sha512-iscKKkBZgm6fGZwFt6poRoWC0Wy2dQOlwUPW++CiPoQiw1enctV2Hj5DBzzjJZfyqs+FAXhgzL4q0Ww03AgSmQ==}
+  /eslint-plugin-vue/9.8.0_eslint@8.29.0:
+    resolution: {integrity: sha512-E/AXwcTzunyzM83C2QqDHxepMzvI2y6x+mmeYHbVDQlKFqmKYvRrhaVixEeeG27uI44p9oKDFiyCRw4XxgtfHA==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      eslint: 8.22.0
-      eslint-utils: 3.0.0_eslint@8.22.0
+      eslint: 8.29.0
+      eslint-utils: 3.0.0_eslint@8.29.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
-      postcss-selector-parser: 6.0.10
-      semver: 7.3.7
-      vue-eslint-parser: 9.0.3_eslint@8.22.0
+      postcss-selector-parser: 6.0.11
+      semver: 7.3.8
+      vue-eslint-parser: 9.1.0_eslint@8.29.0
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -1309,13 +1311,13 @@ packages:
       estraverse: 5.3.0
     dev: true
 
-  /eslint-utils/3.0.0_eslint@8.22.0:
+  /eslint-utils/3.0.0_eslint@8.29.0:
     resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
     engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
     peerDependencies:
       eslint: '>=5'
     dependencies:
-      eslint: 8.22.0
+      eslint: 8.29.0
       eslint-visitor-keys: 2.1.0
     dev: true
 
@@ -1329,14 +1331,15 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /eslint/8.22.0:
-    resolution: {integrity: sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==}
+  /eslint/8.29.0:
+    resolution: {integrity: sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
     dependencies:
-      '@eslint/eslintrc': 1.3.0
-      '@humanwhocodes/config-array': 0.10.4
-      '@humanwhocodes/gitignore-to-minimatch': 1.0.2
+      '@eslint/eslintrc': 1.3.3
+      '@humanwhocodes/config-array': 0.11.7
+      '@humanwhocodes/module-importer': 1.0.1
+      '@nodelib/fs.walk': 1.2.8
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
@@ -1344,23 +1347,23 @@ packages:
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.1.1
-      eslint-utils: 3.0.0_eslint@8.22.0
+      eslint-utils: 3.0.0_eslint@8.29.0
       eslint-visitor-keys: 3.3.0
-      espree: 9.3.3
+      espree: 9.4.1
       esquery: 1.4.0
       esutils: 2.0.3
       fast-deep-equal: 3.1.3
       file-entry-cache: 6.0.1
       find-up: 5.0.0
-      functional-red-black-tree: 1.0.1
       glob-parent: 6.0.2
-      globals: 13.17.0
-      globby: 11.1.0
+      globals: 13.18.0
       grapheme-splitter: 1.0.4
-      ignore: 5.2.0
+      ignore: 5.2.1
       import-fresh: 3.3.0
       imurmurhash: 0.1.4
       is-glob: 4.0.3
+      is-path-inside: 3.0.3
+      js-sdsl: 4.2.0
       js-yaml: 4.1.0
       json-stable-stringify-without-jsonify: 1.0.1
       levn: 0.4.1
@@ -1372,17 +1375,16 @@ packages:
       strip-ansi: 6.0.1
       strip-json-comments: 3.1.1
       text-table: 0.2.0
-      v8-compile-cache: 2.3.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /espree/9.3.3:
-    resolution: {integrity: sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==}
+  /espree/9.4.1:
+    resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      acorn: 8.8.0
-      acorn-jsx: 5.3.2_acorn@8.8.0
+      acorn: 8.8.1
+      acorn-jsx: 5.3.2_acorn@8.8.1
       eslint-visitor-keys: 3.3.0
     dev: true
 
@@ -1432,8 +1434,8 @@ packages:
     resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
     dev: true
 
-  /fast-glob/3.2.11:
-    resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
+  /fast-glob/3.2.12:
+    resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
     engines: {node: '>=8.6.0'}
     dependencies:
       '@nodelib/fs.stat': 2.0.5
@@ -1451,8 +1453,8 @@ packages:
     resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
     dev: true
 
-  /fastq/1.13.0:
-    resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
+  /fastq/1.14.0:
+    resolution: {integrity: sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==}
     dependencies:
       reusify: 1.0.4
     dev: true
@@ -1483,16 +1485,16 @@ packages:
     resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
     engines: {node: ^10.12.0 || >=12.0.0}
     dependencies:
-      flatted: 3.2.6
+      flatted: 3.2.7
       rimraf: 3.0.2
     dev: true
 
-  /flatted/3.2.6:
-    resolution: {integrity: sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==}
+  /flatted/3.2.7:
+    resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
     dev: true
 
-  /follow-redirects/1.15.1:
-    resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
+  /follow-redirects/1.15.2:
+    resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
     engines: {node: '>=4.0'}
     peerDependencies:
       debug: '*'
@@ -1533,10 +1535,6 @@ packages:
     resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
     dev: true
 
-  /functional-red-black-tree/1.0.1:
-    resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
-    dev: true
-
   /get-caller-file/2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
@@ -1571,8 +1569,8 @@ packages:
       path-is-absolute: 1.0.1
     dev: true
 
-  /globals/13.17.0:
-    resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==}
+  /globals/13.18.0:
+    resolution: {integrity: sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==}
     engines: {node: '>=8'}
     dependencies:
       type-fest: 0.20.2
@@ -1584,8 +1582,8 @@ packages:
     dependencies:
       array-union: 2.1.0
       dir-glob: 3.0.1
-      fast-glob: 3.2.11
-      ignore: 5.2.0
+      fast-glob: 3.2.12
+      ignore: 5.2.1
       merge2: 1.4.1
       slash: 3.0.0
     dev: true
@@ -1645,8 +1643,8 @@ packages:
       safer-buffer: 2.1.2
     dev: true
 
-  /ignore/5.2.0:
-    resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
+  /ignore/5.2.1:
+    resolution: {integrity: sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==}
     engines: {node: '>= 4'}
     dev: true
 
@@ -1685,8 +1683,8 @@ packages:
       binary-extensions: 2.2.0
     dev: true
 
-  /is-core-module/2.10.0:
-    resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==}
+  /is-core-module/2.11.0:
+    resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
     dependencies:
       has: 1.0.3
     dev: true
@@ -1713,6 +1711,11 @@ packages:
     engines: {node: '>=0.12.0'}
     dev: true
 
+  /is-path-inside/3.0.3:
+    resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+    engines: {node: '>=8'}
+    dev: true
+
   /is-potential-custom-element-name/1.0.1:
     resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
     dev: true
@@ -1743,6 +1746,10 @@ packages:
       istanbul-lib-report: 3.0.0
     dev: true
 
+  /js-sdsl/4.2.0:
+    resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
+    dev: true
+
   /js-yaml/4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
     hasBin: true
@@ -1750,8 +1757,8 @@ packages:
       argparse: 2.0.1
     dev: true
 
-  /jsdom/20.0.0:
-    resolution: {integrity: sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==}
+  /jsdom/20.0.3:
+    resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==}
     engines: {node: '>=14'}
     peerDependencies:
       canvas: ^2.5.0
@@ -1760,12 +1767,12 @@ packages:
         optional: true
     dependencies:
       abab: 2.0.6
-      acorn: 8.8.0
-      acorn-globals: 6.0.0
+      acorn: 8.8.1
+      acorn-globals: 7.0.1
       cssom: 0.5.0
       cssstyle: 2.3.0
       data-urls: 3.0.2
-      decimal.js: 10.4.0
+      decimal.js: 10.4.3
       domexception: 4.0.0
       escodegen: 2.0.0
       form-data: 4.0.0
@@ -1773,18 +1780,17 @@ packages:
       http-proxy-agent: 5.0.0
       https-proxy-agent: 5.0.1
       is-potential-custom-element-name: 1.0.1
-      nwsapi: 2.2.1
-      parse5: 7.0.0
+      nwsapi: 2.2.2
+      parse5: 7.1.2
       saxes: 6.0.0
       symbol-tree: 3.2.4
-      tough-cookie: 4.0.0
-      w3c-hr-time: 1.0.2
-      w3c-xmlserializer: 3.0.0
+      tough-cookie: 4.1.2
+      w3c-xmlserializer: 4.0.0
       webidl-conversions: 7.0.0
       whatwg-encoding: 2.0.0
       whatwg-mimetype: 3.0.0
       whatwg-url: 11.0.0
-      ws: 8.8.1
+      ws: 8.11.0
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - bufferutil
@@ -1832,8 +1838,8 @@ packages:
     resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
     dev: false
 
-  /lodash-unified/1.0.2_3ib2ivapxullxkx3xftsimdk7u:
-    resolution: {integrity: sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g==}
+  /lodash-unified/1.0.3_3ib2ivapxullxkx3xftsimdk7u:
+    resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
     peerDependencies:
       '@types/lodash-es': '*'
       lodash: '*'
@@ -1851,8 +1857,8 @@ packages:
   /lodash/4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 
-  /loupe/2.3.4:
-    resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
+  /loupe/2.3.6:
+    resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
     dependencies:
       get-func-name: 2.0.0
     dev: true
@@ -1918,6 +1924,10 @@ packages:
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  /natural-compare-lite/1.4.0:
+    resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+    dev: true
+
   /natural-compare/1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
@@ -1937,8 +1947,8 @@ packages:
       boolbase: 1.0.0
     dev: true
 
-  /nwsapi/2.2.1:
-    resolution: {integrity: sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==}
+  /nwsapi/2.2.2:
+    resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==}
     dev: true
 
   /once/1.4.0:
@@ -1992,10 +2002,10 @@ packages:
       callsites: 3.1.0
     dev: true
 
-  /parse5/7.0.0:
-    resolution: {integrity: sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==}
+  /parse5/7.1.2:
+    resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
     dependencies:
-      entities: 4.3.1
+      entities: 4.4.0
     dev: true
 
   /path-exists/4.0.0:
@@ -2034,19 +2044,19 @@ packages:
     engines: {node: '>=8.6'}
     dev: true
 
-  /pinia-plugin-persistedstate/2.1.1_pinia@2.0.18:
-    resolution: {integrity: sha512-HUgsU5IRtM75eAQiIqzT3p1oPEuYH1/B2ipTMU++yE+FV0LkHaBswdKXs0RMWYCmugO8s62oxLTh/N1dLNp+5A==}
+  /pinia-plugin-persistedstate/2.4.0_pinia@2.0.27:
+    resolution: {integrity: sha512-bQcpv47jk3ISl+InuJWsFaS/K7pRZ97kfoD2WCf/suhnlLy48k3BnFM2tI6YZ1xMsDaPv4yOsaPuPAUuSmEO2Q==}
     peerDependencies:
       pinia: ^2.0.0
     peerDependenciesMeta:
       pinia:
         optional: true
     dependencies:
-      pinia: 2.0.18_j6bzmzd4ujpabbp5objtwxyjp4
+      pinia: 2.0.27_mgnvym7yiazkylwwogi5r767ue
     dev: false
 
-  /pinia/2.0.18_j6bzmzd4ujpabbp5objtwxyjp4:
-    resolution: {integrity: sha512-I5MW05UVX6a5Djka136oH3VzYFiZUgeOApBwFjMx6pL91eHtGVlE3adjNUKLgtwGnrxiBRuJ8+4R3LKJKwnyZg==}
+  /pinia/2.0.27_mgnvym7yiazkylwwogi5r767ue:
+    resolution: {integrity: sha512-nOnXP0OFeL8R4WjAHsterU+11vptda643gH02xKNtSCDPiRzVfRYodOLihLDoa0gL1KKuQKV+KOzEgdt3YvqEw==}
     peerDependencies:
       '@vue/composition-api': ^1.4.0
       typescript: '>=4.4.4'
@@ -2057,22 +2067,22 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@vue/devtools-api': 6.2.1
-      typescript: 4.7.4
-      vue: 3.2.37
-      vue-demi: 0.13.8_vue@3.2.37
+      '@vue/devtools-api': 6.4.5
+      typescript: 4.9.3
+      vue: 3.2.45
+      vue-demi: 0.13.11_vue@3.2.45
     dev: false
 
-  /postcss-selector-parser/6.0.10:
-    resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+  /postcss-selector-parser/6.0.11:
+    resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==}
     engines: {node: '>=4'}
     dependencies:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
     dev: true
 
-  /postcss/8.4.16:
-    resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
+  /postcss/8.4.19:
+    resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
       nanoid: 3.3.4
@@ -2096,8 +2106,8 @@ packages:
       fast-diff: 1.2.0
     dev: true
 
-  /prettier/2.7.1:
-    resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
+  /prettier/2.8.0:
+    resolution: {integrity: sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==}
     engines: {node: '>=10.13.0'}
     hasBin: true
     dev: true
@@ -2111,6 +2121,10 @@ packages:
     engines: {node: '>=6'}
     dev: true
 
+  /querystringify/2.2.0:
+    resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+    dev: true
+
   /queue-microtask/1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
     dev: true
@@ -2132,6 +2146,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /requires-port/1.0.0:
+    resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+    dev: true
+
   /resolve-from/4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -2141,7 +2159,7 @@ packages:
     resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
     hasBin: true
     dependencies:
-      is-core-module: 2.10.0
+      is-core-module: 2.11.0
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
     dev: true
@@ -2158,8 +2176,8 @@ packages:
       glob: 7.2.3
     dev: true
 
-  /rollup/2.77.3:
-    resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
+  /rollup/2.79.1:
+    resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
     engines: {node: '>=10.0.0'}
     hasBin: true
     optionalDependencies:
@@ -2172,16 +2190,12 @@ packages:
       queue-microtask: 1.2.3
     dev: true
 
-  /safe-buffer/5.1.2:
-    resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
-    dev: true
-
   /safer-buffer/2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
     dev: true
 
-  /sass/1.54.4:
-    resolution: {integrity: sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA==}
+  /sass/1.56.1:
+    resolution: {integrity: sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==}
     engines: {node: '>=12.0.0'}
     hasBin: true
     dependencies:
@@ -2202,8 +2216,8 @@ packages:
     hasBin: true
     dev: true
 
-  /semver/7.3.7:
-    resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
+  /semver/7.3.8:
+    resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
     engines: {node: '>=10'}
     hasBin: true
     dependencies:
@@ -2241,6 +2255,7 @@ packages:
 
   /sourcemap-codec/1.4.8:
     resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+    deprecated: Please use @jridgewell/sourcemap-codec instead
 
   /string-width/4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -2297,8 +2312,8 @@ packages:
     engines: {node: '>=14.0.0'}
     dev: true
 
-  /tinyspy/1.0.0:
-    resolution: {integrity: sha512-FI5B2QdODQYDRjfuLF+OrJ8bjWRMCXokQPcwKm0W3IzcbUmBNv536cQc7eXGoAuXphZwgx1DFbqImwzz08Fnhw==}
+  /tinyspy/1.0.2:
+    resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==}
     engines: {node: '>=14.0.0'}
     dev: true
 
@@ -2313,13 +2328,14 @@ packages:
       is-number: 7.0.0
     dev: true
 
-  /tough-cookie/4.0.0:
-    resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==}
+  /tough-cookie/4.1.2:
+    resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==}
     engines: {node: '>=6'}
     dependencies:
       psl: 1.9.0
       punycode: 2.1.1
-      universalify: 0.1.2
+      universalify: 0.2.0
+      url-parse: 1.5.10
     dev: true
 
   /tr46/3.0.0:
@@ -2333,14 +2349,14 @@ packages:
     resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
     dev: true
 
-  /tsutils/3.21.0_typescript@4.7.4:
+  /tsutils/3.21.0_typescript@4.9.3:
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
     engines: {node: '>= 6'}
     peerDependencies:
       typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
     dependencies:
       tslib: 1.14.1
-      typescript: 4.7.4
+      typescript: 4.9.3
     dev: true
 
   /type-check/0.3.2:
@@ -2367,13 +2383,13 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
-  /typescript/4.7.4:
-    resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
+  /typescript/4.9.3:
+    resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==}
     engines: {node: '>=4.2.0'}
     hasBin: true
 
-  /universalify/0.1.2:
-    resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+  /universalify/0.2.0:
+    resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
     engines: {node: '>= 4.0.0'}
     dev: true
 
@@ -2383,53 +2399,63 @@ packages:
       punycode: 2.1.1
     dev: true
 
-  /util-deprecate/1.0.2:
-    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+  /url-parse/1.5.10:
+    resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+    dependencies:
+      querystringify: 2.2.0
+      requires-port: 1.0.0
     dev: true
 
-  /v8-compile-cache/2.3.0:
-    resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
+  /util-deprecate/1.0.2:
+    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
     dev: true
 
   /v8-to-istanbul/9.0.1:
     resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==}
     engines: {node: '>=10.12.0'}
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.15
+      '@jridgewell/trace-mapping': 0.3.17
       '@types/istanbul-lib-coverage': 2.0.4
-      convert-source-map: 1.8.0
+      convert-source-map: 1.9.0
     dev: true
 
-  /vite/3.0.8_sass@1.54.4:
-    resolution: {integrity: sha512-AOZ4eN7mrkJiOLuw8IA7piS4IdOQyQCA81GxGsAQvAZzMRi9ZwGB3TOaYsj4uLAWK46T5L4AfQ6InNGlxX30IQ==}
+  /vite/3.2.5_tr7neyh64qpc4cjqb6wcsya4ri:
+    resolution: {integrity: sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
+      '@types/node': '>= 14'
       less: '*'
       sass: '*'
       stylus: '*'
+      sugarss: '*'
       terser: ^5.4.0
     peerDependenciesMeta:
+      '@types/node':
+        optional: true
       less:
         optional: true
       sass:
         optional: true
       stylus:
         optional: true
+      sugarss:
+        optional: true
       terser:
         optional: true
     dependencies:
-      esbuild: 0.14.54
-      postcss: 8.4.16
+      '@types/node': 18.11.11
+      esbuild: 0.15.18
+      postcss: 8.4.19
       resolve: 1.22.1
-      rollup: 2.77.3
-      sass: 1.54.4
+      rollup: 2.79.1
+      sass: 1.56.1
     optionalDependencies:
       fsevents: 2.3.2
     dev: true
 
-  /vitest/0.22.0_jsdom@20.0.0+sass@1.54.4:
-    resolution: {integrity: sha512-BSIro/QOHLaQY08FHwT6THWhqLQ+VPU+N4Rdo4pcP+16XB6oLmNNAXGcSh/MOLUhfUy+mqCwx7AyKmU7Ms5R+g==}
+  /vitest/0.22.1_jsdom@20.0.3+sass@1.56.1:
+    resolution: {integrity: sha512-+x28YTnSLth4KbXg7MCzoDAzPJlJex7YgiZbUh6YLp0/4PqVZ7q7/zyfdL0OaPtKTpNiQFPpMC8Y2MSzk8F7dw==}
     engines: {node: '>=v14.16.0'}
     hasBin: true
     peerDependencies:
@@ -2450,26 +2476,27 @@ packages:
       jsdom:
         optional: true
     dependencies:
-      '@types/chai': 4.3.3
+      '@types/chai': 4.3.4
       '@types/chai-subset': 1.3.3
-      '@types/node': 18.7.6
-      chai: 4.3.6
+      '@types/node': 18.11.11
+      chai: 4.3.7
       debug: 4.3.4
-      jsdom: 20.0.0
+      jsdom: 20.0.3
       local-pkg: 0.4.2
       tinypool: 0.2.4
-      tinyspy: 1.0.0
-      vite: 3.0.8_sass@1.54.4
+      tinyspy: 1.0.2
+      vite: 3.2.5_tr7neyh64qpc4cjqb6wcsya4ri
     transitivePeerDependencies:
       - less
       - sass
       - stylus
+      - sugarss
       - supports-color
       - terser
     dev: true
 
-  /vue-demi/0.13.8_vue@3.2.37:
-    resolution: {integrity: sha512-Vy1zbZhCOdsmvGR6tJhAvO5vhP7eiS8xkbYQSoVa7o6KlIy3W8Rc53ED4qI4qpeRDjv3mLfXSEpYU6Yq4pgXRg==}
+  /vue-demi/0.13.11_vue@3.2.45:
+    resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
@@ -2480,28 +2507,28 @@ packages:
       '@vue/composition-api':
         optional: true
     dependencies:
-      vue: 3.2.37
+      vue: 3.2.45
     dev: false
 
-  /vue-eslint-parser/9.0.3_eslint@8.22.0:
-    resolution: {integrity: sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==}
+  /vue-eslint-parser/9.1.0_eslint@8.29.0:
+    resolution: {integrity: sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
       debug: 4.3.4
-      eslint: 8.22.0
+      eslint: 8.29.0
       eslint-scope: 7.1.1
       eslint-visitor-keys: 3.3.0
-      espree: 9.3.3
+      espree: 9.4.1
       esquery: 1.4.0
       lodash: 4.17.21
-      semver: 7.3.7
+      semver: 7.3.8
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /vue-i18n/9.2.2_vue@3.2.37:
+  /vue-i18n/9.2.2_vue@3.2.45:
     resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
     engines: {node: '>= 14'}
     peerDependencies:
@@ -2510,47 +2537,41 @@ packages:
       '@intlify/core-base': 9.2.2
       '@intlify/shared': 9.2.2
       '@intlify/vue-devtools': 9.2.2
-      '@vue/devtools-api': 6.2.1
-      vue: 3.2.37
+      '@vue/devtools-api': 6.4.5
+      vue: 3.2.45
     dev: false
 
-  /vue-router/4.1.3_vue@3.2.37:
-    resolution: {integrity: sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==}
+  /vue-router/4.1.6_vue@3.2.45:
+    resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==}
     peerDependencies:
       vue: ^3.2.0
     dependencies:
-      '@vue/devtools-api': 6.2.1
-      vue: 3.2.37
+      '@vue/devtools-api': 6.4.5
+      vue: 3.2.45
     dev: false
 
-  /vue-tsc/0.38.9_typescript@4.7.4:
+  /vue-tsc/0.38.9_typescript@4.9.3:
     resolution: {integrity: sha512-Yoy5phgvGqyF98Fb4mYqboR4Q149jrdcGv5kSmufXJUq++RZJ2iMVG0g6zl+v3t4ORVWkQmRpsV4x2szufZ0LQ==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
       '@volar/vue-typescript': 0.38.9
-      typescript: 4.7.4
+      typescript: 4.9.3
     dev: true
 
-  /vue/3.2.37:
-    resolution: {integrity: sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==}
+  /vue/3.2.45:
+    resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==}
     dependencies:
-      '@vue/compiler-dom': 3.2.37
-      '@vue/compiler-sfc': 3.2.37
-      '@vue/runtime-dom': 3.2.37
-      '@vue/server-renderer': 3.2.37_vue@3.2.37
-      '@vue/shared': 3.2.37
+      '@vue/compiler-dom': 3.2.45
+      '@vue/compiler-sfc': 3.2.45
+      '@vue/runtime-dom': 3.2.45
+      '@vue/server-renderer': 3.2.45_vue@3.2.45
+      '@vue/shared': 3.2.45
 
-  /w3c-hr-time/1.0.2:
-    resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
-    dependencies:
-      browser-process-hrtime: 1.0.0
-    dev: true
-
-  /w3c-xmlserializer/3.0.0:
-    resolution: {integrity: sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==}
-    engines: {node: '>=12'}
+  /w3c-xmlserializer/4.0.0:
+    resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
+    engines: {node: '>=14'}
     dependencies:
       xml-name-validator: 4.0.0
     dev: true
@@ -2606,8 +2627,8 @@ packages:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
     dev: true
 
-  /ws/8.8.1:
-    resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==}
+  /ws/8.11.0:
+    resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
     engines: {node: '>=10.0.0'}
     peerDependencies:
       bufferutil: ^4.0.1
diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/api/server/index.ts
similarity index 56%
copy from kyuubi-server/web-ui/src/locales/en_US/index.ts
copy to kyuubi-server/web-ui/src/api/server/index.ts
index 92df340d8..431357d14 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/api/server/index.ts
@@ -15,6 +15,35 @@
  * limitations under the License.
  */
 
-export default {
-  test: 'test'
+import request from '@/utils/request'
+import { IServerSearch, IEngineSearch } from './types'
+
+export function getAllServers(data: IServerSearch) {
+  return request({
+    url: 'api/v1/admin/servers',
+    method: 'get',
+    data
+  })
+}
+
+export function getServerConf() {
+  return request({
+    url: 'api/v1/admin/get/hadoop_conf',
+    method: 'get'
+  })
+}
+
+export function getServerLog() {
+  return request({
+    url: 'api/v1/admin/server/log',
+    method: 'post'
+  })
+}
+
+export function getAllEngines(data: IEngineSearch) {
+  return request({
+    url: 'api/v1/admin/engine',
+    method: 'get',
+    data
+  })
 }
diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/api/server/types.ts
similarity index 80%
copy from kyuubi-server/web-ui/src/locales/en_US/index.ts
copy to kyuubi-server/web-ui/src/api/server/types.ts
index 92df340d8..08d73066c 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/api/server/types.ts
@@ -15,6 +15,14 @@
  * limitations under the License.
  */
 
-export default {
-  test: 'test'
+interface IServerSearch {
+  host: string | null
 }
+
+interface IEngineSearch {
+  enginetype: null | string
+  sharelevel: null | string
+  subdomain: null | string
+}
+
+export { IServerSearch, IEngineSearch }
diff --git a/kyuubi-server/web-ui/src/api/session/index.ts b/kyuubi-server/web-ui/src/api/session/index.ts
new file mode 100644
index 000000000..9ee2c48d8
--- /dev/null
+++ b/kyuubi-server/web-ui/src/api/session/index.ts
@@ -0,0 +1,76 @@
+/*
+ * 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/request'
+import { ISessionSearch, IOperationSearch } from './types'
+
+export function getAllSessions(data: ISessionSearch) {
+  return request({
+    url: 'api/v1/sessions/listSessionInfo',
+    method: 'get',
+    data
+  })
+}
+
+export function getSession(sessionId: string) {
+  return request({
+    url: `api/v1/sessions/${sessionId}`,
+    method: 'get'
+  })
+}
+
+export function deleteSession(sessionId: string) {
+  return request({
+    url: `api/v1/sessions/${sessionId}`,
+    method: 'delete'
+  })
+}
+
+export function getSqlDetails(sessionId: string) {
+  return request({
+    url: `api/v1/sessions/${sessionId}/sqlDetails`,
+    method: 'get'
+  })
+}
+
+export function getAllOperations(data: IOperationSearch) {
+  return request({
+    url: 'api/v1/operations/listOperation',
+    method: 'get',
+    data
+  })
+}
+
+export function cancelOperation(
+  operationId: string,
+  data: {
+    action: 'CANCEL' | 'CLOSE'
+  }
+) {
+  return request({
+    url: `api/v1/operations/${operationId}`,
+    method: 'put',
+    data
+  })
+}
+
+export function getOperationLog(operationId: string) {
+  return request({
+    url: `api/v1/operations/${operationId}/log`,
+    method: 'get'
+  })
+}
diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/api/session/types.ts
similarity index 77%
copy from kyuubi-server/web-ui/src/locales/en_US/index.ts
copy to kyuubi-server/web-ui/src/api/session/types.ts
index 92df340d8..e52afcec7 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/api/session/types.ts
@@ -15,6 +15,15 @@
  * limitations under the License.
  */
 
-export default {
-  test: 'test'
+interface ISessionSearch {
+  user: string | null
+  serverIP: string | null
 }
+
+interface IOperationSearch {
+  sessionHandle: string | null
+  operationType: string | null
+  state: string | null
+}
+
+export { ISessionSearch, IOperationSearch }
diff --git a/kyuubi-server/web-ui/src/components/menu/index.vue b/kyuubi-server/web-ui/src/components/menu/index.vue
index b563b491e..1a266d74b 100644
--- a/kyuubi-server/web-ui/src/components/menu/index.vue
+++ b/kyuubi-server/web-ui/src/components/menu/index.vue
@@ -56,24 +56,22 @@
   </el-menu>
 </template>
 
-<script lang="ts">
-  export default {
-    name: 'MenuIndex',
-    props: {
-      isCollapse: {
-        type: Boolean,
-        required: true
-      },
-      menus: {
-        type: Array,
-        default: () => []
-      },
-      activePath: {
-        type: String,
-        default: '/overview'
-      }
+<script lang="ts" setup>
+  import { PropType } from 'vue'
+  defineProps({
+    isCollapse: {
+      type: Boolean,
+      required: true
+    },
+    menus: {
+      type: Array as PropType<any[]>,
+      default: () => []
+    },
+    activePath: {
+      type: String,
+      default: '/overview'
     }
-  }
+  })
 </script>
 
 <style lang="scss" scoped>
diff --git a/kyuubi-server/web-ui/src/components/modal/index.vue b/kyuubi-server/web-ui/src/components/modal/index.vue
new file mode 100644
index 000000000..d1b415ca8
--- /dev/null
+++ b/kyuubi-server/web-ui/src/components/modal/index.vue
@@ -0,0 +1,112 @@
+<!--
+* 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.
+-->
+
+<template>
+  <el-dialog
+    v-model="visible"
+    :align-center="true"
+    :title="title"
+    :before-close="handleCancel"
+    class="modal-container"
+  >
+    <slot></slot>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button
+          v-if="cancelShow"
+          :clsss="confirmClassName"
+          @click="handleCancel"
+          >{{ cancelText || $t('cancel') }}</el-button
+        >
+        <el-button
+          type="primary"
+          :class="cancelClassName"
+          :disabled="confirmDisabled"
+          :loading="confirmLoading"
+          @click="handleConfirm"
+        >
+          {{ confirmText || $t('confirm') }}
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+  import { PropType, computed } from 'vue'
+
+  const props = defineProps({
+    show: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    title: {
+      type: String as PropType<string>,
+      required: true
+    },
+    cancelText: {
+      type: String as PropType<string>,
+      default: null
+    },
+    cancelShow: {
+      type: Boolean as PropType<boolean>,
+      default: true
+    },
+    confirmText: {
+      type: String as PropType<string>,
+      default: null
+    },
+    confirmClassName: {
+      type: String as PropType<string>,
+      default: ''
+    },
+    cancelClassName: {
+      type: String as PropType<string>,
+      default: ''
+    },
+    confirmDisabled: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    confirmLoading: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    }
+  })
+
+  const emit = defineEmits(['cancel', 'confirm'])
+
+  const visible = computed(() => props.show)
+
+  const handleCancel = () => {
+    emit('cancel')
+  }
+
+  const handleConfirm = () => {
+    emit('confirm')
+  }
+</script>
+
+<style lang="scss">
+  .modal-container {
+    .el-dialog__body {
+      max-height: 60vh;
+      overflow: auto;
+    }
+  }
+</style>
diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/locales/en_US/index.ts
index 92df340d8..1134cbe2d 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/locales/en_US/index.ts
@@ -16,5 +16,44 @@
  */
 
 export default {
-  test: 'test'
+  test: 'test',
+  overview: 'Overview',
+  user: 'User',
+  client_ip: 'Client IP',
+  server_ip: 'Server IP',
+  session_id: 'Session ID',
+  session_name: 'Session Name',
+  running_operations: 'Running Operations',
+  error_operations: 'Error Operations',
+  total_operations: 'Total Operations',
+  start_time: 'Start Time',
+  finish_time: 'Finish Time',
+  duration: 'Duration(hour:min:sec)',
+  operation: 'Operation',
+  statement_id: 'Statement ID',
+  create_time: 'Create Time',
+  statement: 'Statement',
+  engine_id: 'Engine ID',
+  engine_type: 'Engine Type',
+  engine_share_level: 'Engine Share Level',
+  share_level: 'Share Level',
+  failure_reason: 'Failure Reason',
+  operation_id: 'Operation ID',
+  cpu: 'CPU',
+  memory: 'Memory',
+  log: 'Log',
+  view_config: 'View Configuration',
+  engines: 'Engines',
+  status: 'Status',
+  type: 'Type',
+  state: 'State',
+  subdomain: 'Subdomain',
+  confirm: 'Confirm',
+  cancel: 'Cancel',
+  cancel_confirm: 'Cancel?',
+  delete: 'Delete',
+  delete_confirm: 'Delete?',
+  delete_success: 'Delete success',
+  close: 'Close',
+  close_confirm: 'Close?'
 }
diff --git a/kyuubi-server/web-ui/src/locales/zh_CN/index.ts b/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
index 7272109f5..630d17285 100644
--- a/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
+++ b/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
@@ -16,5 +16,46 @@
  */
 
 export default {
-  test: '测试'
+  test: '测试',
+  overview: '概览',
+  user: '用户',
+  client_ip: '客户端IP',
+  server_ip: '服务器IP',
+  session_id: 'Session ID',
+  session_name: 'Session名称',
+  running_operations: '运行操作',
+  error_operations: '错误操作',
+  total_operations: '总共操作',
+  start_time: '开始时间',
+  finish_time: '结束时间',
+  duration: '运行时长(时:分:秒)',
+  operation: '操作',
+  statement_id: 'Statement ID',
+  create_time: '创建时间',
+  statement: 'Statement',
+  engine_id: 'Engine ID',
+  engine_type: 'Engine Type',
+  engine_share_level: 'Engine Share Level',
+  share_level: 'Share Level',
+  failure_reason: '失败原因',
+  operation_id: '操作ID',
+  cpu: 'CPU',
+  memory: '内存',
+  log: '日志',
+  view_config: '查看配置',
+  engines: 'Engines',
+  status: '状态',
+  type: '类型',
+  state: '状态',
+  subdomain: 'Subdomain',
+  confirm: '确定',
+  cancel: '取消',
+  cancel_confirm: '确认取消?',
+  cancel_success: '删除成功',
+  delete: '删除',
+  delete_confirm: '确认删除?',
+  delete_success: '删除成功',
+  close: '关闭',
+  close_confirm: '确认关闭?',
+  close_success: '关闭成功'
 }
diff --git a/kyuubi-server/web-ui/src/router/index.ts b/kyuubi-server/web-ui/src/router/index.ts
index 207a22d56..e95020e95 100644
--- a/kyuubi-server/web-ui/src/router/index.ts
+++ b/kyuubi-server/web-ui/src/router/index.ts
@@ -17,9 +17,9 @@
 
 import { createRouter, createWebHistory } from 'vue-router'
 import overviewRoutes from './overview'
-import workloadRoutes from './workload'
-import operationRoutes from './operation'
-import contactRoutes from './contact'
+import sessionRoutes from './session'
+import serverRoutes from './server'
+import runSqlRoutes from './run-sql'
 
 const routes = [
   {
@@ -36,11 +36,12 @@ const routes = [
     redirect: 'overview',
     children: [
       ...overviewRoutes,
-      ...workloadRoutes,
-      ...operationRoutes,
-      ...contactRoutes
+      ...sessionRoutes,
+      ...serverRoutes,
+      ...runSqlRoutes
     ]
-  }
+  },
+  { path: '/:catchAll(.*)', redirect: '/' }
 ]
 
 const router = createRouter({
diff --git a/kyuubi-server/web-ui/src/router/contact/index.ts b/kyuubi-server/web-ui/src/router/run-sql/index.ts
similarity index 89%
rename from kyuubi-server/web-ui/src/router/contact/index.ts
rename to kyuubi-server/web-ui/src/router/run-sql/index.ts
index a83c653ec..6d7af6e70 100644
--- a/kyuubi-server/web-ui/src/router/contact/index.ts
+++ b/kyuubi-server/web-ui/src/router/run-sql/index.ts
@@ -17,9 +17,9 @@
 
 const routes = [
   {
-    path: '/contact',
-    name: 'contact',
-    component: () => import('@/views/contact/index.vue')
+    path: '/run-sql',
+    name: 'run-sql',
+    component: () => import('@/views/run-sql/index.vue')
   }
 ]
 
diff --git a/kyuubi-server/web-ui/src/router/operation/index.ts b/kyuubi-server/web-ui/src/router/server/index.ts
similarity index 75%
rename from kyuubi-server/web-ui/src/router/operation/index.ts
rename to kyuubi-server/web-ui/src/router/server/index.ts
index 03ba4c285..55984e7df 100644
--- a/kyuubi-server/web-ui/src/router/operation/index.ts
+++ b/kyuubi-server/web-ui/src/router/server/index.ts
@@ -17,14 +17,14 @@
 
 const routes = [
   {
-    path: '/operation/runningJobs',
-    name: 'operation-runningJobs',
-    component: () => import('@/views/operation/runningJobs/index.vue')
+    path: '/server/kyuubi-service',
+    name: 'server-kyuubi-service',
+    component: () => import('@/views/server/kyuubi-service/index.vue')
   },
   {
-    path: '/operation/completedJobs',
-    name: 'operation-completedJobs',
-    component: () => import('@/views/operation/completedJobs/index.vue')
+    path: '/server/engine',
+    name: 'server-engine',
+    component: () => import('@/views/server/engine/index.vue')
   }
 ]
 
diff --git a/kyuubi-server/web-ui/src/router/workload/index.ts b/kyuubi-server/web-ui/src/router/session/index.ts
similarity index 63%
rename from kyuubi-server/web-ui/src/router/workload/index.ts
rename to kyuubi-server/web-ui/src/router/session/index.ts
index 7d7b91a47..7af9b0dbc 100644
--- a/kyuubi-server/web-ui/src/router/workload/index.ts
+++ b/kyuubi-server/web-ui/src/router/session/index.ts
@@ -17,24 +17,19 @@
 
 const routes = [
   {
-    path: '/workload/analysis',
-    name: 'workload-analysis',
-    component: () => import('@/views/workload/analysis/index.vue')
+    path: '/session/session-statistics',
+    name: 'session-statistics',
+    component: () => import('@/views/session/session-statistics/index.vue')
   },
   {
-    path: '/workload/queue',
-    name: 'workload-queue',
-    component: () => import('@/views/workload/queue/index.vue')
+    path: '/session/sql-statistics',
+    name: 'sql-statistics',
+    component: () => import('@/views/session/sql-statistics/index.vue')
   },
   {
-    path: '/workload/session',
-    name: 'workload-session',
-    component: () => import('@/views/workload/session/index.vue')
-  },
-  {
-    path: '/workload/query',
-    name: 'workload-query',
-    component: () => import('@/views/workload/query/index.vue')
+    path: '/session/operation',
+    name: 'operation',
+    component: () => import('@/views/session/operation/index.vue')
   }
 ]
 
diff --git a/kyuubi-server/web-ui/src/styles/element/index.scss b/kyuubi-server/web-ui/src/styles/element/index.scss
index c6aad1578..355c07770 100644
--- a/kyuubi-server/web-ui/src/styles/element/index.scss
+++ b/kyuubi-server/web-ui/src/styles/element/index.scss
@@ -38,7 +38,7 @@ $--colors: (
 
 $--menus: (
   'item-height': 46px,
-  'icon-width': 24px,
+  'icon-width': 12px,
   'text-color': 'rgba(255, 255, 255, .75)',
   'hover-text-color': '#fff',
   'hover-bg-color': '#1890ff',
@@ -46,10 +46,15 @@ $--menus: (
   'active-color': '#1890ff',
 );
 
+$--card: (
+  padding: 14px
+);
+
 @forward 'element-plus/theme-chalk/src/common/var.scss' with (
     // do not use same name, it will override.
     $colors: $--colors,
-    $menu: $--menus
+    $menu: $--menus,
+    $card: $--card
   );
 
 @use "element-plus/theme-chalk/src/index.scss" as *;
diff --git a/kyuubi-server/web-ui/src/styles/index.scss b/kyuubi-server/web-ui/src/styles/index.scss
index 5ae2b5a9e..3cf2c6e5b 100644
--- a/kyuubi-server/web-ui/src/styles/index.scss
+++ b/kyuubi-server/web-ui/src/styles/index.scss
@@ -67,6 +67,16 @@ button:focus-visible {
   outline: 4px auto -webkit-focus-ring-color;
 }
 
+.search-box {
+  > :last-child {
+    margin-right: 0!important;
+  }
+}
+
+.table-container {
+  margin-top: 8px;
+}
+
 #app {
   background: #eee;
   min-height: 100vh;
diff --git a/kyuubi-server/web-ui/src/utils/index.ts b/kyuubi-server/web-ui/src/utils/index.ts
new file mode 100644
index 000000000..98f50b2c9
--- /dev/null
+++ b/kyuubi-server/web-ui/src/utils/index.ts
@@ -0,0 +1,24 @@
+function secondTransfer(val: number) {
+  let theTime = val
+  let middle = 0
+  let hour = 0
+
+  if (theTime >= 60) {
+    middle = Math.floor(theTime / 60)
+    theTime = Math.floor(theTime % 60)
+    if (middle >= 60) {
+      hour = Math.floor(middle / 60)
+      middle = Math.floor(middle % 60)
+    }
+  }
+  let result = String(Math.floor(theTime))
+  if (middle > 0) {
+    result = Math.floor(middle) + ':' + result
+  }
+  if (hour > 0) {
+    result = Math.floor(hour) + ':' + result
+  }
+  return result
+}
+
+export { secondTransfer }
diff --git a/kyuubi-server/web-ui/src/utils/request.ts b/kyuubi-server/web-ui/src/utils/request.ts
index 375e8f9cc..6171ed9cd 100644
--- a/kyuubi-server/web-ui/src/utils/request.ts
+++ b/kyuubi-server/web-ui/src/utils/request.ts
@@ -19,7 +19,7 @@ import axios, { AxiosResponse } from 'axios'
 
 // create an axios instance
 const service = axios.create({
-  baseURL: import.meta.env.VITE_APP_DEV_WEB_URL, // url = base url + request url
+  baseURL: '/', // url = base url + request url
   // withCredentials: true, // send cookies when cross-domain requests
   timeout: 60000 // request timeout
 })
diff --git a/kyuubi-server/web-ui/vite.config.ts b/kyuubi-server/web-ui/src/views/common/use-table.ts
similarity index 61%
copy from kyuubi-server/web-ui/vite.config.ts
copy to kyuubi-server/web-ui/src/views/common/use-table.ts
index 92af99741..71a13a594 100644
--- a/kyuubi-server/web-ui/vite.config.ts
+++ b/kyuubi-server/web-ui/src/views/common/use-table.ts
@@ -15,24 +15,29 @@
  * limitations under the License.
  */
 
-import { defineConfig } from 'vite'
-import Vue from '@vitejs/plugin-vue'
-import path from 'path'
+import { ref, Ref } from 'vue'
 
-export default defineConfig({
-  base: '/ui/',
-  plugins: [Vue()],
-  resolve: {
-    alias: [
-      {
-        find: '@',
-        replacement: path.resolve(__dirname, 'src')
-      },
-      // resolve warning of vue-i18n
-      {
-        find: 'vue-i18n',
-        replacement: 'vue-i18n/dist/vue-i18n.cjs.js'
-      }
-    ]
+export function useTable() {
+  const tableData: Ref<any[]> = ref([])
+  const loading = ref(false)
+  const searchParam = ref()
+
+  const getList = (func: Function, data?: any) => {
+    loading.value = true
+    func(data)
+      .then((res: any[]) => {
+        tableData.value = res || []
+      })
+      .catch(() => (tableData.value = []))
+      .finally(() => {
+        loading.value = false
+      })
+  }
+
+  return {
+    tableData,
+    loading,
+    searchParam,
+    getList
   }
-})
+}
diff --git a/kyuubi-server/web-ui/src/views/layout/components/aside/index.vue b/kyuubi-server/web-ui/src/views/layout/components/aside/index.vue
index 52304abff..64ef90b1e 100644
--- a/kyuubi-server/web-ui/src/views/layout/components/aside/index.vue
+++ b/kyuubi-server/web-ui/src/views/layout/components/aside/index.vue
@@ -25,18 +25,72 @@
 </template>
 
 <script setup lang="ts">
-  import { ref, reactive } from 'vue'
+  import { ref, watch, computed } from 'vue'
   import { useStore } from '@/pinia/layout'
   import { storeToRefs } from 'pinia'
   import { useRoute } from 'vue-router'
-  import { MENUS } from './types'
   import cMenu from '@/components/menu/index.vue'
+  import { useI18n } from 'vue-i18n'
+  const { t, locale } = useI18n()
 
-  const menus = reactive(MENUS)
+  const menus: any = ref([])
   const store = useStore()
   const { isCollapse } = storeToRefs(store)
   const router = useRoute()
-  const activePath = ref(router.path)
+  const activeMenuMap: any = {
+    '/session/sql-statistics': '/session/session-statistics'
+  }
+  const activePath = computed(() => {
+    return activeMenuMap[router.path] || router.path
+  })
+
+  const initMenus = () => {
+    menus.value = [
+      {
+        label: t('overview'),
+        icon: 'Odometer',
+        router: '/overview'
+      },
+      {
+        label: 'Session Management',
+        icon: 'List',
+        children: [
+          {
+            label: 'Session Statistics',
+            router: '/session/session-statistics'
+          },
+          {
+            label: 'Operation',
+            router: '/session/operation'
+          }
+        ]
+      },
+      {
+        label: 'Server Management',
+        icon: 'Coin',
+        children: [
+          {
+            label: 'Kyuubi Server Management',
+            router: '/server/kyuubi-service'
+          },
+          {
+            label: 'Engine Management',
+            router: '/server/engine'
+          }
+        ]
+      },
+      {
+        label: 'Run Sql',
+        icon: 'VideoPlay',
+        router: '/run-sql'
+      }
+    ]
+  }
+
+  watch(locale, () => {
+    initMenus()
+  })
+  initMenus()
 </script>
 
 <style lang="scss" scoped>
diff --git a/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts b/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts
deleted file mode 100644
index ddd32bef4..000000000
--- a/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts
+++ /dev/null
@@ -1,71 +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.
- */
-
-export const MENUS = [
-  {
-    label: 'Overview',
-    icon: 'Odometer',
-    router: '/overview'
-  },
-  {
-    label: 'Workload',
-    icon: 'List',
-    children: [
-      {
-        label: 'Analysis',
-        icon: 'VideoPlay',
-        router: '/workload/analysis'
-      },
-      {
-        label: 'Queue',
-        icon: 'Select',
-        router: '/workload/queue'
-      },
-      {
-        label: 'Session',
-        icon: 'Select',
-        router: '/workload/session'
-      },
-      {
-        label: 'Query',
-        icon: 'Select',
-        router: '/workload/query'
-      }
-    ]
-  },
-  {
-    label: 'Operation',
-    icon: 'List',
-    children: [
-      {
-        label: 'Running Jobs',
-        icon: 'VideoPlay',
-        router: '/operation/runningJobs'
-      },
-      {
-        label: 'Completed Jobs',
-        icon: 'Select',
-        router: '/operation/completedJobs'
-      }
-    ]
-  },
-  {
-    label: 'Contact Us',
-    icon: 'PhoneFilled',
-    router: '/contact'
-  }
-]
diff --git a/kyuubi-server/web-ui/src/views/layout/index.vue b/kyuubi-server/web-ui/src/views/layout/index.vue
index d5ac5f16e..19241c926 100644
--- a/kyuubi-server/web-ui/src/views/layout/index.vue
+++ b/kyuubi-server/web-ui/src/views/layout/index.vue
@@ -54,7 +54,7 @@
     height: 100%;
 
     .el-container {
-      min-height: 100vh;
+      height: 100vh;
 
       ::v-deep(.el-aside) {
         width: auto;
@@ -71,6 +71,11 @@
         background: #fff;
         box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
       }
+
+      .el-main {
+        height: calc(100% - 64px);
+        overflow: auto;
+      }
     }
   }
 </style>
diff --git a/kyuubi-server/web-ui/src/views/operation/completedJobs/index.vue b/kyuubi-server/web-ui/src/views/operation/completedJobs/index.vue
deleted file mode 100644
index 7b587c4fa..000000000
--- a/kyuubi-server/web-ui/src/views/operation/completedJobs/index.vue
+++ /dev/null
@@ -1,29 +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.
--->
-
-<template>
-  <main>Completed jobs page</main>
-</template>
-
-<script lang="ts">
-  export default {
-    name: 'OperationCompletedJobsIndex'
-  }
-</script>
-
-<style scoped></style>
diff --git a/kyuubi-server/web-ui/src/views/operation/runningJobs/index.vue b/kyuubi-server/web-ui/src/views/operation/runningJobs/index.vue
deleted file mode 100644
index 030b48ae9..000000000
--- a/kyuubi-server/web-ui/src/views/operation/runningJobs/index.vue
+++ /dev/null
@@ -1,29 +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.
--->
-
-<template>
-  <main>Running jobs page</main>
-</template>
-
-<script lang="ts">
-  export default {
-    name: 'OperationRunningJobsIndex'
-  }
-</script>
-
-<style scoped></style>
diff --git a/kyuubi-server/web-ui/src/views/contact/index.vue b/kyuubi-server/web-ui/src/views/run-sql/index.vue
similarity index 88%
rename from kyuubi-server/web-ui/src/views/contact/index.vue
rename to kyuubi-server/web-ui/src/views/run-sql/index.vue
index 86b003f08..d2fcb546f 100644
--- a/kyuubi-server/web-ui/src/views/contact/index.vue
+++ b/kyuubi-server/web-ui/src/views/run-sql/index.vue
@@ -17,13 +17,7 @@
 -->
 
 <template>
-  <main>Contact page</main>
+  <main>Run sql page</main>
 </template>
 
-<script lang="ts">
-  export default {
-    name: 'ContactIndex'
-  }
-</script>
-
 <style scoped></style>
diff --git a/kyuubi-server/web-ui/src/views/server/engine/index.vue b/kyuubi-server/web-ui/src/views/server/engine/index.vue
new file mode 100644
index 000000000..1177ad9a4
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/server/engine/index.vue
@@ -0,0 +1,122 @@
+<!--
+* 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.
+-->
+
+<template>
+  <el-card :body-style="{ padding: '10px 14px' }">
+    <header>
+      <el-space class="search-box">
+        <el-select
+          v-model="searchParam.enginetype"
+          :placeholder="$t('engine_type')"
+          @change="getList"
+        >
+          <el-option
+            v-for="item in ['FLINK_SQL', 'TRINO', 'HIVE_SQL', 'JDBC']"
+            :key="item"
+            :label="item"
+            :value="item"
+          />
+        </el-select>
+        <el-select
+          v-model="searchParam.sharelevel"
+          :placeholder="$t('share_level')"
+          @change="getList"
+        >
+          <el-option
+            v-for="item in ['CONNECTION', 'USER', 'GROUP', 'SERVER']"
+            :key="item"
+            :label="item"
+            :value="item"
+          />
+        </el-select>
+        <el-input
+          v-model="searchParam.subdomain"
+          :placeholder="$t('subdomain')"
+          @keyup.enter="getList"
+        />
+        <el-button type="primary" icon="Search" @click="getList" />
+      </el-space>
+    </header>
+  </el-card>
+  <el-card class="table-container">
+    <el-table v-loading="loading" :data="tableData" style="width: 100%">
+      <el-table-column prop="host" :label="$t('server_ip')" min-width="20%" />
+      <el-table-column
+        prop="engineType"
+        :label="$t('engine_type')"
+        min-width="20%"
+      />
+      <el-table-column
+        prop="sharelevel"
+        :label="$t('share_level')"
+        min-width="20%"
+      />
+      <el-table-column
+        prop="memoryTotal"
+        :label="$t('memory')"
+        min-width="20%"
+      />
+      <el-table-column prop="user" :label="$t('user')" min-width="20%" />
+      <el-table-column :label="$t('start_time')" min-width="20%">
+        <template #default="scope">
+          {{
+            scope.row.createTime != null && Number(scope.row.createTime) > 0
+              ? format(Number(scope.row.createTime), 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="cpuTotal" :label="$t('cpu')" min-width="20%" />
+      <el-table-column
+        prop="memoryTotal"
+        :label="$t('memory')"
+        min-width="20%"
+      />
+      <el-table-column prop="status" :label="$t('status')" min-width="20%" />
+    </el-table>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+  import { reactive } from 'vue'
+  import { format } from 'date-fns'
+  import { getAllEngines } from '@/api/server'
+  import { IEngineSearch } from '@/api/server/types'
+  import { useTable } from '@/views/common/use-table'
+
+  const searchParam: IEngineSearch = reactive({
+    enginetype: null,
+    sharelevel: null,
+    subdomain: null
+  })
+
+  const { tableData, loading, getList: _getList } = useTable()
+
+  const getList = () => {
+    _getList(getAllEngines, searchParam)
+  }
+
+  getList()
+</script>
+
+<style scoped lang="scss">
+  header {
+    display: flex;
+    justify-content: flex-end;
+  }
+</style>
diff --git a/kyuubi-server/web-ui/src/views/server/kyuubi-service/index.vue b/kyuubi-server/web-ui/src/views/server/kyuubi-service/index.vue
new file mode 100644
index 000000000..7e35fec08
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/server/kyuubi-service/index.vue
@@ -0,0 +1,79 @@
+<!--
+* 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.
+-->
+
+<template>
+  <el-card :body-style="{ padding: '10px 14px' }">
+    <header>
+      <el-space class="search-box">
+        <el-input
+          v-model="searchParam.host"
+          :placeholder="$t('server_ip')"
+          @keyup.enter="getList"
+        />
+        <el-button type="primary" icon="Search" @click="getList" />
+      </el-space>
+    </header>
+  </el-card>
+  <el-card class="table-container">
+    <el-table v-loading="loading" :data="tableData" style="width: 100%">
+      <el-table-column prop="host" :label="$t('server_ip')" min-width="20%" />
+      <el-table-column prop="cpuTotal" :label="$t('cpu')" min-width="20%" />
+      <el-table-column
+        prop="memoryTotal"
+        :label="$t('memory')"
+        min-width="20%"
+      />
+      <el-table-column :label="$t('start_time')" min-width="20%">
+        <template #default="scope">
+          {{
+            scope.row.createTime != null && scope.row.createTime > 0
+              ? format(scope.row.createTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" :label="$t('status')" min-width="20%" />
+    </el-table>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+  import { reactive } from 'vue'
+  import { format } from 'date-fns'
+  import { getAllServers } from '@/api/server'
+  import { useTable } from '@/views/common/use-table'
+
+  const searchParam = reactive({
+    host: null
+  })
+
+  const { tableData, loading, getList: _getList } = useTable()
+
+  const getList = () => {
+    _getList(getAllServers, searchParam)
+  }
+
+  getList()
+</script>
+
+<style scoped lang="scss">
+  header {
+    display: flex;
+    justify-content: flex-end;
+  }
+</style>
diff --git a/kyuubi-server/web-ui/src/views/session/operation/index.vue b/kyuubi-server/web-ui/src/views/session/operation/index.vue
new file mode 100644
index 000000000..e8b2a4937
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/session/operation/index.vue
@@ -0,0 +1,261 @@
+<!--
+* 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.
+-->
+
+<template>
+  <el-card :body-style="{ padding: '10px 14px' }">
+    <header>
+      <el-space class="search-box">
+        <el-input
+          v-model="searchParam.sessionHandle"
+          :placeholder="$t('session_id')"
+          style="width: 300px"
+          @keyup.enter="getList"
+        />
+        <el-select
+          v-model="searchParam.operationType"
+          :placeholder="$t('type')"
+          @change="getList"
+        >
+          <el-option
+            v-for="item in [
+              'EXECUTE_STATEMENT',
+              'GET_TYPE_INFO',
+              'GET_CATALOGS',
+              'GET_SCHEMAS',
+              'GET_TABLES',
+              'GET_TABLE_TYPES',
+              'GET_COLUMNS',
+              'GET_FUNCTIONS',
+              'UNKNOWN'
+            ]"
+            :key="item"
+            :label="item"
+            :value="item"
+          />
+        </el-select>
+        <el-select
+          v-model="searchParam.state"
+          :placeholder="$t('state')"
+          @change="getList"
+        >
+          <el-option
+            v-for="item in [
+              'INITIALIZED_STATE',
+              'PENDING_STATE',
+              'RUNNING_STATE',
+              'TIMEDOUT_STATE',
+              'CANCELED_STATE',
+              'CLOSED_STATE',
+              'ERROR_STATE',
+              'UKNOWN_STATE'
+            ]"
+            :key="item"
+            :label="item"
+            :value="item"
+          />
+        </el-select>
+        <el-button type="primary" icon="Search" @click="getList" />
+      </el-space>
+    </header>
+  </el-card>
+  <el-card class="table-container">
+    <el-table v-loading="loading" :data="tableData" style="width: 100%">
+      <el-table-column prop="sessionId" :label="$t('session_id')" width="160" />
+      <el-table-column
+        prop="statementId"
+        :label="$t('operation_id')"
+        width="160"
+      />
+      <el-table-column prop="operationType" :label="$t('type')" width="160" />
+      <el-table-column prop="state" :label="$t('state')" width="160" />
+      <el-table-column :label="$t('start_time')" width="200">
+        <template #default="scope">
+          {{
+            scope.row.startTime != null && scope.row.startTime > -1
+              ? format(scope.row.startTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('finish_time')" width="200">
+        <template #default="scope">
+          {{
+            scope.row.completeTime != null && scope.row.completeTime > -1
+              ? format(scope.row.completeTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('duration')" width="200">
+        <template #default="scope">{{
+          scope.row.startTime != null &&
+          scope.row.completeTime != null &&
+          scope.row.startTime > -1 &&
+          scope.row.completeTime > -1
+            ? secondTransfer(
+                (scope.row.completeTime - scope.row.startTime) / 1000
+              )
+            : '-'
+        }}</template>
+      </el-table-column>
+      <el-table-column fixed="right" :label="$t('operation')" width="144">
+        <template #default="scope">
+          <el-space wrap>
+            <el-popconfirm
+              :title="$t('cancel_confirm')"
+              @confirm="handleOperate(scope.row.statementId, 'CANCEL')"
+            >
+              <template #reference>
+                <span>
+                  <el-tooltip
+                    effect="dark"
+                    :content="$t('cancel')"
+                    placement="top"
+                  >
+                    <template #default>
+                      <el-button type="danger" icon="Remove" circle />
+                    </template>
+                  </el-tooltip>
+                </span>
+              </template>
+            </el-popconfirm>
+            <el-popconfirm
+              :title="$t('close_confirm')"
+              @confirm="handleOperate(scope.row.statementId, 'CLOSE')"
+            >
+              <template #reference>
+                <span>
+                  <el-tooltip
+                    effect="dark"
+                    :content="$t('close')"
+                    placement="top"
+                  >
+                    <template #default>
+                      <el-button type="danger" icon="CircleClose" circle />
+                    </template>
+                  </el-tooltip>
+                </span>
+              </template>
+            </el-popconfirm>
+            <el-tooltip effect="dark" :content="$t('log')" placement="top">
+              <el-button
+                type="primary"
+                icon="Tickets"
+                circle
+                @click="openLogModal(scope.row.statementId)"
+              />
+            </el-tooltip>
+          </el-space>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-card>
+  <Modal
+    v-if="logModalVisible"
+    :show="logModalVisible"
+    :title="$t('log')"
+    :cancel-show="false"
+    @cancel="cancelLogModal"
+    @confirm="cancelLogModal"
+  >
+    <div v-loading="logLoading" class="log">{{ log }}</div>
+  </Modal>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive } from 'vue'
+  import { useRoute } from 'vue-router'
+  import {
+    getAllOperations,
+    getOperationLog,
+    cancelOperation
+  } from '@/api/session'
+  import { IOperationSearch } from '@/api/session/types'
+  import { format } from 'date-fns'
+  import { useI18n } from 'vue-i18n'
+  import { ElMessage } from 'element-plus'
+  import { secondTransfer } from '@/utils'
+  import { useTable } from '@/views/common/use-table'
+  import Modal from '@/components/modal/index.vue'
+
+  const logModalVisible = ref(false)
+  const log = ref()
+  const logLoading = ref(false)
+  const searchParam: IOperationSearch = reactive({
+    sessionHandle: null,
+    operationType: null,
+    state: null
+  })
+  const route = useRoute()
+  const { t } = useI18n()
+  searchParam.sessionHandle =
+    route.query.sessionId === '' || route.query.sessionId == null
+      ? null
+      : (route.query.sessionId as string)
+  const { tableData, loading, getList: _getList } = useTable()
+
+  const openLogModal = (id: string) => {
+    logModalVisible.value = true
+    logLoading.value = true
+    getOperationLog(id)
+      .then((res: any) => {
+        log.value = Array.isArray(res.logRowSet) ? res.logRowSet.join('\n') : ''
+      })
+      .catch(() => {
+        log.value = ''
+      })
+      .finally(() => {
+        logLoading.value = false
+      })
+  }
+
+  const cancelLogModal = () => {
+    logModalVisible.value = false
+  }
+
+  const handleOperate = (
+    operationId: string,
+    operation: 'CANCEL' | 'CLOSE'
+  ) => {
+    cancelOperation(operationId, { action: operation }).then(() => {
+      // need add delete success or failed logic after api support
+      ElMessage({
+        message: t(`${operation.toLowerCase()}_success`),
+        type: 'success'
+      })
+      getList()
+    })
+  }
+
+  const getList = () => {
+    _getList(getAllOperations, searchParam)
+  }
+
+  getList()
+</script>
+<style lang="scss" scoped>
+  header {
+    display: flex;
+    justify-content: flex-end;
+  }
+
+  .log {
+    font-family: monospace;
+    white-space: pre-line;
+  }
+</style>
diff --git a/kyuubi-server/web-ui/src/views/session/session-statistics/index.vue b/kyuubi-server/web-ui/src/views/session/session-statistics/index.vue
new file mode 100644
index 000000000..8f65ecdd5
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/session/session-statistics/index.vue
@@ -0,0 +1,172 @@
+<!--
+* 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.
+-->
+
+<template>
+  <el-card :body-style="{ padding: '10px 14px' }">
+    <header>
+      <el-space class="search-box">
+        <el-input
+          v-model="searchParam.user"
+          :placeholder="$t('user')"
+          @keyup.enter="getList"
+        />
+        <el-input
+          v-model="searchParam.serverIP"
+          :placeholder="$t('server_ip')"
+          @keyup.enter="getList"
+        />
+        <el-button type="primary" icon="Search" @click="getList" />
+      </el-space>
+    </header>
+  </el-card>
+  <el-card class="table-container">
+    <el-table v-loading="loading" :data="tableData" style="width: 100%">
+      <el-table-column prop="user" :label="$t('user')" width="160" />
+      <el-table-column prop="clientIP" :label="$t('client_ip')" width="160" />
+      <el-table-column prop="serverIP" :label="$t('server_ip')" width="180" />
+      <el-table-column :label="$t('session_id')" width="160">
+        <template #default="scope">
+          <el-link @click="handleSessionJump(scope.row.sessionId)">{{
+            scope.row.sessionId
+          }}</el-link>
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="sessionName"
+        :label="$t('session_name')"
+        width="160"
+      />
+      <el-table-column
+        prop="runningOperations"
+        :label="$t('running_operations')"
+        width="120"
+      />
+      <el-table-column
+        prop="errorOperations"
+        :label="$t('error_operations')"
+        width="120"
+      />
+      <el-table-column
+        prop="totalOperations"
+        :label="$t('total_operations')"
+        width="120"
+      />
+      <el-table-column :label="$t('start_time')" width="200">
+        <template #default="scope">
+          {{
+            scope.row.startTime != null && scope.row.startTime > -1
+              ? format(scope.row.startTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('finish_time')" width="200">
+        <template #default="scope">
+          {{
+            scope.row.endTime != null && scope.row.endTime > -1
+              ? format(scope.row.endTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('duration')" width="200">
+        <template #default="scope">{{
+          scope.row.startTime != null &&
+          scope.row.endTime != null &&
+          scope.row.startTime > -1 &&
+          scope.row.endTime > -1
+            ? secondTransfer((scope.row.endTime - scope.row.startTime) / 1000)
+            : '-'
+        }}</template>
+      </el-table-column>
+      <el-table-column fixed="right" :label="$t('operation')" width="120">
+        <template #default="scope">
+          <el-popconfirm
+            :title="$t('delete_confirm')"
+            @confirm="handleDeleteSession(scope.row.sessionId)"
+          >
+            <template #reference>
+              <span>
+                <el-tooltip
+                  effect="dark"
+                  :content="$t('delete')"
+                  placement="top"
+                >
+                  <template #default>
+                    <el-button type="danger" icon="Delete" circle />
+                  </template> </el-tooltip
+              ></span>
+            </template>
+          </el-popconfirm>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+  import { reactive } from 'vue'
+  import { format } from 'date-fns'
+  import { secondTransfer } from '@/utils'
+  import { getAllSessions, deleteSession } from '@/api/session'
+  import { Router, useRouter } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import { useI18n } from 'vue-i18n'
+  import { useTable } from '@/views/common/use-table'
+
+  const router: Router = useRouter()
+  const { t } = useI18n()
+  const searchParam = reactive({
+    user: null,
+    serverIP: null
+  })
+  const { tableData, loading, getList: _getList } = useTable()
+
+  const handleSessionJump = (sessionId: string) => {
+    router.push({
+      path: '/session/sql-statistics',
+      query: {
+        sessionId
+      }
+    })
+  }
+
+  const handleDeleteSession = (sessionId: string) => {
+    deleteSession(sessionId).then(() => {
+      // need add delete success or failed logic after api support
+      getList()
+      ElMessage({
+        message: t('delete_success'),
+        type: 'success'
+      })
+    })
+  }
+
+  const getList = () => {
+    _getList(getAllSessions, searchParam)
+  }
+
+  getList()
+</script>
+
+<style scoped lang="scss">
+  header {
+    display: flex;
+    justify-content: flex-end;
+  }
+</style>
diff --git a/kyuubi-server/web-ui/src/views/session/sql-statistics/index.vue b/kyuubi-server/web-ui/src/views/session/sql-statistics/index.vue
new file mode 100644
index 000000000..bd88ba450
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/session/sql-statistics/index.vue
@@ -0,0 +1,203 @@
+<!--
+* 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.
+-->
+<template>
+  <el-card :body-style="{ padding: '10px 14px' }">
+    <header>
+      <el-breadcrumb separator="/">
+        <el-breadcrumb-item :to="{ path: '/session/session-statistics' }"
+          >Session Statistics</el-breadcrumb-item
+        >
+        <el-breadcrumb-item>Sql Statistics</el-breadcrumb-item>
+      </el-breadcrumb>
+    </header>
+  </el-card>
+  <el-card
+    v-loading="sessionPropertiesLoading"
+    class="table-container session-properties-container"
+  >
+    <template #header>
+      <div class="card-header">
+        <span>Session Properties</span>
+      </div>
+    </template>
+    <div class="main">
+      <div
+        v-for="(p, key) in sessionProperties"
+        :key="key"
+        class="session-property"
+        ><el-tag>{{ `${key} : ${p}` }}</el-tag></div
+      >
+    </div>
+  </el-card>
+  <el-card class="table-container">
+    <template #header>
+      <div class="card-header">
+        <span>Sql Details</span>
+      </div>
+    </template>
+    <el-table
+      v-loading="sqlDetailsLoading"
+      :data="sqlDetails"
+      style="width: 100%"
+    >
+      <el-table-column prop="sessionUser" :label="$t('user')" width="160" />
+      <el-table-column
+        prop="statementId"
+        :label="$t('statement_id')"
+        width="160"
+      />
+      <el-table-column :label="$t('create_time')" width="200">
+        <template #default="scope">
+          {{
+            scope.row.createTime != null && scope.row.createTime > -1
+              ? format(scope.row.createTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('finish_time')" width="200">
+        <template #default="scope">
+          {{
+            scope.row.completeTime != null && scope.row.completeTime > -1
+              ? format(scope.row.completeTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('duration')" width="200">
+        <template #default="scope">{{
+          scope.row.createTime != null &&
+          scope.row.completeTime != null &&
+          scope.row.createTime > -1 &&
+          scope.row.completeTime > -1
+            ? secondTransfer(
+                (scope.row.completeTime - scope.row.createTime) / 1000
+              )
+            : '-'
+        }}</template>
+      </el-table-column>
+      <el-table-column prop="statement" :label="$t('statement')" width="160" />
+      <el-table-column prop="engineId" :label="$t('engine_id')" width="160" />
+      <el-table-column
+        prop="engineType"
+        :label="$t('engine_type')"
+        width="160"
+      />
+      <el-table-column
+        prop="engineShareLevel"
+        :label="$t('engine_share_level')"
+        width="160"
+      />
+      <el-table-column
+        prop="exception"
+        :label="$t('failure_reason')"
+        width="160"
+      />
+      <el-table-column fixed="right" :label="$t('operation')" width="120">
+        <template #default="scope">
+          <el-tooltip effect="dark" :content="$t('operation')" placement="top">
+            <el-button
+              type="primary"
+              icon="Operation"
+              circle
+              @click="openOperationPage(scope.row.sessionId)"
+            />
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+  import { Ref, ref } from 'vue'
+  import { getSession, getSqlDetails } from '@/api/session'
+  import { useRoute, useRouter, Router } from 'vue-router'
+  import { format } from 'date-fns'
+  import { secondTransfer } from '@/utils'
+
+  const route = useRoute()
+  const router: Router = useRouter()
+  const sessionProperties: Ref<any> = ref({})
+  const sessionPropertiesLoading = ref(false)
+  const sqlDetails = ref([])
+  const sqlDetailsLoading = ref(false)
+
+  const openOperationPage = (sessionId: string) => {
+    router.push({
+      path: '/session/operation',
+      query: {
+        sessionId
+      }
+    })
+  }
+
+  const getSessionById = () => {
+    const sessionId = route.query.sessionId
+    if (sessionId) {
+      sessionPropertiesLoading.value = true
+      getSession(sessionId as string)
+        .then((res: any) => {
+          sessionProperties.value = res?.conf || {}
+        })
+        .finally(() => {
+          sessionPropertiesLoading.value = false
+        })
+    }
+  }
+
+  const getSqlDetailsById = () => {
+    const sessionId = route.query.sessionId
+    if (sessionId) {
+      sqlDetailsLoading.value = true
+      getSqlDetails(sessionId as string)
+        .then((res: any) => {
+          sqlDetails.value = res || []
+        })
+        .finally(() => {
+          sqlDetailsLoading.value = false
+        })
+    }
+  }
+
+  getSessionById()
+  getSqlDetailsById()
+</script>
+<style lang="scss" scoped>
+  header {
+    display: flex;
+    justify-content: space-between;
+    .el-breadcrumb {
+      line-height: 32px;
+    }
+  }
+
+  .session-properties-container {
+    max-height: 400px;
+    overflow: auto;
+
+    .main {
+      display: flex;
+      flex-wrap: wrap;
+      .session-property {
+        margin-bottom: 6px;
+        margin-right: 6px;
+      }
+    }
+  }
+</style>
diff --git a/kyuubi-server/web-ui/src/views/workload/analysis/index.vue b/kyuubi-server/web-ui/src/views/workload/analysis/index.vue
deleted file mode 100644
index 31b42d46e..000000000
--- a/kyuubi-server/web-ui/src/views/workload/analysis/index.vue
+++ /dev/null
@@ -1,29 +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.
--->
-
-<template>
-  <main>Analysis page</main>
-</template>
-
-<script lang="ts">
-  export default {
-    name: 'WorkloadAnalysisIndex'
-  }
-</script>
-
-<style scoped></style>
diff --git a/kyuubi-server/web-ui/src/views/workload/query/index.vue b/kyuubi-server/web-ui/src/views/workload/query/index.vue
deleted file mode 100644
index 45d0cd91b..000000000
--- a/kyuubi-server/web-ui/src/views/workload/query/index.vue
+++ /dev/null
@@ -1,29 +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.
--->
-
-<template>
-  <main>Query page</main>
-</template>
-
-<script lang="ts">
-  export default {
-    name: 'WorkloadQueryIndex'
-  }
-</script>
-
-<style scoped></style>
diff --git a/kyuubi-server/web-ui/src/views/workload/queue/index.vue b/kyuubi-server/web-ui/src/views/workload/queue/index.vue
deleted file mode 100644
index bbeb8e985..000000000
--- a/kyuubi-server/web-ui/src/views/workload/queue/index.vue
+++ /dev/null
@@ -1,29 +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.
--->
-
-<template>
-  <main>Queue page</main>
-</template>
-
-<script lang="ts">
-  export default {
-    name: 'WorkloadQueueIndex'
-  }
-</script>
-
-<style scoped></style>
diff --git a/kyuubi-server/web-ui/src/views/workload/session/index.vue b/kyuubi-server/web-ui/src/views/workload/session/index.vue
deleted file mode 100644
index bd4ec51d5..000000000
--- a/kyuubi-server/web-ui/src/views/workload/session/index.vue
+++ /dev/null
@@ -1,29 +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.
--->
-
-<template>
-  <main>Session page</main>
-</template>
-
-<script lang="ts">
-  export default {
-    name: 'WorkloadSessionIndex'
-  }
-</script>
-
-<style scoped></style>
diff --git a/kyuubi-server/web-ui/vite.config.ts b/kyuubi-server/web-ui/vite.config.ts
index 92af99741..6d284b59d 100644
--- a/kyuubi-server/web-ui/vite.config.ts
+++ b/kyuubi-server/web-ui/vite.config.ts
@@ -34,5 +34,14 @@ export default defineConfig({
         replacement: 'vue-i18n/dist/vue-i18n.cjs.js'
       }
     ]
+  },
+  server: {
+    // 设置代理
+    proxy: {
+      '/api': {
+        target: 'http://localhost:10099',
+        changeOrigin: true,
+      }
+    }
   }
 })
diff --git a/pom.xml b/pom.xml
index 2ebed48b3..151b2986c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1078,6 +1078,12 @@
                 <version>${jetty.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-proxy</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.scalatest</groupId>
                 <artifactId>scalatest_${scala.binary.version}</artifactId>