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 41f1dc9a5 [Improve] login page style, fix change teamId bug (#1912)
41f1dc9a5 is described below

commit 41f1dc9a5448b6aacf9ddf40e46ff3c57fce8f43
Author: Sizhu Wang <[email protected]>
AuthorDate: Wed Oct 26 23:34:11 2022 +0800

    [Improve] login page style, fix change teamId bug (#1912)
---
 .../streampark-console-newui/package.json          |   2 +-
 .../src/assets/images/logo-bg.jpg                  | Bin 0 -> 758479 bytes
 .../src/components/Button/src/props.ts             |   5 +-
 .../streampark-console-newui/src/design/color.less |   2 +
 .../src/design/public.less                         |  22 +-
 .../src/design/var/index.less                      |  16 -
 .../header/components/user-dropdown/index.vue      |   4 +-
 .../src/store/modules/user.ts                      |  20 +-
 .../src/utils/cache/persistent.ts                  |  17 +-
 .../src/utils/cache/storageCache.ts                |   2 +-
 .../src/views/base/login/Login.vue                 |  70 +----
 .../src/views/base/login/LoginForm.vue             |  25 +-
 .../src/views/base/login/LoginFormTitle.vue        |   2 +-
 .../src/views/flink/app/EditFlink.vue              |   4 +-
 .../src/views/flink/app/View.vue                   | 344 ++-------------------
 .../flink/app/components/AppView/AppDashboard.vue  |  87 ++++++
 .../src/views/flink/app/components/Dependency.vue  |   7 -
 .../app/components/PodTemplate/PomTemplateTab.vue  |   2 +-
 .../src/views/flink/app/hooks/useAppTable.ts       | 128 ++++++++
 .../src/views/flink/app/hooks/useAppTableAction.ts | 293 ++++++++++++++++++
 .../src/views/flink/app/styles/Add.less            |   6 +
 .../src/views/flink/notebook/Submit.less           |  10 +
 .../views/flink/setting/components/AlertModal.vue  |   2 +-
 23 files changed, 622 insertions(+), 448 deletions(-)

diff --git a/streampark-console/streampark-console-newui/package.json 
b/streampark-console/streampark-console-newui/package.json
index c7f8f7899..4a5e16569 100644
--- a/streampark-console/streampark-console-newui/package.json
+++ b/streampark-console/streampark-console-newui/package.json
@@ -1,6 +1,6 @@
 {
   "name": "streampark-webapp",
-  "version": "2.0.0",
+  "version": "1.2.4",
   "author": {
     "name": "streampark",
     "url": "https://streampark.apache.org";
diff --git 
a/streampark-console/streampark-console-newui/src/assets/images/logo-bg.jpg 
b/streampark-console/streampark-console-newui/src/assets/images/logo-bg.jpg
new file mode 100644
index 000000000..8771c8fb8
Binary files /dev/null and 
b/streampark-console/streampark-console-newui/src/assets/images/logo-bg.jpg 
differ
diff --git 
a/streampark-console/streampark-console-newui/src/components/Button/src/props.ts
 
b/streampark-console/streampark-console-newui/src/components/Button/src/props.ts
index f6547e0e4..a8fca6a3d 100644
--- 
a/streampark-console/streampark-console-newui/src/components/Button/src/props.ts
+++ 
b/streampark-console/streampark-console-newui/src/components/Button/src/props.ts
@@ -15,7 +15,10 @@
  * limitations under the License.
  */
 export const buttonProps = {
-  color: { type: String, validator: (v) => ['error', 'warning', 'success', 
''].includes(v) },
+  color: {
+    type: String,
+    validator: (v) => ['error', 'warning', 'success', 'primary', 
''].includes(v),
+  },
   loading: { type: Boolean },
   disabled: { type: Boolean },
   /**
diff --git a/streampark-console/streampark-console-newui/src/design/color.less 
b/streampark-console/streampark-console-newui/src/design/color.less
index 2410e1561..0fc2a3bf4 100644
--- a/streampark-console/streampark-console-newui/src/design/color.less
+++ b/streampark-console/streampark-console-newui/src/design/color.less
@@ -155,3 +155,5 @@ html {
 
 // Modal
 @modal-mask-bg: fade(@black, 45%);
+
+@background-color-base: #e6f7ff;
diff --git a/streampark-console/streampark-console-newui/src/design/public.less 
b/streampark-console/streampark-console-newui/src/design/public.less
index 6268fac5b..bbaa20016 100644
--- a/streampark-console/streampark-console-newui/src/design/public.less
+++ b/streampark-console/streampark-console-newui/src/design/public.less
@@ -24,8 +24,8 @@
 // =================================
 
 ::-webkit-scrollbar {
-  width: 7px;
-  height: 8px;
+  width: 14px;
+  height: 12px;
 }
 
 // ::-webkit-scrollbar-track {
@@ -33,21 +33,21 @@
 // }
 
 ::-webkit-scrollbar-track {
-  background-color: rgb(0 0 0 / 5%);
+  // background-color: rgb(0 0 0 / 5%);
+  background-color: unset;
 }
 
+::-webkit-scrollbar-thumb:hover,
 ::-webkit-scrollbar-thumb {
-  // background: rgba(0, 0, 0, 0.6);
-  background-color: rgb(144 147 153 / 30%);
-  // background-color: rgba(144, 147, 153, 0.3);
-  border-radius: 2px;
-  box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
+  background-color: #e6f7ff;
 }
 
-::-webkit-scrollbar-thumb:hover {
-  background-color: @border-color-dark;
+[data-theme="dark"] {
+  ::-webkit-scrollbar-thumb:hover,
+  ::-webkit-scrollbar-thumb {
+    background-color: #2a2a2a;
+  }
 }
-
 // =================================
 // ==============nprogress==========
 // =================================
diff --git 
a/streampark-console/streampark-console-newui/src/design/var/index.less 
b/streampark-console/streampark-console-newui/src/design/var/index.less
index e4c2a9203..d14f45ca2 100644
--- a/streampark-console/streampark-console-newui/src/design/var/index.less
+++ b/streampark-console/streampark-console-newui/src/design/var/index.less
@@ -53,19 +53,3 @@
     @content();
   }
 }
-
-@background-color-base: #e6f7ff;
-
-@gray-1: #ffffff;
-@gray-2: #fafafa;
-@gray-3: #f5f5f5;
-@gray-4: #f0f0f0;
-@gray-5: #d9d9d9;
-@gray-6: #bfbfbf;
-@gray-7: #8c8c8c;
-@gray-8: #595959;
-@gray-9: #434343;
-@gray-10: #262626;
-@gray-11: #1f1f1f;
-@gray-12: #141414;
-@gray-13: #000000;
diff --git 
a/streampark-console/streampark-console-newui/src/layouts/default/header/components/user-dropdown/index.vue
 
b/streampark-console/streampark-console-newui/src/layouts/default/header/components/user-dropdown/index.vue
index 2bd30ab97..db6fd4d9f 100644
--- 
a/streampark-console/streampark-console-newui/src/layouts/default/header/components/user-dropdown/index.vue
+++ 
b/streampark-console/streampark-console-newui/src/layouts/default/header/components/user-dropdown/index.vue
@@ -17,8 +17,8 @@
 <template>
   <Dropdown placement="bottomLeft" 
:overlayClassName="`${prefixCls}-dropdown-overlay`">
     <span :class="[prefixCls, `${prefixCls}--${theme}`]" class="flex">
-      <img :class="`${prefixCls}__header`" :src="getUserInfo.avatar" />
-      <span :class="`${prefixCls}__info hidden md:block`">
+      <!-- <img :class="`${prefixCls}__header`" :src="getUserInfo.avatar" /> 
-->
+      <span :class="`${prefixCls}__info hidden md:block px-10px`">
         <span :class="`${prefixCls}__name  `" class="truncate">
           {{ getUserInfo.username }}
         </span>
diff --git 
a/streampark-console/streampark-console-newui/src/store/modules/user.ts 
b/streampark-console/streampark-console-newui/src/store/modules/user.ts
index 48999bba4..a99b307ac 100644
--- a/streampark-console/streampark-console-newui/src/store/modules/user.ts
+++ b/streampark-console/streampark-console-newui/src/store/modules/user.ts
@@ -38,6 +38,8 @@ import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
 import { h } from 'vue';
 import { getUserTeamId } from '/@/utils';
 import { usePermission } from '/@/hooks/web/usePermission';
+import { AesEncryption } from '/@/utils/cipher';
+import { cacheCipher } from '/@/settings/encryptionSetting';
 
 interface TeamListType {
   label: string;
@@ -86,7 +88,7 @@ export const useUserStore = defineStore({
       return this.expire || getAuthCache<string>(EXPIRE_KEY);
     },
     getRoleList(): RoleEnum[] {
-      return this.roleList.length > 0 ? this.roleList : 
getAuthCache<RoleEnum[]>(ROLES_KEY);
+      return this.roleList?.length > 0 ? this.roleList : 
getAuthCache<RoleEnum[]>(ROLES_KEY);
     },
     getSessionTimeout(): boolean {
       return !!this.sessionTimeout;
@@ -110,13 +112,20 @@ export const useUserStore = defineStore({
     setToken(info: string | undefined) {
       this.token = info ? info : ''; // for null or undefined value
       setAuthCache(TOKEN_KEY, info);
+      let cacheToken = this.token;
+      // production encrypted
+      if (import.meta.env.PROD && cacheToken) {
+        const encryption = new AesEncryption({ key: cacheCipher.key, iv: 
cacheCipher.iv });
+        cacheToken = encryption.encryptByAES(cacheToken);
+      }
+      localStorage.setItem(TOKEN_KEY, cacheToken);
     },
     setExpire(info: string | undefined) {
       this.expire = info || '';
       setAuthCache(EXPIRE_KEY, info);
     },
     setRoleList(roleList: RoleEnum[]) {
-      this.roleList = roleList;
+      this.roleList = roleList || [];
       setAuthCache(ROLES_KEY, roleList);
     },
     setUserInfo(info: UserInfo | null) {
@@ -140,11 +149,12 @@ export const useUserStore = defineStore({
     setData(data: Recordable) {
       const { token, expire, user, permissions, roles = [] } = data;
 
-      this.setToken(token);
       this.setExpire(expire);
       this.setUserInfo(user);
       this.setRoleList(roles);
       this.setPermissions(permissions);
+      // set token must be placed at the end, need to listen to this value
+      this.setToken(token);
     },
     // set team
     async setTeamId(data: { teamId: string; userId?: string }): 
Promise<boolean> {
@@ -157,7 +167,7 @@ export const useUserStore = defineStore({
         } else {
           const resp = await fetchSetUserTeam(data);
 
-          const { permissions, roles, user } = resp;
+          const { permissions, roles = [], user } = resp;
           this.setUserInfo(user);
           this.setRoleList(roles as RoleEnum[]);
           this.setPermissions(permissions);
@@ -214,11 +224,11 @@ export const useUserStore = defineStore({
           console.log('Token cancellation failed');
         }
       }
-      this.setToken(undefined);
       this.setSessionTimeout(false);
       this.setUserInfo(null);
       sessionStorage.removeItem(APP_TEAMID_KEY_);
       localStorage.removeItem(APP_TEAMID_KEY_);
+      this.setToken(undefined);
       goLogin && router.push(PageEnum.BASE_LOGIN);
     },
 
diff --git 
a/streampark-console/streampark-console-newui/src/utils/cache/persistent.ts 
b/streampark-console/streampark-console-newui/src/utils/cache/persistent.ts
index 8feb24cdb..4488d17e3 100644
--- a/streampark-console/streampark-console-newui/src/utils/cache/persistent.ts
+++ b/streampark-console/streampark-console-newui/src/utils/cache/persistent.ts
@@ -34,7 +34,7 @@ import {
 } from '/@/enums/cacheEnum';
 import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
 import { toRaw } from 'vue';
-import { pick, omit, debounce } from 'lodash-es';
+import { pick, omit } from 'lodash-es';
 
 interface BasicStore {
   [TOKEN_KEY]: string | number | null | undefined;
@@ -131,12 +131,21 @@ window.addEventListener('beforeunload', function () {
 
 function storageChange(e: any) {
   const { key, newValue, oldValue } = e;
-
   if (!key) {
     Persistent.clearAll();
     return;
   }
-
+  // token change reload
+  if (key === TOKEN_KEY && oldValue !== newValue) {
+    const { [TOKEN_KEY]: tokenCache } = pick(ls.get(APP_LOCAL_CACHE_KEY), 
TOKEN_KEY);
+    // no token or token value is invalid
+    if (tokenCache?.value !== newValue) {
+      Persistent.clearLocal();
+      return;
+    }
+    window.location.reload();
+    return;
+  }
   if (!!newValue && !!oldValue) {
     if (APP_LOCAL_CACHE_KEY === key) {
       Persistent.clearLocal();
@@ -147,6 +156,6 @@ function storageChange(e: any) {
   }
 }
 
-window.addEventListener('storage', debounce(storageChange, 500));
+window.addEventListener('storage', storageChange);
 
 initPersistentMemory();
diff --git 
a/streampark-console/streampark-console-newui/src/utils/cache/storageCache.ts 
b/streampark-console/streampark-console-newui/src/utils/cache/storageCache.ts
index afb9bc9d6..e307d1df5 100644
--- 
a/streampark-console/streampark-console-newui/src/utils/cache/storageCache.ts
+++ 
b/streampark-console/streampark-console-newui/src/utils/cache/storageCache.ts
@@ -61,7 +61,7 @@ export const createStorage = ({
       this.hasEncrypt = hasEncrypt;
     }
 
-    private getKey(key: string) {
+    getKey(key: string) {
       return `${this.prefixKey}${key}`.toUpperCase();
     }
 
diff --git 
a/streampark-console/streampark-console-newui/src/views/base/login/Login.vue 
b/streampark-console/streampark-console-newui/src/views/base/login/Login.vue
index 5fcbfa4a8..4297352d8 100644
--- a/streampark-console/streampark-console-newui/src/views/base/login/Login.vue
+++ b/streampark-console/streampark-console-newui/src/views/base/login/Login.vue
@@ -16,34 +16,12 @@
 -->
 <template>
   <div :class="prefixCls" class="relative w-full h-full px-4">
-    <AppLocalePicker
-      class="absolute text-white top-4 right-4 enter-x xl:text-gray-600"
-      :showText="false"
-      v-if="!sessionTimeout && showLocale"
-    />
-    <AppDarkModeToggle class="absolute top-3 right-7 enter-x" 
v-if="!sessionTimeout" />
-
-    <span class="-enter-x xl:hidden">
-      <AppLogo :alwaysShowTitle="true" />
-    </span>
-
     <div class="container relative h-full py-2">
       <div class="flex h-full">
-        <div class="hidden min-h-full pl-4 mr-4 xl:flex xl:flex-col xl:w-4/12">
-          <AppLogo class="!w-auto -enter-x" />
-          <div class="my-auto">
-            <div class="mt-10 font-medium text-white -enter-x">
-              <span class="inline-block mt-4 text-3xl"> {{ 
t('sys.login.signInTitle') }}</span>
-            </div>
-            <div class="mt-5 font-normal text-gray-300 text-md -enter-x">
-              {{ t('sys.login.signInDesc') }}
-            </div>
-          </div>
-        </div>
-        <div class="flex w-full h-full py-5 xl:h-auto xl:py-0 xl:my-0 
xl:w-8/12">
+        <div class="flex w-full h-full py-5 xl:h-auto xl:py-0 xl:my-0">
           <div
             :class="`${prefixCls}-form`"
-            class="relative w-full px-5 py-8 mx-auto my-auto rounded-md 
shadow-md xl:bg-transparent sm:px-8 xl:p-4 xl:shadow-none sm:w-3/4 lg:w-2/4 
xl:w-auto enter-x"
+            class="relative w-auto px-8 bg-[rgba(0,0,0,0.5)] py-10 mx-auto 
my-auto rounded-md shadow-md enter-y"
           >
             <LoginForm />
             <ForgetPasswordForm />
@@ -54,15 +32,9 @@
   </div>
 </template>
 <script lang="ts" setup>
-  // import { computed } from 'vue';
-  import { AppLogo } from '/@/components/Application';
-  import { AppLocalePicker, AppDarkModeToggle } from 
'/@/components/Application';
   import LoginForm from './LoginForm.vue';
   import ForgetPasswordForm from './ForgetPasswordForm.vue';
-  // import { useGlobSetting } from '/@/hooks/setting';
-  import { useI18n } from '/@/hooks/web/useI18n';
   import { useDesign } from '/@/hooks/web/useDesign';
-  import { useLocaleStore } from '/@/store/modules/locale';
 
   defineProps({
     sessionTimeout: {
@@ -72,9 +44,6 @@
 
   // const globSetting = useGlobSetting();
   const { prefixCls } = useDesign('login');
-  const { t } = useI18n();
-  const localeStore = useLocaleStore();
-  const showLocale = localeStore.getShowPicker;
   // const title = computed(() => globSetting?.title ?? '');
 </script>
 <style lang="less">
@@ -99,10 +68,6 @@
         border: 1px solid #4a5569;
       }
 
-      &-form {
-        background: transparent !important;
-      }
-
       .app-iconify {
         color: #fff;
       }
@@ -117,31 +82,18 @@
   .@{prefix-cls} {
     min-height: 100%;
     overflow: hidden;
-    @media (max-width: @screen-xl) {
-      background-color: #293146;
-
-      .@{prefix-cls}-form {
-        background-color: #fff;
-      }
-    }
-
-    &::before {
+    background: url('/@/assets/images/logo-bg.jpg') no-repeat 50%;
+    background-size: cover;
+    &::after {
       content: '';
+      width: 100%;
       position: absolute;
-      top: 0;
       left: 0;
-      width: 100%;
-      height: 100%;
-      margin-left: -66%;
-      background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
-      // background-image: url(/@/assets/svg/login-bg.svg);
-      // background-position: 100%;
-      // background-repeat: no-repeat;
-      // background-size: auto 100%;
-
-      @media (max-width: @screen-xl) {
-        display: none;
-      }
+      top: 0;
+      bottom: -20px;
+      background: inherit;
+      filter: blur(4px);
+      z-index: 2;
     }
 
     .@{logo-prefix-cls} {
diff --git 
a/streampark-console/streampark-console-newui/src/views/base/login/LoginForm.vue
 
b/streampark-console/streampark-console-newui/src/views/base/login/LoginForm.vue
index 9610dcc8f..881eda7c5 100644
--- 
a/streampark-console/streampark-console-newui/src/views/base/login/LoginForm.vue
+++ 
b/streampark-console/streampark-console-newui/src/views/base/login/LoginForm.vue
@@ -15,7 +15,7 @@
   limitations under the License.
 -->
 <template>
-  <LoginFormTitle v-show="getShow" class="enter-x mb-40px" />
+  <LoginFormTitle v-show="getShow" class="enter-x mb-40px text-light-50" />
   <Form
     class="p-4 enter-x"
     :model="formData"
@@ -51,28 +51,27 @@
       <ACol :span="12">
         <FormItem>
           <!-- No logic, you need to deal with it yourself -->
-          <Checkbox v-model:checked="rememberMe" size="small">
+          <Checkbox v-model:checked="rememberMe" size="small" 
class="!text-light-500">
             {{ t('sys.login.rememberMe') }}
           </Checkbox>
         </FormItem>
       </ACol>
-      <ACol :span="12">
+      <!-- No logic, you need to deal with it yourself -->
+      <!-- <ACol :span="12">
         <FormItem :style="{ 'text-align': 'right' }">
-          <!-- No logic, you need to deal with it yourself -->
           <Button type="link" size="small" 
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)">
             {{ t('sys.login.forgetPassword') }}
           </Button>
         </FormItem>
-      </ACol>
+      </ACol> -->
     </ARow>
-
     <FormItem class="enter-x">
       <Button type="primary" block @click="handleLogin" :loading="loading">
-        通过 {{ loginText.buttonText }} 登录
+        {{ loginText.buttonText }}
       </Button>
     </FormItem>
-    <FormItem class="enter-x text-center">
-      <Button type="link" @click="changeLoginType"> 使用 {{ loginText.linkText 
}} </Button>
+    <FormItem class="enter-x text-left">
+      <Button type="link" @click="changeLoginType"> {{ loginText.linkText }} 
</Button>
     </FormItem>
   </Form>
   <TeamModal v-model:visible="modelVisible" :userId="userId" 
@success="handleTeamSuccess" />
@@ -112,7 +111,7 @@
   const { createErrorModal, createMessage } = useMessage();
   const { prefixCls } = useDesign('login');
   const userStore = useUserStore();
-  const { setLoginState, getLoginState } = useLoginState();
+  const { getLoginState } = useLoginState();
   const { getFormRules } = useFormRules();
   interface LoginForm {
     account: string;
@@ -130,12 +129,12 @@
   });
 
   const loginText = computed(() => {
-    const localText = '本地账户';
+    const localText = 'Login';
     const ldapText = 'openLDAP';
     if (loginType.value === LoginTypeEnum.LOCAL) {
-      return { buttonText: localText, linkText: ldapText };
+      return { buttonText: localText, linkText: 'Login by openLDAP' };
     }
-    return { buttonText: ldapText, linkText: localText };
+    return { buttonText: ldapText, linkText: 'Login by Password' };
   });
 
   const { validForm } = useFormValid(formRef);
diff --git 
a/streampark-console/streampark-console-newui/src/views/base/login/LoginFormTitle.vue
 
b/streampark-console/streampark-console-newui/src/views/base/login/LoginFormTitle.vue
index 954a7ef78..05284e4b8 100644
--- 
a/streampark-console/streampark-console-newui/src/views/base/login/LoginFormTitle.vue
+++ 
b/streampark-console/streampark-console-newui/src/views/base/login/LoginFormTitle.vue
@@ -15,7 +15,7 @@
   limitations under the License.
 -->
 <template>
-  <h2 class="mb-3 text-2xl font-bold text-center xl:text-3xl enter-x 
xl:text-left">
+  <h2 class="mb-6 text-2xl font-bold text-center xl:text-3xl enter-x">
     {{ getFormTitle }}
   </h2>
 </template>
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/app/EditFlink.vue 
b/streampark-console/streampark-console-newui/src/views/flink/app/EditFlink.vue
index 0091a21fb..3b724b191 100644
--- 
a/streampark-console/streampark-console-newui/src/views/flink/app/EditFlink.vue
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/app/EditFlink.vue
@@ -225,4 +225,6 @@
     </BasicForm>
   </PageWrapper>
 </template>
-<style scoped></style>
+<style lang="less">
+  @import url('./styles/Add.less');
+</style>
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/app/View.vue 
b/streampark-console/streampark-console-newui/src/views/flink/app/View.vue
index 2e5809072..858dfbbb2 100644
--- a/streampark-console/streampark-console-newui/src/views/flink/app/View.vue
+++ b/streampark-console/streampark-console-newui/src/views/flink/app/View.vue
@@ -15,29 +15,23 @@
   limitations under the License.
 -->
 <script lang="ts">
-  import { defineComponent, onMounted, ref, reactive, unref, onUnmounted, 
watch } from 'vue';
-  import { useI18n } from '/@/hooks/web/useI18n';
+  import { defineComponent, ref, unref, onUnmounted, watch } from 'vue';
   import { useUserStore } from '/@/store/modules/user';
+  import { useAppTableAction } from './hooks/useAppTableAction';
   export default defineComponent({
     name: 'AppView',
   });
 </script>
 <script lang="ts" setup name="AppView">
   import { useTimeoutFn } from '@vueuse/core';
-  import { Row, Col, Tooltip, Badge, Divider, Select, Input, Tag } from 
'ant-design-vue';
-  import { fetchAppRecord, fetchDashboard, fetchAppRemove } from 
'/@/api/flink/app/app';
-  import { fetchFlamegraph } from '/@/api/flink/app/metrics';
-  import { ActionItem, useTable } from '/@/components/Table';
-  import { Icon } from '/@/components/Icon';
-  import { useFlinkApplication } from './hooks/useApp';
-  import { useRouter } from 'vue-router';
-  import { useFlinkAppStore } from '/@/store/modules/flinkApplication';
+  import { Tooltip, Badge, Divider, Tag } from 'ant-design-vue';
+  import { fetchAppRecord } from '/@/api/flink/app/app';
+  import { useTable } from '/@/components/Table';
   import { PageWrapper } from '/@/components/Page';
   import { BasicTable, TableAction } from '/@/components/Table';
   import { AppListRecord } from '/@/api/flink/app/app.type';
-  import { useMessage } from '/@/hooks/web/useMessage';
   import { getAppColumns, launchTitleMap } from './data';
-  import { handleIsStart, handleView } from './utils';
+  import { handleView } from './utils';
   import { useDrawer } from '/@/components/Drawer';
   import { useModal } from '/@/components/Modal';
 
@@ -45,19 +39,10 @@
   import StopApplicationModal from 
'./components/AppView/StopApplicationModal.vue';
   import LogModal from './components/AppView/LogModal.vue';
   import BuildDrawer from './components/AppView/BuildDrawer.vue';
+  import AppDashboard from './components/AppView/AppDashboard.vue';
   import State from './components/State';
 
-  import StatisticCard from './components/AppView/statisticCard.vue';
-
-  const SelectOption = Select.Option;
-  const InputGroup = Input.Group;
-  const InputSearch = Input.Search;
-
-  const { t } = useI18n();
-  const router = useRouter();
   const userStore = useUserStore();
-  const flinkAppStore = useFlinkAppStore();
-  const { createMessage } = useMessage();
 
   const optionApps = {
     starting: new Map(),
@@ -65,52 +50,13 @@
     launch: new Map(),
   };
 
-  const tagsOptions = ref<Recordable>([]);
-  const dashboardLoading = ref(true);
-  const dashBigScreenMap = reactive<Recordable>({});
+  const appDashboardRef = ref<any>();
   const searchText = ref('');
   const tags = ref(undefined);
   const jobType = ref(undefined);
   const userId = ref(undefined);
   const yarn = ref<Nullable<string>>(null);
 
-  // Get Dashboard Metrics Data
-  async function handleDashboard(showLoading: boolean) {
-    try {
-      dashboardLoading.value = showLoading;
-      const res = await fetchDashboard();
-      if (res) {
-        Object.assign(dashBigScreenMap, {
-          availiableTask: {
-            staticstics: { title: 'Available Task Slots', value: 
res.availableSlot },
-            footer: [
-              { title: 'Task Slots', value: res.totalSlot },
-              { title: 'Task Managers', value: res.totalTM },
-            ],
-          },
-          runningJob: {
-            staticstics: { title: 'Running Jobs', value: res.runningJob },
-            footer: [
-              { title: 'Total Task', value: res.task.total },
-              { title: 'Running Task', value: res.task.running },
-            ],
-          },
-          jobManager: {
-            staticstics: { title: 'JobManager Memory', value: res.jmMemory },
-            footer: [{ title: 'Total JobManager Mem', value: `${res.jmMemory} 
MB` }],
-          },
-          taskManager: {
-            staticstics: { title: 'TaskManager Memory', value: res.tmMemory },
-            footer: [{ title: 'Total TaskManager Mem', value: `${res.tmMemory} 
MB` }],
-          },
-        });
-      }
-    } catch (error) {
-      console.error(error);
-    } finally {
-      dashboardLoading.value = false;
-    }
-  }
   const [registerStartModal, { openModal: openStartModal }] = useModal();
   const [registerStopModal, { openModal: openStopModal }] = useModal();
   const [registerLogModal, { openModal: openLogModal }] = useModal();
@@ -173,9 +119,18 @@
     columns: getAppColumns(),
     showIndexColumn: false,
     showTableSetting: true,
+    useSearchForm: true,
     tableSetting: { fullScreen: true, redo: false },
     actionColumn: { dataIndex: 'operation', title: 'Operation', width: 180 },
   });
+  const { getTableActions, getActionDropdown, formConfig } = useAppTableAction(
+    openStartModal,
+    openStopModal,
+    openLogModal,
+    openBuildDrawer,
+    reload,
+    optionApps,
+  );
 
   watch(
     () => userStore.getTeamId,
@@ -194,26 +149,8 @@
     openBuildDrawer(true, { appId: app.id });
   }
 
-  /* Click to edit */
-  function handleEdit(app: AppListRecord) {
-    flinkAppStore.setApplicationId(app.id);
-    if (app.appType === 1) {
-      // jobType( 1 custom code 2: flinkSQL)
-      router.push({ path: '/flink/app/edit_streampark', query: { appId: app.id 
} });
-    } else if (app.appType === 2) {
-      //Apache Flink
-      router.push({ path: '/flink/app/edit_flink' });
-    }
-  }
-  /* Click for details */
-  function handleDetail(app: AppListRecord) {
-    flinkAppStore.setApplicationId(app.id);
-    router.push({ path: '/flink/app/detail', query: { appId: app.id } });
-  }
-
   /* view */
   async function handleJobView(app: AppListRecord) {
-    console.log('app', app.state, app.optionState);
     // Task is running, restarting, in savePoint
     if ([4, 5].includes(app.state) || app['optionState'] === 4) {
       console.log(app);
@@ -221,172 +158,7 @@
       handleView(app, unref(yarn));
     }
   }
-  /* Click to delete */
-  async function handleDelete(app: AppListRecord) {
-    const hide = createMessage.loading('deleting', 0);
-    try {
-      await fetchAppRemove(app.id);
-      createMessage.success('delete successful');
-      reload();
-    } catch (error) {
-      console.error(error);
-    } finally {
-      hide();
-    }
-  }
 
-  async function handleFlameGraph(app: AppListRecord) {
-    const hide = createMessage.loading('flameGraph generating...', 0);
-    try {
-      const { data } = await fetchFlamegraph({
-        appId: app.id,
-        width: document.documentElement.offsetWidth || 
document.body.offsetWidth,
-      });
-      if (data != null) {
-        const blob = new Blob([data], { type: 'image/svg+xml' });
-        const imageUrl = (window.URL || 
window.webkitURL).createObjectURL(blob);
-        window.open(imageUrl);
-      }
-    } catch (error) {
-      console.error(error);
-      createMessage.error('flameGraph generate failed');
-    } finally {
-      hide();
-    }
-  }
-
-  function handleCancel(app: AppListRecord) {
-    if (!optionApps.stopping.get(app.id) || app['optionState'] === 0) {
-      openStopModal(true, { application: app });
-    }
-  }
-  /* log view */
-  function handleSeeLog(app: AppListRecord) {
-    openLogModal(true, { app: unref(app) });
-  }
-  const {
-    handleCheckLaunchApp,
-    handleAppCheckStart,
-    handleCanStop,
-    handleForcedStop,
-    handleCopy,
-    handleMapping,
-    users,
-  } = useFlinkApplication(openStartModal);
-
-  /*  tag */
-  function handleInitTagsOptions() {
-    const params = Object.assign({}, { pageSize: 999999999, pageNum: 1 });
-    fetchAppRecord(params).then((res) => {
-      const dataSource = res?.records || [];
-      dataSource.forEach((record) => {
-        if (record.tags !== null && record.tags !== undefined && record.tags 
!== '') {
-          const tagsArray = record.tags.split(',') as string[];
-          tagsArray.forEach((x: string) => {
-            if (x.length > 0 && tagsOptions.value.indexOf(x) == -1) {
-              tagsOptions.value.push(x);
-            }
-          });
-        }
-      });
-    });
-  }
-
-  /* Operation button */
-  function getTableActions(record: AppListRecord): ActionItem[] {
-    return [
-      {
-        tooltip: { title: 'Edit Application' },
-        auth: 'app:update',
-        icon: 'clarity:note-edit-line',
-        onClick: handleEdit.bind(null, record),
-      },
-      {
-        tooltip: { title: 'Launch Application' },
-        ifShow: [-1, 1, 4].includes(record.launch) && record['optionState'] 
=== 0,
-        icon: 'ant-design:cloud-upload-outlined',
-        onClick: handleCheckLaunchApp.bind(null, record),
-      },
-      {
-        tooltip: { title: 'Launching Progress Detail' },
-        ifShow: [-1, 2].includes(record.launch) || record['optionState'] === 1,
-        auth: 'app:update',
-        icon: 'ant-design:container-outlined',
-        onClick: openBuildProgressDetailDrawer.bind(null, record),
-      },
-      {
-        tooltip: { title: 'Start Application' },
-        ifShow: handleIsStart(record, optionApps),
-        auth: 'app:start',
-        icon: 'ant-design:play-circle-outlined',
-        onClick: handleAppCheckStart.bind(null, record),
-      },
-      {
-        tooltip: { title: 'Cancel Application' },
-        ifShow: record.state === 5 && record['optionState'] === 0,
-        auth: 'app:cancel',
-        icon: 'ant-design:pause-circle-outlined',
-        onClick: handleCancel.bind(null, record),
-      },
-      {
-        tooltip: { title: 'View Application Detail' },
-        auth: 'app:detail',
-        icon: 'ant-design:eye-outlined',
-        onClick: handleDetail.bind(null, record),
-      },
-      {
-        tooltip: { title: 'See Flink Start log' },
-        ifShow: [5, 6].includes(record.executionMode),
-        auth: 'app:detail',
-        icon: 'ant-design:sync-outlined',
-        onClick: handleSeeLog.bind(null, record),
-      },
-      {
-        tooltip: { title: 'Forced Stop Application' },
-        ifShow: handleCanStop(record),
-        auth: 'app:cancel',
-        icon: 'ant-design:pause-circle-outlined',
-        onClick: handleForcedStop.bind(null, record),
-      },
-    ];
-  }
-
-  /* pull down button */
-  function getActionDropdown(record: AppListRecord): ActionItem[] {
-    return [
-      {
-        label: 'Copy Application',
-        auth: 'app:copy',
-        icon: 'ant-design:copy-outlined',
-        onClick: handleCopy.bind(null, record),
-      },
-      {
-        label: 'Remapping Application',
-        ifShow: [0, 7, 10, 11, 13].includes(record.state),
-        auth: 'app:mapping',
-        icon: 'ant-design:deployment-unit-outlined',
-        onClick: handleMapping.bind(null, record),
-      },
-      {
-        label: 'View FlameGraph',
-        ifShow: record.flameGraph,
-        auth: 'app:flameGraph',
-        icon: 'ant-design:fire-outlined',
-        onClick: handleFlameGraph.bind(null, record),
-      },
-      {
-        popConfirm: {
-          title: 'Are you sure delete this job ?',
-          confirm: handleDelete.bind(null, record),
-        },
-        label: 'Delete',
-        ifShow: [0, 7, 9, 10, 13, 18, 19].includes(record.state),
-        auth: 'app:delete',
-        icon: 'ant-design:delete-outlined',
-        color: 'error',
-      },
-    ];
-  }
   /* Update options data */
   function handleOptionApp(data: {
     type: 'starting' | 'stopping' | 'launch';
@@ -398,7 +170,7 @@
 
   const { start, stop } = useTimeoutFn(() => {
     if (!getLoading()) {
-      handleDashboard(false);
+      appDashboardRef.value?.handleDashboard(false);
       reload({ polling: true });
     }
     start();
@@ -408,10 +180,6 @@
     stop();
   });
 
-  onMounted(() => {
-    handleDashboard(true);
-    handleInitTagsOptions();
-  });
   watch(
     () => [tags.value, userId.value, jobType.value],
     () => {
@@ -421,80 +189,8 @@
 </script>
 <template>
   <PageWrapper contentFullHeight>
-    <Row :gutter="24" class="dashboard">
-      <Col
-        class="gutter-row mt-10px"
-        :md="6"
-        :xs="24"
-        v-for="(value, key) in dashBigScreenMap"
-        :key="key"
-      >
-        <StatisticCard
-          :statisticProps="value.staticstics"
-          :footerList="value.footer"
-          :loading="dashboardLoading"
-        />
-      </Col>
-    </Row>
-    <BasicTable @register="registerTable" class="app_list !px-0 mt-20px">
-      <template #headerTop>
-        <div class="text-right my-15px">
-          <InputGroup compact>
-            <div class="pr-16px">
-              <Select
-                placeholder="Tags"
-                show-search
-                allowClear
-                v-model:value="tags"
-                class="!w-150px text-left"
-              >
-                <SelectOption v-for="tag in tagsOptions" :key="tag">{{ tag 
}}</SelectOption>
-              </Select>
-            </div>
-
-            <div class="pr-16px">
-              <Select
-                placeholder="Owner"
-                allowClear
-                v-model:value="userId"
-                class="!w-120px text-left"
-              >
-                <SelectOption v-for="u in users" :key="u.userId">
-                  <span v-if="u.nickName"> {{ u.nickName }} </span>
-                  <span v-else> {{ u.username }} </span>
-                </SelectOption>
-              </Select>
-            </div>
-            <div class="pr-16px">
-              <Select
-                placeholder="Type"
-                allowClear
-                v-model:value="jobType"
-                class="w-100px text-left"
-              >
-                <SelectOption value="1">JAR</SelectOption>
-                <SelectOption value="2">SQL</SelectOption>
-              </Select>
-            </div>
-            <div class="pr-16px">
-              <InputSearch
-                placeholder="Search..."
-                v-model:value="searchText"
-                @search="reload({ polling: true })"
-                class="!w-250px text-left"
-              />
-            </div>
-            <a-button
-              type="primary"
-              style="margin-left: 20px"
-              @click="router.push({ path: '/flink/app/add' })"
-            >
-              <Icon icon="ant-design:plus-outlined" />
-              {{ t('common.add') }}
-            </a-button>
-          </InputGroup>
-        </div>
-      </template>
+    <AppDashboard ref="appDashboardRef" />
+    <BasicTable @register="registerTable" class="app_list !px-0 mt-20px" 
:formConfig="formConfig">
       <template #bodyCell="{ column, record }">
         <template v-if="column.dataIndex === 'jobName'">
           <span class="app_type app_jar" v-if="record['jobType'] === 1"> JAR 
</span>
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/app/components/AppView/AppDashboard.vue
 
b/streampark-console/streampark-console-newui/src/views/flink/app/components/AppView/AppDashboard.vue
new file mode 100644
index 000000000..73b825da2
--- /dev/null
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/app/components/AppView/AppDashboard.vue
@@ -0,0 +1,87 @@
+<!--
+  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" setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import { fetchDashboard } from '/@/api/flink/app/app';
+  import StatisticCard from './statisticCard.vue';
+  import { Row, Col } from 'ant-design-vue';
+  const dashBigScreenMap = reactive<Recordable>({});
+  const dashboardLoading = ref(true);
+
+  // Get Dashboard Metrics Data
+  async function handleDashboard(showLoading: boolean) {
+    try {
+      dashboardLoading.value = showLoading;
+      const res = await fetchDashboard();
+      if (res) {
+        Object.assign(dashBigScreenMap, {
+          availiableTask: {
+            staticstics: { title: 'Available Task Slots', value: 
res.availableSlot },
+            footer: [
+              { title: 'Task Slots', value: res.totalSlot },
+              { title: 'Task Managers', value: res.totalTM },
+            ],
+          },
+          runningJob: {
+            staticstics: { title: 'Running Jobs', value: res.runningJob },
+            footer: [
+              { title: 'Total Task', value: res.task.total },
+              { title: 'Running Task', value: res.task.running },
+            ],
+          },
+          jobManager: {
+            staticstics: { title: 'JobManager Memory', value: res.jmMemory },
+            footer: [{ title: 'Total JobManager Mem', value: `${res.jmMemory} 
MB` }],
+          },
+          taskManager: {
+            staticstics: { title: 'TaskManager Memory', value: res.tmMemory },
+            footer: [{ title: 'Total TaskManager Mem', value: `${res.tmMemory} 
MB` }],
+          },
+        });
+      }
+    } catch (error) {
+      console.error(error);
+    } finally {
+      dashboardLoading.value = false;
+    }
+  }
+
+  onMounted(() => {
+    handleDashboard(true);
+  });
+
+  defineExpose({ handleDashboard });
+</script>
+<template>
+  <Row :gutter="24" class="dashboard">
+    <Col
+      class="gutter-row mt-10px"
+      :md="6"
+      :xs="24"
+      v-for="(value, key) in dashBigScreenMap"
+      :key="key"
+    >
+      <StatisticCard
+        :statisticProps="value.staticstics"
+        :footerList="value.footer"
+        :loading="dashboardLoading"
+      />
+    </Col>
+  </Row>
+</template>
+<style lang="less"></style>
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 734fdfc2a..926f80877 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
@@ -232,10 +232,3 @@
     </TabPane>
   </Tabs>
 </template>
-<style lang="less">
-  .pom-card {
-    .ant-tabs-nav {
-      margin-bottom: 0 !important;
-    }
-  }
-</style>
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/PomTemplateTab.vue
 
b/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/PomTemplateTab.vue
index 3296d16e3..866149e04 100644
--- 
a/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/PomTemplateTab.vue
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/app/components/PodTemplate/PomTemplateTab.vue
@@ -268,7 +268,7 @@
   }
 </script>
 <template>
-  <Tabs type="card" v-model:activeKey="podTemplateTab">
+  <Tabs type="card" v-model:activeKey="podTemplateTab" class="pom-card">
     <TabPane key="pod-template" tab="Pod Template" forceRender>
       <TemplateButtonGroup
         visualType="ptVisual"
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTable.ts
 
b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTable.ts
new file mode 100644
index 000000000..7cebb7e2a
--- /dev/null
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTable.ts
@@ -0,0 +1,128 @@
+/*
+ * 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 { AppListRecord } from '/@/api/flink/app/app.type';
+import { ActionItem } from '/@/components/Table';
+
+export const useAppTable = () => {
+  /* Operation button */
+  function getTableActions(record: AppListRecord): ActionItem[] {
+    return [
+      {
+        tooltip: { title: 'Edit Application' },
+        auth: 'app:update',
+        icon: 'clarity:note-edit-line',
+        onClick: handleEdit.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Launch Application' },
+        ifShow: [-1, 1, 4].includes(record.launch) && record['optionState'] 
=== 0,
+        icon: 'ant-design:cloud-upload-outlined',
+        onClick: handleCheckLaunchApp.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Launching Progress Detail' },
+        ifShow: [-1, 2].includes(record.launch) || record['optionState'] === 1,
+        auth: 'app:update',
+        icon: 'ant-design:container-outlined',
+        onClick: openBuildProgressDetailDrawer.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Start Application' },
+        ifShow: handleIsStart(record, optionApps),
+        auth: 'app:start',
+        icon: 'ant-design:play-circle-outlined',
+        onClick: handleAppCheckStart.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Cancel Application' },
+        ifShow: record.state === 5 && record['optionState'] === 0,
+        auth: 'app:cancel',
+        icon: 'ant-design:pause-circle-outlined',
+        onClick: handleCancel.bind(null, record),
+      },
+      {
+        tooltip: { title: 'View Application Detail' },
+        auth: 'app:detail',
+        icon: 'ant-design:eye-outlined',
+        onClick: handleDetail.bind(null, record),
+      },
+      {
+        tooltip: { title: 'See Flink Start log' },
+        ifShow: [5, 6].includes(record.executionMode),
+        auth: 'app:detail',
+        icon: 'ant-design:sync-outlined',
+        onClick: handleSeeLog.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Forced Stop Application' },
+        ifShow: handleCanStop(record),
+        auth: 'app:cancel',
+        icon: 'ant-design:pause-circle-outlined',
+        onClick: handleForcedStop.bind(null, record),
+      },
+    ];
+  }
+  /* Click to edit */
+  function handleEdit(app: AppListRecord) {
+    flinkAppStore.setApplicationId(app.id);
+    if (app.appType === 1) {
+      // jobType( 1 custom code 2: flinkSQL)
+      router.push({ path: '/flink/app/edit_streampark', query: { appId: app.id 
} });
+    } else if (app.appType === 2) {
+      //Apache Flink
+      router.push({ path: '/flink/app/edit_flink' });
+    }
+  }
+  /* pull down button */
+  function getActionDropdown(record: AppListRecord): ActionItem[] {
+    return [
+      {
+        label: 'Copy Application',
+        auth: 'app:copy',
+        icon: 'ant-design:copy-outlined',
+        onClick: handleCopy.bind(null, record),
+      },
+      {
+        label: 'Remapping Application',
+        ifShow: [0, 7, 10, 11, 13].includes(record.state),
+        auth: 'app:mapping',
+        icon: 'ant-design:deployment-unit-outlined',
+        onClick: handleMapping.bind(null, record),
+      },
+      {
+        label: 'View FlameGraph',
+        ifShow: record.flameGraph,
+        auth: 'app:flameGraph',
+        icon: 'ant-design:fire-outlined',
+        onClick: handleFlameGraph.bind(null, record),
+      },
+      {
+        popConfirm: {
+          title: 'Are you sure delete this job ?',
+          confirm: handleDelete.bind(null, record),
+        },
+        label: 'Delete',
+        ifShow: [0, 7, 9, 10, 13, 18, 19].includes(record.state),
+        auth: 'app:delete',
+        icon: 'ant-design:delete-outlined',
+        color: 'error',
+      },
+    ];
+  }
+  return {};
+};
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
new file mode 100644
index 000000000..38ab6ba05
--- /dev/null
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/app/hooks/useAppTableAction.ts
@@ -0,0 +1,293 @@
+/*
+ * 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 { computed, onMounted, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import { handleIsStart } from '../utils';
+import { useFlinkAppStore } from '/@/store/modules/flinkApplication';
+import { useFlinkApplication } from './useApp';
+import { fetchAppRecord, fetchAppRemove } from '/@/api/flink/app/app';
+import { AppListRecord } from '/@/api/flink/app/app.type';
+import { fetchFlamegraph } from '/@/api/flink/app/metrics';
+import { ActionItem, FormProps } from '/@/components/Table';
+import { useMessage } from '/@/hooks/web/useMessage';
+export enum JobTypeEnum {
+  JAR = 1,
+  SQL = 2,
+}
+
+// Create form configurations and operation functions in the application table
+export const useAppTableAction = (
+  openStartModal: Fn,
+  openStopModal: Fn,
+  openLogModal: Fn,
+  openBuildDrawer: Fn,
+  reload: Fn,
+  optionApps: Recordable,
+) => {
+  const tagsOptions = ref<Recordable>([]);
+
+  const flinkAppStore = useFlinkAppStore();
+  const router = useRouter();
+  const { createMessage } = useMessage();
+
+  const {
+    handleCheckLaunchApp,
+    handleAppCheckStart,
+    handleCanStop,
+    handleForcedStop,
+    handleCopy,
+    handleMapping,
+    users,
+  } = useFlinkApplication(openStartModal);
+
+  /* Operation button */
+  function getTableActions(record: AppListRecord): ActionItem[] {
+    return [
+      {
+        tooltip: { title: 'Edit Application' },
+        auth: 'app:update',
+        icon: 'clarity:note-edit-line',
+        onClick: handleEdit.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Launch Application' },
+        ifShow: [-1, 1, 4].includes(record.launch) && record['optionState'] 
=== 0,
+        icon: 'ant-design:cloud-upload-outlined',
+        onClick: handleCheckLaunchApp.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Launching Progress Detail' },
+        ifShow: [-1, 2].includes(record.launch) || record['optionState'] === 1,
+        auth: 'app:update',
+        icon: 'ant-design:container-outlined',
+        onClick: () => openBuildDrawer(true, { appId: record.id }),
+      },
+      {
+        tooltip: { title: 'Start Application' },
+        ifShow: handleIsStart(record, optionApps),
+        auth: 'app:start',
+        icon: 'ant-design:play-circle-outlined',
+        onClick: handleAppCheckStart.bind(null, record),
+      },
+      {
+        tooltip: { title: 'Cancel Application' },
+        ifShow: record.state === 5 && record['optionState'] === 0,
+        auth: 'app:cancel',
+        icon: 'ant-design:pause-circle-outlined',
+        onClick: handleCancel.bind(null, record),
+      },
+      {
+        tooltip: { title: 'View Application Detail' },
+        auth: 'app:detail',
+        icon: 'ant-design:eye-outlined',
+        onClick: handleDetail.bind(null, record),
+      },
+      {
+        tooltip: { title: 'See Flink Start log' },
+        ifShow: [5, 6].includes(record.executionMode),
+        auth: 'app:detail',
+        icon: 'ant-design:sync-outlined',
+        onClick: () => openLogModal(true, { app: record }),
+      },
+      {
+        tooltip: { title: 'Forced Stop Application' },
+        ifShow: handleCanStop(record),
+        auth: 'app:cancel',
+        icon: 'ant-design:pause-circle-outlined',
+        onClick: handleForcedStop.bind(null, record),
+      },
+    ];
+  }
+  /* Click to edit */
+  function handleEdit(app: AppListRecord) {
+    flinkAppStore.setApplicationId(app.id);
+    if (app.appType === 1) {
+      // jobType( 1 custom code 2: flinkSQL)
+      router.push({ path: '/flink/app/edit_streampark', query: { appId: app.id 
} });
+    } else if (app.appType === 2) {
+      //Apache Flink
+      router.push({ path: '/flink/app/edit_flink' });
+    }
+  }
+
+  /* Click for details */
+  function handleDetail(app: AppListRecord) {
+    flinkAppStore.setApplicationId(app.id);
+    router.push({ path: '/flink/app/detail', query: { appId: app.id } });
+  }
+  // click stop application
+  function handleCancel(app: AppListRecord) {
+    if (!optionApps.stopping.get(app.id) || app['optionState'] === 0) {
+      openStopModal(true, { application: app });
+    }
+  }
+  /* pull down button */
+  function getActionDropdown(record: AppListRecord): ActionItem[] {
+    return [
+      {
+        label: 'Copy Application',
+        auth: 'app:copy',
+        icon: 'ant-design:copy-outlined',
+        onClick: handleCopy.bind(null, record),
+      },
+      {
+        label: 'Remapping Application',
+        ifShow: [0, 7, 10, 11, 13].includes(record.state),
+        auth: 'app:mapping',
+        icon: 'ant-design:deployment-unit-outlined',
+        onClick: handleMapping.bind(null, record),
+      },
+      {
+        label: 'View FlameGraph',
+        ifShow: record.flameGraph,
+        auth: 'app:flameGraph',
+        icon: 'ant-design:fire-outlined',
+        onClick: handleFlameGraph.bind(null, record),
+      },
+      {
+        popConfirm: {
+          title: 'Are you sure delete this job ?',
+          confirm: handleDelete.bind(null, record),
+        },
+        label: 'Delete',
+        ifShow: [0, 7, 9, 10, 13, 18, 19].includes(record.state),
+        auth: 'app:delete',
+        icon: 'ant-design:delete-outlined',
+        color: 'error',
+      },
+    ];
+  }
+
+  async function handleFlameGraph(app: AppListRecord) {
+    const hide = createMessage.loading('flameGraph generating...', 0);
+    try {
+      const { data } = await fetchFlamegraph({
+        appId: app.id,
+        width: document.documentElement.offsetWidth || 
document.body.offsetWidth,
+      });
+      if (data != null) {
+        const blob = new Blob([data], { type: 'image/svg+xml' });
+        const imageUrl = (window.URL || 
window.webkitURL).createObjectURL(blob);
+        window.open(imageUrl);
+      }
+    } catch (error) {
+      console.error(error);
+      createMessage.error('flameGraph generate failed');
+    } finally {
+      hide();
+    }
+  }
+
+  /* Click to delete */
+  async function handleDelete(app: AppListRecord) {
+    const hide = createMessage.loading('deleting', 0);
+    try {
+      await fetchAppRemove(app.id);
+      createMessage.success('delete successful');
+      reload();
+    } catch (error) {
+      console.error(error);
+    } finally {
+      hide();
+    }
+  }
+
+  const formConfig = computed((): Partial<FormProps> => {
+    return {
+      baseColProps: { span: 5, style: { paddingRight: '20px' } },
+      actionColOptions: { span: 4 },
+      showSubmitButton: false,
+      colon: true,
+      resetButtonOptions: {
+        text: 'Add New',
+        color: 'primary',
+        preIcon: 'ant-design:plus-outlined',
+      },
+      async resetFunc() {
+        router.push({ path: '/flink/app/add' });
+      },
+      schemas: [
+        {
+          label: 'Tags',
+          field: 'tag',
+          component: 'Select',
+          componentProps: {
+            placeholder: 'Tags',
+            showSearch: true,
+            options: tagsOptions.value.map((t: Recordable) => ({ label: t, 
value: t })),
+          },
+        },
+        {
+          label: 'Owner',
+          field: 'userId',
+          component: 'Select',
+          componentProps: {
+            placeholder: 'Owner',
+            showSearch: true,
+            options: users.value.map((u: Recordable) => {
+              return { label: u.nickName || u.username, value: u.userId };
+            }),
+          },
+        },
+        {
+          label: 'Type',
+          field: 'jobType',
+          component: 'Select',
+          componentProps: {
+            placeholder: 'Type',
+            showSearch: true,
+            options: [
+              { label: 'JAR', value: JobTypeEnum.JAR },
+              { label: 'SQL', value: JobTypeEnum.SQL },
+            ],
+          },
+        },
+        {
+          label: 'keyWords',
+          field: 'searchText',
+          component: 'InputSearch',
+          componentProps: {
+            placeholder: 'Search',
+            onSearch: () => reload({ polling: true }),
+          },
+        },
+      ],
+    };
+  });
+
+  /*  tag */
+  function handleInitTagsOptions() {
+    const params = Object.assign({}, { pageSize: 999999999, pageNum: 1 });
+    fetchAppRecord(params).then((res) => {
+      const dataSource = res?.records || [];
+      dataSource.forEach((record) => {
+        if (record.tags !== null && record.tags !== undefined && record.tags 
!== '') {
+          const tagsArray = record.tags.split(',') as string[];
+          tagsArray.forEach((x: string) => {
+            if (x.length > 0 && tagsOptions.value.indexOf(x) == -1) {
+              tagsOptions.value.push(x);
+            }
+          });
+        }
+      });
+    });
+  }
+  onMounted(() => {
+    handleInitTagsOptions();
+  });
+  return { getTableActions, getActionDropdown, formConfig };
+};
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 985589e60..df09985fc 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
@@ -296,3 +296,9 @@
   transform: translateX(10px);
   opacity: 0;
 }
+
+.pom-card {
+  .ant-tabs-nav {
+    margin-bottom: 0 !important;
+  }
+}
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/notebook/Submit.less
 
b/streampark-console/streampark-console-newui/src/views/flink/notebook/Submit.less
index 304e614ac..7f8584053 100644
--- 
a/streampark-console/streampark-console-newui/src/views/flink/notebook/Submit.less
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/notebook/Submit.less
@@ -14,6 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+[data-theme="dark"] {
+  .nodebook-submit {
+    .current-line,
+    .current-line-both {
+      border: 1px solid hsl(205deg 100% 74% / 20%) !important;
+      background-color: hsl(205deg 100% 74% / 20%);
+    }
+  }
+}
+
 .nodebook-submit {
   .code-box {
     height: 600px;
diff --git 
a/streampark-console/streampark-console-newui/src/views/flink/setting/components/AlertModal.vue
 
b/streampark-console/streampark-console-newui/src/views/flink/setting/components/AlertModal.vue
index caece4d27..fa405f5bd 100644
--- 
a/streampark-console/streampark-console-newui/src/views/flink/setting/components/AlertModal.vue
+++ 
b/streampark-console/streampark-console-newui/src/views/flink/setting/components/AlertModal.vue
@@ -205,7 +205,7 @@
       <SvgIcon name="alarm" size="25" />
       Alert Setting
     </template>
-    <BasicForm @register="registerForm">
+    <BasicForm @register="registerForm" class="!mt-15px">
       <template #type="{ model, field }">
         <Select
           v-model:value="model[field]"

Reply via email to