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

benjobs pushed a commit to branch dev-2.1.5
in repository https://gitbox.apache.org/repos/asf/incubator-streampark.git


The following commit(s) were added to refs/heads/dev-2.1.5 by this push:
     new a3f6d422c [Improve]: add openapi schema modal (#3918)
a3f6d422c is described below

commit a3f6d422cee0b53edf25eb6bb5f32e59bb29c32a
Author: Kriszu <[email protected]>
AuthorDate: Fri Jul 26 18:50:08 2024 +0800

    [Improve]: add openapi schema modal (#3918)
    
    * [Improve]: add openapi schema modal
    
    * fix: i18n
---
 .../streampark-console-webapp/.npmrc               |  16 ++
 .../src/api/flink/app/savepoint.ts                 |   2 +-
 .../{flink/app/savepoint.ts => system/openapi.ts}  |  34 ++--
 .../src/assets/icons/api.svg                       |   1 +
 .../src/components/OpenApi/index.ts                |  21 ++
 .../src/components/OpenApi/src/index.tsx           | 211 +++++++++++++++++++++
 .../src/design/ant/index.less                      |   7 +-
 .../src/locales/lang/en/component.ts               |   8 +
 .../src/locales/lang/en/flink/app.ts               |   6 +-
 .../src/locales/lang/zh-CN/component.ts            |   8 +
 .../src/locales/lang/zh-CN/flink/app.ts            |  10 +-
 .../src/views/flink/app/Detail.vue                 |  97 ++++------
 .../flink/app/components/AppDetail/DetailTab.vue   |   4 +-
 .../flink/app/components/RequestModal/index.tsx    | 100 ++++++++++
 .../src/views/flink/app/hooks/useApp.tsx           |   4 +-
 15 files changed, 431 insertions(+), 98 deletions(-)

diff --git a/streampark-console/streampark-console-webapp/.npmrc 
b/streampark-console/streampark-console-webapp/.npmrc
new file mode 100644
index 000000000..27dc87179
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/.npmrc
@@ -0,0 +1,16 @@
+# 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-manager-strict=false
diff --git 
a/streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts 
b/streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts
index 0f030e0c8..29f2cdd57 100644
--- 
a/streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts
+++ 
b/streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts
@@ -23,7 +23,7 @@ enum SAVE_POINT_API {
   TRIGGER = '/flink/savepoint/trigger',
 }
 
-export function fetchSavePonitHistory(data: Recordable) {
+export function fetchSavePointHistory(data: Recordable) {
   return defHttp.post({ url: SAVE_POINT_API.HISTORY, data });
 }
 /**
diff --git 
a/streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts 
b/streampark-console/streampark-console-webapp/src/api/system/openapi.ts
similarity index 53%
copy from 
streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts
copy to streampark-console/streampark-console-webapp/src/api/system/openapi.ts
index 0f030e0c8..c42454566 100644
--- 
a/streampark-console/streampark-console-webapp/src/api/flink/app/savepoint.ts
+++ b/streampark-console/streampark-console-webapp/src/api/system/openapi.ts
@@ -16,32 +16,24 @@
  */
 import { defHttp } from '/@/utils/http/axios';
 
-enum SAVE_POINT_API {
-  LATEST = '/flink/savepoint/latest',
-  HISTORY = '/flink/savepoint/history',
-  DELETE = '/flink/savepoint/delete',
-  TRIGGER = '/flink/savepoint/trigger',
+enum Api {
+  CURL = '/openapi/curl',
+  SCHEMA = '/openapi/schema',
 }
 
-export function fetchSavePonitHistory(data: Recordable) {
-  return defHttp.post({ url: SAVE_POINT_API.HISTORY, data });
-}
 /**
- * delete
- * @param data id
- * @returns {Promise<boolean>}
+ * check token
+ * @param data
+ * @returns {Promise<number>}
  */
-export function fetchRemoveSavePoint(data: { appId: any; id: any }): 
Promise<boolean> {
-  return defHttp.post({
-    url: SAVE_POINT_API.DELETE,
-    data,
-  });
+export function fetchApiSchema(data: { name: string }) {
+  return defHttp.post({ url: Api.SCHEMA, data });
 }
-
 /**
- * Trigger a savepoint manually.
- * @param data app id & optional savepoint path.
+ * copyCurl
+ * @param data
+ * @returns {Promise<string>}
  */
-export function trigger(data: { appId: string | number; savepointPath: string 
| null }) {
-  return defHttp.post({ url: SAVE_POINT_API.TRIGGER, data });
+export function fetchCopyCurl(data): Promise<string> {
+  return defHttp.post<string>({ url: Api.CURL, data });
 }
diff --git 
a/streampark-console/streampark-console-webapp/src/assets/icons/api.svg 
b/streampark-console/streampark-console-webapp/src/assets/icons/api.svg
new file mode 100644
index 000000000..a7d1ff984
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/assets/icons/api.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 
1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";><svg 
t="1721919310491" class="icon" viewBox="0 0 1024 1024" version="1.1" 
xmlns="http://www.w3.org/2000/svg"; p-id="12755" 
xmlns:xlink="http://www.w3.org/1999/xlink"; width="200" height="200"><path 
d="M409.6 64l-0.064 128H505.6V64h76.8v128h96V64h76.8v128H896v140.736l128 
0.064v76.8l-128-0.064v96l128 0.064v76.8l-128-0.064v96l128 
0.064v76.8l-128-0.064V896h-140 [...]
\ No newline at end of file
diff --git 
a/streampark-console/streampark-console-webapp/src/components/OpenApi/index.ts 
b/streampark-console/streampark-console-webapp/src/components/OpenApi/index.ts
new file mode 100644
index 000000000..9c3277b4e
--- /dev/null
+++ 
b/streampark-console/streampark-console-webapp/src/components/OpenApi/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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
+ *
+ *    https://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 { withInstall } from '/@/utils';
+
+import openApi from './src';
+
+export const OpenApi = withInstall(openApi);
diff --git 
a/streampark-console/streampark-console-webapp/src/components/OpenApi/src/index.tsx
 
b/streampark-console/streampark-console-webapp/src/components/OpenApi/src/index.tsx
new file mode 100644
index 000000000..d0b0fde78
--- /dev/null
+++ 
b/streampark-console/streampark-console-webapp/src/components/OpenApi/src/index.tsx
@@ -0,0 +1,211 @@
+/*
+ * 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
+ *
+ *    https://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 { Divider, Table, Tag, type TableColumnType, Empty, Skeleton } from 
'ant-design-vue';
+import { defineComponent, ref, shallowRef, watchEffect } from 'vue';
+import { fetchApiSchema } from '/@/api/system/openapi';
+import { baseUrl } from '/@/api';
+import { useAppStore } from '/@/store/modules/app';
+import { ThemeEnum } from '/@/enums/appEnum';
+import { useI18n } from '/@/hooks/web/useI18n';
+interface RequestSchemas {
+  url: string;
+  method?: string;
+  params?: Recordable[];
+  body?: Recordable[];
+  header?: Recordable[];
+}
+
+const methodMap = {
+  GET: {
+    lightColor: '#22c55e',
+    darkColor: '#10b981',
+  },
+  POST: {
+    lightColor: '#f59e0b',
+    darkColor: '#eab308',
+  },
+  PUT: {
+    lightColor: '#3b82f6',
+    darkColor: '#0ea5e9  ',
+  },
+  DELETE: {
+    lightColor: '#ef4444',
+    darkColor: '#f43f5e',
+  },
+  PATCH: {
+    lightColor: '#a855f7',
+    darkColor: '#8b5cf6',
+  },
+  DEFAULT: {
+    lightColor: '#737373',
+    darkColor: '#71717a',
+  },
+} as const;
+export default defineComponent({
+  name: 'OpenApi',
+  props: {
+    name: {
+      type: String,
+      required: true,
+    },
+  },
+  setup(props) {
+    const requestRef = shallowRef<RequestSchemas>({
+      url: '',
+    });
+    const { t } = useI18n();
+    const fetchLoading = ref(false);
+    const appStore = useAppStore();
+    const handleGetRequestSchemas = async () => {
+      try {
+        fetchLoading.value = true;
+        const resp = await fetchApiSchema({
+          name: props.name,
+        });
+        requestRef.value = {
+          url: baseUrl() + resp.url,
+          method: resp.method,
+          params: resp.schema,
+          body: resp.body,
+          header: resp.header,
+        };
+      } catch (error) {
+        console.error(error);
+      } finally {
+        fetchLoading.value = false;
+      }
+    };
+    const tableColumns = shallowRef<TableColumnType[]>([
+      { title: t('component.openApi.param'), dataIndex: 'name', width: 160 },
+      { title: t('component.openApi.defaultValue'), dataIndex: 'defaultValue', 
width: 130 },
+      { title: t('component.openApi.type'), dataIndex: 'type', width: 150 },
+      {
+        title: t('component.openApi.required'),
+        dataIndex: 'required',
+        customRender: ({ text }) => (
+          <Tag color={text ? 'success' : 'error'}>{text ? t('common.yes') : 
t('common.no')}</Tag>
+        ),
+      },
+      { title: t('component.openApi.description'), dataIndex: 'description' },
+    ]);
+
+    watchEffect(() => {
+      if (props.name) {
+        handleGetRequestSchemas();
+      }
+    });
+
+    const renderParams = () => {
+      if (!requestRef.value.params) return null;
+      return (
+        <>
+          <Divider orientation="left">Params</Divider>
+          {renderTableData(requestRef.value.params)}
+        </>
+      );
+    };
+
+    const renderBody = () => {
+      if (!requestRef.value.body) return null;
+      return (
+        <>
+          <Divider orientation="left">Body</Divider>
+          {renderTableData(requestRef.value.body)}
+        </>
+      );
+    };
+    const renderHeader = () => {
+      if (!requestRef.value.header) return null;
+      return (
+        <>
+          <Divider orientation="left">Header</Divider>
+          {renderTableData(requestRef.value.header)}
+        </>
+      );
+    };
+    const renderTableData = (data: Recordable[]) => {
+      if (!data || (Array.isArray(data) && data.length === 0))
+        return (
+          <>
+            <Empty
+              image={Empty.PRESENTED_IMAGE_SIMPLE}
+              v-slots={{
+                description: () => <span 
class="text-gray-4">{t('component.openApi.empty')}</span>,
+              }}
+            ></Empty>
+          </>
+        );
+      return (
+        <>
+          <Table
+            size="small"
+            bordered
+            pagination={false}
+            dataSource={data}
+            columns={tableColumns.value}
+          ></Table>
+        </>
+      );
+    };
+    const renderRequestMethod = () => {
+      if (!requestRef.value.method) return null;
+      const currentMethod =
+        methodMap[requestRef.value.method.toLocaleUpperCase()] ?? 
methodMap.DEFAULT;
+      const darkMode = appStore.getDarkMode === ThemeEnum.DARK;
+      return (
+        <div class="relative flex">
+          <label for="method">
+            <span class="flex overflow-hidden text-ellipsis whitespace-nowrap">
+              <div class="flex flex-1 relative">
+                <div
+                  id="method"
+                  class="flex w-20 rounded-l bg-[#f9fafb] dark:bg-[#1c1c1e] 
px-4 py-2 font-semibold justify-center transition"
+                  style={{
+                    color: darkMode ? currentMethod.lightColor : 
currentMethod.darkColor,
+                  }}
+                >
+                  {requestRef.value.method.toLocaleUpperCase()}
+                </div>
+              </div>
+            </span>
+          </label>
+        </div>
+      );
+    };
+
+    return () => (
+      <>
+        <Skeleton loading={fetchLoading.value} active>
+          <div class="flex-none flex-shrink-0 ">
+            <div class="min-w-[12rem] flex min-h-9 flex-1 whitespace-nowrap 
rounded border dark:border-[#303030] border-[#f3f4f6]">
+              {renderRequestMethod()}
+              <div
+                class="flex flex-1 items-center text-[#111827] 
whitespace-nowrap rounded-r border-l border-[#f3f4f6] bg-[#f9fafb] 
dark:bg-[#1c1c1e] dark:text-[#fff] dark:border-[#303030] transition "
+                style="padding-left:10px"
+              >
+                {requestRef.value.url}
+              </div>
+            </div>
+          </div>
+          {renderHeader()}
+          {renderParams()}
+          {renderBody()}
+        </Skeleton>
+      </>
+    );
+  },
+});
diff --git 
a/streampark-console/streampark-console-webapp/src/design/ant/index.less 
b/streampark-console/streampark-console-webapp/src/design/ant/index.less
index 587b4e5bd..57230f6b4 100644
--- a/streampark-console/streampark-console-webapp/src/design/ant/index.less
+++ b/streampark-console/streampark-console-webapp/src/design/ant/index.less
@@ -78,7 +78,7 @@ span.anticon:not(.app-iconify) {
 }
 
 .app-bar,
-.streampark-basic-title {
+div:not(.ant-modal-title)>.streampark-basic-title {
   background-color: @background-color-base;
   height: 100%;
   font-size: 14px !important;
@@ -90,3 +90,8 @@ span.anticon:not(.app-iconify) {
 .ant-card-body>.streampark-basic-title {
   display: table !important;
 }
+
+[data-theme="dark"] .ant-divider-horizontal.ant-divider-with-text::before,
+[data-theme="dark"] .ant-divider-horizontal.ant-divider-with-text::after {
+  border-top-color: inherit !important;
+}
diff --git 
a/streampark-console/streampark-console-webapp/src/locales/lang/en/component.ts 
b/streampark-console/streampark-console-webapp/src/locales/lang/en/component.ts
index 2a33235c3..810cfcfb8 100644
--- 
a/streampark-console/streampark-console-webapp/src/locales/lang/en/component.ts
+++ 
b/streampark-console/streampark-console-webapp/src/locales/lang/en/component.ts
@@ -120,4 +120,12 @@ export default {
     dragText: 'Hold down the slider and drag',
     successText: 'Verified',
   },
+  openApi: {
+    param: 'Parameter Name',
+    defaultValue: 'Default Value',
+    type: 'Type',
+    required: 'Required',
+    description: 'Description',
+    empty: 'This request has no data of this type',
+  },
 };
diff --git 
a/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/app.ts 
b/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/app.ts
index a294ec2b6..070f8be4b 100644
--- 
a/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/app.ts
+++ 
b/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/app.ts
@@ -117,10 +117,12 @@ export default {
     compare: 'Compare',
     compareSelectTips: 'Please select the target version',
     resetApi: 'Rest Api',
+    copyCurl: 'Copy Curl',
+    apiTitle: 'Api Detail',
     resetApiToolTip:
       'Rest API external call interface,other third-party systems easy to 
access StreamPark',
-    copyStartcURL: 'Copy Start cURL',
-    copyCancelcURL: 'Copy Cancel cURL',
+    copyStartcURL: 'App Start',
+    copyCancelcURL: 'App Cancel',
     apiDocCenter: 'Api Doc Center',
     nullAccessToken: 'access token is null,please contact the administrator to 
add.',
     invalidAccessToken: 'access token is invalid,please contact the 
administrator.',
diff --git 
a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/component.ts
 
b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/component.ts
index e785b88ad..6861f7c38 100644
--- 
a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/component.ts
+++ 
b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/component.ts
@@ -125,4 +125,12 @@ export default {
     dragText: '请按住滑块拖动',
     successText: '验证通过',
   },
+  openApi: {
+    param: '参数名',
+    defaultValue: '默认值',
+    required: '必填',
+    type: '类型',
+    description: '说明',
+    empty: '该请求暂无该类型数据',
+  },
 };
diff --git 
a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/app.ts
 
b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/app.ts
index da35e2893..5f59c6636 100644
--- 
a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/app.ts
+++ 
b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/app.ts
@@ -107,10 +107,12 @@ export default {
     candidate: '侯选',
     compare: '比较',
     compareSelectTips: '请选择目标板本',
-    resetApi: '外部系统调用API',
-    resetApiToolTip: 'Rest API外部调用接口,其他第三方系统可轻松对接StreamPark',
-    copyStartcURL: '复制启动 cURL',
-    copyCancelcURL: '复制取消 cURL',
+    resetApi: 'Open API',
+    copyCurl: '复制 CURL',
+    apiTitle: 'Api 详情',
+    resetApiToolTip: 'OPEN API,第三方系统可轻松对接 StreamPark',
+    copyStartcURL: '作业启动',
+    copyCancelcURL: '作业停止',
     apiDocCenter: 'Api文档',
     nullAccessToken: '访问令牌为空,请联系管理员添加.',
     invalidAccessToken: '访问令牌无效,请联系管理员。',
diff --git 
a/streampark-console/streampark-console-webapp/src/views/flink/app/Detail.vue 
b/streampark-console/streampark-console-webapp/src/views/flink/app/Detail.vue
index b949cc1b7..376557698 100644
--- 
a/streampark-console/streampark-console-webapp/src/views/flink/app/Detail.vue
+++ 
b/streampark-console/streampark-console-webapp/src/views/flink/app/Detail.vue
@@ -14,47 +14,39 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<script lang="ts">
-  import { defineComponent } from 'vue';
+<script setup lang="ts" name="ApplicationDetail">
   import { AppStateEnum, ExecModeEnum } from '/@/enums/flinkEnum';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { fetchAppExternalLink } from '/@/api/flink/setting/externalLink';
   import { ExternalLink } from '/@/api/flink/setting/types/externalLink.type';
-  export default defineComponent({
-    name: 'ApplicationDetail',
-  });
-</script>
-<script setup lang="ts" name="ApplicationDetail">
+  import { useModal } from '/@/components/Modal';
   import { PageWrapper } from '/@/components/Page';
   import { Description, useDescription } from '/@/components/Description';
   import { Icon } from '/@/components/Icon';
   import { useRoute, useRouter } from 'vue-router';
   import { fetchGet, fetchOptionLog, fetchYarn } from '/@/api/flink/app/app';
   import { onUnmounted, reactive, h, unref, ref, onMounted, computed } from 
'vue';
-  import { useIntervalFn, useClipboard } from '@vueuse/core';
+  import { useIntervalFn } from '@vueuse/core';
   import { AppListRecord } from '/@/api/flink/app/app.type';
   import { Tooltip, Divider, Space } from 'ant-design-vue';
   import { handleView } from './utils';
   import { Button } from '/@/components/Button';
   import { getDescSchema } from './data/detail.data';
-  import { fetchCheckToken, fetchCopyCurl } from '/@/api/system/token';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { baseUrl } from '/@/api/index';
   import { fetchListVer } from '/@/api/flink/config';
-  import { fetchSavePonitHistory } from '/@/api/flink/app/savepoint';
+  import { fetchSavePointHistory } from '/@/api/flink/app/savepoint';
   import Mergely from './components/Mergely.vue';
   import DetailTab from './components/AppDetail/DetailTab.vue';
+  import RequestModal from './components/RequestModal';
   import { createDetailProviderContext } from './hooks/useDetailContext';
   import { useDrawer } from '/@/components/Drawer';
   import { ExternalLinkBadge } from '/@/views/setting/ExternalLink/components';
 
+  defineOptions({
+    name: 'ApplicationDetail',
+  });
   const route = useRoute();
   const router = useRouter();
 
-  const { Swal, createMessage } = useMessage();
-  const { copy } = useClipboard({
-    legacy: true,
-  });
   const { t } = useI18n();
 
   const yarn = ref('');
@@ -85,27 +77,30 @@
             Button,
             {
               type: 'primary',
-              shape: 'round',
+              size: 'small',
               class: 'mx-3px px-5px',
-              onClick: () => handleCopyCurl('/flink/app/start'),
+              onClick: () =>
+                openApiModal(true, {
+                  name: 'flinkStart',
+                  app,
+                }),
             },
-            () => [
-              h(Icon, { icon: 'ant-design:copy-outlined' }),
-              t('flink.app.detail.copyStartcURL'),
-            ],
+            () => [t('flink.app.detail.copyStartcURL')],
           ),
           h(
             Button,
             {
               type: 'primary',
-              shape: 'round',
+              size: 'small',
               class: 'mx-3px px-5px',
-              onClick: () => handleCopyCurl('/flink/app/cancel'),
+              onClick: () => {
+                openApiModal(true, {
+                  name: 'flinkCancel',
+                  app,
+                });
+              },
             },
-            () => [
-              h(Icon, { icon: 'ant-design:copy-outlined' }),
-              t('flink.app.detail.copyCancelcURL'),
-            ],
+            () => [t('flink.app.detail.copyCancelcURL')],
           ),
           h(
             Button,
@@ -113,7 +108,12 @@
               type: 'link',
               shape: 'round',
               class: 'mx-3px px-5px',
-              onClick: () => handleDocPage(),
+              onClick: () => {
+                openApiModal(true, {
+                  name: 'flinkCancel',
+                  app,
+                });
+              },
             },
             () => [
               h(Icon, { icon: 'ant-design:link-outlined' }),
@@ -129,6 +129,7 @@
   });
 
   const [registerConfDrawer] = useDrawer();
+  const [registerOpenApi, { openModal: openApiModal }] = useModal();
 
   /* Flink Web UI */
   function handleFlinkView() {
@@ -172,7 +173,7 @@
     };
 
     const confList = await fetchListVer(commonParams);
-    const pointHistory = await fetchSavePonitHistory(commonParams);
+    const pointHistory = await fetchSavePointHistory(commonParams);
     const optionList = await fetchOptionLog(commonParams);
 
     if (confList.records.length > 0) detailTabs.showConf = true;
@@ -185,41 +186,6 @@
     yarn.value = await fetchYarn();
   }
 
-  /* copyCurl */
-  async function handleCopyCurl(urlPath) {
-    const resp = await fetchCheckToken({});
-    const result = parseInt(resp);
-    if (result === 0) {
-      Swal.fire({
-        icon: 'error',
-        title: t('flink.app.detail.nullAccessToken'),
-        showConfirmButton: true,
-        timer: 3500,
-      });
-    } else if (result === 1) {
-      Swal.fire({
-        icon: 'error',
-        title: t('flink.app.detail.invalidAccessToken'),
-        showConfirmButton: true,
-        timer: 3500,
-      });
-    } else {
-      const res = await fetchCopyCurl({
-        appId: app.id,
-        baseUrl: baseUrl(),
-        path: urlPath,
-      });
-      copy(res);
-      createMessage.success(t('flink.app.detail.detailTab.copySuccess'));
-    }
-  }
-
-  /* Documentation page */
-  function handleDocPage() {
-    const res = window.origin.split(':')[1] + ':10000/doc.html';
-    window.open(res);
-  }
-
   async function getExternalLinks() {
     const { data: links } = await fetchAppExternalLink({ appId: 
route.query.appId as string });
     externalLinks.value = links.data;
@@ -270,6 +236,7 @@
     <DetailTab :app="app" :tabConf="detailTabs" />
 
     <Mergely @register="registerConfDrawer" :readOnly="true" />
+    <RequestModal @register="registerOpenApi" />
   </PageWrapper>
 </template>
 <style lang="less">
diff --git 
a/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppDetail/DetailTab.vue
 
b/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppDetail/DetailTab.vue
index d1298d330..af5d5432b 100644
--- 
a/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppDetail/DetailTab.vue
+++ 
b/streampark-console/streampark-console-webapp/src/views/flink/app/components/AppDetail/DetailTab.vue
@@ -39,7 +39,7 @@
   import { getMonacoOptions } from '../../data';
   import { useRoute } from 'vue-router';
   import { fetchGetVer, fetchListVer, fetchRemoveConf } from 
'/@/api/flink/config';
-  import { fetchRemoveSavePoint, fetchSavePonitHistory } from 
'/@/api/flink/app/savepoint';
+  import { fetchRemoveSavePoint, fetchSavePointHistory } from 
'/@/api/flink/app/savepoint';
 
   import {
     fetchBackUps,
@@ -136,7 +136,7 @@
   );
 
   const [registerSavePointTable, { reload: reloadSavePoint }] = useTable({
-    api: fetchSavePonitHistory,
+    api: fetchSavePointHistory,
     columns: getSavePointColumns(),
     ...tableCommonConf,
   });
diff --git 
a/streampark-console/streampark-console-webapp/src/views/flink/app/components/RequestModal/index.tsx
 
b/streampark-console/streampark-console-webapp/src/views/flink/app/components/RequestModal/index.tsx
new file mode 100644
index 000000000..a065b773f
--- /dev/null
+++ 
b/streampark-console/streampark-console-webapp/src/views/flink/app/components/RequestModal/index.tsx
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    https://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 { defineComponent, ref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { OpenApi } from '/@/components/OpenApi';
+import { fetchCheckToken } from '/@/api/system/token';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { fetchCopyCurl } from '/@/api/system/openapi';
+import { baseUrl } from '/@/api';
+import { useClipboard } from '@vueuse/core';
+import { SvgIcon } from '/@/components/Icon';
+
+export default defineComponent({
+  name: 'RequestModal',
+  emits: ['register'],
+  setup() {
+    const currentRef = ref<Recordable>({});
+    const [registerModal, { closeModal, changeOkLoading }] = 
useModalInner(async (data) => {
+      currentRef.value = data;
+    });
+    const { Swal, createMessage } = useMessage();
+    const { copy } = useClipboard({
+      legacy: true,
+    });
+    const { t } = useI18n();
+    const handleCopyCurl = async () => {
+      try {
+        changeOkLoading(true);
+        const resp = await fetchCheckToken({});
+        const result = parseInt(resp);
+        if (result === 0) {
+          Swal.fire({
+            icon: 'error',
+            title: t('flink.app.detail.nullAccessToken'),
+            showConfirmButton: true,
+            timer: 3500,
+          });
+        } else if (result === 1) {
+          Swal.fire({
+            icon: 'error',
+            title: t('flink.app.detail.invalidAccessToken'),
+            showConfirmButton: true,
+            timer: 3500,
+          });
+        } else {
+          const res = await fetchCopyCurl({
+            baseUrl: baseUrl(),
+            appId: currentRef.value.app.id,
+            name: currentRef.value.name,
+          });
+          copy(res);
+          createMessage.success(t('flink.app.detail.detailTab.copySuccess'));
+          closeModal();
+        }
+      } catch (error) {
+        console.log(error);
+      } finally {
+        changeOkLoading(false);
+      }
+    };
+    return () => (
+      <>
+        <BasicModal
+          width={900}
+          onRegister={registerModal}
+          minHeight={400}
+          okText={t('flink.app.detail.copyCurl')}
+          onOk={handleCopyCurl}
+          v-slots={{
+            title: () => (
+              <>
+                <div class="flex items-center">
+                  <SvgIcon name="api" size={24}></SvgIcon>
+                  <div class="pl-6px">{t('flink.app.detail.apiTitle')}</div>
+                </div>
+              </>
+            ),
+          }}
+        >
+          {currentRef.value.name && <OpenApi 
name={currentRef.value.name}></OpenApi>}
+        </BasicModal>
+      </>
+    );
+  },
+});
diff --git 
a/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useApp.tsx
 
b/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useApp.tsx
index 4827a020e..5b9f931f4 100644
--- 
a/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useApp.tsx
+++ 
b/streampark-console/streampark-console-webapp/src/views/flink/app/hooks/useApp.tsx
@@ -19,7 +19,7 @@ import { h, onMounted, reactive, ref, unref, VNode } from 
'vue';
 import { handleAppBuildStatueText } from '../utils';
 import { fetchCheckName, fetchCopy, fetchAbort, fetchMapping } from 
'/@/api/flink/app/app';
 import { fetchBuild, fetchBuildDetail } from '/@/api/flink/app/flinkBuild';
-import { fetchSavePonitHistory } from '/@/api/flink/app/savepoint';
+import { fetchSavePointHistory } from '/@/api/flink/app/savepoint';
 import { fetchAppOwners } from '/@/api/system/user';
 import { SvgIcon } from '/@/components/Icon';
 import { AppStateEnum, ExecModeEnum, OptionStateEnum } from 
'/@/enums/flinkEnum';
@@ -122,7 +122,7 @@ export const useFlinkApplication = (openStartModal: Fn) => {
       Swal.fire('Failed', 'please set flink version first.', 'error');
     } else {
       if (!optionApps.starting.get(app.id) || app['optionState'] === 
OptionStateEnum.NONE) {
-        const resp = await fetchSavePonitHistory({
+        const resp = await fetchSavePointHistory({
           appId: app.id,
           pageNum: 1,
           pageSize: 9999,

Reply via email to