fix: Operation logs Add node information (#8212)

This commit is contained in:
ssongliu 2025-03-21 17:17:24 +08:00 committed by GitHub
parent 57f7cb30b4
commit 337dad2ca6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 121 additions and 39 deletions

View file

@ -137,11 +137,7 @@ func (b *BaseApi) UpdateCommand(c *gin.Context) {
return
}
upMap := make(map[string]interface{})
upMap["name"] = req.Name
upMap["group_id"] = req.GroupID
upMap["command"] = req.Command
if err := commandService.Update(req.ID, upMap); err != nil {
if err := commandService.Update(req); err != nil {
helper.InternalServer(c, err)
return
}

View file

@ -5,9 +5,9 @@ import (
)
type OperationLog struct {
ID uint `json:"id"`
Source string `json:"source"`
ID uint `json:"id"`
Source string `json:"source"`
Node string `json:"node"`
IP string `json:"ip"`
Path string `json:"path"`
Method string `json:"method"`
@ -26,6 +26,7 @@ type SearchOpLogWithPage struct {
PageInfo
Source string `json:"source"`
Status string `json:"status"`
Node string `json:"node"`
Operation string `json:"operation"`
}

View file

@ -8,6 +8,7 @@ type OperationLog struct {
BaseModel
Source string `json:"source"`
IP string `json:"ip"`
Node string `json:"node"`
Path string `json:"path"`
Method string `json:"method"`
UserAgent string `json:"userAgent"`

View file

@ -55,6 +55,11 @@ func WithByStatus(status string) global.DBOption {
return g.Where("status = ?", status)
}
}
func WithByNode(node string) global.DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("node = ?", node)
}
}
func WithByGroupBelong(group string) global.DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("group_belong = ?", group)

View file

@ -15,7 +15,7 @@ type ICommandService interface {
SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error)
SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error)
Create(req dto.CommandOperate) error
Update(id uint, upMap map[string]interface{}) error
Update(req dto.CommandOperate) error
Delete(ids []uint) error
}
@ -123,6 +123,14 @@ func (u *CommandService) Delete(ids []uint) error {
return commandRepo.Delete(repo.WithByIDs(ids))
}
func (u *CommandService) Update(id uint, upMap map[string]interface{}) error {
return commandRepo.Update(id, upMap)
func (u *CommandService) Update(req dto.CommandOperate) error {
command, _ := commandRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type))
if command.ID != req.ID {
return buserr.New("ErrRecordExist")
}
upMap := make(map[string]interface{})
upMap["name"] = req.Name
upMap["group_id"] = req.GroupID
upMap["command"] = req.Command
return commandRepo.Update(req.ID, upMap)
}

View file

@ -122,6 +122,9 @@ func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, inter
if len(req.Status) != 0 {
options = append(options, repo.WithByStatus(req.Status))
}
if len(req.Node) != 0 {
options = append(options, repo.WithByNode(req.Node))
}
total, ops, err := logRepo.PageOperationLog(
req.Page,

View file

@ -2,7 +2,6 @@ package i18n
import (
"embed"
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/core/global"
@ -65,10 +64,9 @@ func GetErrMsg(key string, maps map[string]interface{}) string {
}
func GetMsgByKey(key string) string {
content, err := global.I18n.Localize(&i18n.LocalizeConfig{
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
})
fmt.Println(err)
return content
}

View file

@ -24,6 +24,7 @@ func Init() {
migrations.UpdateXpackHideMemu,
migrations.AddSystemIP,
migrations.InitScriptLibrary,
migrations.AddOperationNode,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -339,3 +339,13 @@ var InitScriptLibrary = &gormigrate.Migration{
return nil
},
}
var AddOperationNode = &gormigrate.Migration{
ID: "20250321-add-operation-node",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.OperationLog{}); err != nil {
return err
}
return nil
},
}

View file

