You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@linkis.apache.org by pe...@apache.org on 2024/09/09 09:47:52 UTC

(linkis) branch master updated: [linkis-web-next]: Modules refactoring & Bug fixing (#5151)

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

peacewong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/linkis.git


The following commit(s) were added to refs/heads/master by this push:
     new 09f5f22f9d [linkis-web-next]: Modules refactoring & Bug fixing (#5151)
09f5f22f9d is described below

commit 09f5f22f9db666f4574bc66db448b5b1733f014d
Author: Linhao-Qian <17...@163.com>
AuthorDate: Mon Sep 9 02:47:47 2024 -0700

    [linkis-web-next]: Modules refactoring & Bug fixing (#5151)
    
    * [linkis-web-next]: globalHistoryManagement部分逻辑与样式完善
    
    * GlobalHistoryManagement and SideBar modules refactoring & previous bug fixing
    
    * [Linkis-web-next]: parameterConfig Module Refactor
    
    * [Linkis-web-next]: Add code comments to ParameterConfig Module
    
    * [linkis-web-next]: Refactored Navbar & Login Page
    
    * [linkis-web-next]: 1. TaskResults & TaskDetails Refactoring; 2. Code Improvement & Bug Fixing;
    
    * [linkis-web-next]: Refactored GlobalVariables Module
    
    * [linkis-web-next]: Add license to newly created file
    
    * [linkis-web-next]: Microservice Management Module Refactoring
    
    * [linkis-web-next]: Resource Management Module Refactoring
    
    * [linkis-web-next]: ECM Management Module Refactoring
---
 linkis-web-next/public/log/noLog.svg               |   1 -
 .../sidebar/{dateReport.svg => globalVariable.svg} |   0
 linkis-web-next/src/components/sidebar/index.vue   |  10 +-
 linkis-web-next/src/locales/en.ts                  |  22 +-
 linkis-web-next/src/locales/zh.ts                  |  22 +-
 linkis-web-next/src/pages/ECMManagement/index.vue  | 288 +++++++++++++++++++++
 linkis-web-next/src/pages/ECMManagement/modal.vue  | 173 +++++++++++++
 .../pages/globalHistoryManagement/drawer/index.vue |  33 ++-
 .../globalHistoryManagement/drawer/taskDetails.vue | 253 ++++++++++++++++++
 .../globalHistoryManagement/drawer/taskLogs.vue    | 154 +++++------
 .../globalHistoryManagement/drawer/taskResults.vue | 184 +++++++++++++
 .../src/pages/globalHistoryManagement/filter.vue   |  46 ++--
 .../src/pages/globalHistoryManagement/index.vue    |   1 -
 .../src/pages/globalVariables/index.vue            | 149 +++++++++++
 .../src/pages/microServiceManagement/index.vue     | 208 +++++++++++++++
 .../src/pages/microServiceManagement/modal.vue     | 149 +++++++++++
 linkis-web-next/src/pages/parameterConfig/card.vue |  11 +-
 .../src/pages/parameterConfig/drawer.vue           |  84 +++---
 .../src/pages/parameterConfig/index.vue            |  16 +-
 .../src/pages/parameterConfig/modal.vue            | 120 +++++----
 .../history}/drawer/index.vue                      |  44 +---
 .../src/pages/resource/history/drawer/log.vue      | 253 ++++++++++++++++++
 .../src/pages/resource/history/index.less          |  17 --
 .../src/pages/resource/history/index.vue           | 237 ++++++++++++++++-
 linkis-web-next/src/pages/resource/index.less      |  17 --
 linkis-web-next/src/pages/resource/index.vue       |  22 +-
 .../src/pages/resource/manage/index.less           |  17 --
 .../src/pages/resource/manage/index.vue            | 237 ++++++++++++++++-
 linkis-web-next/src/router/index.ts                |  41 ++-
 29 files changed, 2479 insertions(+), 330 deletions(-)

diff --git a/linkis-web-next/public/log/noLog.svg b/linkis-web-next/public/log/noLog.svg
index bfa8899949..2c58b8f333 100644
--- a/linkis-web-next/public/log/noLog.svg
+++ b/linkis-web-next/public/log/noLog.svg
@@ -16,7 +16,6 @@
  * limitations under the License.
 -->
 
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="366px" height="166px" viewBox="0 0 366 166" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>资源 10@2x</title>
     <g id="任务概览0728" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
diff --git a/linkis-web-next/public/sidebar/dateReport.svg b/linkis-web-next/public/sidebar/globalVariable.svg
similarity index 100%
rename from linkis-web-next/public/sidebar/dateReport.svg
rename to linkis-web-next/public/sidebar/globalVariable.svg
diff --git a/linkis-web-next/src/components/sidebar/index.vue b/linkis-web-next/src/components/sidebar/index.vue
index ffccf5080f..763f78625a 100644
--- a/linkis-web-next/src/components/sidebar/index.vue
+++ b/linkis-web-next/src/components/sidebar/index.vue
@@ -97,19 +97,19 @@ const menuItemsConfig = [
         path: '/console/parameterConfig',
     },
     {
-        title: 'message.linkis.sideNavList.function.children.dateReport',
-        icon: '/sidebar/dateReport.svg',
-        path: '/x',
+        title: 'message.linkis.sideNavList.function.children.globalVariable',
+        icon: '/sidebar/globalVariable.svg',
+        path: '/console/globalVariables',
     },
     {
         title: 'message.linkis.sideNavList.function.children.ECMManage',
         icon: '/sidebar/ECMManage.svg',
-        path: '/xx',
+        path: '/console/ECMManagement',
     },
     {
         title: 'message.linkis.sideNavList.function.children.microserviceManage',
         icon: '/sidebar/microserviceManage.svg',
-        path: '/xxx',
+        path: '/console/microServiceManagement',
     },
     {
         title: 'message.linkis.sideNavList.function.children.dataSourceManage',
diff --git a/linkis-web-next/src/locales/en.ts b/linkis-web-next/src/locales/en.ts
index 1d10100c49..94b6df48e7 100644
--- a/linkis-web-next/src/locales/en.ts
+++ b/linkis-web-next/src/locales/en.ts
@@ -52,11 +52,11 @@ export default {
             exceptionTips:
                 'Background interface exception, please contact development processing!',
             separator: {
-                comma: 'Comma(,)',
-                tab: 'Tab(\\t)',
-                semicolon: 'Semicolon(;)',
-                space: 'Space()',
-                vertical: 'Vertical bar(|)',
+                comma: 'Comma',
+                tab: 'Tab',
+                semicolon: 'Semicolon',
+                space: 'Space',
+                vertical: 'Vertical bar',
             },
             tabs: {
                 progress: 'Progress',
@@ -358,6 +358,7 @@ export default {
                 resultGroupLineFilter: 'Result Group Line Filter',
                 resultGroup: 'Result Group',
                 all: 'all',
+                allDownLoad: 'All Result Sets',
                 autoFormat: 'Auto Format',
                 downloadMode: 'Download Mode :',
                 deepAnalysis: 'Deep Analysis',
@@ -440,6 +441,9 @@ export default {
             ConfirmEdit: 'Done',
             serviceRegistryCenter: 'Service Registry Center',
             addTags: 'Add Tags',
+            labelEmptyError: 'Label cannot be empty',
+            labelNoSpecialSymbol: 'Spance and Chinese characters are not allowed',
+            labelLengthLimit: 'Label length should not exceed 16',
             unfold: 'Unfold',
             fold: 'Fold',
             jumpPage: 'Please check in the jump page...',
@@ -512,6 +516,8 @@ export default {
             tenant: 'Tenant',
             inputTenant: 'Please Input Tenant',
             globalSettings: 'GlobalSettings',
+            downloadLogs: 'Download Logs',
+            downloadLogsConfirm: 'Are you sure to download logs?',
             resultSet: {
                 prefixText:
                     'Because your result set is large, for a better experience, ',
@@ -544,6 +550,7 @@ export default {
                 executionCode: 'Execution Code',
                 status: 'Status',
                 label: 'Label',
+                editLabel: 'Edit Label',
                 engineVersion: 'Engine Version',
                 engineVersionCannotBeNull: 'Engine Version Cannot Be Null',
                 addApplicationRules:
@@ -592,8 +599,7 @@ export default {
                         resource: 'Resource Management',
                         resourceEngineConnList: 'History Engine',
                         setting: 'Settings',
-                        dateReport: 'Global Variables',
-                        globalValiable: 'Frequently Asked',
+                        globalVariable: 'Global Variables',
                         microserviceManage: 'Microservice Management',
                         ECMManage: 'ECM Management',
                         udfFunctionTitle: 'UDF Function',
@@ -738,7 +744,7 @@ export default {
                 advancedSetting: 'Advanced Settings',
                 dataDev: 'Data Development',
             },
-            globalValiable: 'Global Variables',
+            globalVariable: 'Global Variables',
             rules: {
                 first: {
                     required: 'The key of variable {text} is empty',
diff --git a/linkis-web-next/src/locales/zh.ts b/linkis-web-next/src/locales/zh.ts
index 81f568e6bd..8b741b6967 100644
--- a/linkis-web-next/src/locales/zh.ts
+++ b/linkis-web-next/src/locales/zh.ts
@@ -51,11 +51,11 @@ export default {
             feedback: '反馈',
             exceptionTips: '后台接口异常,请联系开发处理!',
             separator: {
-                comma: '逗号(,)',
-                tab: 'tab键(\\t)',
-                semicolon: '分号(;)',
-                space: '空格()',
-                vertical: '竖线(|)',
+                comma: '逗号',
+                tab: 'tab键',
+                semicolon: '分号',
+                space: '空格',
+                vertical: '竖线',
             },
             tabs: {
                 progress: '进度',
@@ -339,6 +339,7 @@ export default {
                 resultGroupLineFilter: '对结果集的列进行筛选',
                 resultGroup: '结果集',
                 all: '是否全量',
+                allDownLoad: '所有结果集',
                 autoFormat: '自动格式化',
                 downloadMode: '下载方式:',
                 deepAnalysis: '分 析 进 阶',
@@ -418,6 +419,9 @@ export default {
             description: '描述',
             noDescription: '暂无描述',
             addTags: '添加标签',
+            labelEmptyError: '标签不能为空',
+            labelNoSpecialSymbol: '不得存在空格和中文',
+            labelLengthLimit: '标签内容长度不超过16',
             find: '查询',
             initiator: '创建者',
             inputOwnerHint: '请输入创建者',
@@ -489,6 +493,8 @@ export default {
             tenant: '租户标签',
             inputTenant: '请输入租户标签',
             globalSettings: '全局配置',
+            downloadLogs: '下载日志',
+            downloadLogsConfirm: '确认要下载日志吗?',
             resultSet: {
                 prefixText: '因为您的结果集较大,为了更好的体验,',
                 linkText: '点击查看结果集',
@@ -519,6 +525,7 @@ export default {
                 status: '状态',
                 costTime: '已耗时',
                 label: '标签',
+                editLabel: '编辑标签',
                 engineVersion: '引擎版本',
                 engineVersionCannotBeNull: '引擎版本不能为空',
                 addApplicationRules: '应用名称不能有特殊符号和空格',
@@ -564,8 +571,7 @@ export default {
                         resource: '资源管理',
                         resourceEngineConnList: '历史引擎',
                         setting: '参数配置',
-                        dateReport: '全局变量',
-                        globalValiable: '常见问题',
+                        globalVariable: '全局变量',
                         ECMManage: 'ECM管理',
                         microserviceManage: '微服务管理',
                         udfFunctionTitle: 'UDF函数',
@@ -708,7 +714,7 @@ export default {
                 advancedSetting: '高级设置',
                 dataDev: '数据开发',
             },
-            globalValiable: '全局变量',
+            globalVariable: '全局变量',
             rules: {
                 first: {
                     required: '变量 {text} 的key为空',
diff --git a/linkis-web-next/src/pages/ECMManagement/index.vue b/linkis-web-next/src/pages/ECMManagement/index.vue
new file mode 100644
index 0000000000..71b045395e
--- /dev/null
+++ b/linkis-web-next/src/pages/ECMManagement/index.vue
@@ -0,0 +1,288 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+    <FForm layout="inline" :span="4">
+        <FFormItem
+            :label="t('message.linkis.tableColumns.instanceName')"
+            :props="t('message.linkis.tableColumns.instanceName')"
+        >
+            <FInput
+                v-model="instance"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.formItems.status.label')"
+            :props="t('message.linkis.formItems.status.label')"
+        >
+            <FSelect
+                v-model="nodeHealthy"
+                clearable
+                :placeholder="t('message.linkis.unselect')"
+            >
+                <FOption
+                    v-for="(item, index) in nodeHealthyList"
+                    :key="index"
+                    :value="item"
+                    :label="item"
+                />
+            </FSelect>
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.initiator')"
+            :props="t('message.linkis.initiator')"
+        >
+            <FSelect
+                v-model="owner"
+                clearable
+                :placeholder="t('message.linkis.unselect')"
+            >
+                <FOption
+                    v-for="(item, index) in ownerList"
+                    :key="index"
+                    :value="item"
+                    :label="item"
+                />
+            </FSelect>
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.tenant')"
+            :props="t('message.linkis.tenant')"
+        >
+            <FInput
+                v-model="tenantLabel"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem>
+            <div class="btns">
+                <FButton type="primary" @click="search">
+                    {{ t('message.linkis.find') }}
+                </FButton>
+            </div>
+        </FFormItem>
+    </FForm>
+    <f-table
+        :data="dataList"
+        :rowKey="(row: Record<string, number | string>) => row.instance"
+        :emptyText="t('message.linkis.noDataText')"
+    >
+        <template v-for="col in tableColumns" :key="col.label">
+            <f-table-column
+                v-if="col.formatter"
+                :prop="col.prop"
+                :label="col.label"
+                :formatter="col.formatter"
+                :width="col.width"
+            />
+            <f-table-column
+                v-else
+                :prop="col.prop"
+                :label="col.label"
+                :width="col.width"
+            />
+        </template>
+        <f-table-column
+            :label="`\u00A0\u00A0\u00A0\u00A0${t('message.linkis.columns.control.title')}`"
+            :action="[{
+                label: t('message.linkis.tagEdit'),
+                func: handleEdit,
+            }].concat(isHistoryAdmin && isLogAdmin ? [{
+                label: t('message.linkis.killAll'),
+                func: handleKillAll,
+            }] : [])"
+            :width="150"
+        />
+    </f-table>
+    <FPagination
+        class="pagination"
+        show-size-changer
+        show-total
+        :total-count="tableData?.length || 0"
+        v-model:currentPage="currentPage"
+        @change="handleChangePagination"
+        :pageSizeOption="[10, 20, 30, 50, 100]"
+        v-model:pageSize="pageSize"
+    />
+    <Modal ref="modalRef" :instance="currentIns" :handleInitialization="handleInitialization" :nodeHealthyList="nodeHealthyList" />
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { FButton, FMessage, FModal, FSpace, FTag } from '@fesjs/fes-design';
+import dayjs from 'dayjs';
+import { onMounted, reactive, ref, h } from 'vue';
+import Modal from './modal.vue';
+import { useI18n } from 'vue-i18n';
+import { useRouter } from 'vue-router';
+
+const { t } = useI18n();
+const router = useRouter();
+
+const isLogAdmin = ref(false);
+const isHistoryAdmin = ref(false);
+const instance = ref('');
+const nodeHealthy = ref('');
+const nodeHealthyList = ref();
+const owner = ref('');
+const ownerList = reactive<string[]>([]);
+const tenantLabel = ref('');
+const dataList = ref();
+const tableData = ref();
+const currentPage = ref(1);
+const pageSize = ref(10);
+const modalRef = ref();
+const currentIns = ref();
+
+const handleEdit = (instance: any) => {
+    currentIns.value = instance;
+    modalRef.value?.open();
+}
+
+const handleKillAll = (instance: any) => {
+    FModal.confirm({
+        title: t('message.linkis.killAll'),
+        content: t('message.linkis.tipForKill', {instance: instance.instance}),
+        onOk: async () => {
+            try {
+                const res = await api.fetch('/linkisManager/rm/killUnlockEngineByEM', { instance: instance.instance }, 'post');
+                const { killEngineNum, memory, cores } = res.result;
+                // 传回的是Byte
+                FMessage.success(t('message.linkis.killFinishedInfo', { killEngineNum, memory: memory / 1024 / 1024 / 1024, cores }))
+            } catch (err) {
+                window.console.error(err);
+            }
+        }
+    })
+}
+
+// const handleJump = (instance: any) => {
+//     router.push(`/console/ECMManagement/engines/${instance}`)
+// }
+
+const tableColumns = [
+    {
+        prop: 'instance',
+        label: t('message.linkis.tableColumns.instanceName'),
+        // TODO: The ECM Engine Page
+        // formatter: (data: { row: { instance: string }}) =>
+        //     h(FButton, { type: 'link', onClick: () => { handleJump(data.row.instance) }, style: { padding: 0, margin: 0 }}, data.row.instance),
+        width: 120,
+    },
+    {
+        prop: 'nodeHealthy',
+        label: t('message.linkis.tableColumns.status'),
+        width: 100,
+    },
+    {
+        prop: 'labels',
+        label: t('message.linkis.tableColumns.label'),
+        formatter: (data: { row: { labels: { stringValue: string }[] }}) => 
+            h(FSpace, data.row.labels.map(label => h(FTag, { type: 'default', effect: 'plain', size: 'small' }, label.stringValue))),
+        width: 270,
+    },
+    {
+        prop: 'usedResource',
+        label: t('message.linkis.tableColumns.usedResources'),
+        formatter: ({ row }: { row: { usedResource: { cores: number, memory: number, instances: number }}}) =>
+            `${row.usedResource.cores}cores,${row.usedResource.memory / 1024 / 1024 / 1024}G,${row.usedResource.instances}apps`,
+    },
+    {
+        prop: 'maxResource',
+        label: t('message.linkis.tableColumns.maximumAvailableResources'),
+        formatter: ({ row }: { row: { maxResource: { cores: number, memory: number, instances: number }}}) =>
+            `${row.maxResource.cores}cores,${row.maxResource.memory / 1024 / 1024 / 1024}G,${row.maxResource.instances}apps`,
+        width: 120,
+    },
+    {
+        prop: 'lockedResource',
+        label: t('message.linkis.tableColumns.lockedResource'),
+        formatter: ({ row }: { row: { lockedResource: { cores: number, memory: number, instances: number }}}) =>
+            `${row.lockedResource.cores}cores,${row.lockedResource.memory / 1024 / 1024 / 1024}G,${row.lockedResource.instances}apps`,
+    },
+    {
+        prop: 'owner',
+        label: t('message.linkis.tableColumns.initiator'),
+    },
+    {
+        prop: 'startTime',
+        label: t('message.linkis.tableColumns.startTime'),
+        formatter: ({ row }: { row: { startTime: number } }) => dayjs(row.startTime).format('YYYY-MM-DD HH:mm:ss'),
+    },
+];
+
+const search = () => {
+    currentPage.value = 1;
+    api.fetch("/linkisManager/listAllEMs", {
+        instance: instance.value.replace(/ /g, ''),
+        nodeHealthy: nodeHealthy.value,
+        owner: owner.value,
+        tenantLabel: tenantLabel.value,
+    }, "get").then((res: any) => {
+        tableData.value = [...res.EMs] || [];
+        handleChangePagination(1, pageSize.value);
+    })
+}
+
+const handleChangePagination = (currentPage: number, pageSize: number) => {
+    const temp = tableData.value.slice(
+        (currentPage - 1) * pageSize,
+        currentPage * pageSize,
+    );
+    dataList.value = [...temp] as any;
+};
+
+const handleInitialization = () => {
+    currentPage.value = 1;
+    ownerList.length = 0;
+    api.fetch('/linkisManager/listAllECMHealthyStatus', { onlyEditable: true }, 'get').then((res: any) => {
+        nodeHealthyList.value = res.nodeHealthy;
+    })
+    api.fetch("/linkisManager/listAllEMs", "get").then((res: any) => {
+        tableData.value = [...res.EMs] || [];
+        handleChangePagination(1, pageSize.value);
+        res.EMs.forEach((item: any) => {
+            if (ownerList.indexOf(item.owner) === -1) {
+                ownerList.push(item.owner);
+            }
+        })
+    })
+}
+
+onMounted(() => {
+    // Get whether it is a historical administrator(获取是否是历史管理员权限)
+    api.fetch('/jobhistory/governanceStationAdmin', 'get').then(
+        (res: { admin: boolean, historyAdmin: boolean }) => {
+        isLogAdmin.value = res.admin;
+        isHistoryAdmin.value = res.historyAdmin;
+    });
+    handleInitialization();
+})
+</script>
+
+<style lang="less" scoped>
+.btns {
+    display: flex;
+    gap: 12px;
+}
+.pagination {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 16px;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/ECMManagement/modal.vue b/linkis-web-next/src/pages/ECMManagement/modal.vue
new file mode 100644
index 0000000000..4b50443928
--- /dev/null
+++ b/linkis-web-next/src/pages/ECMManagement/modal.vue
@@ -0,0 +1,173 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+  <template>
+    <FModal
+        v-model:show="show"
+        displayDirective="if"
+        @ok="handleConfirm"
+    >
+        <template #title>
+            <div class="modal-title">{{ instance.instance }}</div>
+        </template>
+        <FForm>
+            <FFormItem
+                v-for="(label, index) in labels"
+                :key="index"
+                :label="t('message.linkis.tableColumns.label') + (index + 1)"
+                :rules="[{
+                    trigger: ['change', 'blur'],
+                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                    // @ts-ignore
+                    validator: (rule, value, callback) => {
+                        if (!label.labelKey || !label.stringValue) {
+                            callback(t('message.linkis.labelEmptyError'))
+                        }
+                        if (label.stringValue.length >= 16) {
+                            callback(t('message.linkis.labelLengthLimit'))
+                        }
+                        if (!new RegExp(/^[^\s\u4e00-\u9fa5]*$/).test(label.stringValue)) {
+                            callback(t('message.linkis.labelNoSpecialSymbol'))
+                        }
+                    }
+                }]"
+            >
+                <FSelect
+                    v-model="label.labelKey"
+                    clearable
+                    :placeholder="t('message.linkis.unselect')"
+                >
+                    <FOption
+                        v-for="(item, index) in keyList"
+                        :key="index"
+                        :value="item"
+                        :label="item"
+                    />
+                </FSelect>
+                &nbsp;&nbsp;&nbsp;:&nbsp;&nbsp;&nbsp;
+                <FInput
+                    v-model:modelValue="label.stringValue"
+                    :placeholder="t('message.linkis.datasource.pleaseInput')"
+                />
+                &nbsp;&nbsp;&nbsp;
+                <FButton @click="() => { handleRemove(index) }" type="primary">
+                    {{ t('message.linkis.remove') }}
+                </FButton>
+            </FFormItem>
+        </FForm>
+        <FButton type="info" long @click="handleAdd">{{ t('message.linkis.addTags') }}</FButton>
+        &nbsp;
+        <FForm>
+            <FFormItem
+                :label="t('message.linkis.formItems.status.label')"
+                :props="t('message.linkis.formItems.status.label')"
+            >
+                <FSelect
+                    v-model="instance.nodeHealthy"
+                    clearable
+                    :placeholder="t('message.linkis.unselect')"
+                >
+                    <FOption
+                        v-for="(item, index) in nodeHealthyList"
+                        :key="index"
+                        :value="item"
+                        :label="item"
+                    />
+                </FSelect>
+            </FFormItem>
+        </FForm>
+    </FModal>
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { FMessage } from '@fesjs/fes-design';
+import { onMounted, reactive, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+const { t } = useI18n();
+
+const props = defineProps<{
+    instance: {
+        instance: string,
+        applicationName: string,
+        labels: {
+            labelKey: string,
+            stringValue: string,
+        }[],
+        nodeHealthy: string,
+    },
+    handleInitialization: Function,
+    nodeHealthyList: string[],
+}>();
+const show = ref(false);
+const labels = reactive<{ labelKey: string, stringValue: string }[]>([]);
+const keyList = ref();
+
+const handleAdd = () => {
+    labels.push({
+        labelKey: '',
+        stringValue: '',
+    })
+}
+
+const handleRemove = (index: number) => {
+    labels.splice(index, 1);
+}
+
+const handleConfirm = async () => {
+    try {
+        await api.fetch("/linkisManager/modifyEMInfo", {
+            instance: props.instance.instance,
+            applicationName: props.instance.applicationName,
+            labels,
+            emStatus: props.instance.nodeHealthy,
+        }, "put");
+        FMessage.success(t("message.linkis.editedSuccess"));
+        show.value = false;
+        props.handleInitialization();
+    } catch (error) {
+        window.console.error(error);
+    }
+}
+
+const open = () => {
+    show.value = true;
+};
+
+watch(() => props.instance, () => {
+    labels.length = 0;
+    props.instance.labels.forEach((label: any) => {
+        labels.push(label);
+    })
+})
+
+onMounted(() => {
+    api.fetch("/microservice/modifiableLabelKey", "get").then((res: any) => {
+        keyList.value = res.keyList || [];
+    });
+})
+
+defineExpose({
+    open,
+});
+</script>
+
+<style lang="less" scoped>
+.modal-title {
+    font-weight: bold;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue b/linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue
index ef49880ce0..bb62578772 100644
--- a/linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue
+++ b/linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue
@@ -44,18 +44,15 @@
                 </div>
                 <FTabs @change="onDrawerTabChange" class="drawer-tabs" v-model="activeDrawerTabPane">
                     <template v-for="tab in tabs" :key="tab.name">
-                        <FTabPane
-                            :name="t(tab.name)"
-                            :value="tab.value"
-                        />
+                        <FTabPane :name="t(tab.name)" :value="tab.value" />
                     </template>
                 </FTabs>
             </div>
         </template>
         <template #default>
             <task-logs v-if="activeDrawerTabPane === 1" :task="task" :isFullScreen="isFullScreen" />
-            <div v-if="activeDrawerTabPane === 2"></div>
-            <div v-if="activeDrawerTabPane === 3"></div>
+            <task-results v-if="activeDrawerTabPane === 2" :task="task" :isFullScreen="isFullScreen" />
+            <task-details v-if="activeDrawerTabPane === 3" :task="task" :params="params" :isFullScreen="isFullScreen" />
         </template>
     </FDrawer>
 </template>
@@ -64,6 +61,8 @@
 import { ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 import TaskLogs from './taskLogs.vue';
+import TaskDetails from './taskDetails.vue';
+import TaskResults from './taskResults.vue';
 import api from '@/service/api';
 
 const { t } = useI18n();
@@ -84,15 +83,33 @@ const activeDrawerTabPane = ref(TAB_PANE_VALUE.TASK_LOG);
 const show = ref(false);
 const tableRawData = ref<Record<string, string | number>>();
 const drawerHeight = ref(520);
-const drawerRef = ref<any>();
-const task = ref<any>();
+const drawerRef = ref();
+const task = ref();
+const params = ref();
 const isFullScreen = ref(false);
 
+const formatDate = (date: Date) => {
+    const year = date.getFullYear();
+    const month = (date.getMonth() + 1).toString().padStart(2, '0');
+    const day = date.getDate().toString().padStart(2, '0');
+    const hours = date.getHours().toString().padStart(2, '0');
+    const minutes = date.getMinutes().toString().padStart(2, '0');
+    const seconds = date.getSeconds().toString().padStart(2, '0');
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
 const open = async (rawData: Record<string, string | number>) => {
     tableRawData.value = rawData;
     try {
         const jobhistory = await api.fetch(`/jobhistory/${tableRawData.value.taskID}/get`, 'get')
         task.value = jobhistory.task;
+        let url = '/linkisManager/ecinfo/ecrHistoryList?';
+        const endDate = new Date(); 
+        const startDate = new Date();
+        startDate.setMonth(startDate.getMonth() - 3);
+        url += `instance=${task.value.engineInstance}&startDate=${formatDate(startDate)}&endDate=${formatDate(endDate)}`;
+        const rst = await api.fetch(url,'get');
+        params.value = rst.engineList[0];
     } catch (errorMsg) {
         window.console.error(errorMsg)
     }
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskDetails.vue b/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskDetails.vue
new file mode 100644
index 0000000000..88e5ce408d
--- /dev/null
+++ b/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskDetails.vue
@@ -0,0 +1,253 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+  <template>
+    <div class="sub-tabs">
+        <FInput
+            class="search-input"
+            :placeholder="$t('message.linkis.search')"
+            v-model="searchText"
+            @change="handleSearch"
+        />
+        <div class="btn-group">
+            <FButton class="btn" :disabled="prevDisabled" @click="handleClickPrevBtn" type="primary">{{ t('message.linkis.previousPage') }}</FButton>
+            <FButton class="btn" :disabled="nextDisabled" @click="handleClickNextBtn" type="primary">{{ t('message.linkis.nextPage') }}</FButton>
+        </div>
+        <FButton class="download-btn" type="primary" @click="handleDownloadLogs">{{ t('message.linkis.downloadLogs') }}</FButton>
+        <FTabs @change="onTaskDetailTabChange" class="task-log-tabs" type="card" v-model="activeTaskLogTabPane">
+            <FTabPane name="stdout" value="stdout" />
+            <FTabPane name="stderr" value="stderr" />
+            <FTabPane name="gc" value="gc" />
+            <FTabPane v-if="['hive', 'spark'].includes(task.engineType)" name="yarnApp" value="yarnApp" />
+        </FTabs>
+        <WeEditor
+            :model-value="formattedLogs"
+            :height="props.isFullScreen ? '85vh' : '420px'"
+            v-if="formattedLogs !== ''"
+        />
+        <div v-else :style="{ height: props.isFullScreen ? '85vh' : '420px' }" class="no-log">
+            <img src="/log/noLog.svg" />
+            <div>{{ $t('message.linkis.noLog') }}</div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { forEach, filter } from 'lodash';
+import { onMounted, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import WeEditor from '@/components/editor/index.vue';
+import api from '@/service/api';
+import { FModal } from '@fesjs/fes-design';
+
+const { t } = useI18n();
+
+const props = defineProps<{
+    task: any,
+    params: any
+    isFullScreen: boolean,
+}>();
+
+const activeTaskLogTabPane = ref('stdout');
+const logs = ref<any>({});
+const formattedLogs = ref('');
+const fromLine = ref(0);
+const pageNow = ref(0);
+const searchText = ref('');
+const prevDisabled = ref(false);
+const nextDisabled = ref(true);
+const logPath = ref('');
+
+const onTaskDetailTabChange = (value: string) => {
+    activeTaskLogTabPane.value = value;
+    fromLine.value = 0;
+    pageNow.value = 0;
+    nextDisabled.value = true;
+    handleInitiation(fromLine.value);
+}
+
+const handleClickPrevBtn = () => {
+    pageNow.value = pageNow.value + 1;
+    fromLine.value = pageNow.value * 1000;
+    nextDisabled.value = false;
+    handleInitiation(fromLine.value);
+}
+
+const handleClickNextBtn = () => {
+    pageNow.value = pageNow.value - 1;
+    fromLine.value = pageNow.value * 1000;
+    if (pageNow.value === 0) {
+        nextDisabled.value = true;
+    }
+    handleInitiation(fromLine.value);
+}
+
+const handleDownloadLogs = () => {
+    FModal.confirm({
+        title: t('message.linkis.tip'),
+        content: t('message.linkis.downloadLogsConfirm'),
+        closable: true,
+        okText: t('message.linkis.submit'),
+        cancelText: t('message.linkis.cancel'),
+        onOk() {
+            const url = `/api/rest_j/v1/engineconnManager/downloadEngineLog?emInstance=${props.params.ecmInstance}&instance=${props.task.engineInstance}&logDirSuffix=${encodeURIComponent(logPath.value)}&logType=${activeTaskLogTabPane.value}`;
+            const downloadLink = document.createElement('a');
+            downloadLink.href = url;
+            downloadLink.download = 'log';
+            downloadLink.style.display = 'none';
+            document.body.appendChild(downloadLink);
+            downloadLink.click();
+            document.body.removeChild(downloadLink);
+        },
+    });
+}
+
+const getSearchList = (log:string) => {
+    let MatchText = '';
+    const val = searchText.value;
+    if (!log) return MatchText;
+    if (val) {
+        // This part of the code is to make the regular expression not report an error, so add \ in front of the special symbol(这部分代码是为了让正则表达式不报错,所以在特殊符号前面加上\)
+        let formattedVal = '';
+        forEach(val, (o) => {
+            if (/^[\w\u4e00-\u9fa5]$/.test(o)) {
+                formattedVal += o;
+            } else {
+                formattedVal += `\\${o}`;
+            }
+        });
+        // Global and case-insensitive mode, the regular pattern is to match characters other than newlines before and after searchText(全局和不区分大小写模式,正则是匹配searchText前后出了换行符之外的字符)
+        const regexp = new RegExp(`.*${formattedVal}.*`, 'gi');
+        MatchText = filter(log.split('\n'), (item) => {
+            return regexp.test(item);
+        }).join('\n');
+    } else {
+        MatchText = log;
+    }
+    return MatchText;
+}
+
+const getFormattedLogs = () => {
+    const processedLogs:{ [key: string]: any } = {};
+    Object.keys(logs.value).map((key) => {
+        processedLogs[key] = getSearchList(logs.value[key]);
+    });
+    return processedLogs[activeTaskLogTabPane.value];
+}
+
+const handleSearch = () => {
+    formattedLogs.value = getFormattedLogs();
+}
+
+const handleInitiation = async (fromLine: number) => {
+    if(props.task && props.params.ecmInstance) {
+        try {
+          let res = await api.fetch('/linkisManager/openEngineLog', {
+            applicationName: 'linkis-cg-engineconn',
+            emInstance: props.params.ecmInstance,
+            instance: props.task.engineInstance,
+            parameters: {
+                pageSize: 1000,
+                fromLine,
+                logType: activeTaskLogTabPane.value,
+                logDirSuffix: props.params.logDirSuffix,
+            }
+          }, 'post') || {};
+          if (res && res.result) {
+            if (res.result.rows < 1000) {
+                prevDisabled.value = true;
+            } else {
+                prevDisabled.value = false;
+            }
+            logs.value = {
+              [activeTaskLogTabPane.value]: res.result.logs ? res.result.logs.join('\n') : ''
+            }
+            logPath.value = res.result.logPath.split('/').slice(0, -1).join('/');
+          }
+          handleSearch();
+        } catch (err) {
+          return;
+        }
+    }
+}
+
+onMounted(() => {
+    handleInitiation(0);
+})
+
+watch(
+    () => props.task,
+    () => {
+        activeTaskLogTabPane.value = 'stdout';
+        handleInitiation(0);
+    }
+)
+</script>
+
+<style scoped lang="less">
+.sub-tabs {
+    position: relative;
+    .task-log-tabs {
+        background-color: #f7f7f8;
+        padding-left: 10px;
+    }
+    :deep(.fes-tabs-tab-active) {
+        background-color: #fff;
+    }
+    :deep(.fes-tabs-tab-pane-wrapper) {
+        display: none;
+    }
+    .search-input {
+        margin-top: 4px;
+        width: 140px;
+        position: absolute;
+        left: 480px;
+        z-index: 1000;
+    }
+    .btn-group {
+        display: flex;
+        margin-top: 4px;
+        height: 24px;
+        position: absolute;
+        left: 670px;
+        z-index: 1000;
+        gap: 10px;
+        .btn {
+            height: 24px;
+        }
+    }
+    .download-btn {
+        margin-top: 4px;
+        height: 24px;
+        position: absolute;
+        right: 16px;
+        z-index: 1000;
+    }
+    :deep(.fes-input-inner) {
+        height: 24px;
+    }
+    .no-log {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        gap: 30px;
+        color: #b7b7bc;
+        font-size: 14px;
+    }
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskLogs.vue b/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskLogs.vue
index d6de0d2f1b..f1b9956620 100644
--- a/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskLogs.vue
+++ b/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskLogs.vue
@@ -19,7 +19,7 @@
     <div class="sub-tabs">
         <FInput
             class="search-input"
-            :placeholder="$t('message.linkis.search')"
+            :placeholder="t('message.linkis.search')"
             v-model="searchText"
             @change="handleSearch"
         />
@@ -33,25 +33,24 @@
                     "
                     :value="tab.value"
                     :style="`color:${tab.color}`"
-                >
-                    <WeEditor
-                        :model-value="formattedLogs"
-                        :height="props.isFullScreen ? '85vh' : '420px'"
-                        v-if="formattedLogs !== ''"
-                    />
-                    <div v-else :style="{ height: props.isFullScreen ? '85vh' : '420px' }" class="no-log">
-                        <img src="/log/noLog.svg" />
-                        <div>{{ $t('message.linkis.noLog') }}</div>
-                    </div>
-                </FTabPane>
+                />
             </template>
         </FTabs>
+        <WeEditor
+            :model-value="formattedLogs"
+            :height="props.isFullScreen ? '85vh' : '420px'"
+            v-if="formattedLogs !== ''"
+        />
+        <div v-else :style="{ height: props.isFullScreen ? '85vh' : '420px' }" class="no-log">
+            <img src="/log/noLog.svg" />
+            <div>{{ t('message.linkis.noLog') }}</div>
+        </div>
     </div>
 </template>
 
 <script setup lang="ts">
 import { trim, forEach, filter } from 'lodash';
-import { reactive, ref, watch } from 'vue';
+import { onMounted, reactive, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import WeEditor from '@/components/editor/index.vue';
 import api from '@/service/api';
@@ -160,60 +159,68 @@ const handleSearch = () => {
     formattedLogs.value = getFormattedLogs();
 }
 
-watch(
-    () => props.task,
-    async () => {
-        if(props.task) {
-            if (!props.task.logPath) {
-                const errCode = props.task.errCode
-                ? `\n${t('message.linkis.errorCode')}:${
-                    props.task.errCode
-                }`
-                : ''
-                const errDesc = props.task.errDesc
-                ? `\n${t('message.linkis.errorDescription')}:${
-                    props.task.errDesc
-                }`
-                : ''
-                const info = t('message.linkis.notLog') + errCode + errDesc
-                logs.value = { all: info, error: '', warning: '', info: '' }
-                fromLine.value = 1
-                return
-            }
-            const params:any = {
-                path: props.task.logPath
-            }
-            if (storage.get('isAdminView') === 'true') {
-                params.proxyUser = props.task.umUser
-            }
-            let openLog:{ log: string | Array<string> } = { log: [] }
-            try {
-                if (props.task.status === 'Scheduled' || props.task.status === 'Running') {
-                    const tempParams = {
-                    fromLine: fromLine.value,
-                    size: -1,
-                    }
-                    openLog = await api.fetch(`/entrance/${props.task.execID}/log`, tempParams, 'get')
-                } else {
-                    openLog = await api.fetch('/filesystem/openLog', params, 'get')
+const handleInitiation = async () => {
+    if(props.task) {
+        if (!props.task.logPath) {
+            const errCode = props.task.errCode
+            ? `\n${t('message.linkis.errorCode')}:${
+                props.task.errCode
+            }`
+            : ''
+            const errDesc = props.task.errDesc
+            ? `\n${t('message.linkis.errorDescription')}:${
+                props.task.errDesc
+            }`
+            : ''
+            const info = t('message.linkis.notLog') + errCode + errDesc
+            logs.value = { all: info, error: '', warning: '', info: '' }
+            fromLine.value = 1
+            return
+        }
+        const params:any = {
+            path: props.task.logPath
+        }
+        if (storage.get('isAdminView') === 'true') {
+            params.proxyUser = props.task.umUser
+        }
+        let openLog:{ log: string | Array<string> } = { log: [] }
+        try {
+            if (props.task.status === 'Scheduled' || props.task.status === 'Running') {
+                const tempParams = {
+                fromLine: fromLine.value,
+                size: -1,
                 }
-                if (openLog) {
-                    const log:{ [key: string]: any } = { all: '', error: '', warning: '', info: '' }
-                    const convertLogs = util.convertLog(openLog.log)
-                    Object.keys(convertLogs).forEach(key => {
-                    if (convertLogs[key]) {
-                        log[key] += convertLogs[key] + '\n'
-                    }
-                    })
-                    logs.value = log
-                    fromLine.value = log['all'].split('\n').length
-                    formattedLogs.value = getFormattedLogs();
+                openLog = await api.fetch(`/entrance/${props.task.execID}/log`, tempParams, 'get')
+            } else {
+                openLog = await api.fetch('/filesystem/openLog', params, 'get')
+            }
+            if (openLog) {
+                const log:{ [key: string]: any } = { all: '', error: '', warning: '', info: '' }
+                const convertLogs = util.convertLog(openLog.log)
+                Object.keys(convertLogs).forEach(key => {
+                if (convertLogs[key]) {
+                    log[key] += convertLogs[key] + '\n'
                 }
-            } catch (errorMsg) {
-                window.console.error(errorMsg)
+                })
+                logs.value = log
+                fromLine.value = log['all'].split('\n').length
+                formattedLogs.value = getFormattedLogs();
             }
-            activeTaskLogTabPane.value = LOG_CLASS_PANE_VALUE.ALL;
+        } catch (errorMsg) {
+            window.console.error(errorMsg)
         }
+        activeTaskLogTabPane.value = LOG_CLASS_PANE_VALUE.ALL;
+    }
+}
+
+onMounted(() => {
+    handleInitiation();
+})
+
+watch(
+    () => props.task,
+    () => {
+        handleInitiation();
     }
 )
 </script>
@@ -224,19 +231,13 @@ watch(
     .task-log-tabs {
         background-color: #f7f7f8;
         padding-left: 10px;
-        .no-log {
-            display: flex;
-            flex-direction: column;
-            justify-content: center;
-            align-items: center;
-            gap: 30px;
-            color: #b7b7bc;
-            font-size: 14px;
-        }
     }
     :deep(.fes-tabs-tab-active) {
         background-color: #fff;
     }
+    :deep(.fes-tabs-tab-pane-wrapper) {
+        display: none;
+    }
     .search-input {
         margin-top: 4px;
         width: 140px;
@@ -247,5 +248,14 @@ watch(
     :deep(.fes-input-inner) {
         height: 24px;
     }
+    .no-log {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        gap: 30px;
+        color: #b7b7bc;
+        font-size: 14px;
+    }
 }
 </style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskResults.vue b/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskResults.vue
new file mode 100644
index 0000000000..a1392cde53
--- /dev/null
+++ b/linkis-web-next/src/pages/globalHistoryManagement/drawer/taskResults.vue
@@ -0,0 +1,184 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+    <FButton class="btn" v-if="resultType" type="primary" @click="isShowModal=true">{{ t('message.common.download') }}</FButton>
+    <div v-if="resultType === '1'" class="text">
+        <p v-for="text in textArr">{{ text }}</p>
+    </div>
+    <div v-else-if="resultType === '2'" class="table">
+        <FTable
+            :columns="columnsData"
+            :data="tableData.fileContent"
+            :height="props.isFullScreen ? 600 : 400"
+        />
+    </div>
+    <div v-else :style="{ height: props.isFullScreen ? '85vh' : '420px' }" class="no-data">
+        <img src="/log/noLog.svg" />
+        <div>{{ t('message.linkis.noDataText') }}</div>
+    </div>
+    <FModal v-model:show="isShowModal" @ok="handleDownload">
+        <FForm>
+            <FFormItem :label="t('message.common.toolbar.format')">
+                <FRadioGroup v-model="format">
+                    <FRadio value="csv">CSV</FRadio>
+                    <FRadio v-if="resultType === '2'" value="xlsx">Excel</FRadio>
+                </FRadioGroup>
+            </FFormItem>
+            <FFormItem :label="t('message.common.toolbar.coding')">
+                <FRadioGroup v-model="charset">
+                    <FRadio value="utf-8">UTF-8</FRadio>
+                    <FRadio value="gbk">GBK</FRadio>
+                </FRadioGroup>
+            </FFormItem>
+            <FFormItem :label="t('message.common.toolbar.replace')">
+                <FRadioGroup v-model="nullValue">
+                    <FRadio value="NULL">NULL</FRadio>
+                    <FRadio value="BLANK">{{ t('message.common.toolbar.emptyString') }}</FRadio>
+                </FRadioGroup>
+            </FFormItem>
+            <FFormItem :label="t('message.common.toolbar.selectSeparator')" v-if="format === 'csv'">
+                <FRadioGroup v-model="separator">
+                    <FRadio value=",">{{ t('message.common.separator.comma') }}</FRadio>
+                    <FRadio :value="`\t`">{{ t('message.common.separator.tab') }}</FRadio>
+                    <FRadio value=" ">{{ t('message.common.separator.space') }}</FRadio>
+                    <FRadio value="|">{{ t('message.common.separator.vertical') }}</FRadio>
+                    <FRadio value=";">{{ t('message.common.separator.semicolon') }}</FRadio>
+                </FRadioGroup>
+            </FFormItem>
+            <FFormItem :label="t('message.common.toolbar.downloadMode')">
+                <FCheckbox :label="t('message.common.toolbar.allDownLoad')" v-model="allDownload" style="margin-right: 20px;"/>
+                <FCheckbox :label="t('message.common.toolbar.autoFormat')" v-model="autoFormat" v-if="format === 'xlsx'" />
+            </FFormItem>
+        </FForm>
+    </FModal>
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { onMounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const props = defineProps<{
+    task: any,
+    isFullScreen: boolean,
+}>();
+
+const resultType = ref();
+const resultList = ref([]);
+const currentResultPath = ref('');
+const tableData = ref();
+const columnsData = ref();
+const textArr = ref();
+const isShowModal = ref(false);
+const format = ref('csv');
+const charset = ref('utf-8');
+const nullValue = ref('NULL');
+const separator = ref(',');
+const allDownload = ref(false);
+const autoFormat = ref(false);
+
+const downloadFromHref = (url: string) => {
+    const downloadLink = document.createElement('a');
+    downloadLink.href = url;
+    downloadLink.style.display = 'none';
+    document.body.appendChild(downloadLink);
+    downloadLink.click();
+    document.body.removeChild(downloadLink);
+}
+
+const handleDownload = async () => {
+    // Before downloading, use the heartbeat interface to confirm whether to log in(下载之前条用心跳接口确认是否登录)
+    await api.fetch('/user/heartbeat', 'get');
+    let url = `http://${window.location.host}/api/rest_j/v1/filesystem/resultsetToExcel?charset=${charset.value}&outputFileType=${format.value}&nullValue=${nullValue.value}`;
+    if (format.value === 'csv') {
+        url += `&csvSeparator=${encodeURIComponent(separator.value)}`;
+        if (allDownload.value) {
+            resultList.value.forEach((v: any, i: number) => {
+                downloadFromHref(`${url}&path=${v.path}&outputFileName=ResultSet${i + 1}`);
+            })
+        } else {
+            downloadFromHref(`${url}&path=${currentResultPath.value}&outputFileName=ResultSet${props.task.taskID}`);
+        }
+    }
+    if (format.value === 'xlsx') {
+        downloadFromHref(`${url}&autoFormat==${autoFormat.value}&path=${currentResultPath.value}&outputFileName=ResultSet${props.task.taskID}`);
+    }
+}
+
+onMounted(async () => {
+    if (props.task.resultLocation) {
+        try {
+            const rst = await api.fetch('/filesystem/getDirFileTrees', { path: props.task.resultLocation }, 'get');
+            if (rst.dirFileTrees) {
+                // The order of the result set in the background is sorted by string according to the name of the result set. When displaying, there will be a problem that the result set cannot be matched, so add sorting(后台的结果集顺序是根据结果集名称按字符串排序的,展示时会出现结果集对应不上的问题,所以加上排序)
+                resultList.value = rst.dirFileTrees.children.sort((a: any, b: any) =>
+                    parseInt(a.name.split('_')[1].split('.')[0], 10) - parseInt(b.name.split('_')[1].split('.')[0], 10)
+                )
+                if (resultList.value.length) {
+                    currentResultPath.value = rst.dirFileTrees.children[0].path;
+                    const ret = await api.fetch('/filesystem/openFile', {
+                        path: currentResultPath.value,
+                        page: 1,
+                        pageSize: 5000
+                    }, 'get');
+                    resultType.value = ret.type;
+                    if (ret.type === '1') {
+                        textArr.value = ret.fileContent;
+                    }
+                    if (ret.type === '2') {
+                        tableData.value = ret;
+                        columnsData.value = ret.metadata.map((v: any, i: number) => ({
+                            prop: i,
+                            label: v.columnName,
+                            sortable: true,
+                        }))
+                    }
+                }
+            }
+        } catch (error) {
+            window.console.error(error)
+        }
+    }
+})
+</script>
+
+<style lang="less" scoped>
+.btn {
+    margin: 10px;
+}
+.text {
+    margin: 0 10px;
+    white-space: normal;
+    word-break: break-all;
+    word-wrap: break-word;
+}
+.table {
+    margin: 0 10px;
+}
+.no-data {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    gap: 30px;
+    color: #b7b7bc;
+    font-size: 14px;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/filter.vue b/linkis-web-next/src/pages/globalHistoryManagement/filter.vue
index 35dfb56af7..a7dcd5dfe0 100644
--- a/linkis-web-next/src/pages/globalHistoryManagement/filter.vue
+++ b/linkis-web-next/src/pages/globalHistoryManagement/filter.vue
@@ -16,12 +16,12 @@
   -->
 
 <template>
-    <div class="label">{{ $t('message.linkis.globalTaskQuery') }}</div>
+    <div class="label">{{ t('message.linkis.globalTaskQuery') }}</div>
     <div>
         <FForm v-show="!isCheckingTaskToStop" layout="inline" :span="4">
             <FFormItem
-                :label="$t('message.linkis.columns.taskID')"
-                :props="$t('message.linkis.columns.taskID')"
+                :label="t('message.linkis.columns.taskID')"
+                :props="t('message.linkis.columns.taskID')"
             >
                 <FInput
                     v-model="formData.taskID"
@@ -30,8 +30,8 @@
                 />
             </FFormItem>
             <FFormItem
-                :label="$t('message.linkis.columns.status')"
-                :props="$t('message.linkis.columns.status')"
+                :label="t('message.linkis.columns.status')"
+                :props="t('message.linkis.columns.status')"
             >
                 <FSelect
                     v-model="formData.status"
@@ -42,13 +42,13 @@
                         v-for="(item, index) in statusList"
                         :key="index"
                         :value="item.value"
-                        :label="$t(item.label)"
+                        :label="t(item.label)"
                     />
                 </FSelect>
             </FFormItem>
             <FFormItem
-                :label="$t('message.linkis.columns.requestApplicationName')"
-                :props="$t('message.linkis.columns.requestApplicationName')"
+                :label="t('message.linkis.columns.requestApplicationName')"
+                :props="t('message.linkis.columns.requestApplicationName')"
             >
                 <FInput
                     v-model="formData.creator"
@@ -56,8 +56,8 @@
                 />
             </FFormItem>
             <FFormItem
-                :label="$t('message.linkis.columns.executeApplicationName')"
-                :props="$t('message.linkis.columns.executeApplicationName')"
+                :label="t('message.linkis.columns.executeApplicationName')"
+                :props="t('message.linkis.columns.executeApplicationName')"
             >
                 <FSelect
                     v-model="formData.executeApplicationName"
@@ -68,26 +68,26 @@
                         v-for="(item, index) in engineList"
                         :key="index"
                         :value="item.value"
-                        :label="$t(item.label)"
+                        :label="t(item.label)"
                     />
                 </FSelect>
             </FFormItem>
             <FFormItem
-                :label="$t('message.linkis.columns.createdTime')"
-                :prop="$t('message.linkis.columns.createdTime')"
+                :label="t('message.linkis.columns.createdTime')"
+                :prop="t('message.linkis.columns.createdTime')"
                 :span="8"
             >
                 <!-- 日期修改完成后需要让日期选择组件失焦 -->
                 <FDatePicker
                     type="daterange"
                     v-model="formData.timeRange"
-                    :shortcuts="$props.shortcuts"
+                    :shortcuts="props.shortcuts"
                     @focus="(e:any) => { e.target.blur() }"
                 />
             </FFormItem>
             <FFormItem
-                :label="$t('message.linkis.columns.umUser')"
-                :props="$t('message.linkis.columns.umUser')"
+                :label="t('message.linkis.columns.umUser')"
+                :props="t('message.linkis.columns.umUser')"
                 v-if="isAdminView"
             >
                 <FInput
@@ -99,16 +99,16 @@
         <div class="buttons">
             <template v-if="!isCheckingTaskToStop">
                 <FButton type="primary" @click="search">
-                    {{ $t('message.linkis.find') }}
+                    {{ t('message.linkis.find') }}
                 </FButton>
                 <FButton @click="reset">
-                    {{ $t('message.linkis.reset') }}
+                    {{ t('message.linkis.reset') }}
                 </FButton>
                 <FButton @click="switchAdmin" v-show="isLogAdmin || isHistoryAdmin">
-                    {{ isAdminView ? $t('message.linkis.generalView') : $t('message.linkis.manageView') }}
+                    {{ isAdminView ? t('message.linkis.generalView') : t('message.linkis.manageView') }}
                 </FButton>
                 <FButton @click="isCheckingTaskToStop = true">
-                    {{ $t('message.linkis.batchStop') }}
+                    {{ t('message.linkis.batchStop') }}
                 </FButton>
             </template>
         </div>
@@ -119,13 +119,13 @@
                     class="confirm-stop-button"
                     @click="confirmStop"
                 >
-                    {{ $t('message.linkis.confirmToStop') }}
+                    {{ t('message.linkis.confirmToStop') }}
                 </FButton>
                 <FButton
                     class="confirm-stop-button"
                     @click="isCheckingTaskToStop = false"
                 >
-                    {{ $t('message.linkis.cancel') }}
+                    {{ t('message.linkis.cancel') }}
                 </FButton>
             </div>
         </template>
@@ -133,7 +133,7 @@
 </template>
 
 <script lang="ts" setup>
-import { FForm, FMessage } from '@fesjs/fes-design';
+import { FMessage } from '@fesjs/fes-design';
 import { ref, onMounted, reactive } from 'vue';
 import { useI18n } from 'vue-i18n';
 import api from '@/service/api';
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/index.vue b/linkis-web-next/src/pages/globalHistoryManagement/index.vue
index cfdf22faf2..0838f2d6d1 100644
--- a/linkis-web-next/src/pages/globalHistoryManagement/index.vue
+++ b/linkis-web-next/src/pages/globalHistoryManagement/index.vue
@@ -82,7 +82,6 @@
 <script setup lang="ts">
 import { ref, h, Ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { FMessage } from '@fesjs/fes-design';
 import dayjs from 'dayjs';
 import Drawer from './drawer/index.vue';
 import Filter from './filter.vue';
diff --git a/linkis-web-next/src/pages/globalVariables/index.vue b/linkis-web-next/src/pages/globalVariables/index.vue
new file mode 100644
index 0000000000..526f6b2678
--- /dev/null
+++ b/linkis-web-next/src/pages/globalVariables/index.vue
@@ -0,0 +1,149 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+    <div class="btn-group">
+        <FButton type="primary" @click="handleEdit" v-if="!isEditing">
+            {{ t('message.linkis.edit') }}
+        </FButton>
+        <FButton type="primary" @click="handleSave" v-if="isEditing">
+            {{ t('message.linkis.save') }}
+        </FButton>
+        <FButton @click="handleCancel" v-if="isEditing">
+            {{ t('message.linkis.cancel') }}
+        </FButton>
+    </div>
+    <FForm>
+        <FFormItem
+            v-for="(variable, index) in globalVariables"
+            :key="index"
+            :label="t('message.linkis.globalVariable') + (index + 1)"
+            :rules="[{
+                trigger: ['change', 'blur'],
+                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                // @ts-ignore
+                validator: (rule, value, callback) => {
+                    if (variable.key.length === 0) {
+                        callback(t('message.linkis.rules.first.required'))
+                    }
+                    if (variable.value.length === 0) {
+                        callback(t('message.linkis.rules.second.required'))
+                    }
+                    if (!new RegExp(/^[a-zA-z][^\s\u4e00-\u9fa5]*$/).test(variable.key)) {
+                        callback(t('message.linkis.rules.first.letterTypeLimit'))
+                    }
+                    if (globalVariables.findIndex(v => v.key === variable.key) !== index) {
+                        callback(t('message.linkis.sameName'))
+                    }
+                }
+            }]"
+        >
+            <FInput
+                v-model:modelValue="variable.key"
+                :placeholder="t('message.linkis.rules.first.placeholder')"
+                :disabled="!isEditing"
+            />
+            &nbsp;&nbsp;&nbsp;:&nbsp;&nbsp;&nbsp;
+            <FInput
+                v-model:modelValue="variable.value"
+                :placeholder="t('message.linkis.rules.second.placeholder')"
+                :disabled="!isEditing"
+            />
+            <FButton @click="() => { handleRemove(index) }" v-if="isEditing" class="remove-btn" type="primary">
+                {{ t('message.linkis.remove') }}
+            </FButton>
+        </FFormItem>
+    </FForm>
+    <FButton type="info" long @click="handleAdd" v-if="isEditing">{{ t('message.linkis.addArgs') }}</FButton>
+</template>
+
+<script setup lang="ts">
+import api from '@/service/api';
+import { FMessage } from '@fesjs/fes-design';
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+let globalVariables = reactive<{ key: string, value: string }[]>([]);
+let originGlobalVariables = reactive<{ key: string, value: string }[]>([]);
+const isEditing = ref(false);
+
+
+const getGlobalVariableList = async () => {
+    const res = await api.fetch('/variable/listGlobalVariable', 'get');
+    globalVariables.length = 0;
+    res.globalVariables.forEach((v: any) => {
+        globalVariables.push(v);
+    })
+}
+
+const handleEdit = () => {
+    isEditing.value = true;
+    originGlobalVariables.length = 0;
+    globalVariables.forEach((v: any) => {
+        originGlobalVariables.push(v);
+    });
+}
+
+const handleSave = async () => {
+    try {
+        await api.fetch('/variable/saveGlobalVariable ', { globalVariables });
+        FMessage.success(t('message.linkis.success.update'));
+    } catch (error) {
+        window.console.error(error);
+    } finally {
+        isEditing.value = false;
+        getGlobalVariableList();
+    }
+}
+
+const handleCancel = () => {
+    isEditing.value = false;
+    globalVariables.length = 0;
+    originGlobalVariables.forEach((v: any) => {
+        globalVariables.push(v);
+    });
+}
+
+const handleRemove = (index: number) => {
+    globalVariables.splice(index, 1);
+}
+
+const handleAdd = () => {
+    globalVariables.push({
+        key: '',
+        value: '',
+    })
+}
+
+onMounted(() => {
+    getGlobalVariableList();
+})
+</script>
+
+<style lang="less" scoped>
+.btn-group {
+    display: flex;
+    gap: 20px;
+    margin-bottom: 20px;
+}
+
+.remove-btn {
+    margin-left: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/microServiceManagement/index.vue b/linkis-web-next/src/pages/microServiceManagement/index.vue
new file mode 100644
index 0000000000..15ad4455c2
--- /dev/null
+++ b/linkis-web-next/src/pages/microServiceManagement/index.vue
@@ -0,0 +1,208 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+    <FForm layout="inline" :span="8">
+        <FFormItem
+            :label="t('message.linkis.tableColumns.instanceName')"
+            :props="t('message.linkis.tableColumns.instanceName')"
+        >
+            <FInput
+                v-model="instanceName"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.tableColumns.engineType')"
+            :props="t('message.linkis.tableColumns.engineType')"
+        >
+            <FSelect
+                v-model="engineType"
+                clearable
+                :placeholder="t('message.linkis.unselect')"
+            >
+                <FOption
+                    v-for="(item, index) in engineTypes"
+                    :key="index"
+                    :value="item"
+                    :label="item"
+                />
+            </FSelect>
+        </FFormItem>
+        <FFormItem>
+            <div class="btns">
+                <FButton type="primary" @click="search">
+                    {{ t('message.linkis.find') }}
+                </FButton>
+                <FButton type="primary" @click="handleJump">
+                    {{ t('message.linkis.serviceRegistryCenter') }}
+                </FButton>
+            </div>
+        </FFormItem>
+    </FForm>
+    <f-table
+        :data="dataList"
+        :rowKey="(row: Record<string, number | string>) => row.instance"
+        :emptyText="t('message.linkis.noDataText')"
+    >
+        <template v-for="col in tableColumns" :key="col.label">
+            <f-table-column
+                v-if="col.formatter"
+                :prop="col.prop"
+                :label="col.label"
+                :formatter="col.formatter"
+            />
+            <f-table-column
+                v-else
+                :prop="col.prop"
+                :label="col.label"
+                :action="col.action"
+            />
+        </template>
+    </f-table>
+    <FPagination
+        class="pagination"
+        show-size-changer
+        show-total
+        :total-count="tableData?.length || 0"
+        v-model:currentPage="currentPage"
+        @change="handleChangePagination"
+        :pageSizeOption="[10, 20, 30, 50, 100]"
+        v-model:pageSize="pageSize"
+    />
+    <Modal ref="modalRef" :instance="currentIns" :handleInitialization="handleInitialization" />
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { FSpace, FTag } from '@fesjs/fes-design';
+import dayjs from 'dayjs';
+import { onMounted, reactive, ref, h } from 'vue';
+import Modal from './modal.vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const instanceName = ref('');
+const engineType = ref('');
+const engineTypes = reactive<{ label: string, value: string }[]>([]);
+const dataList = ref();
+const tableData = ref();
+const allInstance = ref();
+const currentPage = ref(1);
+const pageSize = ref(10);
+const modalRef = ref();
+const currentIns = ref();
+
+const handleEdit = (instance: any) => {
+    currentIns.value = instance;
+    modalRef.value?.open();
+}
+
+const tableColumns = [
+    {
+        prop: 'instance',
+        label: t('message.linkis.tableColumns.instanceName'),
+    },
+    {
+        prop: 'labels',
+        label: t('message.linkis.tableColumns.label'),
+        formatter: (data: { row: { labels: { stringValue: string }[] }}) => 
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            // @ts-ignore
+            h(FSpace, data.row.labels.map(label => h(FTag, { type: 'default', effect: 'plain', size: 'small' }, label.stringValue)))
+    },
+    {
+        prop: 'applicationName',
+        label: t('message.linkis.tableColumns.serveType'),
+    },
+    {
+        prop: 'createTime',
+        label: t('message.linkis.tableColumns.createdTime'),
+        formatter: ({ row }: { row: { createTime: number } }) => dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+        prop: 'updateTime',
+        label: t('message.linkis.tableColumns.updateTime'),
+        formatter: ({ row }: { row: { updateTime: number } }) => dayjs(row.updateTime).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+        // 在“操作”两个字前添加空格,使其能与“编辑”上下对齐
+        label: `\u00A0\u00A0\u00A0\u00A0${t('message.linkis.columns.control.title')}`,
+        action: [{
+            label: t('message.linkis.tableColumns.editLabel'),
+            func: handleEdit,
+        }],
+    },
+];
+
+const search = () => {
+    if (instanceName.value || engineType.value) {
+        tableData.value = allInstance.value.filter((item: any) => 
+            item.instance.match(instanceName.value) && item.applicationName.match(engineType.value)
+        );
+    } else {
+        tableData.value = [...allInstance.value];
+    }
+    currentPage.value = 1;
+    handleChangePagination(currentPage.value, pageSize.value);
+}
+
+const handleJump = () => {
+    api.fetch("/microservice/serviceRegistryURL", "get").then((rst: any) => {
+        window.open(rst.url, "_blank");
+    });
+}
+
+// eslint-disable-next-line no-shadow
+const handleChangePagination = (currentPage: number, pageSize: number) => {
+    const temp = tableData.value.slice(
+        (currentPage - 1) * pageSize,
+        currentPage * pageSize,
+    );
+    dataList.value = [...temp] as any;
+};
+
+const handleInitialization = () => {
+    api.fetch("/microservice/allInstance", "get").then((res: any) => {
+        allInstance.value = [...res.instances] || [];
+        tableData.value = [...res.instances] || [];
+        handleChangePagination(currentPage.value, pageSize.value);
+        res.instances.forEach((item: any) => {
+            if (engineTypes.indexOf(item.applicationName) === -1) {
+                engineTypes.push(item.applicationName);
+            }
+        })
+    })
+}
+
+onMounted(() => {
+    handleInitialization();
+})
+</script>
+
+<style lang="less" scoped>
+.btns {
+    display: flex;
+    gap: 12px;
+}
+.pagination {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 16px;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/microServiceManagement/modal.vue b/linkis-web-next/src/pages/microServiceManagement/modal.vue
new file mode 100644
index 0000000000..7b308e7b78
--- /dev/null
+++ b/linkis-web-next/src/pages/microServiceManagement/modal.vue
@@ -0,0 +1,149 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+    <FModal
+        v-model:show="show"
+        displayDirective="if"
+        @ok="handleConfirm"
+    >
+        <template #title>
+            <div class="modal-title">{{ instance.instance }}</div>
+        </template>
+        <FForm>
+            <FFormItem
+                v-for="(label, index) in labels"
+                :key="index"
+                :rules="[{
+                    trigger: ['change', 'blur'],
+                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                    // @ts-ignore
+                    validator: (rule, value, callback) => {
+                        if (!label.labelKey || !label.stringValue) {
+                            callback(t('message.linkis.labelEmptyError'))
+                        }
+                        if (label.stringValue.length >= 16) {
+                            callback(t('message.linkis.labelLengthLimit'))
+                        }
+                        if (!new RegExp(/^[^\s\u4e00-\u9fa5]*$/).test(label.stringValue)) {
+                            callback(t('message.linkis.labelNoSpecialSymbol'))
+                        }
+                    }
+                }]"
+            >
+                <FSelect
+                    v-model="label.labelKey"
+                    clearable
+                    :placeholder="t('message.linkis.unselect')"
+                >
+                    <FOption
+                        v-for="(item, index) in keyList"
+                        :key="index"
+                        :value="item"
+                        :label="item"
+                    />
+                </FSelect>
+                &nbsp;&nbsp;&nbsp;:&nbsp;&nbsp;&nbsp;
+                <FInput
+                    v-model:modelValue="label.stringValue"
+                    :placeholder="t('message.linkis.datasource.pleaseInput')"
+                />
+                &nbsp;&nbsp;&nbsp;
+                <FButton @click="() => { handleRemove(index) }" type="primary">
+                    {{ t('message.linkis.remove') }}
+                </FButton>
+            </FFormItem>
+        </FForm>
+        <FButton type="info" long @click="handleAdd">{{ t('message.linkis.addTags') }}</FButton>
+    </FModal>
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { FMessage } from '@fesjs/fes-design';
+import { onMounted, reactive, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+const { t } = useI18n();
+
+const props = defineProps<{
+    instance: {
+        instance: string,
+        applicationName: string,
+        labels: {
+            labelKey: string,
+            stringValue: string,
+        }[],
+    },
+    handleInitialization: Function,
+}>();
+const show = ref(false);
+const labels = reactive<{ labelKey: string, stringValue: string }[]>([]);
+const keyList = ref();
+
+const handleAdd = () => {
+    labels.push({
+        labelKey: '',
+        stringValue: '',
+    })
+}
+
+const handleRemove = (index: number) => {
+    labels.splice(index, 1);
+}
+
+const handleConfirm = async () => {
+    try {
+        await api.fetch("/microservice/instanceLabel", {
+            instance: props.instance.instance,
+            applicationName: props.instance.applicationName,
+            labels,
+        }, "put");
+        FMessage.success(t("message.linkis.editedSuccess"));
+        show.value = false;
+        props.handleInitialization();
+    } catch (error) {
+        window.console.error(error);
+    }
+}
+
+const open = () => {
+    show.value = true;
+};
+
+watch(() => props.instance, () => {
+    labels.length = 0;
+    props.instance.labels.forEach((label: any) => {
+        labels.push(label);
+    })
+})
+
+onMounted(() => {
+    api.fetch("/microservice/modifiableLabelKey", "get").then((res: any) => {
+        keyList.value = res.keyList || [];
+    });
+})
+
+defineExpose({
+    open,
+});
+</script>
+
+<style lang="less" scoped>
+.modal-title {
+    font-weight: bold;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/parameterConfig/card.vue b/linkis-web-next/src/pages/parameterConfig/card.vue
index 369d5a2769..c969b5f9b3 100644
--- a/linkis-web-next/src/pages/parameterConfig/card.vue
+++ b/linkis-web-next/src/pages/parameterConfig/card.vue
@@ -130,16 +130,23 @@ import { ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 const { t } = useI18n();
 
+type DataList = Array<{
+    name: string;
+    description: string;
+    value: string | number;
+    settings: any;
+}>;
+
 const props = defineProps<{
     name: string;
     description: string;
-    dataList: any;
+    dataList: DataList;
     creator: string;
     engineType: string;
 }>();
 const isEditing = ref(false);
 const listData = ref([...props.dataList]);
-const listDataBeforeEditing = ref<any>([]);
+const listDataBeforeEditing = ref<DataList>([]);
 const isDisabled = ref(false);
 
 const options = {
diff --git a/linkis-web-next/src/pages/parameterConfig/drawer.vue b/linkis-web-next/src/pages/parameterConfig/drawer.vue
index ee0f391a85..25e6a4d78a 100644
--- a/linkis-web-next/src/pages/parameterConfig/drawer.vue
+++ b/linkis-web-next/src/pages/parameterConfig/drawer.vue
@@ -227,6 +227,8 @@ const handleDeleteCreator = (row: any) => {
     FModal.confirm({
         title: t('message.common.userMenu.title'),
         content: t('message.linkis.confirmToDeleteCreator'),
+        okText: t('message.linkis.submit'),
+        cancelText: t('message.linkis.cancel'),
         closable: true,
         onOk: async () => {
             await api.fetch("/configuration/deleteCategory", { categoryId: row.categoryId }, "post")
@@ -272,7 +274,7 @@ const tableColumns = [
         formatter: ({ row }: { row: { updateTime: number } }) => dayjs(row.updateTime).format('YYYY-MM-DD HH:mm:ss'),
     },
     {
-        // 在“操作”两个字前添加空格,使其能与“查看”上下对齐
+        // 在“操作”两个字前添加空格,使其能与“编辑”上下对齐
         label: `\u00A0\u00A0\u00A0\u00A0${t('message.linkis.columns.control.title')}`,
         width: 140,
         action: [
@@ -350,6 +352,8 @@ const confirmEditing = () => {
         title: t('message.common.userMenu.title'),
         content: t('message.linkis.confirmToModifyCreator'),
         closable: true,
+        okText: t('message.linkis.submit'),
+        cancelText: t('message.linkis.cancel'),
         onOk: async () => {
             if (isAddingApplicationAndEngine.value) {
                 // 添加新应用时,先创建应用
@@ -373,20 +377,19 @@ const confirmEditing = () => {
                     await refresh();
                     return;
                 }
-                // 获取categoryId后,依次创建引擎列表中的各个引擎
-                for (const cate of tmpData.value.childCategory) {
-                    try {
-                        await api.fetch("/configuration/createSecondCategory", {
-                            categoryId,
-                            engineType: cate.engineType,
-                            version: cate.version,
-                            description: cate.description,
-                        }, "post")
+                // 获取categoryId后,创建引擎列表中的各个引擎
+                await Promise.all(tmpData.value.childCategory.map((cate: any) => {
+                    return api.fetch("/configuration/createSecondCategory", {
+                        categoryId,
+                        engineType: cate.engineType,
+                        version: cate.version,
+                        description: cate.description,
+                    }, "post").then(() => {
                         FMessage.success(cate.engineType + cate.version + t('message.linkis.successfullyAddEngine'));
-                    } catch (error) {
+                    }).catch(() => {
                         FMessage.error(cate.engineType + cate.version + t('message.linkis.failedToAddEngine'));
-                    }
-                }
+                    });
+                }))
             }
             if (isEditingApplicationAndEngine.value) {
                 // 编辑应用时,先判断是否需要更改应用描述
@@ -423,49 +426,46 @@ const confirmEditing = () => {
                         }
                     }
                 })
-                // 经过上述操作,原始列表tmpDataBeforeEditing.value.childCategory中只会保留需要被删除的引擎,将这些引擎遍历删除
-                for (const cateToDelete of tmpDataBeforeEditing.value.childCategory) {
-                    try {
-                        await api.fetch("/configuration/deleteCategory", { categoryId: cateToDelete.categoryId }, "post")
+                // 经过上述操作,原始列表tmpDataBeforeEditing.value.childCategory中只会保留需要被删除的引擎,将这些引擎删除
+                await Promise.all(tmpDataBeforeEditing.value.childCategory.map((cateToDelete: any) => {
+                    return api.fetch("/configuration/deleteCategory", { categoryId: cateToDelete.categoryId }, "post").then(() => {
                         FMessage.success(
                             cateToDelete.engineType +
                             cateToDelete.version +
                             t('message.linkis.basedataManagement.engineConfigurationTemplate.delSuccess')
                         );
-                    } catch (error) {
+                    }).catch(() => {
                         FMessage.error(
                             cateToDelete.engineType +
                             cateToDelete.version +
                             t('message.linkis.basedataManagement.engineConfigurationTemplate.delFail')
                         );
-                    }
-                }
+                    });
+                }))
                 // 遍历添加addList中的引擎
-                for (const cateToAdd of addList) {
-                    try {
-                        await api.fetch("/configuration/createSecondCategory", {
-                            categoryId: tmpData.value.categoryId,
-                            engineType: cateToAdd.engineType,
-                            version: cateToAdd.version,
-                            description: cateToAdd.description,
-                        }, "post")
+                await Promise.all(addList.map((cateToAdd: any) => {
+                    return api.fetch("/configuration/createSecondCategory", {
+                        categoryId: tmpData.value.categoryId,
+                        engineType: cateToAdd.engineType,
+                        version: cateToAdd.version,
+                        description: cateToAdd.description,
+                    }, "post").then(() => {
                         FMessage.success(cateToAdd.engineType + cateToAdd.version + t('message.linkis.successfullyAddEngine'));
-                    } catch (error) {
+                    }).catch(() => {
                         FMessage.error(cateToAdd.engineType + cateToAdd.version + t('message.linkis.failedToAddEngine'));
-                    }
-                }
+                    });
+                }))
                 // 遍历修改modifyList中的引擎
-                for (const cateToModify of modifyList) {
-                    try {
-                        await api.fetch("configuration/updateCategoryInfo", {
-                            categoryId: cateToModify.categoryId,
-                            description: cateToModify.description
-                        }, "post")
+                await Promise.all(modifyList.map((cateToModify: any) => {
+                    return api.fetch("configuration/updateCategoryInfo", {
+                        categoryId: cateToModify.categoryId,
+                        description: cateToModify.description
+                    }, "post").then(() => {
                         FMessage.success(cateToModify.engineType + cateToModify.version + t('message.linkis.successfullyUpdateEngine'));
-                    } catch (error) {
+                    }).catch(() => {
                         FMessage.error(cateToModify.engineType + cateToModify.version + t('message.linkis.failedToUpdateEngine'));
-                    }
-                }
+                    });
+                }))
             }
             // 完成编辑操作后刷新页面
             await refresh();
@@ -487,13 +487,13 @@ const handleInitialization = async () => {
     // Get settings directory(获取设置目录)
     const cateRes = await api.fetch("/configuration/getCategory", "get");
     menuList.value = cateRes.Category.filter((menu: any) => menu.categoryName !== 'GlobalSettings') || [];
-    handleChangePagination(currentPage.value, pageSize.value)
+    handleChangePagination(currentPage.value, pageSize.value);
     // Get all engine types(获取所有引擎类型)
     const engineTypeRes = await api.fetch("/configuration/engineType", "get");
     engineTypes.value = engineTypeRes.engineType.map((type: string) => ({ name: type, value: type }));
     // Get all engine type versions(获取所有引擎版本)
     engineTypeRes.engineType.forEach(async (type: string) => {
-        const engineTypeVersionRes = await api.fetch(`/engineplugin/getTypeVersionList/${type}`, {}, "get")
+        const engineTypeVersionRes = await api.fetch(`/engineplugin/getTypeVersionList/${type}`, {}, "get");
         engineTypeVersions.value[type] = (engineTypeVersionRes.queryList || []).map((item: string) => {
             if (/^v/.test(item)) {
                 const version = item.replace(/v/, '');
diff --git a/linkis-web-next/src/pages/parameterConfig/index.vue b/linkis-web-next/src/pages/parameterConfig/index.vue
index fd7b7bca49..ea25ceb189 100644
--- a/linkis-web-next/src/pages/parameterConfig/index.vue
+++ b/linkis-web-next/src/pages/parameterConfig/index.vue
@@ -16,7 +16,7 @@
   -->
 
 <template>
-    <FTabs @change="handleChangeTab">
+    <FTabs @change="handleChangeTab" :modelValue="activeTabKey">
         <FTabPane v-for="menu in menuList" :key="menu.categoryName" :name="menu.categoryName" :value="menu.categoryName">
             <div class="card-list">
                 <template v-for="item in childCategory" :key="`${menu.categoryName}-${item.categoryName}`">
@@ -58,6 +58,7 @@ const drawerRef = ref<Ref<{ open: () => void }> | null>(null);
 const modalRef = ref<Ref<{ open: () => void }> | null>(null);
 const isLogAdmin = ref<boolean>(false);
 const isHistoryAdmin = ref<boolean>(false);
+const activeTabKey = ref();
 const menuList = ref<Array<{ categoryName: string, childCategory: Array<any> }>>([]);
 const childCategory = ref<Array<{
     categoryName: string,
@@ -138,6 +139,7 @@ const formatValidateRange = (value: any, type: string) => {
 
 const handleChangeTab = (menu: any) => {
     childCategory.value = menuList.value.find((v: any) => v.categoryName === menu)?.childCategory;
+    activeTabKey.value = menu;
 }
 
 const getCategory = async () => {
@@ -145,13 +147,11 @@ const getCategory = async () => {
     // get settings directory(获取设置目录)
     const rst = await api.fetch("/configuration/getCategory", "get");
     menuList.value = rst.Category.filter((menu: any) => menu.categoryName !== 'GlobalSettings') || [];
-    for (const menu of menuList.value) {
-        for (const category of menu.childCategory) {
-            const arr = category.categoryName.split('-');
-            const res = await getAppVariable(menu.categoryName, arr[0], arr[1]);
-            category.fullTree = res.fullTree;
-        }
-    }
+    await Promise.all(menuList.value.map((menu: any) => Promise.all(menu.childCategory.map(async (category: any) => {
+        const arr = category.categoryName.split('-');
+        const res = await getAppVariable(menu.categoryName, arr[0], arr[1]);
+        category.fullTree = res.fullTree;
+    }))))
     handleChangeTab(menuList.value?.[0].categoryName)
 }
 
diff --git a/linkis-web-next/src/pages/parameterConfig/modal.vue b/linkis-web-next/src/pages/parameterConfig/modal.vue
index 1e2f2bee53..5522d9a26b 100644
--- a/linkis-web-next/src/pages/parameterConfig/modal.vue
+++ b/linkis-web-next/src/pages/parameterConfig/modal.vue
@@ -16,69 +16,67 @@
   -->
 
 <template>
-    <FSpace>
-        <FModal
-            v-model:show="show"
-            displayDirective="if"
-            @ok="handleSaveGlobalSettings"
-        >
-            <template #title>
-                <div>{{ t('message.linkis.globalSettings') }}</div>
-            </template>
-            <div class="list-wrapper"> 
-                <template v-for="dataItem in dataList[0].settings" :key="dataItem.key">
-                    <div class="line">
-                        <div>
-                            <div class="title">{{ dataItem.name || dataItem.key }}</div>
-                            <div class="description">
-                                {{ dataItem.name ? `[${dataItem.key}]` : '' }}
-                            </div>
-                        </div>
-                        <div>
-                            <FSpace>
-                                <div style="width: 120px">
-                                    <FInputNumber
-                                        v-if="dataItem.validateType === 'NumInterval'"
-                                        style="width: 120px"
-                                        :precision="0"
-                                        v-model="dataItem.configValue"
-                                        :placeholder="dataItem.defaultValue ? `${$t('message.linkis.defaultValue')}: ${dataItem.defaultValue}` : $t('message.linkis.datasource.pleaseInput')"
-                                        :min="JSON.parse(dataItem.validateRange)[0]"
-                                        :max="JSON.parse(dataItem.validateRange)[1]"
-                                    />
-                                    <FForm v-else-if="dataItem.validateType === 'Regex'">
-                                        <FFormItem
-                                            :value="dataItem.configValue"
-                                            :rules="[{
-                                                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-                                                // @ts-ignore 
-                                                trigger: ['change', 'blur'],
-                                                message: dataItem.description,
-                                                type: 'regexp',
-                                                validator: () => new RegExp(dataItem.validateRange).test(dataItem.configValue)
-                                            }]"
-                                        >
-                                            <FInput
-                                                class="regex-input"
-                                                v-model="dataItem.configValue"
-                                                :placeholder="dataItem.defaultValue ? `${$t('message.linkis.defaultValue')}: ${dataItem.defaultValue}` : $t('message.linkis.datasource.pleaseInput')"
-                                            />
-                                        </FFormItem>
-                                    </FForm>
-                                    <FInput
-                                        v-else
-                                        v-model="dataItem.configValue"
-                                        :placeholder="dataItem.defaultValue ? `${$t('message.linkis.defaultValue')}: ${dataItem.defaultValue}` : $t('message.linkis.datasource.pleaseInput')"
-                                    />
-                                </div>
-                            </FSpace>
+    <FModal
+        v-model:show="show"
+        displayDirective="if"
+        @ok="handleSaveGlobalSettings"
+    >
+        <template #title>
+            <div>{{ t('message.linkis.globalSettings') }}</div>
+        </template>
+        <div class="list-wrapper"> 
+            <template v-for="dataItem in dataList[0].settings" :key="dataItem.key">
+                <div class="line">
+                    <div>
+                        <div class="title">{{ dataItem.name || dataItem.key }}</div>
+                        <div class="description">
+                            {{ dataItem.name ? `[${dataItem.key}]` : '' }}
                         </div>
                     </div>
-                    <FDivider />
-                </template>
-            </div>
-        </FModal>
-    </FSpace>
+                    <div>
+                        <FSpace>
+                            <div style="width: 120px">
+                                <FInputNumber
+                                    v-if="dataItem.validateType === 'NumInterval'"
+                                    style="width: 120px"
+                                    :precision="0"
+                                    v-model="dataItem.configValue"
+                                    :placeholder="dataItem.defaultValue ? `${$t('message.linkis.defaultValue')}: ${dataItem.defaultValue}` : $t('message.linkis.datasource.pleaseInput')"
+                                    :min="JSON.parse(dataItem.validateRange)[0]"
+                                    :max="JSON.parse(dataItem.validateRange)[1]"
+                                />
+                                <FForm v-else-if="dataItem.validateType === 'Regex'">
+                                    <FFormItem
+                                        :value="dataItem.configValue"
+                                        :rules="[{
+                                            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+                                            // @ts-ignore 
+                                            trigger: ['change', 'blur'],
+                                            message: dataItem.description,
+                                            type: 'regexp',
+                                            validator: () => new RegExp(dataItem.validateRange).test(dataItem.configValue)
+                                        }]"
+                                    >
+                                        <FInput
+                                            class="regex-input"
+                                            v-model="dataItem.configValue"
+                                            :placeholder="dataItem.defaultValue ? `${$t('message.linkis.defaultValue')}: ${dataItem.defaultValue}` : $t('message.linkis.datasource.pleaseInput')"
+                                        />
+                                    </FFormItem>
+                                </FForm>
+                                <FInput
+                                    v-else
+                                    v-model="dataItem.configValue"
+                                    :placeholder="dataItem.defaultValue ? `${$t('message.linkis.defaultValue')}: ${dataItem.defaultValue}` : $t('message.linkis.datasource.pleaseInput')"
+                                />
+                            </div>
+                        </FSpace>
+                    </div>
+                </div>
+                <FDivider />
+            </template>
+        </div>
+    </FModal>
 </template>
 
 <script setup lang="ts">
diff --git a/linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue b/linkis-web-next/src/pages/resource/history/drawer/index.vue
similarity index 73%
copy from linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue
copy to linkis-web-next/src/pages/resource/history/drawer/index.vue
index ef49880ce0..1348200d55 100644
--- a/linkis-web-next/src/pages/globalHistoryManagement/drawer/index.vue
+++ b/linkis-web-next/src/pages/resource/history/drawer/index.vue
@@ -15,9 +15,8 @@
   ~ limitations under the License.
   -->
 
-<template>
+  <template>
     <FDrawer
-        ref="drawerRef"
         v-model:show="show"
         placement="bottom"
         @ok="show = false"
@@ -40,22 +39,17 @@
                     </div>
                 </div>
                 <div class="title-text">
-                    {{ $t('message.linkis.jobId') }}:{{ tableRawData?.taskID }}
+                    {{ $t('message.linkis.tableColumns.engineInstance') }}:{{ engine?.serviceInstance }}
                 </div>
                 <FTabs @change="onDrawerTabChange" class="drawer-tabs" v-model="activeDrawerTabPane">
                     <template v-for="tab in tabs" :key="tab.name">
-                        <FTabPane
-                            :name="t(tab.name)"
-                            :value="tab.value"
-                        />
+                        <FTabPane :name="t(tab.name)" :value="tab.value" />
                     </template>
                 </FTabs>
             </div>
         </template>
         <template #default>
-            <task-logs v-if="activeDrawerTabPane === 1" :task="task" :isFullScreen="isFullScreen" />
-            <div v-if="activeDrawerTabPane === 2"></div>
-            <div v-if="activeDrawerTabPane === 3"></div>
+            <Log v-if="activeDrawerTabPane === 1" :engine="props.engine" :isFullScreen="isFullScreen" />
         </template>
     </FDrawer>
 </template>
@@ -63,40 +57,28 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 import { useI18n } from 'vue-i18n';
-import TaskLogs from './taskLogs.vue';
-import api from '@/service/api';
+import Log from './log.vue';
 
 const { t } = useI18n();
 
+const props = defineProps<{
+    engine: any,
+}>();
+
 enum TAB_PANE_VALUE {
-    'TASK_LOG' = 1,
-    'TASK_RESULT' = 2,
-    'ENGINE_LOG' = 3,
+    'ENGINE_LOG' = 1,
 }
 
 const tabs = [
-    { name: 'message.linkis.log', value: TAB_PANE_VALUE.TASK_LOG },
-    { name: 'message.linkis.result', value: TAB_PANE_VALUE.TASK_RESULT },
-    { name: 'message.linkis.detail', value: TAB_PANE_VALUE.ENGINE_LOG },
+    { name: 'message.common.log', value: TAB_PANE_VALUE.ENGINE_LOG },
 ];
 
-const activeDrawerTabPane = ref(TAB_PANE_VALUE.TASK_LOG);
+const activeDrawerTabPane = ref(TAB_PANE_VALUE.ENGINE_LOG);
 const show = ref(false);
-const tableRawData = ref<Record<string, string | number>>();
 const drawerHeight = ref(520);
-const drawerRef = ref<any>();
-const task = ref<any>();
 const isFullScreen = ref(false);
 
-const open = async (rawData: Record<string, string | number>) => {
-    tableRawData.value = rawData;
-    try {
-        const jobhistory = await api.fetch(`/jobhistory/${tableRawData.value.taskID}/get`, 'get')
-        task.value = jobhistory.task;
-    } catch (errorMsg) {
-        window.console.error(errorMsg)
-    }
-    activeDrawerTabPane.value = TAB_PANE_VALUE.TASK_LOG;
+const open = () => {
     show.value = true;
 };
 
diff --git a/linkis-web-next/src/pages/resource/history/drawer/log.vue b/linkis-web-next/src/pages/resource/history/drawer/log.vue
new file mode 100644
index 0000000000..e309ce9bbe
--- /dev/null
+++ b/linkis-web-next/src/pages/resource/history/drawer/log.vue
@@ -0,0 +1,253 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+  <template>
+    <div class="sub-tabs">
+        <FInput
+            class="search-input"
+            :placeholder="$t('message.linkis.search')"
+            v-model="searchText"
+            @change="handleSearch"
+        />
+        <div class="btn-group">
+            <FButton class="btn" :disabled="prevDisabled" @click="handleClickPrevBtn" type="primary">{{ t('message.linkis.previousPage') }}</FButton>
+            <FButton class="btn" :disabled="nextDisabled" @click="handleClickNextBtn" type="primary">{{ t('message.linkis.nextPage') }}</FButton>
+        </div>
+        <FButton class="download-btn" type="primary" @click="handleDownloadLogs">{{ t('message.linkis.downloadLogs') }}</FButton>
+        <FTabs @change="onTaskDetailTabChange" class="task-log-tabs" type="card" v-model="activeTaskLogTabPane">
+            <FTabPane name="stdout" value="stdout" />
+            <FTabPane name="stderr" value="stderr" />
+            <FTabPane name="gc" value="gc" />
+            <FTabPane v-if="['hive', 'spark'].includes(engine?.engineType)" name="yarnApp" value="yarnApp" />
+        </FTabs>
+        <WeEditor
+            :model-value="formattedLogs"
+            :height="props.isFullScreen ? '85vh' : '420px'"
+            v-if="formattedLogs !== ''"
+        />
+        <div v-else :style="{ height: props.isFullScreen ? '85vh' : '420px' }" class="no-log">
+            <img src="/log/noLog.svg" />
+            <div>{{ t('message.linkis.noLog') }}</div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { forEach, filter } from 'lodash';
+import { onMounted, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import WeEditor from '@/components/editor/index.vue';
+import api from '@/service/api';
+import { FModal } from '@fesjs/fes-design';
+
+const { t } = useI18n();
+
+const props = defineProps<{
+    engine: any,
+    isFullScreen: boolean,
+}>();
+
+const activeTaskLogTabPane = ref('stdout');
+const logs = ref<any>({});
+const formattedLogs = ref('');
+const fromLine = ref(0);
+const pageNow = ref(0);
+const searchText = ref('');
+const prevDisabled = ref(false);
+const nextDisabled = ref(true);
+const logPath = ref('');
+
+const onTaskDetailTabChange = (value: string) => {
+    activeTaskLogTabPane.value = value;
+    fromLine.value = 0;
+    pageNow.value = 0;
+    nextDisabled.value = true;
+    handleInitiation(fromLine.value);
+}
+
+const handleClickPrevBtn = () => {
+    pageNow.value = pageNow.value + 1;
+    fromLine.value = pageNow.value * 1000;
+    nextDisabled.value = false;
+    handleInitiation(fromLine.value);
+}
+
+const handleClickNextBtn = () => {
+    pageNow.value = pageNow.value - 1;
+    fromLine.value = pageNow.value * 1000;
+    if (pageNow.value === 0) {
+        nextDisabled.value = true;
+    }
+    handleInitiation(fromLine.value);
+}
+
+const handleDownloadLogs = () => {
+    FModal.confirm({
+        title: t('message.linkis.tip'),
+        content: t('message.linkis.downloadLogsConfirm'),
+        closable: true,
+        okText: t('message.linkis.submit'),
+        cancelText: t('message.linkis.cancel'),
+        onOk() {
+            const url = `/api/rest_j/v1/engineconnManager/downloadEngineLog?emInstance=${props.engine?.ecmInstance}&instance=${props.engine?.serviceInstance}&logDirSuffix=${encodeURIComponent(logPath.value)}&logType=${activeTaskLogTabPane.value}`;
+            const downloadLink = document.createElement('a');
+            downloadLink.href = url;
+            downloadLink.download = 'log';
+            downloadLink.style.display = 'none';
+            document.body.appendChild(downloadLink);
+            downloadLink.click();
+            document.body.removeChild(downloadLink);
+        },
+    });
+}
+
+const getSearchList = (log:string) => {
+    let MatchText = '';
+    const val = searchText.value;
+    if (!log) return MatchText;
+    if (val) {
+        // This part of the code is to make the regular expression not report an error, so add \ in front of the special symbol(这部分代码是为了让正则表达式不报错,所以在特殊符号前面加上\)
+        let formattedVal = '';
+        forEach(val, (o) => {
+            if (/^[\w\u4e00-\u9fa5]$/.test(o)) {
+                formattedVal += o;
+            } else {
+                formattedVal += `\\${o}`;
+            }
+        });
+        // Global and case-insensitive mode, the regular pattern is to match characters other than newlines before and after searchText(全局和不区分大小写模式,正则是匹配searchText前后出了换行符之外的字符)
+        const regexp = new RegExp(`.*${formattedVal}.*`, 'gi');
+        MatchText = filter(log.split('\n'), (item) => {
+            return regexp.test(item);
+        }).join('\n');
+    } else {
+        MatchText = log;
+    }
+    return MatchText;
+}
+
+const getFormattedLogs = () => {
+    const processedLogs:{ [key: string]: any } = {};
+    Object.keys(logs.value).map((key) => {
+        processedLogs[key] = getSearchList(logs.value[key]);
+    });
+    return processedLogs[activeTaskLogTabPane.value];
+}
+
+const handleSearch = () => {
+    formattedLogs.value = getFormattedLogs();
+}
+
+const handleInitiation = async (fromLine: number) => {
+    if(props.engine?.ecmInstance) {
+        try {
+          let res = await api.fetch('/linkisManager/openEngineLog', {
+            applicationName: 'linkis-cg-engineconn',
+            emInstance: props.engine?.ecmInstance,
+            instance: props.engine?.serviceInstance,
+            parameters: {
+                pageSize: 1000,
+                fromLine,
+                logType: activeTaskLogTabPane.value,
+                logDirSuffix: props.engine?.logDirSuffix,
+                ticketId: props.engine?.ticketId,
+            }
+          }, 'post') || {};
+          if (res && res.result) {
+            if (res.result.rows < 1000) {
+                prevDisabled.value = true;
+            } else {
+                prevDisabled.value = false;
+            }
+            logs.value = {
+              [activeTaskLogTabPane.value]: res.result.logs ? res.result.logs.join('\n') : ''
+            }
+            logPath.value = res.result.logPath.split('/').slice(0, -1).join('/');
+          }
+          handleSearch();
+        } catch (err) {
+          return;
+        }
+    }
+}
+
+onMounted(() => {
+    handleInitiation(0);
+})
+
+watch(
+    () => props.engine,
+    () => {
+        activeTaskLogTabPane.value = 'stdout';
+        handleInitiation(0);
+    }
+)
+</script>
+
+<style scoped lang="less">
+.sub-tabs {
+    position: relative;
+    .task-log-tabs {
+        background-color: #f7f7f8;
+        padding-left: 10px;
+    }
+    :deep(.fes-tabs-tab-active) {
+        background-color: #fff;
+    }
+    :deep(.fes-tabs-tab-pane-wrapper) {
+        display: none;
+    }
+    .search-input {
+        margin-top: 4px;
+        width: 140px;
+        position: absolute;
+        left: 480px;
+        z-index: 1000;
+    }
+    .btn-group {
+        display: flex;
+        margin-top: 4px;
+        height: 24px;
+        position: absolute;
+        left: 670px;
+        z-index: 1000;
+        gap: 10px;
+        .btn {
+            height: 24px;
+        }
+    }
+    .download-btn {
+        margin-top: 4px;
+        height: 24px;
+        position: absolute;
+        right: 16px;
+        z-index: 1000;
+    }
+    :deep(.fes-input-inner) {
+        height: 24px;
+    }
+    .no-log {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        gap: 30px;
+        color: #b7b7bc;
+        font-size: 14px;
+    }
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/resource/history/index.less b/linkis-web-next/src/pages/resource/history/index.less
deleted file mode 100644
index e0ccf0488f..0000000000
--- a/linkis-web-next/src/pages/resource/history/index.less
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
diff --git a/linkis-web-next/src/pages/resource/history/index.vue b/linkis-web-next/src/pages/resource/history/index.vue
index f40aa981bb..9ff1765b36 100644
--- a/linkis-web-next/src/pages/resource/history/index.vue
+++ b/linkis-web-next/src/pages/resource/history/index.vue
@@ -15,4 +15,239 @@
   ~ limitations under the License.
   -->
 
-<template>history</template>
+<template>
+    <FForm layout="inline" :span="4">
+        <FFormItem
+            :label="t('message.linkis.tableColumns.instanceName')"
+            :props="t('message.linkis.tableColumns.instanceName')"
+        >
+            <FInput
+                v-model="instanceName"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.initiator')"
+            :props="t('message.linkis.initiator')"
+        >
+            <FInput
+                v-model="creator"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem
+                :label="t('message.linkis.columns.createdTime')"
+                :prop="t('message.linkis.columns.createdTime')"
+                :span="8"
+            >
+                <!-- 日期修改完成后需要让日期选择组件失焦 -->
+                <FDatePicker
+                    type="daterange"
+                    v-model="timeRange"
+                    :shortcuts="shortcuts"
+                    @focus="(e:any) => { e.target.blur() }"
+                />
+            </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.tableColumns.engineType')"
+            :props="t('message.linkis.tableColumns.engineType')"
+        >
+            <FSelect
+                v-model="engineType"
+                clearable
+                :placeholder="t('message.linkis.unselect')"
+            >
+                <FOption
+                    v-for="(item, index) in engineTypes"
+                    :key="index"
+                    :value="item"
+                    :label="item"
+                />
+            </FSelect>
+        </FFormItem>
+        <FFormItem>
+            <div class="btns">
+                <FButton type="primary" @click="() => { search(currentPage) }">
+                    {{ t('message.linkis.find') }}
+                </FButton>
+            </div>
+        </FFormItem>
+    </FForm>
+    <f-table
+        :data="dataList"
+        class="table"
+        :rowKey="(row: Record<string, number | string>) => row.serviceInstance"
+        :emptyText="t('message.linkis.noDataText')"
+    >
+        <template v-for="col in tableColumns" :key="col.label">
+            <f-table-column
+                v-if="col.formatter"
+                :prop="col.prop"
+                :label="col.label"
+                :formatter="col.formatter"
+            />
+            <f-table-column
+                v-else
+                :prop="col.prop"
+                :label="col.label"
+                :action="col.action"
+            />
+        </template>
+    </f-table>
+    <FPagination
+        class="pagination"
+        show-size-changer
+        show-total
+        :total-count="totalSize"
+        v-model:currentPage="currentPage"
+        @change="handleChangePagination"
+        :pageSizeOption="[10, 20, 30, 50, 100]"
+        v-model:pageSize="pageSize"
+    />
+    <Drawer ref="drawerRef" :engine="currentEngine" />
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { FSpace, FTag } from '@fesjs/fes-design';
+import dayjs from 'dayjs';
+import { onMounted, reactive, ref, h } from 'vue';
+import Drawer from './drawer/index.vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const instanceName = ref('');
+const creator = ref('');
+const timeRange = ref([new Date().getTime() - 3600 * 1000 * 24 * 7, new Date().getTime()]);
+const engineType = ref('');
+const engineTypes = reactive<string[]>([]);
+const dataList = ref();
+const totalSize = ref(0);
+const currentPage = ref(1);
+const pageSize = ref(10);
+const drawerRef = ref();
+const currentEngine = ref();
+
+const shortcuts = {
+    [t('message.linkis.shortcuts.today')] : () => {
+        // 获取今天的开始时间和结束时间
+        const today = new Date();
+        const todayStartTime = new Date(
+            today.getFullYear(),
+            today.getMonth(),
+            today.getDate(),
+            0,
+            0,
+            0
+        );
+        const todayEndTime = new Date(
+            today.getFullYear(),
+            today.getMonth(),
+            today.getDate(),
+            23,
+            59,
+            59
+        );
+        return [todayStartTime.getTime(), todayEndTime.getTime()];
+    },
+    [t('message.linkis.shortcuts.week')] : () => [new Date().getTime() - 3600 * 1000 * 24 * 7, new Date().getTime()],
+    [t('message.linkis.shortcuts.month')] : () => [new Date().getTime() - 3600 * 1000 * 24 * 30, new Date().getTime()],
+}
+
+const handleViewLog = (engine: any) => {
+    currentEngine.value = engine;
+    drawerRef.value?.open();
+}
+
+const tableColumns = [
+    {
+        prop: 'serviceInstance',
+        label: t('message.linkis.tableColumns.engineInstance'),
+    },
+    {
+        prop: 'engineType',
+        label: t('message.linkis.tableColumns.engineType'),
+    },
+    {
+        prop: 'status',
+        label: t('message.linkis.tableColumns.status'),
+    },
+    {
+        prop: 'labelValue',
+        label: t('message.linkis.tableColumns.label'),
+        formatter: (data: { row: { labelValue: string }}) => 
+            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            // @ts-ignore
+            h(FSpace, data.row.labelValue.split(',').map(label => h(FTag, { type: 'default', effect: 'plain', size: 'small' }, label)))
+    },
+    {
+        prop: 'usedResource',
+        label: t('message.linkis.tableColumns.usedResources'),
+        formatter: ({ row }: { row: { usedResource: { cores: number, memory: number, instance: number }}}) =>
+            `${row.usedResource.cores}cores,${row.usedResource.memory / 1024 / 1024 / 1024}G,${row.usedResource.instance}apps`,
+    },
+    {
+        prop: 'createUser',
+        label: t('message.linkis.tableColumns.requestApplicationName'),
+    },
+    {
+        prop: 'usedTime',
+        label: t('message.linkis.tableColumns.startTime'),
+        formatter: ({ row }: { row: { usedTime: number } }) => dayjs(row.usedTime).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+        // 在“操作”两个字前添加空格,使其能与“查看日志”上下对齐
+        label: `\u00A0\u00A0\u00A0\u00A0${t('message.linkis.columns.control.title')}`,
+        action: [{
+            label: t('message.linkis.viewlog'),
+            func: handleViewLog,
+        }],
+    },
+];
+
+const search = (currentPage = 1) => {
+    api.fetch('/linkisManager/ecinfo/ecrHistoryList', {
+        instance: instanceName.value,
+        creator: creator.value,
+        startDate: dayjs(timeRange.value[0]).format('YYYY-MM-DD HH:mm:ss'),
+        endDate: dayjs(timeRange.value[1]).format('YYYY-MM-DD HH:mm:ss'),
+        engineType: engineType.value,
+        pageNow: currentPage,
+        pageSize: pageSize.value,
+    }, 'get').then((res: any) => {
+        dataList.value = [...res.engineList] || [];
+        totalSize.value = res.totalPage;
+    })
+}
+
+const handleChangePagination = () => {
+    search(currentPage.value)
+};
+
+const handleInitialization = () => {
+    engineTypes.length = 0;
+    api.fetch('/configuration/engineType', 'get').then((res: any) => {
+        res.engineType.forEach((engineType: string) => {
+            engineTypes.push(engineType);
+        })
+    })
+    search();
+}
+
+onMounted(() => {
+    handleInitialization();
+})
+</script>
+
+<style lang="less" scoped>
+.btns {
+    display: flex;
+    gap: 12px;
+}
+.pagination {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 16px;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/pages/resource/index.less b/linkis-web-next/src/pages/resource/index.less
deleted file mode 100644
index e0ccf0488f..0000000000
--- a/linkis-web-next/src/pages/resource/index.less
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
diff --git a/linkis-web-next/src/pages/resource/index.vue b/linkis-web-next/src/pages/resource/index.vue
index 698363e84e..7fa089f91c 100644
--- a/linkis-web-next/src/pages/resource/index.vue
+++ b/linkis-web-next/src/pages/resource/index.vue
@@ -17,18 +17,24 @@
 
 <template>
     <FTabs @change="onTabChange">
-        <FTabPane name="资源管理" value="/console/resource/manage"></FTabPane>
         <FTabPane
-            name="历史管理"
+            :name="t('message.linkis.sideNavList.function.children.resourceEngineConnList')"
             value="/console/resource/history"
-            displayDirective="show"
-        >
-        </FTabPane>
+        />
+        <FTabPane
+            :name="t('message.linkis.sideNavList.function.children.resource')"
+            value="/console/resource/manage"
+        />
     </FTabs>
-    <router-view></router-view>
+    <div class="content">
+        <router-view />
+    </div>
 </template>
 <script setup lang="ts">
 import { useRouter } from 'vue-router';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 const router = useRouter();
 const onTabChange = (key: string) => {
@@ -39,4 +45,8 @@ const onTabChange = (key: string) => {
 :deep(.fes-tabs-tab-pane-wrapper) {
     display: none;
 }
+
+.content {
+    margin-top: 20px;
+}
 </style>
diff --git a/linkis-web-next/src/pages/resource/manage/index.less b/linkis-web-next/src/pages/resource/manage/index.less
deleted file mode 100644
index e0ccf0488f..0000000000
--- a/linkis-web-next/src/pages/resource/manage/index.less
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
diff --git a/linkis-web-next/src/pages/resource/manage/index.vue b/linkis-web-next/src/pages/resource/manage/index.vue
index a4a28968d0..13cb63d4c6 100644
--- a/linkis-web-next/src/pages/resource/manage/index.vue
+++ b/linkis-web-next/src/pages/resource/manage/index.vue
@@ -15,4 +15,239 @@
   ~ limitations under the License.
   -->
 
-<template>manage</template>
+<template>
+    <FForm layout="inline" :span="4" v-if="isHistoryAdmin && isLogAdmin">
+        <FFormItem
+            :label="t('message.linkis.userName')"
+            :props="t('message.linkis.userName')"
+        >
+            <FInput
+                v-model="userName"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.formItems.appType')"
+            :props="t('message.linkis.formItems.appType')"
+        >
+            <FInput
+                v-model="creator"
+                :placeholder="t('message.linkis.datasource.pleaseInput')"
+            />
+        </FFormItem>
+        <FFormItem
+            :label="t('message.linkis.tableColumns.engineType')"
+            :props="t('message.linkis.tableColumns.engineType')"
+        >
+            <FSelect
+                v-model="engineType"
+                clearable
+                :placeholder="t('message.linkis.unselect')"
+            >
+                <FOption
+                    v-for="(item, index) in engineTypes"
+                    :key="index"
+                    :value="item"
+                    :label="item"
+                />
+            </FSelect>
+        </FFormItem>
+        <FFormItem>
+            <div class="btns">
+                <FButton type="primary" @click="search">
+                    {{ t('message.linkis.find') }}
+                </FButton>
+                <FButton type="danger" @click="handleReset">
+                    {{ t('message.linkis.resetAll') }}
+                </FButton>
+            </div>
+        </FFormItem>
+    </FForm>
+    <f-table
+        :data="dataList"
+        class="table"
+        :rowKey="(row: Record<string, number | string>) => row.id"
+        :emptyText="t('message.linkis.noDataText')"
+    >
+        <template v-for="col in tableColumns" :key="col.label">
+          <f-table-column
+              v-if="col.formatter"
+              :prop="col.prop"
+              :label="col.label"
+              :formatter="col.formatter"
+          />
+          <f-table-column
+              v-else
+              :prop="col.prop"
+              :label="col.label"
+              :action="col.action"
+          />
+        </template>
+        <f-table-column
+            v-if="isHistoryAdmin && isLogAdmin"
+            :label="`\u00A0\u00A0\u00A0\u00A0${t('message.linkis.columns.control.title')}`"
+            :action="[{
+                label: t('message.linkis.reset'),
+                func: (resource: any) => {
+                  handleReset(resource.id);
+                },
+            }]"
+        />
+    </f-table>
+    <FPagination
+        class="pagination"
+        show-size-changer
+        show-total
+        :total-count="totalSize"
+        v-model:currentPage="currentPage"
+        @change="handleChangePagination"
+        :pageSizeOption="[10, 20, 30, 50, 100]"
+        v-model:pageSize="pageSize"
+    />
+</template>
+
+<script lang="ts" setup>
+import api from '@/service/api';
+import { FMessage, FModal } from '@fesjs/fes-design';
+import { isNumber } from 'lodash';
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const isHistoryAdmin = ref(false);
+const isLogAdmin = ref(false);
+const userName = ref('');
+const creator = ref('');
+const engineType = ref('');
+const engineTypes = reactive<string[]>([]);
+const dataList = ref();
+const currentPage = ref(1);
+const pageSize = ref(10);
+const totalSize = ref(0);
+
+const handleReset = (resourceId: number) => {
+  FModal.confirm({
+    title: t('message.linkis.reset'),
+    content: isNumber(resourceId) ? t('message.linkis.resetTip'): t('message.linkis.resetAllTip'),
+    onOk: () => {
+      const param = isNumber(resourceId) ? { resourceId } : { }
+      api.fetch('linkisManager/rm/resetResource', param, 'delete').then(() => {
+        FMessage.success(t('message.linkis.udf.success'));
+        search();
+      }).catch((err: any) => {
+        window.console.error(err);
+      })
+    }
+  })
+}
+
+const tableColumns = reactive<{ prop?: string, label: string, action?: any, formatter?: any }[]>([
+    {
+        prop: 'username',
+        label: t('message.linkis.userName'),
+    },
+    {
+        prop: 'creator',
+        label: t('message.linkis.tableColumns.appType'),
+    },
+    {
+        prop: 'engineTypeWithVersion',
+        label: t('message.linkis.tableColumns.engineType'),
+    },
+    {
+        prop: 'usedResource',
+        label: t('message.linkis.tableColumns.engineUsed'),
+        formatter: ({ row }: { row: { usedResource: { cores: number, memory: number, instance: number }}}) =>
+            `${row.usedResource.cores}cores,${row.usedResource.memory / 1024 / 1024 / 1024}G,${row.usedResource.instance}apps`,
+    },
+    {
+        prop: 'maxResource',
+        label: t('message.linkis.tableColumns.engineTop'),
+        formatter: ({ row }: { row: { maxResource: { cores: number, memory: number, instance: number }}}) =>
+            `${row.maxResource.cores}cores,${row.maxResource.memory / 1024 / 1024 / 1024}G,${row.maxResource.instance}apps`,
+    },
+    {
+        prop: 'leftResource',
+        label: t('message.linkis.tableColumns.engineRemain'),
+        formatter: ({ row }: { row: { leftResource: { cores: number, memory: number, instance: number }}}) =>
+            `${row.leftResource.cores}cores,${row.leftResource.memory / 1024 / 1024 / 1024}G,${row.leftResource.instance}apps`,
+    },
+    {
+        prop: 'yarnUsedResource',
+        label: t('message.linkis.tableColumns.queueUsed'),
+        formatter: ({ row }: { row: { yarnUsedResource?: { cores: number, memory: number, instance: number }}}) => `
+            ${row.yarnUsedResource?.cores || 0}cores,
+            ${(row.yarnUsedResource?.memory || 0) / 1024 / 1024 / 1024}G,
+            ${row.yarnUsedResource?.instance || 0}apps
+        `,
+    },
+    {
+        prop: 'yarnMaxResource',
+        label: t('message.linkis.tableColumns.queueTop'),
+        formatter: ({ row }: { row: { yarnMaxResource?: { cores: number, memory: number, instance: number }}}) => `
+            ${row.yarnMaxResource?.cores || 0}cores,
+            ${(row.yarnMaxResource?.memory || 0) / 1024 / 1024 / 1024}G,
+            ${row.yarnMaxResource?.instance || 0}apps
+        `,
+    },
+    {
+        prop: 'yarnLeftResource',
+        label: t('message.linkis.tableColumns.queenRemain'),
+        formatter: ({ row }: { row: { yarnLeftResource?: { cores: number, memory: number, instance: number }}}) => `
+            ${row.yarnLeftResource?.cores || 0}cores,
+            ${(row.yarnLeftResource?.memory || 0) / 1024 / 1024 / 1024}G,
+            ${row.yarnLeftResource?.instance || 0}apps
+        `,
+    },
+])
+
+const search = (currentPage = 1) => {
+    api.fetch('/linkisManager/rm/allUserResource', {
+        username: userName.value,
+        creator: creator.value,
+        engineType: engineType.value,
+        pageNow: currentPage,
+        pageSize: pageSize.value,
+    }, 'get').then((res: any) => {
+        dataList.value = [...res.resources] || [];
+        totalSize.value = res.total;
+    })
+}
+
+const handleChangePagination = () => {
+    search(currentPage.value)
+}
+
+const handleInitialization = () => {
+    engineTypes.length = 0;
+    api.fetch('/linkisManager/rm/engineType', 'get').then((res: any) => {
+        res.engineType.forEach((engineType: string) => {
+            engineTypes.push(engineType);
+        })
+    })
+    search();
+}
+
+onMounted(() => {
+    // Get whether it is a historical administrator(获取是否是历史管理员权限)
+    api.fetch('/jobhistory/governanceStationAdmin', 'get').then(
+        (res: { admin: boolean, historyAdmin: boolean }) => {
+        isLogAdmin.value = res.admin;
+        isHistoryAdmin.value = res.historyAdmin;
+    });
+    handleInitialization();
+})
+</script>
+
+<style lang="less" scoped>
+.btns {
+    display: flex;
+    gap: 12px;
+}
+.pagination {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 16px;
+}
+</style>
\ No newline at end of file
diff --git a/linkis-web-next/src/router/index.ts b/linkis-web-next/src/router/index.ts
index 7d7c97662d..7e3981a500 100644
--- a/linkis-web-next/src/router/index.ts
+++ b/linkis-web-next/src/router/index.ts
@@ -52,7 +52,7 @@ const routes: Array<RouteRecordRaw> = [
                     {
                         name: 'resource',
                         path: '/console/resource',
-                        redirect: '/console/resource/manage',
+                        redirect: '/console/resource/history',
                         component: () => import('@/pages/resource/index.vue'),
                         meta: {
                             title: 'Resource',
@@ -91,6 +91,45 @@ const routes: Array<RouteRecordRaw> = [
                         },
                         children: [],
                     },
+                    {
+                        name: 'globalVariables',
+                        path: '/console/globalVariables',
+                        component: () => import('@/pages/globalVariables/index.vue'),
+                        meta: {
+                            title: 'Global Variables',
+                            publicPage: true,
+                        },
+                        children: [],
+                    },
+                    {
+                        name: 'ECMManagement',
+                        path: '/console/ECMManagement',
+                        component: () => import('@/pages/ECMManagement/index.vue'),
+                        meta: {
+                            title: 'ECMManagement',
+                            publicPage: true,
+                        },
+                        children: [],
+                    },
+                    {
+                        name: 'Engines',
+                        path: '/console/ECMManagement/engines/:instance',
+                        component: () => import('@/pages/ECMManagement/engine.vue'),
+                        meta: {
+                            title: 'ECM Engines',
+                            publicPage: true,
+                        },
+                    },
+                    {
+                        name: 'microServiceManagement',
+                        path: '/console/microServiceManagement',
+                        component: () => import('@/pages/microServiceManagement/index.vue'),
+                        meta: {
+                            title: 'Microservice Management',
+                            publicPage: true,
+                        },
+                        children: [],
+                    },
                 ]
             },
         ],


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@linkis.apache.org
For additional commands, e-mail: commits-help@linkis.apache.org