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:
parent
e11a0d5766
commit
4a4e568d4a
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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`);
|
||||||
|
};
|
||||||
|
@ -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>
|
||||||
|
@ -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}`;
|
||||||
|
@ -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>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
@use '@/styles/var.scss' as *;
|
||||||
.el-menu {
|
.el-menu {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -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>
|
||||||
|
@ -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%;
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user