1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 14:08:06 +08:00

feat(task): Add Display for Number of Running Tasks (#7564)

This commit is contained in:
zhengkunwang 2024-12-26 10:15:57 +08:00 committed by GitHub
parent e11a0d5766
commit 4a4e568d4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 106 additions and 36 deletions

View File

@ -29,3 +29,17 @@ func (b *BaseApi) PageTasks(c *gin.Context) {
Total: total, 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)
}

View File

@ -19,6 +19,7 @@ type ITaskRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.Task, error) Page(page, size int, opts ...DBOption) (int64, []model.Task, error)
Update(ctx context.Context, task *model.Task) error Update(ctx context.Context, task *model.Task) error
UpdateRunningTaskToFailed() error UpdateRunningTaskToFailed() error
CountExecutingTask() (int64, error)
WithByID(id string) DBOption WithByID(id string) DBOption
WithResourceID(id uint) 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 { 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 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
}

View File

@ -10,6 +10,7 @@ type TaskLogService struct{}
type ITaskLogService interface { type ITaskLogService interface {
Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error)
SyncForRestart() error SyncForRestart() error
CountExecutingTask() (int64, error)
} }
func NewITaskService() ITaskLogService { func NewITaskService() ITaskLogService {
@ -45,3 +46,7 @@ func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, e
func (u *TaskLogService) SyncForRestart() error { func (u *TaskLogService) SyncForRestart() error {
return taskRepo.UpdateRunningTaskToFailed() return taskRepo.UpdateRunningTaskToFailed()
} }
func (u *TaskLogService) CountExecutingTask() (int64, error) {
return taskRepo.CountExecutingTask()
}

View File

@ -14,5 +14,6 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
operationRouter.GET("/system/files", baseApi.GetSystemFiles) operationRouter.GET("/system/files", baseApi.GetSystemFiles)
operationRouter.POST("/system", baseApi.GetSystemLogs) operationRouter.POST("/system", baseApi.GetSystemLogs)
operationRouter.POST("/tasks/search", baseApi.PageTasks) operationRouter.POST("/tasks/search", baseApi.PageTasks)
operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks)
} }
} }

View File

