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>
+ :
+ <FInput
+ v-model:modelValue="label.stringValue"
+ :placeholder="t('message.linkis.datasource.pleaseInput')"
+ />
+
+ <FButton @click="() => { handleRemove(index) }" type="primary">
+ {{ t('message.linkis.remove') }}
+ </FButton>
+ </FFormItem>
+ </FForm>
+ <FButton type="info" long @click="handleAdd">{{ t('message.linkis.addTags') }}</FButton>
+
+ <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"
+ />
+ :
+ <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>
+ :
+ <FInput
+ v-model:modelValue="label.stringValue"
+ :placeholder="t('message.linkis.datasource.pleaseInput')"
+ />
+
+ <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