You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampark.apache.org by be...@apache.org on 2022/11/07 01:51:30 UTC

[incubator-streampark] branch dev updated: [Improve]: add npm registry and change router menu logic (#1974)

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

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


The following commit(s) were added to refs/heads/dev by this push:
     new a381406e2 [Improve]: add npm registry and change router menu logic (#1974)
a381406e2 is described below

commit a381406e29a5c72fd892074630dbcfd37f507d28
Author: Sizhu Wang <12...@qq.com>
AuthorDate: Mon Nov 7 09:51:23 2022 +0800

    [Improve]: add npm registry and change router menu logic (#1974)
    
    * [Improve]: add npm registry and change router menu logic
    
    * [Fix] license header
---
 .../streampark-console-newui/.gitignore            |   1 -
 streampark-console/streampark-console-newui/.npmrc |  16 ++
 .../src/router/helper/routeHelper.ts               |  12 +-
 .../src/store/modules/permission.ts                |  31 ---
 .../flink/app/components/AppView/StatisticCard.vue |  60 ++++++
 .../src/views/flink/app/components/CustomForm.tsx  | 117 +++++++++++
 .../src/views/flink/app/components/Dependency.vue  |  91 ++++-----
 .../src/views/flink/app/components/FlinkSql.vue    | 223 +++++++++++++++++++++
 .../views/flink/app/components/FlinkSqlHistory.vue |  60 ++++++
 .../components/PodTemplate/TemplateButtonGroup.vue |   2 +-
 .../src/views/flink/app/hooks/useApp.tsx           |  11 +-
 .../src/views/flink/app/hooks/useAppTableAction.ts |  22 +-
 .../src/views/flink/app/styles/Add.less            |  10 +-
 .../setting/components/FlinkClusterSetting.vue     |   4 +-
 .../src/views/system/menu/Menu.vue                 |  21 +-
 .../src/views/system/menu/MenuDrawer.vue           |   3 +-
 .../src/views/system/menu/menu.data.ts             |  39 +---
 17 files changed, 573 insertions(+), 150 deletions(-)

diff --git a/streampark-console/streampark-console-newui/.gitignore b/streampark-console/streampark-console-newui/.gitignore
index e6922c46d..dc8b94064 100644
--- a/streampark-console/streampark-console-newui/.gitignore
+++ b/streampark-console/streampark-console-newui/.gitignore
@@ -1,7 +1,6 @@
 node_modules
 .DS_Store
 dist
-.npmrc
 .cache
 
 tests/server/static
diff --git a/streampark-console/streampark-console-newui/.npmrc b/streampark-console/streampark-console-newui/.npmrc
new file mode 100644
index 000000000..ec2a37c72
--- /dev/null
+++ b/streampark-console/streampark-console-newui/.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
+#
+#    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.
+
+registry = 'https://registry.npmmirror.com'
diff --git a/streampark-console/streampark-console-newui/src/router/helper/routeHelper.ts b/streampark-console/streampark-console-newui/src/router/helper/routeHelper.ts
index ec37773b6..57df5c84f 100644
--- a/streampark-console/streampark-console-newui/src/router/helper/routeHelper.ts
+++ b/streampark-console/streampark-console-newui/src/router/helper/routeHelper.ts
@@ -42,14 +42,15 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
     if (!item.component && item.meta?.frameSrc) {
       item.component = 'IFRAME';
     }
-    const { component, name } = item;
-    const { children } = item;
+    const { component, name, children } = item;
     if (component) {
       const layoutFound = LayoutMap.get(component.toUpperCase());
       item.component = layoutFound || dynamicImport(dynamicViewsModules, component as string);
     } else if (name) {
       item.component = getParentLayout();
     }
+    // Determine if all submenus are hidden, add hidenChildrenInMenu if all are hidden
+    item.meta.hideChildrenInMenu = (children || []).every((child) => child.meta?.hidden);
     children && asyncImportRoute(children);
   });
 }
@@ -102,7 +103,12 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
         const meta = route.meta || {};
         meta.single = true;
         meta.affix = false;
