1.修复上传漏洞和SQL注入漏洞-增强登录验证功能

This commit is contained in:
2026-06-24 11:47:27 +08:00
parent 546d350f3a
commit 5187330c38
25 changed files with 303 additions and 1462 deletions

View File

@@ -24,8 +24,6 @@ export const getTablePkUrl = '/admin/ajax/getTablePk'
export const getTableListUrl = '/admin/ajax/getTableList'
export const getTableFieldListUrl = '/admin/ajax/getTableFieldList'
export const getDatabaseConnectionListUrl = '/admin/ajax/getDatabaseConnectionList'
export const terminalUrl = adminBaseRoutePath + '/ajax/terminal'
export const changeTerminalConfigUrl = '/admin/ajax/changeTerminalConfig'
export const clearCacheUrl = '/admin/ajax/clearCache'
// 公共
@@ -177,27 +175,6 @@ export function postClearCache(type: string) {
)
}
/**
* 构建命令执行窗口url
*/
export function buildTerminalUrl(commandKey: string, uuid: string, extend: string) {
const adminInfo = useAdminInfo()
return (
getUrl() + terminalUrl + '?command=' + commandKey + '&uuid=' + uuid + '&extend=' + extend + '&batoken=' + adminInfo.getToken() + '&server=1'
)
}
/**
* 请求修改终端配置
*/
export function postChangeTerminalConfig(data: { manager?: string; port?: string }) {
return createAxios({
url: changeTerminalConfigUrl,
method: 'POST',
data: data,
})
}
/**
* 远程下拉框数据获取
*/

View File

