mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat(task): Add Display for Number of Running Tasks (#7564)
This commit is contained in:
parent
e11a0d5766
commit
4a4e568d4a
@ -29,3 +29,17 @@ func (b *BaseApi) PageTasks(c *gin.Context) {
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags TaskLog
|
||||
// @Summary Get the number of executing tasks
|
||||
// @Success 200 {object} int64
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /logs/tasks/executing/count [get]
|
||||
func (b *BaseApi) CountExecutingTasks(c *gin.Context) {
|
||||
count, err := taskService.CountExecutingTask()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, count)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ type ITaskRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Task, error)
|
||||
Update(ctx context.Context, task *model.Task) error
|
||||
UpdateRunningTaskToFailed() error
|
||||
CountExecutingTask() (int64, error)
|
||||
|
||||
WithByID(id string) DBOption
|
||||
WithResourceID(id uint) DBOption
|
||||
@ -95,3 +96,9 @@ func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
||||
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
||||
return getTaskDb(commonRepo.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) CountExecutingTask() (int64, error) {
|
||||
var count int64
|
||||
err := getTaskDb(commonRepo.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ type TaskLogService struct{}
|
||||
type ITaskLogService interface {
|
||||
Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error)
|
||||
SyncForRestart() error
|
||||
CountExecutingTask() (int64, error)
|
||||
}
|
||||
|
||||
func NewITaskService() ITaskLogService {
|
||||
@ -45,3 +46,7 @@ func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, e
|
||||
func (u *TaskLogService) SyncForRestart() error {
|
||||
return taskRepo.UpdateRunningTaskToFailed()
|
||||
}
|
||||
|
||||
func (u *TaskLogService) CountExecutingTask() (int64, error) {
|
||||
return taskRepo.CountExecutingTask()
|
||||
}
|
||||
|
@ -14,5 +14,6 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
operationRouter.GET("/system/files", baseApi.GetSystemFiles)
|
||||
operationRouter.POST("/system", baseApi.GetSystemLogs)
|
||||
operationRouter.POST("/tasks/search", baseApi.PageTasks)
|
||||
operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks)
|
||||
}
|
||||
}
|
||||
|
@ -24,3 +24,7 @@ export const cleanLogs = (param: Log.CleanLog) => {
|
||||
export const searchTasks = (req: Log.SearchTaskReq) => {
|
||||
return http.post<ResPage<Log.Task>>(`/logs/tasks/search`, req);
|
||||
};
|
||||
|
||||
export const countExecutingTask = () => {
|
||||
return http.get<number>(`/tasks/executing/count`);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-tooltip v-if="msg" effect="dark" placement="bottom">
|
||||
<template #content>
|
||||
<div style="width: 300px; word-break: break-all">{{ msg }}</div>
|
||||
<div class="content">{{ msg }}</div>
|
||||
</template>
|
||||
<el-tag size="small" :type="getType(statusItem)" round effect="light">
|
||||
<span class="flx-align-center">
|
||||
@ -19,6 +19,10 @@
|
||||
<el-icon v-if="loadingIcon(statusItem)" class="is-loading">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<el-icon size="15" v-if="operate">
|
||||
<CaretRight v-if="statusItem == 'running'" />
|
||||
<CaretBottom v-if="statusItem == 'stopped'" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-tag>
|
||||
</template>
|
||||
@ -29,6 +33,11 @@ import { computed } from 'vue';
|
||||
const props = defineProps({
|
||||
status: String,
|
||||
msg: String,
|
||||
operate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const statusItem = computed(() => {
|
||||
@ -88,3 +97,10 @@ const loadingIcon = (status: string): boolean => {
|
||||
return loadingStatus.indexOf(status) > -1;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
width: 300px;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
@ -19,11 +19,9 @@ const props = defineProps({
|
||||
default: '#005eeb',
|
||||
},
|
||||
});
|
||||
// 图标在 iconfont 中的名字
|
||||
const iconClassName = computed(() => {
|
||||
return `#${props.iconName}`;
|
||||
});
|
||||
// 给图标添加上类名
|
||||
const svgClass = computed(() => {
|
||||
if (props.className) {
|
||||
return `svg-icon ${props.className}`;
|
||||
|
@ -39,5 +39,5 @@ defineProps<{ menuList: RouteRecordRaw[] }>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../index.scss';
|
||||
@use '../index.scss';
|
||||
</style>
|
||||
|
@ -1,3 +1,4 @@
|
||||
@use '@/styles/var.scss' as *;
|
||||
.el-menu {
|
||||
user-select: none;
|
||||
background: none;
|
||||
|
@ -7,24 +7,37 @@
|
||||
element-loading-background="rgba(122, 122, 122, 0.01)"
|
||||
>
|
||||
<Logo :isCollapse="isCollapse" />
|
||||
|
||||
<span v-if="nodes.length !== 0" class="el-dropdown-link">
|
||||
<div class="el-dropdown-link flex justify-between items-center">
|
||||
<el-button type="text" @click="openChangeNode" @mouseenter="openChangeNode">
|
||||
<span>
|
||||
{{ loadCurrentName() }}
|
||||
</span>
|
||||
<el-dropdown v-if="nodes.length !== 0" placement="right-start" @command="changeNode">
|
||||
<el-icon class="ico"><Switch /></el-icon>
|
||||
</el-button>
|
||||
<div>
|
||||
<el-dropdown
|
||||
ref="nodeChangeRef"
|
||||
trigger="contextmenu"
|
||||
v-if="nodes.length > 0"
|
||||
placement="right-start"
|
||||
@command="changeNode"
|
||||
>
|
||||
<span></span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="local">
|
||||
<SvgIcon class="ico" iconName="p-host" />
|
||||
{{ $t('terminal.local') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-for="item in nodes" :key="item.name" :command="item.name">
|
||||
<SvgIcon class="ico" iconName="p-host" />
|
||||
{{ item.name }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
</div>
|
||||
<el-tag type="danger" size="small" effect="light" class="mr-2">{{ taskCount }}</el-tag>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
@ -59,16 +72,18 @@ import SubItem from './components/SubItem.vue';
|
||||
import router, { menuList } from '@/routers/router';
|
||||
import { logOutApi } from '@/api/modules/auth';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { DropdownInstance, ElMessageBox } from 'element-plus';
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { isString } from '@vueuse/core';
|
||||
import { getSettingInfo, listNodeOptions } from '@/api/modules/setting';
|
||||
import { countExecutingTask } from '@/api/modules/log';
|
||||
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
const globalStore = GlobalStore();
|
||||
const nodes = ref([]);
|
||||
const nodeChangeRef = ref<DropdownInstance>();
|
||||
defineProps({
|
||||
menuRouter: {
|
||||
type: Boolean,
|
||||
@ -86,6 +101,10 @@ let routerMenus = computed((): RouteRecordRaw[] => {
|
||||
return menuStore.menuList.filter((route) => route.meta && !route.meta.hideInSidebar);
|
||||
});
|
||||
|
||||
const openChangeNode = () => {
|
||||
nodeChangeRef.value?.handleOpen();
|
||||
};
|
||||
|
||||
const loadCurrentName = () => {
|
||||
if (globalStore.currentNode) {
|
||||
return globalStore.currentNode === 'local' ? i18n.global.t('terminal.local') : globalStore.currentNode;
|
||||
@ -223,15 +242,26 @@ const search = async () => {
|
||||
menuStore.menuList = rstMenuList;
|
||||
};
|
||||
|
||||
const taskCount = ref(0);
|
||||
const checkTask = async () => {
|
||||
try {
|
||||
const res = await countExecutingTask();
|
||||
taskCount.value = res.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
menuStore.setMenuList(menuList);
|
||||
search();
|
||||
loadNodes();
|
||||
checkTask();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
@use './index.scss';
|
||||
|
||||
.sidebar-container {
|
||||
position: relative;
|
||||
@ -252,20 +282,13 @@ onMounted(() => {
|
||||
|
||||
.el-dropdown-link {
|
||||
margin-top: -5px;
|
||||
margin-left: 30px;
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
height: 38px;
|
||||
}
|
||||
.ico {
|
||||
margin-top: -20px;
|
||||
display: flex;
|
||||
float: left;
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
height: 20px !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -732,7 +732,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../index.scss';
|
||||
@use '../index.scss';
|
||||
@media only screen and (max-width: 1300px) {
|
||||
.install-card-col-12 {
|
||||
max-width: 100%;
|
||||
|
@ -138,10 +138,11 @@
|
||||
<template #default="{ row }">
|
||||
<Status
|
||||
v-if="row.status === 'Running'"
|
||||
:operate="true"
|
||||
:status="row.status"
|
||||
@click="opWebsite('stop', row.id)"
|
||||
/>
|
||||
<Status v-else :status="row.status" @click="opWebsite('start', row.id)" />
|
||||
<Status v-else :status="row.status" :operate="true" @click="opWebsite('start', row.id)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
Loading…
x
Reference in New Issue
Block a user