-        route.meta = meta;
+        route.meta = {
+          ...meta,
+          hidden: false,
+          test: 1,
+          hideMenu: meta.hidden,
+        };
       }
     } else {
       warn('Configure the routing correctly:' + route?.name + ' component attribute');
diff --git a/streampark-console/streampark-console-newui/src/store/modules/permission.ts b/streampark-console/streampark-console-newui/src/store/modules/permission.ts
index 1c2c3b71e..1938690dd 100644
--- a/streampark-console/streampark-console-newui/src/store/modules/permission.ts
+++ b/streampark-console/streampark-console-newui/src/store/modules/permission.ts
@@ -186,37 +186,6 @@ export const usePermissionStore = defineStore({
         }
         return hasAuth;
       };
-      /**
-       * @description Fix the affix tag in routes according to the homepage path set (fixed homepage)
-       * */
-      // const checkCurrentRouter = (routes: AppRouteRecordRaw[]) => {
-      //   if (!routes || routes.length === 0) return;
-      //   let homePath: string = nextPath || userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
-
-      //   function patcher(routes: AppRouteRecordRaw[], parentPath = '') {
-      //     if (parentPath) parentPath = parentPath + '/';
-      //     routes.forEach((route: AppRouteRecordRaw) => {
-      //       const { path, children, redirect } = route;
-      //       const currentPath = path.startsWith('/') ? path : parentPath + path;
-      //       if (currentPath === homePath) {
-      //         if (redirect) {
-      //           homePath = route.redirect! as string;
-      //         } else {
-      //           route.meta = Object.assign({}, route.meta, { affix: true });
-      //           throw new Error('end');
-      //         }
-      //       }
-      //       children && children.length > 0 && patcher(children, currentPath);
-      //     });
-      //   }
-
-      //   try {
-      //     patcher(routes);
-      //   } catch (e) {
-      //     // Processed out of loop
-      //   }
-      //   return;
-      // };
 
       switch (permissionMode) {
         // Role authorization
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/components/AppView/StatisticCard.vue b/streampark-console/streampark-console-newui/src/views/flink/app/components/AppView/StatisticCard.vue
new file mode 100644
index 000000000..acb79ba28
--- /dev/null
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/components/AppView/StatisticCard.vue
@@ -0,0 +1,60 @@
+<!--
+  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.
+-->
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    name: 'StatisticCard',
+  });
+</script>
+<script setup lang="ts" name="StatisticCard">
+  import { Card, Statistic, Divider, Skeleton } from 'ant-design-vue';
+  defineProps({
+    loading: { type: Boolean, default: false },
+    statisticProps: {
+      type: Object as PropType<Recordable>,
+      default: () => ({ title: '', value: 0 }),
+    },
+    footerList: {
+      type: Array as PropType<Array<{ title: string; value: string | number }>>,
+      default: () => [],
+    },
+  });
+</script>
+<template>
+  <div class="gutter-box">
+    <Skeleton :loading="loading" active>
+      <Card :bordered="false" class="dash-statistic">
+        <Statistic
+          v-bind="statisticProps"
+          :value-style="{
+            color: '#3f8600',
+            fontSize: '45px',
+            fontWeight: 500,
+            textShadow: '1px 1px 0 rgba(0,0,0,0.2)',
+          }"
+        />
+      </Card>
+      <Divider class="def-margin-bottom" />
+      <template v-for="(item, index) in footerList" :key="item.field">
+        <span> {{ item.title }} </span>
+        <strong class="pl-10px">{{ item.value }}</strong>
+        <Divider type="vertical" v-if="index !== footerList.length - 1" />
+      </template>
+    </Skeleton>
+  </div>
+</template>
+<style scoped></style>
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/components/CustomForm.tsx b/streampark-console/streampark-console-newui/src/views/flink/app/components/CustomForm.tsx
new file mode 100644
index 000000000..480b5b7f5
--- /dev/null
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/components/CustomForm.tsx
@@ -0,0 +1,117 @@
+/* 
+  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 } from 'vue';
+import type { PropType } from 'vue';
+import { Button, Form, Input, InputNumber, Tag, Select } from 'ant-design-vue';
+import Icon from '/@/components/Icon';
+export interface CheckPointFailure {
+  cpMaxFailureInterval: number;
+  cpFailureRateInterval: number;
+  cpFailureAction: string;
+}
+import { cpTriggerAction } from '../data';
+export default defineComponent({
+  props: {
+    value: {
+      type: Object as PropType<CheckPointFailure>,
+      required: true,
+    },
+  },
+  emits: ['updateValue'],
+  setup(props, { emit }) {
+    const formItemContext = Form.useInjectFormItemContext();
+    const triggerChange = (changedValue: Partial<CheckPointFailure>) => {
+      emit('updateValue', { ...props.value, ...changedValue });
+      formItemContext.onFieldChange();
+    };
+    const handleCpFailureRateIntervalChange = (value: any) => {
+      triggerChange({ cpFailureRateInterval: value });
+    };
+    const handleCpMaxFailureIntervalChange = (value: any) => {
+      // const newNumber = (e.target as any).value;
+      triggerChange({ cpMaxFailureInterval: value });
+    };
+    const handleFailureActionChange = (value: any) => {
+      triggerChange({ cpFailureAction: value });
+    };
+    return () => {
+      return (
+        <div>
+          <Input.Group compact class="!flex">
+            <InputNumber
+              min={1}
+              step={1}
+              name="cpMaxFailureInterval"
+              placeholder="checkpoint failure rate interval"
+              allow-clear
+              class="!w-260px mr-10px"
+              value={props.value?.cpMaxFailureInterval}
+              onChange={(value: any) => handleCpMaxFailureIntervalChange(value)}
+            />
+            <Button style="width: 70px"> minute </Button>
+            <InputNumber
+              style="margin-left: 1%"
+              name="cpFailureRateInterval"
+              min={1}
+              step={1}
+              placeholder="max failures per interval"
+              class="!mb-0 !w-200px"
+              value={props.value?.cpFailureRateInterval}
+              onChange={(value: any) => handleCpFailureRateIntervalChange(value)}
+            />
+
+            <Button style="width: 70px"> count </Button>
+            <Select
+              name="cpFailureAction"
+              style="margin-left: 1%"
+              placeholder="trigger action"
+              allow-clear
+              class="!mb-0 flex-1"
+              value={props.value?.cpFailureAction}
+              onChange={(e: any) => handleFailureActionChange(e)}
+            >
+              {cpTriggerAction.map((o) => {
+                return (
+                  <Select.Option key={o.value}>
+                    <Icon
+                      icon={
+                        o.value === 1 ? 'ant-design:alert-outlined' : 'ant-design:sync-outlined'
+                      }
+                    />
+                    {o.label}
+                  </Select.Option>
+                );
+              })}
+            </Select>
+          </Input.Group>
+          <p class="conf-desc mt-10px">
+            <span class="note-info">
+              <Tag color="#2db7f5" class="tag-note">
+                Note
+              </Tag>
+              Operation after checkpoint failure, e.g:
+              <br />
+              Within <span class="note-elem">5 minutes</span>(checkpoint failure rate interval), if
+              the number of checkpoint failures reaches <span class="note-elem">10</span> (max
+              failures per interval),action will be triggered(alert or restart job)
+            </span>
+          </p>
+        </div>
+      );
+    };
+  },
+});
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/components/Dependency.vue b/streampark-console/streampark-console-newui/src/views/flink/app/components/Dependency.vue
index cfe83b0a2..b45af62fb 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/app/components/Dependency.vue
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/components/Dependency.vue
@@ -263,6 +263,9 @@
   <Tabs type="card" v-model:activeKey="activeTab" class="pom-card">
     <TabPane key="pom" tab="Maven pom">
       <div ref="pomBox" class="pom-box syntax-true" style="height: 300px"></div>
+      <a-button type="primary" class="apply-pom" @click="handleApplyPom()">
+        {{ t('common.apply') }}
+      </a-button>
     </TabPane>
     <TabPane key="jar" tab="Upload Jar">
       <template v-if="isK8sExecMode(formModel?.executionMode)">
@@ -285,55 +288,45 @@
       <UploadJobJar :custom-request="handleCustomDepsRequest" v-model:loading="loading" />
     </TabPane>
   </Tabs>
-  <div class="flex justify-end">
-    <div class="dependency-box" v-if="dependencyRecords.length > 0 || uploadJars.length > 0">
-      <Alert
-        class="dependency-item"
-        v-for="(value, index) in dependencyRecords"
-        :key="`dependency_${index}`"
-        type="info"
-        @click="handleEditPom(value)"
-      >
-        <template #message>
-          <Space @click="handleEditPom(value)" class="tag-dependency-pom">
-            <Tag class="tag-dependency" color="#2db7f5">POM</Tag>
-            {{ value.artifactId }}-{{ value.version }}.jar
-            <Icon
-              :size="12"
-              icon="ant-design:close-outlined"
-              class="icon-close cursor-pointer"
-              @click.stop="handleRemovePom(value)"
-            />
-          </Space>
-        </template>
-      </Alert>
-      <Alert
-        class="dependency-item"
-        v-for="(value, index) in uploadJars"
-        :key="`upload_jars_${index}`"
-        type="info"
-      >
-        <template #message>
-          <Space>
-            <Tag class="tag-dependency" color="#108ee9">JAR</Tag>
-            {{ value }}
-            <Icon
-              icon="ant-design:close-outlined"
-              class="icon-close cursor-pointer"
-              :size="12"
-              @click="handleRemoveJar(value)"
-            />
-          </Space>
-        </template>
-      </Alert>
-    </div>
-    <a-button
-      type="primary"
-      class="apply-pom"
-      @click="handleApplyPom()"
-      v-show="activeTab == 'pom'"
+  <div class="dependency-box" v-if="dependencyRecords.length > 0 || uploadJars.length > 0">
+    <Alert
+      class="dependency-item"
+      v-for="(value, index) in dependencyRecords"
+      :key="`dependency_${index}`"
+      type="info"
+      @click="handleEditPom(value)"
     >
-      {{ t('common.apply') }}
-    </a-button>
+      <template #message>
+        <Space @click="handleEditPom(value)" class="tag-dependency-pom">
+          <Tag class="tag-dependency" color="#2db7f5">POM</Tag>
+          {{ value.artifactId }}-{{ value.version }}.jar
+          <Icon
+            :size="12"
+            icon="ant-design:close-outlined"
+            class="icon-close cursor-pointer"
+            @click.stop="handleRemovePom(value)"
+          />
+        </Space>
+      </template>
+    </Alert>
+    <Alert
+      class="dependency-item"
+      v-for="(value, index) in uploadJars"
+      :key="`upload_jars_${index}`"
+      type="info"
+    >
+      <template #message>
+        <Space>
+          <Tag class="tag-dependency" color="#108ee9">JAR</Tag>
+          {{ value }}
+          <Icon
+            icon="ant-design:close-outlined"
+            class="icon-close cursor-pointer"
+            :size="12"
+            @click="handleRemoveJar(value)"
+          />
+        </Space>
+      </template>
+    </Alert>
   </div>
 </template>
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/components/FlinkSql.vue b/streampark-console/streampark-console-newui/src/views/flink/app/components/FlinkSql.vue
new file mode 100644
index 000000000..5b11877fc
--- /dev/null
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/components/FlinkSql.vue
@@ -0,0 +1,223 @@
+<!--
+  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.
+-->
+<script lang="ts">
+  export default {
+    name: 'FlinkSql',
+  };
+</script>
+
+<script setup lang="ts" name="FlinkSql">
+  import { computed, reactive, ref, unref, watchEffect } from 'vue';
+  import { getMonacoOptions } from '../data';
+  import { Icon } from '/@/components/Icon';
+  import { useMonaco } from '/@/hooks/web/useMonaco';
+  import { Button } from 'ant-design-vue';
+  import { isEmpty } from '/@/utils/is';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { fetchFlinkSqlVerify } from '/@/api/flink/app/flinkSql';
+  import { format } from '../FlinkSqlFormatter';
+  import { useFullscreen } from '@vueuse/core';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  const ButtonGroup = Button.Group;
+  const { t } = useI18n();
+  const vertifyRes = reactive({
+    errorMsg: '',
+    verified: false,
+    errorStart: 0,
+    errorEnd: 0,
+  });
+  const flinkSql = ref();
+  const flinkScreen = ref();
+  const { isFullscreen, toggle } = useFullscreen(flinkScreen);
+  const emit = defineEmits(['update:value', 'preview']);
+  const { createMessage } = useMessage();
+
+  const props = defineProps({
+    value: {
+      type: String,
+      default: '',
+    },
+    versionId: {
+      type: String as PropType<Nullable<string>>,
+    },
+    suggestions: {
+      type: Array as PropType<Array<{ text: string; description: string }>>,
+      default: () => [],
+    },
+  });
+  const defaultValue = '';
+
+  /* verify */
+  async function handleVerifySql() {
+    if (isEmpty(props.value)) {
+      vertifyRes.errorMsg = 'empty sql';
+      return false;
+    }
+
+    if (!props.versionId) {
+      createMessage.error(t('flink.app.dependencyError'));
+      return false;
+    } else {
+      try {
+        const { data } = await fetchFlinkSqlVerify({
+          sql: props.value,
+          versionId: props.versionId,
+        });
+        const success = data.data === true || data.data === 'true';
+        if (success) {
+          vertifyRes.verified = true;
+          vertifyRes.errorMsg = '';
+          syntaxError();
+          return true;
+        } else {
+          vertifyRes.errorStart = parseInt(data.start);
+          vertifyRes.errorEnd = parseInt(data.end);
+          switch (data.type) {
+            case 4:
+              vertifyRes.errorMsg = 'Unsupported sql';
+              break;
+            case 5:
+              vertifyRes.errorMsg = "SQL is not endWith ';'";
+              break;
+            default:
+              vertifyRes.errorMsg = data.message;
+              break;
+          }
+          syntaxError();
+          return false;
+        }
+      } catch (error) {
+        console.error(error);
+        return false;
+      }
+    }
+  }
+
+  async function syntaxError() {
+    const editor = await getInstance();
+    if (editor) {
+      const model = editor.getModel();
+      const monaco = await getMonacoInstance();
+      if (vertifyRes.errorMsg) {
+        try {
+          monaco.editor.setModelMarkers(model, 'sql', [
+            {
+              startLineNumber: 1,
+              endLineNumber: 4,
+              severity: monaco.MarkerSeverity.Error,
+              message: 'dsadfs',
+            },
+          ]);
+        } catch (e) {
+          console.log(e);
+        }
+      } else {
+        monaco.editor.setModelMarkers(model, 'sql', []);
+      }
+    }
+  }
+  /* format */
+  function handleFormatSql() {
+    if (isEmpty(props.value)) return;
+    const formatSql = format(props.value);
+    setContent(formatSql);
+  }
+  /* full screen */
+  function handleBigScreen() {
+    toggle();
+    unref(flinkSql).style.width = '0';
+    setTimeout(() => {
+      unref(flinkSql).style.width = '100%';
+      unref(flinkSql).style.height = isFullscreen.value ? '100vh' : '550px';
+    }, 100);
+  }
+  const { onChange, setContent, getInstance, getMonacoInstance, setMonacoSuggest } = useMonaco(
+    flinkSql,
+    {
+      language: 'sql',
+      code: props.value || defaultValue,
+      options: {
+        minimap: { enabled: true },
+        ...(getMonacoOptions(false) as any),
+        autoClosingBrackets: 'never',
+      },
+    },
+  );
+
+  watchEffect(() => {
+    if (props.suggestions.length > 0) {
+      setMonacoSuggest(props.suggestions);
+    }
+  });
+  const canPreview = computed(() => {
+    return /\${.+}/.test(props.value);
+  });
+
+  onChange((data) => {
+    emit('update:value', data);
+  });
+
+  defineExpose({ handleVerifySql, setContent });
+</script>
+
+<template>
+  <div>
+    <div ref="flinkScreen">
+      <div
+        class="sql-box"
+        ref="flinkSql"
+        :class="'syntax-' + (vertifyRes.errorMsg ? 'false' : 'true')"
+      ></div>
+      <ButtonGroup class="flinksql-tool">
+        <a-button
+          class="flinksql-tool-item"
+          size="small"
+          v-if="canPreview"
+          @click="emit('preview', value)"
+        >
+          <Icon icon="ant-design:eye-outlined" />
+          preview
+        </a-button>
+        <a-button size="small" class="flinksql-tool-item" type="primary" @click="handleVerifySql">
+          <Icon icon="ant-design:check-outlined" />
+          {{ t('flink.app.flinkSql.verify') }}
+        </a-button>
+        <a-button class="flinksql-tool-item" size="small" type="default" @click="handleFormatSql">
+          <Icon icon="ant-design:thunderbolt-outlined" />
+          {{ t('flink.app.flinkSql.format') }}
+        </a-button>
+        <a-button class="flinksql-tool-item" size="small" type="default" @click="handleBigScreen">
+          <Icon
+            :icon="
+              isFullscreen
+                ? 'ant-design:fullscreen-exit-outlined'
+                : 'ant-design:fullscreen-outlined'
+            "
+          />
+          {{ isFullscreen ? t('flink.app.flinkSql.exit') : '' }}
+          {{ t('flink.app.flinkSql.fullScreen') }}
+        </a-button>
+      </ButtonGroup>
+      <p class="conf-desc mt-10px">
+        <span class="text-red-600" v-if="vertifyRes.errorMsg"> {{ vertifyRes.errorMsg }} </span>
+        <span v-else class="text-green-700">
+          <span v-if="vertifyRes.verified"> {{ t('flink.app.flinkSql.successful') }} </span>
+        </span>
+      </p>
+    </div>
+  </div>
+</template>
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/components/FlinkSqlHistory.vue b/streampark-console/streampark-console-newui/src/views/flink/app/components/FlinkSqlHistory.vue
new file mode 100644
index 000000000..d3cd8eed3
--- /dev/null
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/components/FlinkSqlHistory.vue
@@ -0,0 +1,60 @@
+<!--
+  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.
+-->
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  export default defineComponent({
+    name: 'FlinkSqlHistory',
+  });
+</script>
+<script setup lang="ts" name="FlinkSqlHistory">
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  const { t } = useI18n();
+  const [registerModalInner] = useModalInner();
+  const [registerForm] = useForm({
+    labelWidth: 120,
+    colon: true,
+    schemas: [
+      {
+        field: 'compare_sql',
+        label: 'Version',
+        component: 'Select',
+        componentProps: {
+          placeholder: 'Please select the sql version to compare',
+          mode: 'multiple',
+          maxTagCount: 2,
+        },
+      },
+    ],
+    baseColProps: { span: 24 },
+    labelCol: { lg: { span: 5, offset: 0 }, sm: { span: 7, offset: 0 } },
+    wrapperCol: { lg: { span: 16, offset: 0 }, sm: { span: 17, offset: 0 } },
+    showActionButtonGroup: false,
+  });
+</script>
+<template>
+  <BasicModal @register="registerModalInner">
+    <template #title>
+      <Icon icon="ant-design:swap-outlined" style="color: #4a9ff5" />
+      {{ t('flink.app.flinkSql.compareFlinkSQL') }}
+    </template>
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<style scoped></style>
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/TemplateButtonGroup.vue b/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/TemplateButtonGroup.vue
index 547f87b69..7a521c10a 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/TemplateButtonGroup.vue
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/TemplateButtonGroup.vue
@@ -71,7 +71,7 @@
         {{ t('flink.app.pod.host') }}
       </div>
     </a-button>