@@ -1,439 +0,0 @@
<template>
<div>
<el-dialog v-model="terminal.state.show" :title="t('terminal.Terminal')" class="ba-terminal-dialog main-dialog">
<el-scrollbar ref="terminalScrollbarRef" :max-height="500" class="terminal-scrollbar">
<el-alert
class="terminal-warning-alert"
v-if="!terminal.state.phpDevelopmentServer"
:title="t('terminal.The current terminal is not running under the installation service, and some commands may not be executed')"
type="error"
/>
<el-timeline class="terminal-timeline" v-if="terminal.state.taskList.length">
<el-timeline-item
v-for="(item, idx) in terminal.state.taskList"
:key="idx"
class="task-item"
:class="'task-status-' + item.status"
:type="getTaskStatus(item.status)['statusType']"
center
:timestamp="item.createTime"
placement="top"
>
<el-card>
<div>
<el-tag :type="getTaskStatus(item.status)['statusType']">{{ getTaskStatus(item.status)['statusText'] }}</el-tag>
<el-tag
class="block-on-failure-tag"
v-if="(item.status == taskStatus.Failed || item.status == taskStatus.Unknown) && item.blockOnFailure"
type="warning"
>
{{ t('terminal.Failure to execute this command will block the execution of the queue') }}
</el-tag>
<el-tag
class="block-on-failure-tag"
v-if="item.status == taskStatus.Executing || item.status == taskStatus.Connecting"
type="danger"
>
{{ t('terminal.Do not refresh the browser') }}
</el-tag>
<span class="command">{{ item.command }}</span>
<div class="task-opt">
<el-button
:title="t('Retry')"
v-if="item.status == taskStatus.Failed || item.status == taskStatus.Unknown"
size="small"
v-blur
type="warning"
icon="el-icon-RefreshRight"
circle
@click="terminal.retryTask(idx)"
/>
<el-button
@click="terminal.delTask(idx)"
:title="t('Delete')"
size="small"
v-blur
type="danger"
icon="el-icon-Delete"
circle
/>
</div>
</div>
<template v-if="item.status != taskStatus.Waiting">
<div
v-if="item.status != taskStatus.Connecting && item.status != taskStatus.Executing"
@click="terminal.setTaskShowMessage(idx)"
class="toggle-message-display"
>
<span>{{ t('terminal.Command run log') }}</span>
<Icon :name="item.showMessage ? 'el-icon-ArrowUp' : 'el-icon-ArrowDown'" size="16" color="#909399" />
</div>
<div
v-if="
item.status == taskStatus.Connecting ||
item.status == taskStatus.Executing ||
(item.status > taskStatus.Executing && item.showMessage)
"
class="exec-message"
:class="'exec-message-' + item.uuid"
>
<pre v-for="(msg, index) in item.message" :key="index" class="message-item">{{ msg }}</pre>
</div>
</template>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else :image-size="80" :description="t('terminal.No mission yet')" />
</el-scrollbar>
<div class="terminal-buttons">
<el-button class="terminal-menu-item" icon="el-icon-MagicStick" v-blur @click="addTerminalTask('test', true, false)">
{{ t('terminal.Test command') }}
</el-button>
<el-dropdown class="terminal-menu-item">
<el-button icon="el-icon-Download" v-blur>
{{ t('terminal.Install dependent packages') }}
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="addTerminalTask('web-install', true)" v-if="terminal.state.packageManager != 'none'">
{{ terminal.state.packageManager }} run install
</el-dropdown-item>
<el-dropdown-item @click="addTerminalTask('composer.update', false)">composer update</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button class="terminal-menu-item" icon="el-icon-Sell" v-blur @click="webBuild()">{{ t('terminal.Republish') }}</el-button>
<el-button class="terminal-menu-item" icon="el-icon-Delete" v-blur @click="terminal.clearSuccessTask()">
{{ t('terminal.Clean up task list') }}
</el-button>
<el-button class="terminal-menu-item" icon="el-icon-Tools" v-blur @click="terminal.toggleConfigDialog()">
{{ t('terminal.Terminal settings') }}
</el-button>
</div>
</el-dialog>
<el-dialog
@close="terminal.toggleConfigDialog(false)"
:model-value="terminal.state.showConfig"
class="ba-terminal-dialog"
:title="t('terminal.Terminal settings')"
>
<el-form label-position="left" label-width="140">
<FormItem
:label="'NPM ' + t('terminal.Source')"
:model-value="terminal.state.npmRegistry"
:key="terminal.state.npmRegistry"
v-loading="state.registryLoading && state.registryLoadingType == 'npm'"
type="select"
:input-attr="{
border: true,
content: getSourceContent('npm'),
teleported: false,
onChange: (val: string) => changeRegistry(val, 'npm'),
}"
/>
<FormItem
:label="'Composer ' + t('terminal.Source')"
:model-value="terminal.state.composerRegistry"
:key="terminal.state.composerRegistry"
v-loading="state.registryLoading && state.registryLoadingType == 'composer'"
type="select"
:input-attr="{
border: true,
content: getSourceContent('composer'),
teleported: false,
onChange: (val: string) => changeRegistry(val, 'composer'),
}"
/>
<FormItem
:label="t('terminal.NPM package manager')"
:model-value="terminal.state.packageManager"
v-loading="state.packageManagerLoading"
type="select"
:input-attr="{
border: true,
content: { npm: 'NPM', cnpm: 'CNPM', pnpm: 'PNPM', yarn: 'YARN', ni: 'NI', none: t('terminal.Manual execution') },
teleported: false,
onChange: (val: string) => changePackageManager(val),
}"
:tip="t('terminal.NPM package manager tip')"
/>
<FormItem
:label="t('terminal.Clear successful task')"
:model-value="terminal.state.automaticCleanupTask"
type="radio"
:input-attr="{
border: true,
content: { '0': t('Disable'), '1': t('Enable') },
onChange: terminal.changeAutomaticCleanupTask,
}"
:tip="t('terminal.Clear successful task tip')"
/>
</el-form>
<div class="config-buttons">
<el-button @click="terminal.toggleConfigDialog(false)">{{ t('terminal.Back to terminal') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import type { TimelineItemProps } from 'element-plus'
import { ElMessageBox, ElScrollbar } from 'element-plus'
import { nextTick, onMounted, reactive, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { postChangeTerminalConfig } from '/@/api/common'
import FormItem from '/@/components/formItem/index.vue'
import { taskStatus } from '/@/stores/constant/terminalTaskStatus'
import { useTerminal } from '/@/stores/terminal'
import { changeListenDirtyFileSwitch } from '/@/utils/vite'
type SourceType = 'npm' | 'composer'
const { t } = useI18n()
const terminal = useTerminal()
const terminalScrollbarRef = useTemplateRef('terminalScrollbarRef')
const state = reactive({
registryLoading: false,
registryLoadingType: 'npm',
packageManagerLoading: false,
})
const getTaskStatus = (status: number) => {
let statusText = t('terminal.unknown')
let statusType: TimelineItemProps['type'] = 'info'
switch (status) {
case taskStatus.Waiting:
statusText = t('terminal.Waiting for execution')
statusType = 'info'
break
case taskStatus.Connecting:
statusText = t('terminal.Connecting')
statusType = 'warning'
break
case taskStatus.Executing:
statusText = t('terminal.Executing')
statusType = 'warning'
break
case taskStatus.Success:
statusText = t('terminal.Successful execution')
statusType = 'success'
break
case taskStatus.Failed:
statusText = t('terminal.Execution failed')
statusType = 'danger'
break
case taskStatus.Unknown:
statusText = t('terminal.Unknown execution result')
statusType = 'danger'
break
}
return {
statusText: statusText,
statusType: statusType,
}
}
const addTerminalTask = (command: string, pm: boolean, blockOnFailure = true, extend = '', callback: Function = () => {}) => {
if (pm) {
terminal.addTaskPM(command, blockOnFailure, extend, callback)
} else {
terminal.addTask(command, blockOnFailure, extend, callback)
}
// 任务列表滚动条滚动到底部
nextTick(() => {
if (terminalScrollbarRef.value && terminalScrollbarRef.value.wrapRef) {
terminalScrollbarRef.value.setScrollTop(terminalScrollbarRef.value.wrapRef.scrollHeight)
}
})
}
const webBuild = () => {
ElMessageBox.confirm(t('terminal.Are you sure you want to republish?'), t('Reminder'), {
confirmButtonText: t('Confirm'),
cancelButtonText: t('Cancel'),
type: 'warning',
}).then(() => {
changeListenDirtyFileSwitch(false)
addTerminalTask('web-build', true, true, '', () => {
changeListenDirtyFileSwitch(true)
})
})
}
const changePackageManager = (val: string) => {
state.packageManagerLoading = true
postChangeTerminalConfig({ manager: val })
.then((res) => {
if (res.code == 1) {
terminal.changePackageManager(val)
}
})
.finally(() => {
state.packageManagerLoading = false
})
}
const changeRegistry = (val: string, type: SourceType) => {
const oldVal = type == 'npm' ? terminal.state.npmRegistry : terminal.state.composerRegistry
terminal.changeRegistry(val, type)
state.registryLoading = true
state.registryLoadingType = type
terminal.addTask(`set-${type}-registry.${val}`, false, '', (res: taskStatus) => {
state.registryLoading = false
if (res == taskStatus.Failed || res == taskStatus.Unknown) {
ElMessageBox.confirm(t('terminal.Failed to modify the source command, Please try again manually'), t('Reminder'), {
confirmButtonText: t('Confirm'),
showCancelButton: false,
type: 'warning',
}).then(() => {
terminal.changeRegistry(oldVal, type)
})
}
})
}
const getSourceContent = (type: SourceType) => {
let content: anyObj = {}
if (type == 'npm') {
content = { npm: 'npm', taobao: 'taobao', tencent: 'tencent' }
} else if (type == 'composer') {
content = {
composer: 'composer',
huawei: 'huawei',
aliyun: 'aliyun',
tencent: 'tencent',
kkame: 'kkame',
}
}
// 如果值为 unknown则 unknown 选项
if (terminal.state[type == 'npm' ? 'npmRegistry' : 'composerRegistry'] == 'unknown') {
content.unknown = t('Unknown')
}
return content
}
onMounted(() => {
terminal.init()
})
</script>
<style scoped lang="scss">
.terminal-warning-alert {
margin: 0 0 20px 0;
}
.terminal-timeline {
padding: 0 15px;
}
.command {
font-size: var(--el-font-size-large);
font-weight: bold;
margin-left: 10px;
}
.exec-message {
color: var(--ba-bg-color-overlay);
font-size: 12px;
line-height: 16px;
padding: 6px;
background-color: #424251;
margin-top: 10px;
min-height: 30px;
max-height: 200px;
overflow: auto;
&::-webkit-scrollbar {
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-thumb {
background: #c8c9cc;
border-radius: 4px;
box-shadow: none;
-webkit-box-shadow: none;
}
&::-webkit-scrollbar-track {
background: var(--ba-bg-color);
}
&:hover {
&::-webkit-scrollbar-thumb:hover {
background: #909399;
}
}
}
@supports not (selector(::-webkit-scrollbar)) {
.exec-message {
scrollbar-width: thin;
scrollbar-color: #c8c9cc #eaeaea;
}
}
.toggle-message-display {
padding-top: 10px;
font-size: 13px;
color: var(--el-text-color-secondary);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.task-opt {
display: none;
float: right;
}
.task-item.task-status-0:hover,
.task-item.task-status-3:hover,
.task-item.task-status-4:hover,
.task-item.task-status-5:hover {
.task-opt {
display: inline;
}
}
.block-on-failure-tag {
margin-left: 10px;
}
.terminal-menu-item {
margin-bottom: 12px;
}
.terminal-menu-item + .terminal-menu-item {
margin-left: 12px;
margin-bottom: 12px;
}
.terminal-buttons {
display: block;
width: fit-content;
margin: 0 auto;
padding-top: 12px;
}
.config-buttons {
display: flex;
align-items: center;
justify-content: flex-end;
padding-top: 20px;
padding-right: 20px;
}
:deep(.main-dialog) {
--el-dialog-padding-primary: 16px 16px 0 16px;
.el-dialog__body {
margin-top: 16px;
}
}
:deep(.ba-terminal-dialog) {
--el-dialog-width: 46% !important;
.el-loading-spinner {
--el-loading-spinner-size: 20px;
}
}
@media screen and (max-width: 768px) {
:deep(.ba-terminal-dialog) {
--el-dialog-width: 80% !important;
}
}
@media screen and (max-width: 540px) {
:deep(.ba-terminal-dialog) {
--el-dialog-width: 94% !important;
}
}
</style>

View File

@@ -71,13 +71,6 @@
<Icon :color="configStore.getColorVal('headerBarTabColor')" class="nav-menu-icon" v-else name="el-icon-FullScreen" size="18" />
</div>
<!-- 终端 - 仅超管 -->
<div v-if="adminInfo.super" @click="terminal.toggle()" class="nav-menu-item pt2">
<el-badge :is-dot="terminal.state.showDot">
<Icon :color="configStore.getColorVal('headerBarTabColor')" class="nav-menu-icon" name="local-terminal" size="26" />
</el-badge>
</div>
<!-- 清理缓存 - 仅超管 -->
<el-dropdown
v-if="adminInfo.super"
@@ -140,7 +133,6 @@
</div>
<Config />
<Terminal />
</div>
</template>
@@ -152,14 +144,12 @@ import { useI18n } from 'vue-i18n'
import Config from './config.vue'
import { logout } from '/@/api/backend/index'
import { postClearCache } from '/@/api/common'
import Terminal from '/@/components/terminal/index.vue'
import { editDefaultLang } from '/@/lang'
import router from '/@/router'
import { useAdminInfo } from '/@/stores/adminInfo'
import { useConfig } from '/@/stores/config'
import { ADMIN_INFO, BA_ACCOUNT } from '/@/stores/constant/cacheKey'
import { useSiteConfig } from '/@/stores/siteConfig'
import { useTerminal } from '/@/stores/terminal'
import { fullUrl, timeFormat } from '/@/utils/common'
import { routePush } from '/@/utils/router'
import { Local, Session } from '/@/utils/storage'
@@ -169,7 +159,6 @@ const { t } = useI18n()
const adminInfo = useAdminInfo()
const configStore = useConfig()
const terminal = useTerminal()
const siteConfig = useSiteConfig()
const reloadHotServerPopover = useTemplateRef('reloadHotServerPopover')

View File

@@ -6,7 +6,6 @@
import { reactive } from 'vue'
import { useConfig } from '/@/stores/config'
import { useNavTabs } from '/@/stores/navTabs'
import { useTerminal } from '/@/stores/terminal'
import { useSiteConfig } from '/@/stores/siteConfig'
import { useAdminInfo } from '/@/stores/adminInfo'
import { useRoute } from 'vue-router'
@@ -29,7 +28,6 @@ defineOptions({
components: { Default, Classic, Streamline, Double },
})
const terminal = useTerminal()
const navTabs = useNavTabs()
const config = useConfig()
const route = useRoute()
@@ -59,8 +57,6 @@ const init = () => {
index()
.then((res) => {
siteConfig.dataFill(res.data.siteConfig)
terminal.changePackageManager(res.data.terminal.npmPackageManager)
terminal.changePHPDevelopmentServer(res.data.terminal.phpDevelopmentServer)
siteConfig.setInitialize(true)
if (!isEmpty(res.data.adminInfo)) {

View File

@@ -9,8 +9,6 @@ export const ADMIN_INFO = 'adminInfo'
export const STORE_CONFIG = 'storeConfig_v2'
// 后台标签页
export const STORE_TAB_VIEW_CONFIG = 'storeTabViewConfig'
// 终端
export const STORE_TERMINAL = 'storeTerminal'
// 工作时间
export const WORKING_TIME = 'workingTime'

View File

@@ -1,8 +0,0 @@
export const enum taskStatus {
Waiting,
Connecting,
Executing,
Success,
Failed,
Unknown,
}

View File

@@ -134,48 +134,6 @@ export interface UserInfo {
refresh_token: string
}
export interface TaskItem {
// 任务唯一标识
uuid: string
// 创建时间
createTime: string
// 状态
status: number
// 命令
command: string
// 命令执行日志
message: string[]
// 显示命令执行日志
showMessage: boolean
// 失败阻断后续命令执行
blockOnFailure: boolean
// 扩展信息,自动发送到后台
extend: string
// 执行结果回调
callback: Function
}
export interface Terminal {
// 显示终端窗口
show: boolean
// 在后台终端按钮上显示一个红点
showDot: boolean
// 任务列表
taskList: TaskItem[]
// 包管理器
packageManager: string
// 显示终端设置窗口
showConfig: boolean
// 开始任务时自动清理已完成任务
automaticCleanupTask: string
// PHP 开发服务环境
phpDevelopmentServer: boolean
// NPM 源
npmRegistry: string
// composer 源
composerRegistry: string
}
export interface SiteConfig {
// 站点名称
siteName: string

View File

@@ -1,292 +0,0 @@
import { ElNotification } from 'element-plus'
import { defineStore } from 'pinia'
import { nextTick, reactive } from 'vue'
import { buildTerminalUrl } from '/@/api/common'
import { i18n } from '/@/lang/index'
import { STORE_TERMINAL } from '/@/stores/constant/cacheKey'
import { SYSTEM_ZINDEX } from '/@/stores/constant/common'
import { taskStatus } from '/@/stores/constant/terminalTaskStatus'
import type { Terminal } from '/@/stores/interface/index'
import { timeFormat } from '/@/utils/common'
import { uuid } from '/@/utils/random'
import { closeHotUpdate, openHotUpdate } from '/@/utils/vite'
export const useTerminal = defineStore(
'terminal',
() => {
const state: Terminal = reactive({
show: false,
showDot: false,
taskList: [],
packageManager: 'pnpm',
showConfig: false,
automaticCleanupTask: '1',
phpDevelopmentServer: false,
npmRegistry: 'unknown',
composerRegistry: 'unknown',
})
function init() {
for (const key in state.taskList) {
if (state.taskList[key].status == taskStatus.Connecting || state.taskList[key].status == taskStatus.Executing) {
state.taskList[key].status = taskStatus.Unknown
}
}
}
function toggle(val = !state.show) {
state.show = val
if (val) {
toggleDot(false)
}
}
function toggleDot(val = !state.showDot) {
state.showDot = val
}
function toggleConfigDialog(val = !state.showConfig) {
toggle(!val)
state.showConfig = val
}
function changeRegistry(val: string, type: 'npm' | 'composer') {
state[type == 'npm' ? 'npmRegistry' : 'composerRegistry'] = val
}
function changePackageManager(val: string) {
state.packageManager = val
}
function changePHPDevelopmentServer(val: boolean) {
state.phpDevelopmentServer = val
}
function changeAutomaticCleanupTask(val: '0' | '1') {
state.automaticCleanupTask = val
}
function setTaskStatus(idx: number, status: number) {
state.taskList[idx].status = status
if ((status == taskStatus.Failed || status == taskStatus.Unknown) && state.taskList[idx].blockOnFailure) {
setTaskShowMessage(idx, true)
}
}
function taskCompleted(idx: number) {
// 命令执行完毕,重新打开热更新
openHotUpdate('terminal')
if (typeof state.taskList[idx].callback != 'function') return
const status = state.taskList[idx].status
if (status == taskStatus.Failed || status == taskStatus.Unknown) {
state.taskList[idx].callback(taskStatus.Failed)
} else if (status == taskStatus.Success) {
state.taskList[idx].callback(taskStatus.Success)
}
}
function setTaskShowMessage(idx: number, val = !state.taskList[idx].showMessage) {
state.taskList[idx].showMessage = val
}
function addTaskMessage(idx: number, message: string) {
if (!state.show) toggleDot(true)
state.taskList[idx].message = state.taskList[idx].message.concat(message)
nextTick(() => {
execMessageScrollbarKeep(state.taskList[idx].uuid)
})
}
function addTask(command: string, blockOnFailure = true, extend = '', callback: Function = () => {}) {
if (!state.show) toggleDot(true)
state.taskList = state.taskList.concat({
uuid: uuid(),
createTime: timeFormat(),
status: taskStatus.Waiting,
command: command,
message: [],
showMessage: false,
blockOnFailure: blockOnFailure,
extend: extend,
callback: callback,
})
// 清理任务列表
if (parseInt(state.automaticCleanupTask) === 1) {
clearSuccessTask()
}
// 检查是否有已经失败的任务
if (state.show === false) {
for (const key in state.taskList) {
if (state.taskList[key].status == taskStatus.Failed || state.taskList[key].status == taskStatus.Unknown) {
ElNotification({
type: 'error',
message: i18n.global.t('terminal.Newly added tasks will never start because they are blocked by failed tasks'),
zIndex: SYSTEM_ZINDEX,
})
break
}
}
}
startTask()
}
function addTaskPM(command: string, blockOnFailure = true, extend = '', callback: Function = () => {}) {
addTask(command + '.' + state.packageManager, blockOnFailure, extend, callback)
}
function delTask(idx: number) {
if (state.taskList[idx].status != taskStatus.Connecting && state.taskList[idx].status != taskStatus.Executing) {
state.taskList.splice(idx, 1)
}
startTask()
}
function startTask() {
let taskKey = null
// 寻找可以开始执行的命令
for (const key in state.taskList) {
if (state.taskList[key].status == taskStatus.Waiting) {
taskKey = parseInt(key)
break
}
if (state.taskList[key].status == taskStatus.Connecting || state.taskList[key].status == taskStatus.Executing) {
break
}
if (state.taskList[key].status == taskStatus.Success) {
continue
}
if (state.taskList[key].status == taskStatus.Failed || state.taskList[key].status == taskStatus.Unknown) {
if (state.taskList[key].blockOnFailure) {
break
} else {
continue
}
}
}
if (taskKey !== null) {
setTaskStatus(taskKey, taskStatus.Connecting)
startEventSource(taskKey)
}
}
function startEventSource(taskKey: number) {
// 命令执行期间禁用热更新
closeHotUpdate('terminal')
window.eventSource = new EventSource(
buildTerminalUrl(state.taskList[taskKey].command, state.taskList[taskKey].uuid, state.taskList[taskKey].extend)
)
window.eventSource.onmessage = function (e) {
const data = JSON.parse(e.data)
if (!data || !data.data) {
return
}
const taskIdx = findTaskIdxFromUuid(data.uuid)
if (taskIdx === false) {
return
}
if (data.data == 'command-exec-error') {
setTaskStatus(taskIdx, taskStatus.Failed)
window.eventSource.close()
taskCompleted(taskIdx)
startTask()
} else if (data.data == 'command-exec-completed') {
window.eventSource.close()
if (state.taskList[taskIdx].status != taskStatus.Success) {
setTaskStatus(taskIdx, taskStatus.Failed)
}
taskCompleted(taskIdx)
startTask()
} else if (data.data == 'command-link-success') {
setTaskStatus(taskIdx, taskStatus.Executing)
} else if (data.data == 'command-exec-success') {
setTaskStatus(taskIdx, taskStatus.Success)
} else {
addTaskMessage(taskIdx, data.data)
}
}
window.eventSource.onerror = function () {
window.eventSource.close()
const taskIdx = findTaskIdxFromGuess(taskKey)
if (taskIdx === false) return
setTaskStatus(taskIdx, taskStatus.Failed)
taskCompleted(taskIdx)
}
}
function retryTask(idx: number) {
state.taskList[idx].message = []
setTaskStatus(idx, taskStatus.Waiting)
startTask()
}
function clearSuccessTask() {
state.taskList = state.taskList.filter((item) => item.status != taskStatus.Success)
}
function findTaskIdxFromUuid(uuid: string) {
for (const key in state.taskList) {
if (state.taskList[key].uuid == uuid) {
return parseInt(key)
}
}
return false
}
function findTaskIdxFromGuess(idx: number) {
if (!state.taskList[idx]) {
let taskKey = -1
for (const key in state.taskList) {
if (state.taskList[key].status == taskStatus.Connecting || state.taskList[key].status == taskStatus.Executing) {
taskKey = parseInt(key)
}
}
return taskKey === -1 ? false : taskKey
} else {
return idx
}
}
function execMessageScrollbarKeep(uuid: string) {
const execMessageEl = document.querySelector('.exec-message-' + uuid) as Element
if (execMessageEl && execMessageEl.scrollHeight) {
execMessageEl.scrollTop = execMessageEl.scrollHeight
}
}
return {
state,
init,
toggle,
toggleDot,
setTaskStatus,
setTaskShowMessage,
addTaskMessage,
addTask,
addTaskPM,
delTask,
startTask,
retryTask,
clearSuccessTask,
toggleConfigDialog,
changeRegistry,
changePackageManager,
changePHPDevelopmentServer,
changeAutomaticCleanupTask,
}
},
{
persist: {
key: STORE_TERMINAL,
pick: ['state.showDot', 'state.taskList', 'state.automaticCleanupTask', 'state.npmRegistry', 'state.composerRegistry'],
},
}
)

View File

@@ -287,7 +287,25 @@ export const checkFileMimetype = (fileName: string, fileType: string) => {
? siteConfig.upload.allowedMimeTypes
: siteConfig.upload.allowedMimeTypes.toLowerCase().split(',')
const forbiddenSuffixes = siteConfig.upload.forbiddenSuffixes
? isArray(siteConfig.upload.forbiddenSuffixes)
? siteConfig.upload.forbiddenSuffixes
: siteConfig.upload.forbiddenSuffixes.toLowerCase().split(',')
: []
const nameParts = fileName.toLowerCase().split('.')
if (nameParts.length > 1) {
for (let i = 0; i < nameParts.length - 1; i++) {
if (forbiddenSuffixes.includes(nameParts[i])) {
return false
}
}
}
const fileSuffix = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()
if (forbiddenSuffixes.includes(fileSuffix)) {
return false
}
if (allowedSuffixes.includes(fileSuffix) || allowedSuffixes.includes('.' + fileSuffix)) {
return true
}

View File

@@ -691,7 +691,6 @@ import { getDatabaseConnectionListUrl, getTableFieldList, getTableListUrl } from
import BaInput from '/@/components/baInput/index.vue'
import FormItem from '/@/components/formItem/index.vue'
import { useConfig } from '/@/stores/config'
import { useTerminal } from '/@/stores/terminal'
import { getArrayKey } from '/@/utils/common'
import { uuid } from '/@/utils/random'
import { buildValidatorData, regularVarName } from '/@/utils/validate'
@@ -702,7 +701,6 @@ import { changeStep, state as crudState, designTypes, fieldItem, getTableAttr, t
let nameRepeatCount = 1
const { t } = useI18n()
const config = useConfig()
const terminal = useTerminal()
const formRef = useTemplateRef('formRef')
const tabsRefs = useTemplateRefsList<HTMLElement>()
const designWindowRef = useTemplateRef('designWindowRef')
@@ -1161,22 +1159,15 @@ const startGenerate = () => {
})
.then((res) => {
const callback = () => {
const webViewsDir = state.table.webViewsDir.replace(/^web/, '.')
terminal.toggle(true)
terminal.addTask('npx.prettier', false, webViewsDir, () => {
terminal.toggle(false)
terminal.toggleDot(true)
nextTick(() => {
// 要求 Vite 服务端重启
if (import.meta.hot) {
reloadServer('crud')
} else {
ElNotification({
type: 'error',
message: t('crud.crud.Vite hot warning'),
})
}
})
nextTick(() => {
if (import.meta.hot) {
reloadServer('crud')
} else {
ElNotification({
type: 'error',
message: t('crud.crud.Vite hot warning'),
})
}
})
}