@ -32,8 +32,10 @@ func OperationLog() gin.HandlerFunc {
source := loadLogInfo(c.Request.URL.Path)
pathItem := strings.TrimPrefix(c.Request.URL.Path, "/api/v2")
pathItem = strings.TrimPrefix(pathItem, "/api/v2/core")
currentNode := c.Request.Header.Get("CurrentNode")
record := &model.OperationLog{
Source: source,
Node: currentNode,
IP: c.ClientIP(),
Method: strings.ToLower(c.Request.Method),
Path: pathItem,

View file

@ -89,7 +89,7 @@ const systemRules: TokenRule[] = [
const taskRules: TokenRule[] = [
{
type: 'bracket-text',
pattern: /\[([^\]]+)\]/g,
pattern: /\[([^\,]]+)\]/g,
color: '#B87A2B',
},
];

View file

@ -31,11 +31,12 @@
v-if="version !== 'Waiting' && !globalStore.hasNewVersion"
type="primary"
:underline="false"
class="ml-2"
@click="onLoadUpgradeInfo"
>
{{ $t('commons.button.update') }}
</el-link>
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">
<el-tag v-if="version === 'Waiting'" round class="ml-2.5">
{{ $t('setting.upgrading') }}
</el-tag>
</div>

View file

@ -7,15 +7,19 @@
@mouseenter="collapseButtonShow = true"
@mouseleave="collapseButtonShow = false"
>
<el-affix v-if="collapseButtonShow" :offset="124" class="affix">
<el-button
size="small"
circle
:style="{ 'margin-left': menuStore.isCollapse ? '60px' : '165px', position: 'absolute' }"
:icon="menuStore.isCollapse ? 'ArrowRight' : 'ArrowLeft'"
plain
@click="handleCollapse()"
></el-button>
<el-affix v-if="collapseButtonShow" :offset="18" class="affix">
<el-tooltip
:content="menuStore.isCollapse ? $t('commons.button.expand') : $t('commons.button.collapse')"
>
<el-button
size="small"
circle
:style="{ 'margin-left': menuStore.isCollapse ? '60px' : '165px', position: 'absolute' }"
:icon="menuStore.isCollapse ? 'ArrowRight' : 'ArrowLeft'"
plain
@click="handleCollapse()"
></el-button>
</el-tooltip>
</el-affix>
<Sidebar @menu-click="handleMenuClick" :menu-router="!classObj.openMenuTabs" @open-task="openTask" />
</div>

View file

@ -29,6 +29,7 @@
@sort-change="search"
@search="search"
:data="data"
:heightDiff="300"
>
<el-table-column type="selection" fix />
<el-table-column

View file

@ -387,7 +387,6 @@ const forDetail = async (row: Cronjob.Record) => {
};
const loadRecord = async (row: Cronjob.Record) => {
currentRecord.value = row;
console.log(currentRecord.value);
if (row.records) {
const res = await getRecordLog(row.id);
let log = res.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');

View file

@ -29,7 +29,7 @@
:pagination-config="paginationConfig"
:data="data"
@search="search"
:heightDiff="370"
:heightDiff="300"
>
<el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="name" min-width="60">

View file

@ -87,6 +87,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
dialogData.value.rowData.groupList = dialogData.value.rowData.groupList || [];
dialogData.value.rowData.groups = dialogData.value.rowData.groupList.join(',');
if (dialogData.value.title === 'create') {
await addScript(dialogData.value.rowData)

View file

@ -1,7 +1,11 @@
<template>
<div v-loading="loading">
<LayoutContent :title="props.database + ' ' + $t('commons.button.set')" backName="PostgreSQL">
<LayoutContent backName="PostgreSQL">
<template #leftToolBar>
<el-text class="mx-1">
{{ props.database }}
</el-text>
<el-divider direction="vertical" />
<el-button type="primary" :plain="activeName !== 'conf'" @click="jumpToConf">
{{ $t('database.confChange') }}
</el-button>

View file

@ -156,7 +156,7 @@ const buttons = [
},
},
{
label: i18n.global.t('commons.button.delete'),
label: i18n.global.t('commons.button.unbind'),
click: (row: Database.DatabaseInfo) => {
onDelete(row);
},

View file

@ -1,7 +1,11 @@
<template>
<div v-show="settingShow" v-loading="loading">
<LayoutContent :title="database + ' ' + $t('commons.button.set')" :reload="true">
<LayoutContent :reload="true">
<template #leftToolBar>
<el-text class="mx-1">
{{ database }}
</el-text>
<el-divider direction="vertical" />
<el-button type="primary" :plain="activeName !== 'conf'" @click="changeTab('conf')">
{{ $t('database.confChange') }}
</el-button>

View file

@ -10,7 +10,7 @@
</el-button>
</template>
<template #rightToolBar>
<el-select v-model="searchGroup" @change="search()" clearable class="p-w-200 mr-2.5">
<el-select v-model="searchGroup" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('logs.resource') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('logs.detail.apps')" value="apps"></el-option>
@ -25,12 +25,18 @@
<el-option :label="$t('logs.detail.logs')" value="logs"></el-option>
<el-option :label="$t('logs.detail.settings')" value="settings"></el-option>
</el-select>
<el-select v-model="searchStatus" @change="search()" clearable class="p-w-200 mr-2.5">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-select v-model="searchStatus" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('xpack.node.node') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
</el-select>
<el-select v-model="searchNode" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('xpack.node.node') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('xpack.node.master')" value="local" />
<el-option v-for="(node, index) in nodes" :key="index" :label="node.name" :value="node.name" />
</el-select>
<TableSearch @search="search()" v-model:searchName="searchName" />
<TableRefresh @search="search()" />
<TableSetting title="operation-log-refresh" @search="search()" />
@ -52,7 +58,11 @@
<span v-if="globalStore.language === 'en'">{{ row.detailEN }}</span>
</template>
</el-table-column>
<el-table-column v-if="globalStore.isMasterProductPro" :label="$t('xpack.node.node')" prop="node">
<template #default="{ row }">
<span>{{ row.node === 'local' ? $t('xpack.node.master') : row.node }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status">
<template #default="{ row }">
<Status :status="row.status" :msg="row.message" />
@ -81,6 +91,7 @@ import { onMounted, reactive, ref } from '@vue/runtime-core';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
import { listNodeOptions } from '@/api/modules/setting';
const loading = ref();
const data = ref();
@ -94,6 +105,8 @@ const paginationConfig = reactive({
const searchName = ref<string>('');
const searchGroup = ref<string>('');
const searchStatus = ref<string>('');
const searchNode = ref<string>('');
const nodes = ref();
const globalStore = GlobalStore();
@ -104,6 +117,7 @@ const search = async () => {
pageSize: paginationConfig.pageSize,
status: searchStatus.value,
source: searchGroup.value,
node: searchNode.value,
};
loading.value = true;
await getOperationLogs(params)
@ -140,6 +154,23 @@ const loadDetail = (log: string) => {
return log;
};
const loadNodes = async () => {
await listNodeOptions()
.then((res) => {
if (!res) {
nodes.value = [];
return;
}
nodes.value = res.data || [];
if (nodes.value.length === 0) {
globalStore.currentNode = 'local';
}
})
.catch(() => {
nodes.value = [];
});
};
const replacements = {
'[enable]': 'commons.button.enable',
'[Enable]': 'commons.button.enable',
@ -172,6 +203,9 @@ const onSubmitClean = async () => {
};
onMounted(() => {
if (globalStore.isMasterProductPro) {
loadNodes();
}
search();
});
</script>

View file

@ -14,7 +14,12 @@
{{ $t('commons.button.watch') }}
</el-checkbox>
</el-button>
<el-radio-group class="ml-2" @change="search()" v-model="itemType">
<el-radio-group
v-if="globalStore.currentNode === 'local'"
class="ml-2"
@change="search()"
v-model="itemType"
>
<el-radio-button :label="$t('logs.agent')" value="Agent" />
<el-radio-button :label="$t('logs.core')" value="Core" />
</el-radio-group>
@ -39,6 +44,8 @@ import LogFile from '@/components/log/file/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { nextTick, onMounted, reactive, ref } from 'vue';
import { getSystemFiles } from '@/api/modules/log';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const loading = ref();
const isWatch = ref();

View file

@ -297,6 +297,8 @@ const agreeWithLogin = () => {
const login = (formEl: FormInstance | undefined) => {
if (!formEl || isLoggingIn) return;
errAuthInfo.value = false;
errCaptcha.value = false;
formEl.validate(async (valid) => {
if (!valid) return;
if (isIntl.value) {

View file

@ -157,7 +157,7 @@
inactive-value="Disable"
/>
<span class="input-help">{{ $t('setting.apiInterfaceHelper') }}</span>
<div v-if="form.apiInterfaceStatus === 'enable'">
<div v-if="form.apiInterfaceStatus === 'Enable'">
<div>
<el-button link type="primary" @click="onChangeApiInterfaceStatus">
{{ $t('commons.button.view') }}

View file

@ -1,5 +1,5 @@
<template>
<div @keydown.esc="toggleFullscreen()">
<div tabindex="0">
<el-tabs
type="card"
class="terminal-tabs"
@ -164,12 +164,12 @@ const mobile = computed(() => {
return globalStore.isMobile();
});
function toggleFullscreen() {
const toggleFullscreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle();
}
globalStore.isFullScreen = !screenfull.isFullscreen;
}
};
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
};