mirror of
https://github.com/geekgeekrun/geekgeekrun.git
synced 2026-07-20 20:22:13 +08:00
add page to show running task
This commit is contained in:
@@ -162,10 +162,10 @@ function handleMessage(socket, message) {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// 如果是心跳,更新最后心跳时间
|
||||
if (message.type === 'worker-heartbeat') {
|
||||
workerInfo.lastHeartbeat = Date.now();
|
||||
}
|
||||
// // 如果是心跳,更新最后心跳时间
|
||||
// if (message.type === 'worker-heartbeat') {
|
||||
// workerInfo.lastHeartbeat = Date.now();
|
||||
// }
|
||||
} else {
|
||||
sendResponse(socket, _callbackUuid, { error: '未注册的工具进程连接' });
|
||||
}
|
||||
@@ -328,9 +328,10 @@ function startWorker({ workerId, command, args, env }, restartCount = 0) {
|
||||
status: 'running',
|
||||
startTime: Date.now(),
|
||||
restartCount, // 使用传入的重启次数
|
||||
socket: null, // 工具进程的TCP连接,稍后由工具进程注册
|
||||
lastHeartbeat: null,
|
||||
// socket: null, // 工具进程的TCP连接,稍后由工具进程注册
|
||||
// lastHeartbeat: null,
|
||||
command,
|
||||
args,
|
||||
env,
|
||||
workerId,
|
||||
}
|
||||
@@ -388,8 +389,11 @@ function getWorkersStatus() {
|
||||
status: workerInfo.status,
|
||||
uptime: Date.now() - workerInfo.startTime,
|
||||
restartCount: workerInfo.restartCount || 0,
|
||||
connected: workerInfo.socket !== null && !workerInfo.socket.destroyed,
|
||||
lastHeartbeat: workerInfo.lastHeartbeat
|
||||
// connected: workerInfo.socket !== null && !workerInfo.socket.destroyed,
|
||||
// lastHeartbeat: workerInfo.lastHeartbeat,
|
||||
command: workerInfo.command,
|
||||
args: workerInfo.args,
|
||||
pid: workerInfo.process?.pid
|
||||
});
|
||||
}
|
||||
return status;
|
||||
|
||||
@@ -129,16 +129,6 @@ export function sendToDaemon(message, {
|
||||
// sendToDaemon({ type: 'start-worker', workerId, command, args, env });
|
||||
// });
|
||||
|
||||
// // IPC处理:停止工具进程
|
||||
// ipcMain.on('stop-worker', (event, workerId) => {
|
||||
// sendToDaemon({ type: 'stop-worker', workerId });
|
||||
// });
|
||||
|
||||
// // IPC处理:获取所有工具进程状态
|
||||
// ipcMain.on('get-workers-status', () => {
|
||||
// sendToDaemon({ type: 'get-status' });
|
||||
// });
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (daemonClient) {
|
||||
daemonClient.destroy();
|
||||
@@ -149,4 +139,4 @@ app.on('before-quit', () => {
|
||||
if (daemonClient) {
|
||||
daemonClient.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -423,6 +423,31 @@ export default function initIpc() {
|
||||
mainWindow?.webContents.send('read-no-reply-auto-reminder-stopped')
|
||||
})
|
||||
|
||||
ipcMain.handle('get-task-manager-list', async () => {
|
||||
const result = await sendToDaemon(
|
||||
{
|
||||
type: 'get-status'
|
||||
},
|
||||
{
|
||||
needCallback: true
|
||||
}
|
||||
)
|
||||
return result
|
||||
})
|
||||
|
||||
// IPC处理:停止工具进程
|
||||
ipcMain.handle('stop-task', async (_, workerId) => {
|
||||
await sendToDaemon(
|
||||
{
|
||||
type: 'stop-worker',
|
||||
workerId
|
||||
},
|
||||
{
|
||||
needCallback: true
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
let subProcessOfBossZhipinLoginPageWithPreloadExtension: ChildProcess | null = null
|
||||
ipcMain.on('launch-bosszhipin-login-page-with-preload-extension', async () => {
|
||||
try {
|
||||
|
||||
69
packages/ui/src/renderer/src/features/TaskManager/Item.vue
Normal file
69
packages/ui/src/renderer/src/features/TaskManager/Item.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="task-item" flex>
|
||||
<div>
|
||||
<img height="160" width="256" />
|
||||
</div>
|
||||
<div ml-40px>
|
||||
<dl>
|
||||
<dt>workerId</dt>
|
||||
<dd>{{ task.workerId }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>status</dt>
|
||||
<dd>{{ task.status }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>重启次数</dt>
|
||||
<dd>{{ task.restartCount }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>已运行时间</dt>
|
||||
<dd>{{ task.uptime ?? '-' }} 毫秒</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>命令行</dt>
|
||||
<dd>{{ task.command }} {{ task.args.join(' ') }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>PID</dt>
|
||||
<dd>{{ task.pid }}</dd>
|
||||
</dl>
|
||||
<el-button type="danger" @click="stopTask(task.workerId)">结束任务</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
task: {
|
||||
type: Object as PropType<any>
|
||||
}
|
||||
})
|
||||
|
||||
const { ipcRenderer } = electron
|
||||
const stopTask = async (workerId: string) => {
|
||||
await ipcRenderer.invoke('stop-task', workerId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-item {
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
dl {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid #eee;
|
||||
dt {
|
||||
width: 6em;
|
||||
flex: 0 0 6em;
|
||||
}
|
||||
dd {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
18
packages/ui/src/renderer/src/features/TaskManager/List.vue
Normal file
18
packages/ui/src/renderer/src/features/TaskManager/List.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="task in runningTasks" :key="task.workerId">
|
||||
<div>
|
||||
<TaskManagerItem :task="task" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTaskManagerStore } from '@renderer/store'
|
||||
import { computed } from 'vue'
|
||||
import TaskManagerItem from './Item.vue'
|
||||
|
||||
const taskManagerStore = useTaskManagerStore()
|
||||
const runningTasks = computed(() => taskManagerStore.runningTasks?.workers || [])
|
||||
</script>
|
||||
@@ -1,53 +1,29 @@
|
||||
<template><RouterView :status="currentStatus" /></template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { ipcRenderer } = electron
|
||||
|
||||
const currentStatus = ref('')
|
||||
onMounted(() => {
|
||||
const promise = Promise.resolve()
|
||||
promise
|
||||
.then(() => {
|
||||
switch (route.query.flow) {
|
||||
case 'geek-auto-start-chat-with-boss': {
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/runningStatus'
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'read-no-reply-reminder': {
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/runningStatusForReadNoReplyReminder'
|
||||
})
|
||||
break
|
||||
}
|
||||
default: {
|
||||
router.replace('/')
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(async (err) => {
|
||||
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
|
||||
ElMessage.error({
|
||||
message: `核心组件损坏,正在尝试修复`
|
||||
})
|
||||
router.replace('/')
|
||||
}
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
|
||||
const needToCheckRuntimeDependenciesHandler = () => {
|
||||
router.replace('/')
|
||||
}
|
||||
|
||||
ipcRenderer.on('need-to-check-runtime-dependencies', needToCheckRuntimeDependenciesHandler)
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeListener('need-to-check-runtime-dependencies', needToCheckRuntimeDependenciesHandler)
|
||||
switch (route.query.flow) {
|
||||
case 'geek-auto-start-chat-with-boss': {
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/runningStatus'
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'read-no-reply-reminder': {
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/runningStatusForReadNoReplyReminder'
|
||||
})
|
||||
break
|
||||
}
|
||||
default: {
|
||||
router.replace('/')
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1049,12 +1049,15 @@
|
||||
<el-button @click="handleSave">仅保存配置</el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 保存配置,并开始求职! </el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button @click="handleStopButtonClick">结束任务</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref, watch, nextTick } from 'vue'
|
||||
import { computed, onBeforeUnmount, ref, watch, nextTick, onUnmounted } from 'vue'
|
||||
import { ElForm, ElMessage } from 'element-plus'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -1445,13 +1448,28 @@ const handleSubmit = async () => {
|
||||
delete clonedFormContent.__jobSourceList
|
||||
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(clonedFormContent))
|
||||
mittBus.emit('auto-start-chat-with-boss-config-saved')
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||
query: { flow: 'geek-auto-start-chat-with-boss' }
|
||||
})
|
||||
gtagRenderer('config_saved_and_launch_auto_start_chat', {
|
||||
has_dingtalk_robot_token: !!formContent.value?.dingtalkRobotAccessToken
|
||||
})
|
||||
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('run-geek-auto-start-chat-with-boss')
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
|
||||
gtagRenderer('gascwb_cannot_run_for_corrupt')
|
||||
ElMessage.error({
|
||||
message: `核心组件损坏,正在尝试修复`
|
||||
})
|
||||
router.replace('/')
|
||||
}
|
||||
console.error(err)
|
||||
gtagRenderer('gascwb_cannot_run_for_unknown_error', { err })
|
||||
}
|
||||
|
||||
// {
|
||||
// path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||
// query: { flow: 'geek-auto-start-chat-with-boss' }
|
||||
// }
|
||||
}
|
||||
const handleSave = async () => {
|
||||
gtagRenderer('save_config_clicked', {
|
||||
@@ -1803,6 +1821,22 @@ const combineRecommendJobFilterTypeOptions = [
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
|
||||
const needToCheckRuntimeDependenciesHandler = () => {
|
||||
router.replace('/')
|
||||
}
|
||||
electron.ipcRenderer.on('need-to-check-runtime-dependencies', needToCheckRuntimeDependenciesHandler)
|
||||
onUnmounted(() => {
|
||||
electron.ipcRenderer.removeListener(
|
||||
'need-to-check-runtime-dependencies',
|
||||
needToCheckRuntimeDependenciesHandler
|
||||
)
|
||||
})
|
||||
|
||||
const handleStopButtonClick = async () => {
|
||||
gtagRenderer('gascwb_stop_button_clicked')
|
||||
electron.ipcRenderer.invoke('stop-geek-auto-start-chat-with-boss')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -219,6 +219,9 @@
|
||||
<el-form-item class="last-form-item">
|
||||
<el-button type="primary" @click="handleSubmit">开始提醒</el-button>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-button @click="handleStopButtonClick">结束任务</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -471,10 +474,25 @@ const handleSubmit = async () => {
|
||||
}
|
||||
}
|
||||
gtagRenderer('run_read_no_reply_reminder_launched')
|
||||
router.replace({
|
||||
path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||
query: { flow: 'read-no-reply-reminder' }
|
||||
})
|
||||
|
||||
try {
|
||||
await electron.ipcRenderer.invoke('run-read-no-reply-auto-reminder')
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
|
||||
gtagRenderer('rnrr_cannot_run_for_corrupt')
|
||||
ElMessage.error({
|
||||
message: `核心组件损坏,正在尝试修复`
|
||||
})
|
||||
router.replace('/')
|
||||
}
|
||||
console.error(err)
|
||||
gtagRenderer('rnrr_cannot_run_for_unknown_error', { err })
|
||||
}
|
||||
|
||||
// {
|
||||
// path: '/geekAutoStartChatWithBoss/prepareRun',
|
||||
// query: { flow: 'read-no-reply-reminder' }
|
||||
// }
|
||||
}
|
||||
function handleThrottleIntervalMinutesBlur() {
|
||||
if (formContent.value.autoReminder.throttleIntervalMinutes < 3) {
|
||||
@@ -559,6 +577,22 @@ async function handleTestEffectClicked() {
|
||||
autoReminderConfig: JSON.parse(JSON.stringify(formContent.value.autoReminder))
|
||||
})
|
||||
}
|
||||
|
||||
const needToCheckRuntimeDependenciesHandler = () => {
|
||||
router.replace('/')
|
||||
}
|
||||
electron.ipcRenderer.on('need-to-check-runtime-dependencies', needToCheckRuntimeDependenciesHandler)
|
||||
onUnmounted(() => {
|
||||
electron.ipcRenderer.removeListener(
|
||||
'need-to-check-runtime-dependencies',
|
||||
needToCheckRuntimeDependenciesHandler
|
||||
)
|
||||
})
|
||||
|
||||
const handleStopButtonClick = async () => {
|
||||
gtagRenderer('rnrr_stop_button_clicked')
|
||||
electron.ipcRenderer.invoke('stop-read-no-reply-auto-reminder')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<TaskManagerList />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TaskManagerList from '../../features/TaskManager/List.vue'
|
||||
</script>
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="flex h100vh">
|
||||
<div class="flex flex-col min-w200px w200px pt30px pl30px aside-nav of-hidden">
|
||||
<div class="nav-list flex-1 of-auto">
|
||||
<RouterLink to="./TaskManager">任务管理</RouterLink>
|
||||
<RouterLink to="./GeekAutoStartChatWithBoss">
|
||||
Boss炸弹
|
||||
<el-tooltip
|
||||
@@ -136,7 +137,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RouterView v-slot="{ Component }" class="flex-1">
|
||||
<RouterView v-slot="{ Component }" class="flex-1 of-hidden">
|
||||
<KeepAlive>
|
||||
<component :is="Component" />
|
||||
</KeepAlive>
|
||||
@@ -151,7 +152,7 @@ import { TopRight, QuestionFilled } from '@element-plus/icons-vue'
|
||||
import useBuildInfo from '@renderer/hooks/useBuildInfo'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { gtagRenderer } from '@renderer/utils/gtag'
|
||||
import { useUpdateStore } from '../../store/index'
|
||||
import { useUpdateStore, useTaskManagerStore } from '../../store/index'
|
||||
|
||||
const router = useRouter()
|
||||
const unmountedCbs: Array<InstanceType<typeof Function>> = []
|
||||
@@ -217,6 +218,9 @@ function handleViewNewReleaseClick() {
|
||||
gtagRenderer('click_view_release_form_nav')
|
||||
electron.ipcRenderer.send('open-external-link', updateStore.availableNewRelease!.releasePageUrl)
|
||||
}
|
||||
|
||||
const taskManagerStore = useTaskManagerStore()
|
||||
void taskManagerStore
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -43,6 +43,13 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@renderer/page/MainLayout/index.vue'),
|
||||
redirect: '/main-layout/GeekAutoStartChatWithBoss',
|
||||
children: [
|
||||
{
|
||||
path: 'taskManager',
|
||||
component: () => import('@renderer/page/MainLayout/TaskManager.vue'),
|
||||
meta: {
|
||||
title: '任务管理'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'GeekAutoStartChatWithBoss',
|
||||
component: () => import('@renderer/page/MainLayout/GeekAutoStartChatWithBoss/index.vue'),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { NewReleaseInfo } from '../../../common/types/update'
|
||||
import { ref } from 'vue'
|
||||
import { throttle } from 'lodash-es'
|
||||
|
||||
export const useUpdateStore = defineStore('update', () => {
|
||||
const availableNewRelease = ref<NewReleaseInfo | null>(null)
|
||||
@@ -18,3 +19,16 @@ export const useUpdateStore = defineStore('update', () => {
|
||||
setInterval(checkUpdate, 30 * 30 * 1000)
|
||||
return { availableNewRelease }
|
||||
})
|
||||
|
||||
export const useTaskManagerStore = defineStore('taskManager', () => {
|
||||
const runningTasks = ref<unknown[]>([])
|
||||
function getRunningTasks() {
|
||||
const { ipcRenderer } = electron
|
||||
ipcRenderer.invoke('get-task-manager-list').then(res => {
|
||||
runningTasks.value = res
|
||||
})
|
||||
}
|
||||
const throttledGetRunningTasks = throttle(getRunningTasks, 2000)
|
||||
setInterval(throttledGetRunningTasks, 2 * 1000)
|
||||
return { runningTasks, getRunningTasks: throttledGetRunningTasks }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user