fix: Fix the problem that some Settings on the panel are abnormal (#8195)

This commit is contained in:
ssongliu 2025-03-19 17:17:22 +08:00 committed by GitHub
parent eeda0125bb
commit 4470b13d18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 211 additions and 143 deletions

View file

@ -71,16 +71,19 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if req.Key == "SecurityEntrance" {
if !checkEntrancePattern(req.Value) {
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", fmt.Errorf("the format of the security entrance %s is incorrect.", req.Value))
return
}
}
if err := settingService.Update(req.Key, req.Value); err != nil {
helper.InternalServer(c, err)
return
}
if req.Key == "SecurityEntrance" {
entranceValue := base64.StdEncoding.EncodeToString([]byte(req.Value))
if !checkEntrancePattern(entranceValue) {
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", fmt.Errorf("the format of the security entrance %s is incorrect.", entranceValue))
return
}
c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true)
}
helper.SuccessWithOutData(c)

View file

@ -14,7 +14,7 @@ type ICommandService interface {
List(req dto.OperateByType) ([]dto.CommandInfo, error)
SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error)
SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error)
Create(commandDto dto.CommandOperate) error
Create(req dto.CommandOperate) error
Update(id uint, upMap map[string]interface{}) error
Delete(ids []uint) error
}
@ -98,12 +98,12 @@ func (u *CommandService) SearchWithPage(req dto.SearchCommandWithPage) (int64, i
return total, dtoCommands, err
}
func (u *CommandService) Create(commandDto dto.CommandOperate) error {
command, _ := commandRepo.Get(repo.WithByName(commandDto.Name))
func (u *CommandService) Create(req dto.CommandOperate) error {
command, _ := commandRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type))
if command.ID != 0 {
return buserr.New("ErrRecordExist")
}
if err := copier.Copy(&command, &commandDto); err != nil {
if err := copier.Copy(&command, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
if err := commandRepo.Create(&command); err != nil {

View file

@ -23,7 +23,7 @@ type IHostService interface {
GetHostByID(id uint) (*dto.HostInfo, error)
SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error)
SearchWithPage(search dto.SearchPageWithGroup) (int64, interface{}, error)
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
Create(req dto.HostOperate) (*dto.HostInfo, error)
Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error)
Delete(id []uint) error
@ -300,16 +300,19 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
}
func (u *HostService) Delete(ids []uint) error {
hosts, _ := hostRepo.GetList(repo.WithByIDs(ids))
for _, host := range hosts {
for _, id := range ids {
host, _ := hostRepo.Get(repo.WithByID(id))
if host.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if host.Addr == "127.0.0.1" {
if host.Name == "local" {
return errors.New("the local connection information cannot be deleted!")
}
if err := hostRepo.Delete(repo.WithByID(id)); err != nil {
return err
}
}
return hostRepo.Delete(repo.WithByIDs(ids))
return nil
}
func (u *HostService) Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error) {

View file

@ -218,6 +218,9 @@ func Routers() *gin.Engine {
if global.CONF.Base.IsDemo {
Router.Use(middleware.DemoHandle())
}
Router.Use(middleware.JwtAuth())
Router.Use(middleware.SessionAuth())
Router.Use(middleware.PasswordExpired())
Router.Use(middleware.GlobalLoading())
Router.Use(Proxy())

View file

@ -13,11 +13,11 @@
<div>
<el-button class="close" link @click="closePage" icon="Close"></el-button>
<div>
<el-button type="text" icon="HomeFilled" @click="jump(-1)"></el-button>
<el-button v-if="paths.length > 0" type="text" @click="jump(0)">/{{ paths[0] }}</el-button>
<el-button link icon="HomeFilled" @click="jump(-1)"></el-button>
<el-button v-if="paths.length > 0" link @click="jump(0)">/{{ paths[0] }}</el-button>
<el-popover v-if="paths.length > 2" placement="bottom" trigger="hover">
<template #reference>
<el-button type="text">...</el-button>
<el-button link>...</el-button>
</template>
<div class="hidden-paths">
<div v-for="(item, index) in paths.slice(1, -1)" :key="index">
@ -26,7 +26,7 @@
</div>
</div>
</el-popover>
<el-button v-if="paths.length > 1" type="text" @click="jump(paths.length - 1)">
<el-button v-if="paths.length > 1" link @click="jump(paths.length - 1)">
/{{ paths[paths.length - 1] }}
</el-button>
</div>

View file

@ -1292,6 +1292,7 @@ const message = {
databases: 'Database',
licenses: 'License',
nodes: 'Node',
commands: 'Quick Commands',
},
websiteLog: 'Website Logs',
runLog: 'Run Log',

View file

@ -1229,6 +1229,7 @@ const message = {
databases: 'データベース',
licenses: 'ライセンス',
nodes: 'ノード',
commands: 'クイックコマンド',
},
websiteLog: 'ウェブサイトログ',
runLog: 'ログを実行します',

View file

@ -1217,6 +1217,7 @@ const message = {
databases: '데이터베이스',
licenses: '라이선스',
nodes: '노드',
commands: '빠른 명령',
},
websiteLog: '웹사이트 로그',
runLog: '실행 로그',

View file

@ -1272,6 +1272,7 @@ const message = {
databases: 'Pangkalan',
licenses: 'lesen',
nodes: 'nod',
commands: 'Perintah Pantas',
},
websiteLog: 'Log Laman Web',
runLog: 'Log Jalankan',

View file

@ -1255,6 +1255,7 @@ const message = {
databases: 'Bancos de Dados',
licenses: 'licenças',
nodes: 'nós',
commands: 'Comandos Rápidos',
},
websiteLog: 'Logs do website',
runLog: 'Logs de execução',

View file

@ -1263,6 +1263,7 @@ const message = {
databases: 'Базы данных',
licenses: 'лицензии',
nodes: 'ноды',
commands: 'Быстрые команды',
},
websiteLog: 'Логи веб-сайта',
runLog: 'Логи выполнения',

View file

@ -1220,6 +1220,7 @@ const message = {
databases: '資料庫',
licenses: '許可證',
nodes: '節點',
commands: '快速命令',
},
websiteLog: '網站日誌',
runLog: '運行日誌',

View file

@ -1218,6 +1218,7 @@ const message = {
databases: '数据库',
licenses: '许可证',
nodes: '节点',
commands: '快速命令',
},
websiteLog: '网站日志',
runLog: '运行日志',

View file

@ -57,7 +57,7 @@
<div v-if="operateReq.operate === 'upgrade'">
<el-text type="warning">{{ $t('app.upgradeWarn') }}</el-text>
<el-button class="ml-1.5" type="text" @click="openDiff()">{{ $t('app.showDiff') }}</el-button>
<el-button class="ml-1.5" link @click="openDiff()">{{ $t('app.showDiff') }}</el-button>
<div>
<el-checkbox v-model="useNewCompose" :label="$t('app.useCustom')" size="large" />
</div>

View file

@ -53,7 +53,7 @@
<el-divider border-style="dashed" />
<div v-if="form.from === 'local'">
<el-form-item :label="$t('commons.login.username')" prop="username">
<el-input type="text" readonly disabled v-model="form.username">
<el-input readonly disabled v-model="form.username">
<template #append>
<el-button-group>
<CopyButton :content="form.username" />

View file

@ -397,7 +397,7 @@ const loadDataFromDB = async () => {
i18n.locale.value = res.data.language;
i18n.warnHtmlMessage = false;
globalStore.entrance = res.data.securityEntrance;
globalStore.setOpenMenuTabs(res.data.menuTabs === 'enable');
globalStore.setOpenMenuTabs(res.data.menuTabs === 'Enable');
globalStore.updateLanguage(res.data.language);
let theme = globalStore.themeConfig.theme === res.data.theme ? res.data.theme : globalStore.themeConfig.theme;
globalStore.setThemeConfig({ ...themeConfig.value, theme: theme, panelName: res.data.panelName });

View file

@ -13,7 +13,7 @@
</div>
<h3 class="description">{{ globalStore.themeConfig.title || $t('setting.description') }}</h3>
<div class="flex justify-center">
<SystemUpgrade />
<SystemUpgrade class="upgrade" />
</div>
<div class="flex w-full justify-center my-5 flex-wrap md:flex-row gap-4">
<el-link @click="toDoc" class="system-link">
@ -94,4 +94,7 @@ onMounted(() => {
height: 45px;
}
}
.upgrade {
all: initial;
}
</style>

View file

@ -100,7 +100,7 @@ const submitChangePassword = async (formEl: FormInstance | undefined) => {
const search = async () => {
const res = await getSettingInfo();
let settingForm = res.data;
isComplexity.value = settingForm?.complexityVerification === 'enable';
isComplexity.value = settingForm?.complexityVerification === 'Enable';
};
onMounted(() => {

View file

@ -85,7 +85,7 @@ const formRef = ref();
const apiURL = `${window.location.protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : ''
}/1panel/swagger/index.html`;
const panelURL = `${globalStore.docsUrl}/user_manual/api_manual/`;
const panelURL = `${globalStore.docsUrl}/dev_manual/api_manual/`;
const form = reactive({
apiKey: '',
ipWhiteList: '',

View file

@ -452,7 +452,7 @@ const onSave = async (key: string, val: any) => {
handleThemeChange(val);
}
if (key === 'MenuTabs') {
globalStore.setOpenMenuTabs(val === 'enable');
globalStore.setOpenMenuTabs(val === 'Enable');
}
MsgSuccess(i18n.t('commons.msg.operationSuccess'));
search();

View file

@ -5,7 +5,7 @@
<el-alert type="info" :title="$t('terminal.quickCommandHelper')" :closable="false" />
</template>
<template #leftToolBar>
<el-button type="primary" @click="onCreate()">
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}{{ $t('terminal.quickCommand') }}
</el-button>
<el-button type="primary" plain @click="onOpenGroupDialog()">
@ -28,6 +28,7 @@
</div>
</el-select>
<TableSearch @search="search()" v-model:searchName="info" />
<TableRefresh @search="search()" />
</template>
<template #main>
<ComplexTable
@ -81,50 +82,9 @@
</ComplexTable>
</template>
</LayoutContent>
<DrawerPro
v-model="cmdVisible"
:header="$t('commons.button.' + operate) + $t('terminal.quickCommand')"
@close="handleClose"
size="small"
>
<el-form
@submit.prevent
ref="commandInfoRef"
label-width="100px"
label-position="top"
:model="commandInfo"
:rules="rules"
>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model="commandInfo.name" />
</el-form-item>
<el-form-item :label="$t('commons.table.group')" prop="name">
<el-select filterable v-model="commandInfo.groupID" clearable style="width: 100%">
<div v-for="item in groupList" :key="item.id">
<el-option
v-if="item.name === 'Default'"
:label="$t('commons.table.default')"
:value="item.id"
/>
<el-option v-else :label="item.name" :value="item.id" />
</div>
</el-select>
</el-form-item>
<el-form-item :label="$t('terminal.command')" prop="command">
<el-input type="textarea" clearable v-model="commandInfo.command" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="cmdVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submitAddCommand(commandInfoRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DrawerPro>
<OpDialog ref="opRef" @search="search" />
<OperateDialog @search="search" ref="dialogRef" />
<GroupDialog @search="loadGroups" ref="dialogGroupRef" />
</div>
</template>
@ -132,10 +92,9 @@
<script setup lang="ts">
import { Command } from '@/api/interface/command';
import GroupDialog from '@/components/group/index.vue';
import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/command';
import OperateDialog from '@/views/terminal/command/operate/index.vue';
import { editCommand, deleteCommand, getCommandPage } from '@/api/modules/command';
import { reactive, ref } from 'vue';
import type { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { getGroupList } from '@/api/modules/group';
@ -154,55 +113,30 @@ const paginationConfig = reactive({
});
const info = ref();
const group = ref<string>('');
const dialogRef = ref();
const opRef = ref();
type FormInstance = InstanceType<typeof ElForm>;
const commandInfoRef = ref<FormInstance>();
const rules = reactive({
name: [Rules.requiredInput],
command: [Rules.requiredInput],
});
let operate = ref<string>('create');
const acceptParams = () => {
search();
loadGroups();
};
const defaultGroupID = ref();
let commandInfo = reactive<Command.CommandOperate>({
id: 0,
type: 'command',
name: '',
groupID: 0,
command: '',
});
const cmdVisible = ref<boolean>(false);
const loadGroups = async () => {
const res = await getGroupList('command');
groupList.value = res.data;
for (const group of groupList.value) {
if (group.isDefault) {
defaultGroupID.value = group.id;
break;
}
}
groupList.value = res.data || [];
};
const onCreate = async () => {
commandInfo.id = 0;
commandInfo.name = '';
commandInfo.command = '';
commandInfo.groupID = defaultGroupID.value;
operate.value = 'create';
cmdVisible.value = true;
};
const handleClose = () => {
cmdVisible.value = false;
const onOpenDialog = async (
title: string,
rowData: Partial<Command.CommandInfo> = {
type: 'command',
},
) => {
let params = {
title,
rowData: { ...rowData },
};
dialogRef.value!.acceptParams(params);
};
const dialogGroupRef = ref();
@ -210,21 +144,6 @@ const onOpenGroupDialog = () => {
dialogGroupRef.value!.acceptParams({ type: 'command' });
};
const submitAddCommand = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (operate.value === 'create') {
await addCommand(commandInfo);
} else {
await editCommand(commandInfo);
}
cmdVisible.value = false;
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
const updateGroup = async (row: any) => {
await editCommand(row);
search();
@ -260,9 +179,7 @@ const buttons = [
label: i18n.global.t('commons.button.edit'),
icon: 'Edit',
click: (row: any) => {
commandInfo = row;
operate.value = 'edit';
cmdVisible.value = true;
onOpenDialog('edit', row);
},
},
{

View file

@ -0,0 +1,125 @@
<template>
<DrawerPro
v-model="open"
:header="title"
:resource="dialogData.title === 'create' ? '' : dialogData.rowData?.name"
@close="handleClose"
size="small"
>
<el-form
@submit.prevent
ref="formRef"
label-width="100px"
label-position="top"
:model="dialogData.rowData"
:rules="rules"
>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input clearable v-model="dialogData.rowData!.name" />
</el-form-item>
<el-form-item :label="$t('commons.table.group')" prop="groupID">
<el-select filterable v-model="dialogData.rowData!.groupID" clearable style="width: 100%">
<div v-for="item in groupList" :key="item.id">
<el-option
v-if="item.name === 'Default'"
:label="$t('commons.table.default')"
:value="item.id"
/>
<el-option v-else :label="item.name" :value="item.id" />
</div>
</el-select>
</el-form-item>
<el-form-item :label="$t('terminal.command')" prop="command">
<el-input type="textarea" clearable v-model="dialogData.rowData!.command" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="open = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { Command } from '@/api/interface/command';
import { addCommand, editCommand } from '@/api/modules/command';
import { getGroupList } from '@/api/modules/group';
const loading = ref();
const open = ref();
const groupList = ref();
const rules = reactive({
name: [Rules.requiredInput],
command: [Rules.requiredInput],
});
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
interface DialogProps {
title: string;
rowData?: Command.CommandInfo;
getTableList?: () => Promise<any>;
}
const title = ref<string>('');
const dialogData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
title.value = i18n.global.t('commons.button.' + dialogData.value.title) + i18n.global.t('terminal.quickCommand');
loadGroups();
open.value = true;
};
const handleClose = () => {
open.value = false;
};
const emit = defineEmits<{ (e: 'search'): void }>();
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
if (!valid) return;
if (dialogData.value.title === 'create') {
await addCommand(dialogData.value.rowData);
} else {
await editCommand(dialogData.value.rowData);
}
open.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
const loadGroups = async () => {
const res = await getGroupList('command');
groupList.value = res.data;
if (dialogData.value.title === 'edit') {
return;
}
for (const group of groupList.value) {
if (group.isDefault) {
dialogData.value.rowData.groupID = group.id;
break;
}
}
};
defineExpose({
acceptParams,
});
</script>

View file

@ -107,7 +107,7 @@ const acceptParams = () => {
};
function selectable(row) {
return row.addr !== '127.0.0.1';
return row.name !== 'local';
}
const dialogRef = ref();
const onOpenDialog = async (
@ -174,12 +174,12 @@ const buttons = [
},
{
label: i18n.global.t('commons.button.delete'),
disabled: (row: any) => {
return row.name === 'local';
},
click: (row: Host.Host) => {
onBatchDelete(row);
},
disabled: (row: any) => {
return row.addr === '127.0.0.1';
},
},
];

View file

@ -60,7 +60,7 @@
</div>
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-form-item :label="$t('commons.table.title')" prop="name">
<el-tag v-if="itemName === 'local'">local</el-tag>
<el-input v-else clearable v-model="dialogData.rowData!.name" />
</el-form-item>

View file

@ -54,6 +54,10 @@
</div>
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.table.title')" prop="name">
<el-tag v-if="isLocal">local</el-tag>
<el-input v-else clearable v-model="hostInfo.name" />
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input clearable v-model="hostInfo.description" />
</el-form-item>
@ -112,7 +116,14 @@ const rules = reactive({
authMode: [Rules.requiredSelect],
password: [Rules.requiredInput],
privateKey: [Rules.requiredInput],
name: [{ validator: checkName, trigger: 'blur' }],
});
function checkName(rule: any, value: any, callback: any) {
if (value === 'local' && !isLocal.value) {
return callback(new Error(i18n.global.t('terminal.localHelper')));
}
callback();
}
const isLocal = ref(false);
interface DialogProps {
@ -166,7 +177,7 @@ const loadLocal = async () => {
const setDefault = () => {
hostInfo.addr = '';
hostInfo.name = 'local';
hostInfo.name = '';
hostInfo.groupID = defaultGroup.value;
hostInfo.port = 22;
hostInfo.user = '';
@ -204,7 +215,7 @@ const submitAddHost = (formEl: FormInstance | undefined, ops: string) => {
if (res.data.name.length !== 0) {
title = res.data.name + '-' + title;
}
let isLocal = hostInfo.addr === '127.0.0.1';
let isLocal = hostInfo.name === 'local';
emit('on-conn-terminal', title, res.data.id, isLocal);
emit('load-host-tree');
break;

View file

@ -1,5 +1,5 @@
<template>
<div>
<div @keydown.esc="toggleFullscreen()">
<el-tabs
type="card"
class="terminal-tabs"

View file

@ -3,12 +3,7 @@
<el-col :span="22" :offset="1">
<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item prop="funcs">
<el-input
type="text"
v-model="form.funcs"
label="value"
:placeholder="$t('php.disableFunctionHelper')"
/>
<el-input v-model="form.funcs" label="value" :placeholder="$t('php.disableFunctionHelper')" />
</el-form-item>
</el-form>
<ComplexTable :data="data" v-loading="loading">

View file

@ -15,7 +15,7 @@
</el-form-item>
<div v-if="form.enable">
<el-form-item :label="$t('website.extends')" prop="extends">
<el-input v-model="form.extends" type="text"></el-input>
<el-input v-model="form.extends"></el-input>
</el-form-item>
<el-form-item :label="$t('website.browserCache')" prop="cache">
<el-switch v-model="form.cache" />
@ -41,7 +41,7 @@
<el-input v-model="form.domains" type="textarea" :rows="6"></el-input>
</el-form-item>
<el-form-item :label="$t('website.leechReturn')" prop="return">
<el-input v-model="form.return" type="text" :maxlength="35"></el-input>
<el-input v-model="form.return" :maxlength="35"></el-input>
</el-form-item>
<el-form-item>
<el-button

View file

@ -52,7 +52,7 @@
</el-form-item>
<el-form-item prop="ipOther" v-if="req.ipHeader === 'other'">
<el-input type="text" v-model.trim="req.ipOther" />
<el-input v-model.trim="req.ipOther" />
</el-form-item>
</div>
<el-form-item>