Merge branch 'main' of github.com:engigu/baihu
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
# 白虎面板 [](https://hits.sh/github.com/engigu/baihu-panel/)
|
# 白虎面板
|
||||||
|
|
||||||
|
[](https://hits.sh/github.com/engigu/baihu-panel/)
|
||||||
|
[](https://ghcr-badge.egpl.dev/engigu/baihu/tags)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
白虎面板是一个轻量级定时任务管理系统,基于`Go`+`Vue3`构建,`docker`或者`docker-compose`,内置`python3`、`nodejs`、`bash`环境,开箱即用。
|
白虎面板是一个轻量级定时任务管理系统,基于`Go`+`Vue3`构建,`docker`或者`docker-compose`,内置`python3`、`nodejs`、`bash`环境,开箱即用。
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function initTerminal(forceConnect = false) {
|
|||||||
try {
|
try {
|
||||||
fitAddon?.fit()
|
fitAddon?.fit()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Terminal fit failed:', e)
|
// 忽略 fit 错误
|
||||||
}
|
}
|
||||||
}, 50)
|
}, 50)
|
||||||
|
|
||||||
@@ -79,10 +79,10 @@ function initTerminal(forceConnect = false) {
|
|||||||
|
|
||||||
// autoConnect 或者强制连接时才连接
|
// autoConnect 或者强制连接时才连接
|
||||||
if (props.autoConnect || forceConnect) {
|
if (props.autoConnect || forceConnect) {
|
||||||
// 延迟连接,确保终端完全初始化
|
// 延迟连接,确保终端完全初始化和旧连接完全关闭
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
connectWebSocket()
|
connectWebSocket()
|
||||||
}, 100)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除当前输入行(Windows 模式用)
|
// 清除当前输入行(Windows 模式用)
|
||||||
@@ -144,12 +144,23 @@ function initTerminal(forceConnect = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
|
// 如果已有连接,先关闭
|
||||||
|
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
||||||
|
ws.close()
|
||||||
|
ws = null
|
||||||
|
}
|
||||||
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||||
const baseUrl = (window as any).__BASE_URL__ || ''
|
const baseUrl = (window as any).__BASE_URL__ || ''
|
||||||
const apiVersion = (window as any).__API_VERSION__ || '/api/v1'
|
const apiVersion = (window as any).__API_VERSION__ || '/api/v1'
|
||||||
const wsUrl = `${protocol}//${window.location.host}${baseUrl}${apiVersion}/terminal/ws`
|
const wsUrl = `${protocol}//${window.location.host}${baseUrl}${apiVersion}/terminal/ws`
|
||||||
|
|
||||||
ws = new WebSocket(wsUrl)
|
try {
|
||||||
|
ws = new WebSocket(wsUrl)
|
||||||
|
} catch {
|
||||||
|
terminal?.writeln('\x1b[31m无法创建 WebSocket 连接\x1b[0m')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
terminal?.writeln('\x1b[32m已连接到终端\x1b[0m')
|
terminal?.writeln('\x1b[32m已连接到终端\x1b[0m')
|
||||||
@@ -159,7 +170,6 @@ function connectWebSocket() {
|
|||||||
if (props.initialCommand) {
|
if (props.initialCommand) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
// 直接发送命令,PTY 会回显
|
|
||||||
ws.send(props.initialCommand + '\r')
|
ws.send(props.initialCommand + '\r')
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const router = createRouter({
|
|||||||
children: [
|
children: [
|
||||||
{ path: '', name: 'dashboard', component: () => import('@/views/dashboard/Dashboard.vue') },
|
{ path: '', name: 'dashboard', component: () => import('@/views/dashboard/Dashboard.vue') },
|
||||||
{ path: 'tasks', name: 'tasks', component: () => import('@/views/tasks/Tasks.vue') },
|
{ path: 'tasks', name: 'tasks', component: () => import('@/views/tasks/Tasks.vue') },
|
||||||
{ path: 'editor/:path(.*)?', name: 'editor', component: () => import('@/views/editor/Editor.vue') },
|
{ path: 'editor', name: 'editor', component: () => import('@/views/editor/Editor.vue') },
|
||||||
{ path: 'environments', name: 'environments', component: () => import('@/views/environments/Environments.vue') },
|
{ path: 'environments', name: 'environments', component: () => import('@/views/environments/Environments.vue') },
|
||||||
{ path: 'dependencies', name: 'dependencies', component: () => import('@/views/dependencies/Dependencies.vue') },
|
{ path: 'dependencies', name: 'dependencies', component: () => import('@/views/dependencies/Dependencies.vue') },
|
||||||
{ path: 'agents', name: 'agents', component: () => import('@/views/agents/Agents.vue') },
|
{ path: 'agents', name: 'agents', component: () => import('@/views/agents/Agents.vue') },
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ async function loadTree() {
|
|||||||
|
|
||||||
async function handleSelect(node: FileNode) {
|
async function handleSelect(node: FileNode) {
|
||||||
selectedPath.value = node.path
|
selectedPath.value = node.path
|
||||||
// 更新 URL
|
// 更新 URL 使用 query 参数
|
||||||
router.replace({ name: 'editor', params: { path: node.path } })
|
router.replace({ name: 'editor', query: { file: node.path } })
|
||||||
|
|
||||||
if (node.isDir) {
|
if (node.isDir) {
|
||||||
if (expandedDirs.value.has(node.path)) {
|
if (expandedDirs.value.has(node.path)) {
|
||||||
@@ -195,14 +195,19 @@ async function runScript() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showTerminalDialog.value = true
|
showTerminalDialog.value = true
|
||||||
// 等待 DOM 更新后初始化终端
|
// 等待 DOM 更新后初始化终端,增加延迟确保 Dialog 完全渲染
|
||||||
await nextTick()
|
await nextTick()
|
||||||
terminalRef.value?.initTerminal(true)
|
setTimeout(() => {
|
||||||
|
terminalRef.value?.initTerminal(true)
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeTerminal() {
|
function closeTerminal() {
|
||||||
showTerminalDialog.value = false
|
showTerminalDialog.value = false
|
||||||
terminalRef.value?.dispose()
|
// 延迟清理,确保 Dialog 关闭动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
terminalRef.value?.dispose()
|
||||||
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMove(oldPath: string, newPath: string) {
|
async function handleMove(oldPath: string, newPath: string) {
|
||||||
@@ -212,10 +217,10 @@ async function handleMove(oldPath: string, newPath: string) {
|
|||||||
if (selectedFile.value === oldPath) {
|
if (selectedFile.value === oldPath) {
|
||||||
selectedFile.value = newPath
|
selectedFile.value = newPath
|
||||||
selectedPath.value = newPath
|
selectedPath.value = newPath
|
||||||
router.replace({ name: 'editor', params: { path: newPath } })
|
router.replace({ name: 'editor', query: { file: newPath } })
|
||||||
} else if (selectedPath.value === oldPath) {
|
} else if (selectedPath.value === oldPath) {
|
||||||
selectedPath.value = newPath
|
selectedPath.value = newPath
|
||||||
router.replace({ name: 'editor', params: { path: newPath } })
|
router.replace({ name: 'editor', query: { file: newPath } })
|
||||||
}
|
}
|
||||||
await loadTree()
|
await loadTree()
|
||||||
} catch {
|
} catch {
|
||||||
@@ -309,7 +314,7 @@ function expandParentDirs(path: string) {
|
|||||||
// 从 URL 初始化选中状态
|
// 从 URL 初始化选中状态
|
||||||
async function initFromUrl() {
|
async function initFromUrl() {
|
||||||
await loadTree()
|
await loadTree()
|
||||||
const urlPath = route.params.path as string
|
const urlPath = route.query.file as string
|
||||||
if (urlPath) {
|
if (urlPath) {
|
||||||
selectedPath.value = urlPath
|
selectedPath.value = urlPath
|
||||||
expandParentDirs(urlPath)
|
expandParentDirs(urlPath)
|
||||||
@@ -319,6 +324,7 @@ async function initFromUrl() {
|
|||||||
selectedFile.value = urlPath
|
selectedFile.value = urlPath
|
||||||
fileContent.value = res.content
|
fileContent.value = res.content
|
||||||
originalContent.value = res.content
|
originalContent.value = res.content
|
||||||
|
isEditMode.value = false
|
||||||
} catch {
|
} catch {
|
||||||
// 可能是文件夹,忽略错误
|
// 可能是文件夹,忽略错误
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user