1.修复上传漏洞和SQL注入漏洞-增强登录验证功能
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程下拉框数据获取
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export const enum taskStatus {
|
||||
Waiting,
|
||||
Connecting,
|
||||
Executing,
|
||||
Success,
|
||||
Failed,
|
||||
Unknown,
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user