@ -24,3 +24,7 @@ export const cleanLogs = (param: Log.CleanLog) => {
export const searchTasks = (req: Log.SearchTaskReq) => { export const searchTasks = (req: Log.SearchTaskReq) => {
return http.post<ResPage<Log.Task>>(`/logs/tasks/search`, req); return http.post<ResPage<Log.Task>>(`/logs/tasks/search`, req);
}; };
export const countExecutingTask = () => {
return http.get<number>(`/tasks/executing/count`);
};

View File

@ -1,7 +1,7 @@
<template> <template>
<el-tooltip v-if="msg" effect="dark" placement="bottom"> <el-tooltip v-if="msg" effect="dark" placement="bottom">
<template #content> <template #content>
<div style="width: 300px; word-break: break-all">{{ msg }}</div> <div class="content">{{ msg }}</div>
</template> </template>
<el-tag size="small" :type="getType(statusItem)" round effect="light"> <el-tag size="small" :type="getType(statusItem)" round effect="light">
<span class="flx-align-center"> <span class="flx-align-center">
@ -19,6 +19,10 @@
<el-icon v-if="loadingIcon(statusItem)" class="is-loading"> <el-icon v-if="loadingIcon(statusItem)" class="is-loading">
<Loading /> <Loading />
</el-icon> </el-icon>
<el-icon size="15" v-if="operate">
<CaretRight v-if="statusItem == 'running'" />
<CaretBottom v-if="statusItem == 'stopped'" />
</el-icon>
</span> </span>
</el-tag> </el-tag>
</template> </template>
@ -29,6 +33,11 @@ import { computed } from 'vue';
const props = defineProps({ const props = defineProps({
status: String, status: String,
msg: String, msg: String,
operate: {
type: Boolean,
default: false,
required: false,
},
}); });
const statusItem = computed(() => { const statusItem = computed(() => {
@ -88,3 +97,10 @@ const loadingIcon = (status: string): boolean => {
return loadingStatus.indexOf(status) > -1; return loadingStatus.indexOf(status) > -1;
}; };
</script> </script>
<style lang="scss" scoped>
.content {
width: 300px;
word-break: break-all;
}
</style>

View File

@ -19,11 +19,9 @@ const props = defineProps({
default: '#005eeb', default: '#005eeb',
}, },
}); });
// iconfont
const iconClassName = computed(() => { const iconClassName = computed(() => {
return `#${props.iconName}`; return `#${props.iconName}`;
}); });
//
const svgClass = computed(() => { const svgClass = computed(() => {
if (props.className) { if (props.className) {
return `svg-icon ${props.className}`; return `svg-icon ${props.className}`;

View File

@ -39,5 +39,5 @@ defineProps<{ menuList: RouteRecordRaw[] }>();
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '../index.scss'; @use '../index.scss';
</style> </style>

View File

@ -1,3 +1,4 @@
@use '@/styles/var.scss' as *;
.el-menu { .el-menu {
user-select: none; user-select: none;
background: none; background: none;

View File

@ -7,24 +7,37 @@
element-loading-background="rgba(122, 122, 122, 0.01)" element-loading-background="rgba(122, 122, 122, 0.01)"
> >
<Logo :isCollapse="isCollapse" /> <Logo :isCollapse="isCollapse" />
<div class="el-dropdown-link flex justify-between items-center">
<span v-if="nodes.length !== 0" class="el-dropdown-link"> <el-button type="text" @click="openChangeNode" @mouseenter="openChangeNode">
{{ loadCurrentName() }} <span>
</span> {{ loadCurrentName() }}
<el-dropdown v-if="nodes.length !== 0" placement="right-start" @command="changeNode"> </span>
<el-icon class="ico"><Switch /></el-icon> </el-button>
<template #dropdown> <div>
<el-dropdown-menu> <el-dropdown
<el-dropdown-item command="local"> ref="nodeChangeRef"
{{ $t('terminal.local') }} trigger="contextmenu"
</el-dropdown-item> v-if="nodes.length > 0"
<el-dropdown-item v-for="item in nodes" :key="item.name" :command="item.name"> placement="right-start"
{{ item.name }} @command="changeNode"
</el-dropdown-item> >
</el-dropdown-menu> <span></span>
</template> <template #dropdown>
</el-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-scrollbar>
<el-menu <el-menu
:default-active="activeMenu" :default-active="activeMenu"
@ -59,16 +72,18 @@ import SubItem from './components/SubItem.vue';
import router, { menuList } from '@/routers/router'; import router, { menuList } from '@/routers/router';
import { logOutApi } from '@/api/modules/auth'; import { logOutApi } from '@/api/modules/auth';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElMessageBox } from 'element-plus'; import { DropdownInstance, ElMessageBox } from 'element-plus';
import { GlobalStore, MenuStore } from '@/store'; import { GlobalStore, MenuStore } from '@/store';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { isString } from '@vueuse/core'; import { isString } from '@vueuse/core';
import { getSettingInfo, listNodeOptions } from '@/api/modules/setting'; import { getSettingInfo, listNodeOptions } from '@/api/modules/setting';
import { countExecutingTask } from '@/api/modules/log';
const route = useRoute(); const route = useRoute();
const menuStore = MenuStore(); const menuStore = MenuStore();
const globalStore = GlobalStore(); const globalStore = GlobalStore();
const nodes = ref([]); const nodes = ref([]);
const nodeChangeRef = ref<DropdownInstance>();
defineProps({ defineProps({
menuRouter: { menuRouter: {
type: Boolean, type: Boolean,
@ -86,6 +101,10 @@ let routerMenus = computed((): RouteRecordRaw[] => {
return menuStore.menuList.filter((route) => route.meta && !route.meta.hideInSidebar); return menuStore.menuList.filter((route) => route.meta && !route.meta.hideInSidebar);
}); });
const openChangeNode = () => {
nodeChangeRef.value?.handleOpen();
};
const loadCurrentName = () => { const loadCurrentName = () => {
if (globalStore.currentNode) { if (globalStore.currentNode) {
return globalStore.currentNode === 'local' ? i18n.global.t('terminal.local') : globalStore.currentNode; return globalStore.currentNode === 'local' ? i18n.global.t('terminal.local') : globalStore.currentNode;
@ -223,15 +242,26 @@ const search = async () => {
menuStore.menuList = rstMenuList; 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(() => { onMounted(() => {
menuStore.setMenuList(menuList); menuStore.setMenuList(menuList);
search(); search();
loadNodes(); loadNodes();
checkTask();
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
@import './index.scss'; @use './index.scss';
.sidebar-container { .sidebar-container {
position: relative; position: relative;
@ -252,20 +282,13 @@ onMounted(() => {
.el-dropdown-link { .el-dropdown-link {
margin-top: -5px; margin-top: -5px;
margin-left: 30px; margin-left: 15px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
cursor: pointer;
color: var(--el-color-primary); color: var(--el-color-primary);
display: flex; height: 38px;
align-items: center;
height: 28px;
} }
.ico { .ico {
margin-top: -20px; height: 20px !important;
display: flex;
float: left;
position: absolute;
right: 25px;
} }
</style> </style>

View File

@ -732,7 +732,7 @@ onUnmounted(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '../index.scss'; @use '../index.scss';
@media only screen and (max-width: 1300px) { @media only screen and (max-width: 1300px) {
.install-card-col-12 { .install-card-col-12 {
max-width: 100%; max-width: 100%;

View File

@ -138,10 +138,11 @@
<template #default="{ row }"> <template #default="{ row }">
<Status <Status
v-if="row.status === 'Running'" v-if="row.status === 'Running'"
:operate="true"
:status="row.status" :status="row.status"
@click="opWebsite('stop', row.id)" @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> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column