[KYUUBI #3646][UI] Init Session Statistic Page

### _Why are the changes needed?_

Init Session Statistic Page

Close #3646

### _How was this patch tested?_
- [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible

- [ ] Add screenshots for manual tests if appropriate

- [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request

![popo_2023-03-20  17-47-24](https://user-images.githubusercontent.com/52876270/226303508-ab55d7d5-62c5-4062-bfab-70c483517e99.jpg)

Closes #4564 from zwangsheng/KYUUBI_3646.

Closes #3646

cbe0842ba [zwangsheng] [KYUUBI #3646] fix style
930cbb12a [zwangsheng] [KYUUBI #3646] bracket same line
d2ab1d5fd [zwangsheng] [KYUUBI #3646] Fix i18n about status
7f059e2df [zwangsheng] [KYUUBI #3646] Fix i18n about status
452c3ee5a [zwangsheng] [KYUUBI #3646] Remove unused style class
8967652bc [zwangsheng] [KYUUBI #3646] Add date fns license
70f2472c2 [zwangsheng] [KYUUBI #3646] install date-fns
8a3e845ff [zwangsheng] [KYUUBI #3646][UI] Init Session Statistic Page

Lead-authored-by: He Zhao <hezhao2@cisco.com>
Co-authored-by: zwangsheng <2213335496@qq.com>
Signed-off-by: Cheng Pan <chengpan@apache.org>
This commit is contained in:
He Zhao 2023-03-24 11:43:15 +08:00 committed by Cheng Pan
parent 6f803c0015
commit 0d5eaa2d0b
No known key found for this signature in database
GPG Key ID: 8001952629BCC75D
12 changed files with 289 additions and 2 deletions

View File

@ -456,6 +456,8 @@ is auto-generated by `pnpm licenses list --prod`.
├────────────────────────────────────┼──────────────┤
│ csstype │ MIT │
├────────────────────────────────────┼──────────────┤
│ date-fns │ MIT │
├────────────────────────────────────┼──────────────┤
│ dayjs │ MIT │
├────────────────────────────────────┼──────────────┤
│ delayed-stream │ MIT │

View File

@ -69,6 +69,9 @@
"exports": "never",
"functions": "never"
}],
"prettier/prettier": ["error", {
"bracketSameLine": true
}],
"vue/multi-word-component-names": "off",
"vue/component-definition-name-casing": "off",
"vue/require-valid-default-prop": "off",

View File

@ -17,6 +17,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.0.9",
"axios": "^0.27.2",
"date-fns": "^2.29.3",
"element-plus": "^2.2.12",
"pinia": "^2.0.18",
"pinia-plugin-persistedstate": "^2.1.1",

View File

@ -12,6 +12,7 @@ specifiers:
'@vue/eslint-config-typescript': ^11.0.0
'@vue/test-utils': ^2.0.2
axios: ^0.27.2
date-fns: ^2.29.3
element-plus: ^2.2.12
eslint: ^8.21.0
eslint-plugin-prettier: ^4.2.1
@ -32,6 +33,7 @@ specifiers:
dependencies:
'@element-plus/icons-vue': 2.0.9_vue@3.2.37
axios: 0.27.2
date-fns: 2.29.3
element-plus: 2.2.13_vue@3.2.37
pinia: 2.0.18_j6bzmzd4ujpabbp5objtwxyjp4
pinia-plugin-persistedstate: 2.1.1_pinia@2.0.18
@ -907,6 +909,11 @@ packages:
whatwg-url: 11.0.0
dev: true
/date-fns/2.29.3:
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
engines: {node: '>=0.11'}
dev: false
/dayjs/1.11.5:
resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==}
dev: false

View File

@ -0,0 +1,32 @@
/*
* 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.
*/
import request from '@/utils/request'
export function getAllSessions() {
return request({
url: 'api/v1/sessions',
method: 'get'
})
}
export function deleteSession(sessionId: string) {
return request({
url: `api/v1/sessions/${sessionId}`,
method: 'delete'
})
}

View File

@ -16,5 +16,16 @@
*/
export default {
test: 'test'
test: 'test',
user: 'User',
client_ip: 'Client IP',
kyuubi_instance: 'Kyuubi Instance',
session_id: 'Session ID',
create_time: 'Create Time',
operation: 'Operation',
delete_confirm: 'Delete Confirm',
message: {
delete_succeeded: 'Delete {name} Succeeded',
delete_failed: 'Delete {name} Failed'
}
}

View File

@ -16,5 +16,16 @@
*/
export default {
test: '测试'
test: '测试',
user: '用户',
client_ip: '客户端地址',
kyuubi_instance: '服务端地址',
session_id: 'Session ID',
create_time: '创建时间',
operation: '操作',
delete_confirm: '确认删除',
message: {
delete_succeeded: '删除 {name} 成功',
delete_failed: '删除 {name} 失败'
}
}

View File

@ -20,6 +20,7 @@ import overviewRoutes from './overview'
import workloadRoutes from './workload'
import operationRoutes from './operation'
import contactRoutes from './contact'
import sessionRoutes from './session'
const routes = [
{
@ -36,6 +37,7 @@ const routes = [
redirect: 'overview',
children: [
...overviewRoutes,
...sessionRoutes,
...workloadRoutes,
...operationRoutes,
...contactRoutes

View File

@ -0,0 +1,26 @@
/*
* 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.
*/
const routes = [
{
path: '/session/session-statistics',
name: 'session-statistics',
component: () => import('@/views/session/session-statistics/index.vue')
}
]
export default routes

View File

@ -0,0 +1,78 @@
/*
* 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.
*/
import { ref, Ref } from 'vue'
export function useTable() {
const list: Ref<any[]> = ref([])
const tableData: Ref<any[]> = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const totalPage = ref(1)
const handleSizeChange = (val: number) => {
if (
currentPage.value === 1 ||
(currentPage.value > 1 && totalPage.value > (currentPage.value - 1) * val)
) {
loading.value = true
setTimeout(() => {
setTableData()
}, 200)
}
}
const handleCurrentChange = () => {
loading.value = true
setTimeout(() => {
setTableData()
}, 200)
}
const setTableData = () => {
tableData.value = [...list.value].splice(
(currentPage.value - 1) * pageSize.value,
pageSize.value
)
loading.value = false
}
const getList = (func: Function, data?: any) => {
loading.value = true
func(data)
.then((res: any[]) => (list.value = res || []))
.catch(() => (list.value = []))
.finally(() => {
currentPage.value = 1
pageSize.value = 10
totalPage.value = list.value.length
setTableData()
})
}
return {
tableData,
loading,
currentPage,
pageSize,
totalPage,
handleSizeChange,
handleCurrentChange,
getList
}
}

View File

@ -21,6 +21,16 @@ export const MENUS = [
icon: 'Odometer',
router: '/overview'
},
{
label: 'Session Management',
icon: 'List',
children: [
{
label: 'Session Statistics',
router: '/session/session-statistics'
}
]
},
{
label: 'Workload',
icon: 'List',

View File

@ -0,0 +1,104 @@
<!--
* 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>
<!-- TODO we need search here -->
<el-card>
<el-table
v-loading="loading"
:data="tableData"
max-height="500px"
style="width: 100%">
<el-table-column prop="user" :label="$t('user')" width="160px" />
<!-- TODO need jump to engine page -->
<el-table-column prop="engineId" :label="$t('engine_ip')" width="160px" />
<el-table-column prop="ipAddr" :label="$t('client_ip')" width="160px" />
<el-table-column
prop="kyuubiInstance"
:label="$t('kyuubi_instance')"
width="180px" />
<!-- TODO need jump to session page -->
<el-table-column
prop="identifier"
:label="$t('session_id')"
width="300px" />
<el-table-column :label="$t('create_time')" width="200">
<template #default="scope">
{{
scope.row.createTime != null && scope.row.createTime > -1
? format(scope.row.createTime, 'yyyy-MM-dd HH:mm:ss')
: '-'
}}
</template>
</el-table-column>
<el-table-column fixed="right" :label="$t('operation')">
<template #default="scope">
<el-popconfirm
:title="$t('delete_confirm')"
@confirm="handleDeleteSession(scope.row.identifier)">
<template #reference>
<span>
<el-tooltip
effect="dark"
:content="$t('delete')"
placement="top">
<template #default>
<el-button type="danger" icon="Delete" circle />
</template>
</el-tooltip>
</span>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script lang="ts" setup>
import { format } from 'date-fns'
import { getAllSessions, deleteSession } from '@/api/session'
import { ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n'
import { useTable } from '@/views/common/use-table'
const { t } = useI18n()
const { tableData, loading, getList: _getList } = useTable()
const handleDeleteSession = (sessionId: string) => {
deleteSession(sessionId)
.then(() => {
// need add delete success or failed logic after api support
ElMessage({
message: t('message.delete_succeeded', { name: 'session' }),
type: 'success'
})
})
.catch(() => {
ElMessage({
message: t('message.delete_failed', { name: 'session' }),
type: 'error'
})
})
.finally(() => {
getList()
})
}
const getList = () => {
_getList(getAllSessions)
}
getList()
</script>