-    <a-button type="default" disabled class="pod-template-tool-item">
+    <a-button type="default" disabled size="small" class="pod-template-tool-item">
       <div class="flex items-center">
         <Icon icon="ant-design:hdd-outlined" class="pr-5px" />
         PVC
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useApp.tsx b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useApp.tsx
index 7911d6ce8..5ab909306 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useApp.tsx
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useApp.tsx
@@ -283,7 +283,8 @@ export const useFlinkApplication = (openStartModal: Fn) => {
             name="mappingForm"
             labelCol={{ lg: { span: 7 }, sm: { span: 7 } }}
             wrapperCol={{ lg: { span: 16 }, sm: { span: 4 } }}
-            v-model:model={formValue}>
+            v-model:model={formValue}
+          >
             <Form.Item label="Application Name">
               <Alert message={app.jobName} type="info" />
             </Form.Item>
@@ -291,14 +292,16 @@ export const useFlinkApplication = (openStartModal: Fn) => {
               <Form.Item
                 label="YARN Application Id"
                 name="appId"
-                rules={[{ required: true, message: 'YARN ApplicationId is required' }]}>
+                rules={[{ required: true, message: 'YARN ApplicationId is required' }]}
+              >
                 <Input type="text" placeholder="ApplicationId" v-model:value={formValue.appId} />
               </Form.Item>
             )}
             <Form.Item
               label="JobId"
               name="jobId"
