chore: history page support sharedworker.js

This commit is contained in:
duorameng
2026-05-09 08:42:15 +08:00
parent 95a7de599e
commit 9c174d8db2
10 changed files with 92 additions and 25 deletions
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/services" "github.com/engigu/baihu-panel/internal/services"
"github.com/engigu/baihu-panel/internal/services/repo" "github.com/engigu/baihu-panel/internal/services/repo"
"github.com/engigu/baihu-panel/internal/utils" "github.com/engigu/baihu-panel/internal/utils"
@@ -70,10 +71,10 @@ func Run(args []string) {
fs.Parse(args) fs.Parse(args)
// 处理 $SCRIPTS_DIR$ 代号替换 // 处理 $SCRIPTS_DIR$ 代号替换
if strings.Contains(cfg.TargetPath, "$SCRIPTS_DIR$") { if strings.Contains(cfg.TargetPath, constant.ScriptsDirPlaceholder) {
scriptsDir := os.Getenv("BH_SCRIPTS_DIR") scriptsDir := os.Getenv("BH_SCRIPTS_DIR")
if scriptsDir != "" { if scriptsDir != "" {
cfg.TargetPath = filepath.Clean(strings.ReplaceAll(cfg.TargetPath, "$SCRIPTS_DIR$", scriptsDir)) cfg.TargetPath = filepath.Clean(strings.ReplaceAll(cfg.TargetPath, constant.ScriptsDirPlaceholder, scriptsDir))
} }
} }
+3
View File
@@ -177,6 +177,9 @@ const (
MaxMessageSize = 1024 * 1024 // 1MB MaxMessageSize = 1024 * 1024 // 1MB
// MaxLogSize 允许的最大日志大小 (保留末尾 10MB) // MaxLogSize 允许的最大日志大小 (保留末尾 10MB)
MaxLogSize = 10 * 1024 * 1024 // 10MB MaxLogSize = 10 * 1024 * 1024 // 10MB
// ScriptsDirPlaceholder 脚本目录占位符
ScriptsDirPlaceholder = "$SCRIPTS_DIR$"
) )
// TablePrefix 表前缀,从配置文件读取 // TablePrefix 表前缀,从配置文件读取
+4 -4
View File
@@ -42,7 +42,7 @@ func resolveWorkDir(workDir string) string {
return absPath return absPath
} }
// 如果已经是绝对路径,直接返回 // 如果已经是绝对路径,直接返回
if strings.HasPrefix(workDir, "$SCRIPTS_DIR$") { if strings.HasPrefix(workDir, constant.ScriptsDirPlaceholder) {
return workDir return workDir
} }
if filepath.IsAbs(workDir) { if filepath.IsAbs(workDir) {
@@ -381,7 +381,7 @@ func (tc *TaskController) deleteRepoPhysicalFiles(task *models.Task) {
} }
} }
if targetPath == "" || targetPath == "$SCRIPTS_DIR$" { if targetPath == "" || targetPath == constant.ScriptsDirPlaceholder {
logger.Warnf("[Controller] 任务 %s 无法确定有效的物理删除路径,跳过", task.Name) logger.Warnf("[Controller] 任务 %s 无法确定有效的物理删除路径,跳过", task.Name)
return return
} }
@@ -389,8 +389,8 @@ func (tc *TaskController) deleteRepoPhysicalFiles(task *models.Task) {
// 确定绝对路径 // 确定绝对路径
scriptsDir, _ := filepath.Abs(constant.ScriptsWorkDir) scriptsDir, _ := filepath.Abs(constant.ScriptsWorkDir)
fullPath := targetPath fullPath := targetPath
if strings.HasPrefix(targetPath, "$SCRIPTS_DIR$") { if strings.HasPrefix(targetPath, constant.ScriptsDirPlaceholder) {
fullPath = filepath.Join(scriptsDir, strings.TrimPrefix(targetPath, "$SCRIPTS_DIR$")) fullPath = filepath.Join(scriptsDir, strings.TrimPrefix(targetPath, constant.ScriptsDirPlaceholder))
} else if !filepath.IsAbs(targetPath) { } else if !filepath.IsAbs(targetPath) {
fullPath = filepath.Join(scriptsDir, targetPath) fullPath = filepath.Join(scriptsDir, targetPath)
} }
+2 -2
View File
@@ -134,9 +134,9 @@ func ParseRepoScriptsAndAddCron(taskID string, logWriter io.Writer, forceComment
displayPath = filepath.Base(path) displayPath = filepath.Base(path)
relDir, _ := filepath.Rel(absScriptsDir, filepath.Dir(absPath)) relDir, _ := filepath.Rel(absScriptsDir, filepath.Dir(absPath))
if relDir == "." { if relDir == "." {
displayWorkDir = "$SCRIPTS_DIR$" displayWorkDir = constant.ScriptsDirPlaceholder
} else { } else {
displayWorkDir = filepath.ToSlash(filepath.Join("$SCRIPTS_DIR$", relDir)) displayWorkDir = filepath.ToSlash(filepath.Join(constant.ScriptsDirPlaceholder, relDir))
} }
} }
+5 -3
View File
@@ -291,6 +291,7 @@ func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest,
eventbus.DefaultBus.Publish(eventbus.Event{ eventbus.DefaultBus.Publish(eventbus.Event{
Type: eventType, Type: eventType,
Payload: map[string]interface{}{ Payload: map[string]interface{}{
"log_id": req.LogID,
"task_id": task.ID, "task_id": task.ID,
"task_name": task.Name, "task_name": task.Name,
"status": result.Status, "status": result.Status,
@@ -372,6 +373,7 @@ func (h *ServerSchedulerHandler) OnTaskFailed(req *executor.ExecutionRequest, er
eventbus.DefaultBus.Publish(eventbus.Event{ eventbus.DefaultBus.Publish(eventbus.Event{
Type: constant.EventTaskFailed, Type: constant.EventTaskFailed,
Payload: map[string]interface{}{ Payload: map[string]interface{}{
"log_id": req.LogID,
"task_id": taskID, "task_id": taskID,
"task_name": taskName, "task_name": taskName,
"error": err.Error(), "error": err.Error(),
@@ -1099,9 +1101,9 @@ func BuildRepoCommand(task *models.Task) (string, string) {
displayTargetPath := absTargetPath displayTargetPath := absTargetPath
if rel, err := filepath.Rel(scriptsDir, absTargetPath); err == nil && !strings.HasPrefix(rel, "..") { if rel, err := filepath.Rel(scriptsDir, absTargetPath); err == nil && !strings.HasPrefix(rel, "..") {
if rel == "." { if rel == "." {
displayTargetPath = "$SCRIPTS_DIR$" displayTargetPath = constant.ScriptsDirPlaceholder
} else { } else {
displayTargetPath = "$SCRIPTS_DIR$/" + filepath.ToSlash(rel) displayTargetPath = constant.ScriptsDirPlaceholder + "/" + filepath.ToSlash(rel)
} }
} }
@@ -1233,7 +1235,7 @@ func (es *ExecutorService) refreshExecutionRequestEnvs(req *executor.ExecutionRe
func (es *ExecutorService) ResolvePath(path string) string { func (es *ExecutorService) ResolvePath(path string) string {
absScriptsDir := resolveAbsScriptsDir() absScriptsDir := resolveAbsScriptsDir()
return strings.ReplaceAll(path, "$SCRIPTS_DIR$", absScriptsDir) return strings.ReplaceAll(path, constant.ScriptsDirPlaceholder, absScriptsDir)
} }
func buildRepoCommandEnvPrefix() string { func buildRepoCommandEnvPrefix() string {
+11
View File
@@ -8,6 +8,8 @@ export const PATHS = {
CONFIGS_DIR: '/app/configs', CONFIGS_DIR: '/app/configs',
// 环境目录 // 环境目录
ENVS_DIR: '/app/envs', ENVS_DIR: '/app/envs',
// 脚本目录占位符
SCRIPTS_DIR_PLACEHOLDER: '$SCRIPTS_DIR$',
} as const } as const
// 文件扩展名对应的运行命令 // 文件扩展名对应的运行命令
@@ -61,3 +63,12 @@ export const ENV_TYPE = {
NORMAL: 'normal', NORMAL: 'normal',
SECRET: 'secret', SECRET: 'secret',
} as const } as const
// 任务事件类型
export const TASK_EVENTS = {
SUCCESS: 'task_success',
FAILED: 'task_failed',
TIMEOUT: 'task_timeout',
RUNNING: 'task_running',
QUEUED: 'task_queued',
} as const
+16
View File
@@ -0,0 +1,16 @@
export interface WSMessage {
type: string
timestamp: number
payload: any
}
export interface TaskStatusPayload {
id: string
task_id: string
task_name: string
status: string
duration: number
start_time: string | null
end_time: string | null
error?: string | null
}
+5 -4
View File
@@ -1,4 +1,5 @@
import type { RepoConfig, Task } from '@/api' import type { RepoConfig, Task } from '@/api'
import { PATHS } from '@/constants'
/** /**
* 仓库命令解析结果 * 仓库命令解析结果
@@ -64,10 +65,10 @@ export function parseBaihuCommand(command: string): ParsedRepoResult | null {
} }
break break
case '--target-path': case '--target-path':
// 处理 $SCRIPTS_DIR$ 占位符 // 处理脚本目录占位符
if (value.startsWith('$SCRIPTS_DIR$/')) { if (value.startsWith(`${PATHS.SCRIPTS_DIR_PLACEHOLDER}/`)) {
repoConfig.target_path = value.replace('$SCRIPTS_DIR$/', '') repoConfig.target_path = value.replace(`${PATHS.SCRIPTS_DIR_PLACEHOLDER}/`, '')
} else if (value === '$SCRIPTS_DIR$') { } else if (value === PATHS.SCRIPTS_DIR_PLACEHOLDER) {
repoConfig.target_path = '' repoConfig.target_path = ''
} else { } else {
repoConfig.target_path = value repoConfig.target_path = value
+35 -1
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed, watch, nextTick } from 'vue' import { ref, onMounted, computed, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { TASK_STATUS, TASK_TYPE, TASK_STATUS_TEXT } from '@/constants' import { TASK_STATUS, TASK_TYPE, TASK_STATUS_TEXT, TASK_EVENTS } from '@/constants'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import Pagination from '@/components/Pagination.vue' import Pagination from '@/components/Pagination.vue'
@@ -17,6 +17,7 @@ import BaihuDialog from '@/components/ui/BaihuDialog.vue'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { useSiteSettings } from '@/composables/useSiteSettings' import { useSiteSettings } from '@/composables/useSiteSettings'
import TextOverflow from '@/components/TextOverflow.vue' import TextOverflow from '@/components/TextOverflow.vue'
import { useEventBus } from '@/composables/useEventBus'
const route = useRoute() const route = useRoute()
const { pageSize } = useSiteSettings() const { pageSize } = useSiteSettings()
@@ -34,6 +35,39 @@ let durationTimer: ReturnType<typeof setInterval> | null = null
const isRefreshing = ref(false) const isRefreshing = ref(false)
// 监听实时任务事件
useEventBus(Object.values(TASK_EVENTS), (payload, type) => {
const { log_id, status, duration, end_time } = payload
// 查找列表中的记录
const logItem = logs.value.find(l => l.id === log_id)
if (logItem) {
// 更新已有记录状态
logItem.status = status
if (duration !== undefined) logItem.duration = duration
if (end_time !== undefined) logItem.end_time = end_time
// 如果详情页打开的是这个记录,也同步更新
if (selectedLog.value && selectedLog.value.id === log_id) {
selectedLog.value.status = status
if (duration !== undefined) selectedLog.value.duration = duration
if (end_time !== undefined) selectedLog.value.end_time = end_time
// 如果任务完成了,停止详情页的轮询定时器
if (status !== TASK_STATUS.RUNNING && durationTimer) {
clearInterval(durationTimer)
durationTimer = null
}
}
} else if (type === TASK_EVENTS.RUNNING) {
// 新任务开始执行,如果在第一页且没有特定筛选条件,则刷新列表
if (currentPage.value === 1 && !filterTaskId.value && !filterKeyword.value && (!filterStatus.value || filterStatus.value === 'all')) {
loadLogs()
}
}
})
// 全屏查看 // 全屏查看
const showFullscreen = ref(false) const showFullscreen = ref(false)
+8 -9
View File
@@ -58,7 +58,6 @@ const concurrency = ref(0)
const concurrencyEnabled = ref(false) const concurrencyEnabled = ref(false)
const commentToTaskEnabled = ref(false) const commentToTaskEnabled = ref(false)
const allEnvsEnabled = ref(false) const allEnvsEnabled = ref(false)
const SCRIPTS_DIR_PLACEHPLDER = '$SCRIPTS_DIR$'
const scriptsDir = ref<string>(PATHS.SCRIPTS_DIR) const scriptsDir = ref<string>(PATHS.SCRIPTS_DIR)
const cronDescription = computed(() => { const cronDescription = computed(() => {
@@ -366,9 +365,9 @@ function removeEnv(id: string) {
function normalizeLocalWorkDirForDisplay(workDir?: string | null): string { function normalizeLocalWorkDirForDisplay(workDir?: string | null): string {
if (!workDir) return '' if (!workDir) return ''
if (workDir === SCRIPTS_DIR_PLACEHPLDER) return '' if (workDir === PATHS.SCRIPTS_DIR_PLACEHOLDER) return ''
if (workDir.startsWith(`${SCRIPTS_DIR_PLACEHPLDER}/`)) { if (workDir.startsWith(`${PATHS.SCRIPTS_DIR_PLACEHOLDER}/`)) {
return workDir.slice(SCRIPTS_DIR_PLACEHPLDER.length + 1) return workDir.slice(PATHS.SCRIPTS_DIR_PLACEHOLDER.length + 1)
} }
const base = scriptsDir.value || PATHS.SCRIPTS_DIR const base = scriptsDir.value || PATHS.SCRIPTS_DIR
if (workDir === base) return '' if (workDir === base) return ''
@@ -380,16 +379,16 @@ function normalizeLocalWorkDirForDisplay(workDir?: string | null): string {
function encodeLocalWorkDir(workDir?: string | null): string { function encodeLocalWorkDir(workDir?: string | null): string {
const value = workDir?.trim() || '' const value = workDir?.trim() || ''
if (!value) return SCRIPTS_DIR_PLACEHPLDER if (!value) return PATHS.SCRIPTS_DIR_PLACEHOLDER
if (value === SCRIPTS_DIR_PLACEHPLDER || value.startsWith(`${SCRIPTS_DIR_PLACEHPLDER}/`)) { if (value === PATHS.SCRIPTS_DIR_PLACEHOLDER || value.startsWith(`${PATHS.SCRIPTS_DIR_PLACEHOLDER}/`)) {
return value return value
} }
const base = scriptsDir.value || PATHS.SCRIPTS_DIR const base = scriptsDir.value || PATHS.SCRIPTS_DIR
if (value === base) return SCRIPTS_DIR_PLACEHPLDER if (value === base) return PATHS.SCRIPTS_DIR_PLACEHOLDER
if (value.startsWith(`${base}/`)) { if (value.startsWith(`${base}/`)) {
return `${SCRIPTS_DIR_PLACEHPLDER}/${value.slice(base.length + 1)}` return `${PATHS.SCRIPTS_DIR_PLACEHOLDER}/${value.slice(base.length + 1)}`
} }
return `${SCRIPTS_DIR_PLACEHPLDER}/${value.replace(/^\/+/, '')}` return `${PATHS.SCRIPTS_DIR_PLACEHOLDER}/${value.replace(/^\/+/, '')}`
} }
async function save() { async function save() {