-              rules={[{ required: true, message: 'ApplicationId is required' }]}>
+              rules={[{ required: true, message: 'ApplicationId is required' }]}
+            >
               <Input type="text" placeholder="JobId" v-model:value={formValue.jobId} />
             </Form.Item>
           </Form>
@@ -312,7 +315,7 @@ export const useFlinkApplication = (openStartModal: Fn) => {
           await fetchMapping({
             id: app.id,
             appId: formValue.appId,
-            jobId: formValue.jobId
+            jobId: formValue.jobId,
           });
           Swal.fire({
             icon: 'success',
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTableAction.ts b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTableAction.ts
index bc5eab9eb..e0083ffbd 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTableAction.ts
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTableAction.ts
@@ -25,6 +25,7 @@ import { fetchFlamegraph } from '/@/api/flink/app/metrics';
 import { ActionItem, FormProps } from '/@/components/Table';
 import { useMessage } from '/@/hooks/web/useMessage';
 import { ExecModeEnum } from '/@/enums/flinkEnum';
+import { usePermission } from '/@/hooks/web/usePermission';
 export enum JobTypeEnum {
   JAR = 1,
   SQL = 2,
@@ -44,7 +45,7 @@ export const useAppTableAction = (
   const flinkAppStore = useFlinkAppStore();
   const router = useRouter();
   const { createMessage } = useMessage();
-
+  const { hasPermission } = usePermission();
   const {
     handleCheckLaunchApp,
     handleAppCheckStart,
@@ -210,16 +211,12 @@ export const useAppTableAction = (
   }
 
   const formConfig = computed((): Partial<FormProps> => {
-    return {
+    const tableFormConfig: FormProps = {
       baseColProps: { span: 5, style: { paddingRight: '20px' } },
       actionColOptions: { span: 4 },
       showSubmitButton: false,
+      showResetButton: false,
       colon: true,
-      resetButtonOptions: {
-        text: 'Add New',
-        color: 'primary',
-        preIcon: 'ant-design:plus-outlined',
-      },
       async resetFunc() {
         router.push({ path: '/flink/app/add' });
       },
@@ -274,6 +271,17 @@ export const useAppTableAction = (
         },
       ],
     };
+    if (hasPermission('app:create')) {
+      Object.assign(tableFormConfig, {
+        showResetButton: true,
+        resetButtonOptions: {
+          text: 'Add New',
+          color: 'primary',
+          preIcon: 'ant-design:plus-outlined',
+        },
+      });
+    }
+    return tableFormConfig;
   });
 
   /*  tag */
diff --git a/streampark-console/streampark-console-newui/src/views/flink/app/styles/Add.less b/streampark-console/streampark-console-newui/src/views/flink/app/styles/Add.less
index 40ad231bf..3fda47049 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/app/styles/Add.less
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/styles/Add.less
@@ -163,14 +163,7 @@
     color: #1890ff;
   }
 
-  .apply-pom {
-    margin-top: 17px;
-    cursor: pointer;
-    height: 26px;
-    padding: 0 12px;
-    font-size: 12px;
-  }
-
+  .apply-pom,
   .apply-testing,
   .verify-sql {
     z-index: 99;
@@ -193,7 +186,6 @@
   }
 
   .dependency-box {
-    flex: 1;
     margin-right: 10px;
     margin-top: 15px;
     margin-bottom: -10px;
diff --git a/streampark-console/streampark-console-newui/src/views/flink/setting/components/FlinkClusterSetting.vue b/streampark-console/streampark-console-newui/src/views/flink/setting/components/FlinkClusterSetting.vue
index eceb9b7d3..f67b100b1 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/setting/components/FlinkClusterSetting.vue
+++ b/streampark-console/streampark-console-newui/src/views/flink/setting/components/FlinkClusterSetting.vue
@@ -218,7 +218,7 @@
         <Tooltip :title="t('flink.setting.cluster.edit')">
           <a-button
             v-if="handleIsStart(item) && item.executionMode == executionMap.YARN_SESSION"
-            v-auth="'app:update'"
+            v-auth="'cluster:update'"
             :disabled="true"
             @click="handleEditCluster(item)"
             shape="circle"
@@ -229,7 +229,7 @@
           </a-button>
           <a-button
             v-if="!handleIsStart(item) || item.executionMode == executionMap.REMOTE"
-            v-auth="'app:update'"
+            v-auth="'cluster:update'"
             @click="handleEditCluster(item)"
             shape="circle"
             size="large"
diff --git a/streampark-console/streampark-console-newui/src/views/system/menu/Menu.vue b/streampark-console/streampark-console-newui/src/views/system/menu/Menu.vue
index 6474415b5..f9c08d098 100644
--- a/streampark-console/streampark-console-newui/src/views/system/menu/Menu.vue
+++ b/streampark-console/streampark-console-newui/src/views/system/menu/Menu.vue
@@ -17,12 +17,12 @@
 <template>
   <div>
     <BasicTable @register="registerTable" @fetch-success="onFetchSuccess">
-      <template #toolbar>
+      <!-- <template #toolbar>
         <a-button type="primary" @click="handleCreate" v-auth="'menu:add'">
           <Icon icon="ant-design:plus-outlined" />
           {{ t('common.add') }}
         </a-button>
-      </template>
+      </template> -->
       <template #bodyCell="{ column, record }">
         <template v-if="column.dataIndex === 'action'">
           <TableAction
@@ -52,11 +52,10 @@
   import { columns, searchFormSchema } from './menu.data';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useI18n } from '/@/hooks/web/useI18n';
-  import Icon from '/@/components/Icon';
 
   export default defineComponent({
     name: 'MenuManagement',
-    components: { BasicTable, MenuDrawer, TableAction, Icon },
+    components: { BasicTable, MenuDrawer, TableAction },
     setup() {
       const [registerDrawer, { openDrawer }] = useDrawer();
       const { createMessage } = useMessage();
@@ -71,9 +70,7 @@
           schemas: searchFormSchema,
           fieldMapToTime: [['createTime', ['createTimeFrom', 'createTimeTo'], 'YYYY-MM-DD']],
         },
-        fetchSetting: {
-          listField: 'rows.children',
-        },
+        fetchSetting: { listField: 'rows.children' },
         isTreeTable: true,
         pagination: false,
         striped: false,
@@ -82,11 +79,11 @@
         bordered: true,
         showIndexColumn: false,
         canResize: false,
-        actionColumn: {
-          width: 100,
-          title: 'Operation',
-          dataIndex: 'action',
-        },
+        // actionColumn: {
+        //   width: 100,
+        //   title: 'Operation',
+        //   dataIndex: 'action',
+        // },
       });
 
       function handleCreate() {
diff --git a/streampark-console/streampark-console-newui/src/views/system/menu/MenuDrawer.vue b/streampark-console/streampark-console-newui/src/views/system/menu/MenuDrawer.vue
index 6b0256bc8..8df679642 100644
--- a/streampark-console/streampark-console-newui/src/views/system/menu/MenuDrawer.vue
+++ b/streampark-console/streampark-console-newui/src/views/system/menu/MenuDrawer.vue
@@ -54,7 +54,7 @@
         resetFields();
         setDrawerProps({ confirmLoading: false });
         isUpdate.value = !!data?.isUpdate;
-
+        console.log('data.record', data.record);
         if (unref(isUpdate)) {
           setFieldsValue({
             ...data.record,
@@ -76,6 +76,7 @@
       async function handleSubmit() {
         try {
           const values = await validate();
+          values.display = !!values.display;
           setDrawerProps({ confirmLoading: true });
           unref(isUpdate) ? await editMenu(values) : await addMenu(values);
           closeDrawer();
diff --git a/streampark-console/streampark-console-newui/src/views/system/menu/menu.data.ts b/streampark-console/streampark-console-newui/src/views/system/menu/menu.data.ts
index 0a7073fa6..75d1041f3 100644
--- a/streampark-console/streampark-console-newui/src/views/system/menu/menu.data.ts
+++ b/streampark-console/streampark-console-newui/src/views/system/menu/menu.data.ts
@@ -26,7 +26,7 @@ export const enum TypeEnum {
   Dir = '2',
 }
 
-const isDir = (type: string) => type === TypeEnum.Dir;
+// const isDir = (type: string) => type === TypeEnum.Dir;
 const isMenu = (type: string) => type === TypeEnum.Menu;
 const isButton = (type: string) => type === TypeEnum.Button;
 
@@ -50,35 +50,17 @@ export const columns: BasicColumn[] = [
   {
     title: 'Type',
     dataIndex: 'type',
+    width: 90,
     customRender: ({ record }) => {
       const text = isMenu(record.type) ? 'menu' : 'button';
       return h(Tag, { color: isMenu(record.type) ? 'cyan' : 'pink' }, () => text);
     },
   },
-  {
-    title: 'Path',
-    dataIndex: 'path',
-  },
-  {
-    title: 'Vue Component',
-    dataIndex: 'component',
-  },
-  {
-    title: 'Permission',
-    dataIndex: 'permission',
-  },
-  {
-    title: 'Order By',
-    dataIndex: 'order',
-  },
-  {
-    title: 'Create Time',
-    dataIndex: 'createTime',
-  },
-  {
-    title: 'Modify Time',
-    dataIndex: 'modifyTime',
-  },
+  { title: 'Path', dataIndex: 'path' },
+  { title: 'Vue Component', dataIndex: 'component' },
+  { title: 'Permission', dataIndex: 'permission', width: 150 },
+  { title: 'Order By', dataIndex: 'order', width: 90 },
+  { title: 'Create Time', dataIndex: 'createTime' },
 ];
 
 export const searchFormSchema: FormSchema[] = [
@@ -146,7 +128,6 @@ export const formSchema: FormSchema[] = [
     label: 'sort',
     component: 'InputNumber',
     componentProps: { class: '!w-full' },
-    ifShow: ({ values }) => !isButton(values.type),
   },
   {
     field: 'icon',
@@ -172,16 +153,14 @@ export const formSchema: FormSchema[] = [
     label: 'Related permissions',
     component: 'Input',
     rules: [{ max: 50, message: 'Length cannot exceed 50 characters' }],
-    ifShow: ({ values }) => !isDir(values.type),
+    ifShow: ({ values }) => isButton(values.type),
   },
   {
     field: 'display',
     label: 'whether to display',
     component: 'Switch',
-    defaultValue: '1',
+    defaultValue: true,
     componentProps: {
-      checkedValue: '1',
-      unCheckedValue: '0',
       checkedChildren: 'Yes',
       unCheckedChildren: 'No',
     },