Compare commits

...

1 Commits
v1.10.4 ... dev

Author SHA1 Message Date
meilin.huang
aa6ad39b83 feat: 新增数据库导出按钮权限等 2026-01-07 21:27:25 +08:00
15 changed files with 104 additions and 63 deletions

View File

@@ -11,14 +11,14 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@logicflow/core": "^2.1.4",
"@logicflow/extension": "^2.1.6",
"@logicflow/core": "^2.1.7",
"@logicflow/extension": "^2.1.9",
"@vueuse/core": "^14.1.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-search": "^0.16.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"asciinema-player": "^3.13.5",
"asciinema-player": "^3.14.0",
"axios": "^1.6.2",
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
@@ -37,8 +37,8 @@
"sql-formatter": "^15.6.12",
"trzsz": "^1.1.5",
"uuid": "^13.0.0",
"vue": "^v3.6.0-beta.1",
"vue-i18n": "^11.2.7",
"vue": "^v3.6.0-beta.2",
"vue-i18n": "^11.2.8",
"vue-router": "^4.6.4",
"vuedraggable": "^4.1.0",
"xlsx": "^0.18.5"

View File

@@ -15,7 +15,7 @@ const config = {
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本
version: 'v1.10.4',
version: 'v1.10.5',
};
export default config;

View File

@@ -49,8 +49,9 @@ export default {
dbms: 'DBMS',
dbDataOp: 'Data Operation',
dbDataOpBase: 'Base Permission',
dbDataOpSqlScriptRun: 'SQL Script Run',
dbDataOpBase: 'DB-Base Permission',
dbDataOpSqlScriptRun: 'DB-SQL Script Run',
dbDataExport: 'DB-Data Export',
dbInstance: 'DB Instance',
dbInstanceBase: 'Base Permission',
dbInstanceSave: 'Save Instance',
@@ -77,21 +78,20 @@ export default {
dbTransferFileRun: 'Transfer File-Run',
redis: 'Redis',
redisDataOp: 'Data Operation',
redisDataOpBase: 'Base Permission',
redisDataOpSave: 'Save Data',
redisDataOpDelete: 'Delete Data',
redisDataOp: 'Redis - Data Operation',
redisDataOpBase: 'Redis - Base Permission',
redisDataOpSave: 'Redis - Save Data',
redisDataOpDelete: 'Redis - Delete Data',
redisManage: 'Redis Manage',
redisManageBase: 'Base Permission',
redisManageBase: 'Redis - Base Permission',
mongo: 'Mongo',
mongoDataOp: 'Data Operation',
mongoDataOpBase: 'Base Permission',
mongoDataOpSave: 'Save Data',
mongoDataOpDelete: 'Delete Data',
mongoDataOp: 'Mongo - Data Operation',
mongoDataOpBase: 'Mongo - Base Permission',
mongoDataOpSave: 'Mongo - Save Data',
mongoDataOpDelete: 'Mongo - Delete Data',
mongoManage: 'Mongo Manage',
mongoManageBase: 'Base Permission',
mongoManageBase: 'Mongo - Base Permission',
containerManageBase: 'Container Manage - Base Permission',
flow: 'Flow',

View File

@@ -48,16 +48,17 @@ export default {
machineSecurityCmdDelete: '机器-命令配置-删除',
dbms: 'DBMS',
dbDataOp: '数据操作',
dbDataOpBase: 'Db-数据操作-基本权限',
dbDataOpSqlScriptRun: 'Db-SQL脚本执行',
dbDataOp: 'DB-数据操作',
dbDataOpBase: 'DB-数据操作-基本权限',
dbDataOpSqlScriptRun: 'DB-SQL脚本执行',
dbDataExport: 'DB-数据导出',
dbInstance: '数据库实例',
dbInstanceBase: 'Db-基本权限',
dbInstanceSave: 'Db-保存实例',
dbInstanceDelete: 'Db-删除实例',
dbInstanceBase: 'DB-基本权限',
dbInstanceSave: 'DB-保存实例',
dbInstanceDelete: 'DB-删除实例',
dbBase: '数据库基本权限',
dbSave: 'Db-保存数据库',
dbDelete: 'Db-删除数据库',
dbSave: 'DB-保存数据库',
dbDelete: 'DB-删除数据库',
dbDataSync: '数据同步',
dbDataSyncBase: '基本权限',
dbDataSyncSave: '保存同步',

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div>
<div class="card !p-1 flex items-center justify-between">
<div class="card p-1! flex items-center justify-between">
<div>
<el-link @click="onRunSql()" underline="never" class="ml-3.5" icon="VideoPlay"> </el-link>
<el-divider direction="vertical" border-style="dashed" />
@@ -39,32 +39,32 @@
</div>
</div>
<el-splitter style="height: calc(100vh - 200px)" layout="vertical" @resize-end="onResizeTableHeight">
<el-splitter style="height: calc(100vh - 215px)" layout="vertical" @resize-end="onResizeTableHeight">
<el-splitter-panel :size="state.editorSize" max="80%">
<MonacoEditor ref="monacoEditorRef" class="mt-1" v-model="state.sql" language="sql" height="100%" :id="'MonacoTextarea-' + getKey()" />
</el-splitter-panel>
<el-splitter-panel>
<div class="sql-exec-res !h-full">
<div class="sql-exec-res h-full!">
<el-tabs
class="!h-full !w-full"
class="h-full! w-full!"
v-if="state.execResTabs.length > 0"
@tab-remove="onRemoveTab"
@tab-change="active"
v-model="state.activeTab"
>
<el-tab-pane class="!h-full" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
<el-tab-pane class="h-full!" closable v-for="dt in state.execResTabs" :label="dt.id" :name="dt.id" :key="dt.id">
<template #label>
<el-popover :show-after="1000" placement="top-start" :title="$t('db.execInfo')" trigger="hover" :width="300">
<template #reference>
<div>
<span>
<span v-if="dt.loading">
<SvgIcon class="!mb-0.5 is-loading" name="Loading" color="var(--el-color-primary)" />
<SvgIcon class="mb-0.5! is-loading" name="Loading" color="var(--el-color-primary)" />
</span>
<span v-else>
<SvgIcon class="!mb-0.5" v-if="!dt.errorMsg" name="CircleCheck" color="var(--el-color-success)" />
<SvgIcon class="!mb-0.5" v-if="dt.errorMsg" name="CircleClose" color="var(--el-color-error)" />
<SvgIcon class="mb-0.5!" v-if="!dt.errorMsg" name="CircleCheck" color="var(--el-color-success)" />
<SvgIcon class="mb-0.5!" v-if="dt.errorMsg" name="CircleClose" color="var(--el-color-error)" />
</span>
</span>

View File

@@ -39,7 +39,7 @@
<span v-if="column.dataTypeSubscript === 'icon-clock'">
<SvgIcon :size="9" name="Clock" style="cursor: unset" />
</span>
<span class="!text-[8px]" v-else>{{ column.dataTypeSubscript }}</span>
<span class="text-[8px]!" v-else>{{ column.dataTypeSubscript }}</span>
</div>
<div v-if="showColumnTip">
@@ -53,7 +53,7 @@
<div
v-if="dbConfig.showColumnComment"
style="color: var(--el-color-info-light-3)"
class="!text-[10px] el-text el-text--small is-truncated"
class="text-[10px]! el-text el-text--small is-truncated"
>
{{ column.columnComment }}
</div>
@@ -160,7 +160,7 @@
<SvgIcon class="is-loading" name="loading" color="var(--el-color-primary)" :size="28" />
<el-text class="ml-1" tag="b">{{ $t('db.execTime') }} - {{ state.execTime.toFixed(1) }}s</el-text>
</div>
<div v-if="loading && abortFn" class="!mt-2">
<div v-if="loading && abortFn" class="mt-2!">
<el-button @click="cancelLoading" type="info" size="small" plain>{{ $t('common.cancel') }}</el-button>
</div>
</div>
@@ -303,16 +303,23 @@ const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
const cmDataGenJson = new ContextmenuItem('genJson', 'db.genJson').withIcon('tickets').withOnClick(() => onGenerateJson());
const cmDataExportCsv = new ContextmenuItem('exportCsv', 'db.exportCsv').withIcon('document').withOnClick(() => onExportCsv());
const cmDataExportCsv = new ContextmenuItem('exportCsv', 'db.exportCsv')
.withIcon('document')
.withOnClick(() => onExportCsv())
.withPermission('db:data:export');
const cmDataExportExcel = new ContextmenuItem('exportExcel', 'db.exportExcel').withIcon('document').withOnClick(() => onExportExcel());
const cmDataExportExcel = new ContextmenuItem('exportExcel', 'db.exportExcel')
.withIcon('document')
.withOnClick(() => onExportExcel())
.withPermission('db:data:export');
const cmDataExportSql = new ContextmenuItem('exportSql', 'db.exportSql')
.withIcon('document')
.withOnClick(() => onExportSql())
.withHideFunc(() => {
return state.table == '';
});
})
.withPermission('db:data:export');
class NowUpdateCell {
rowIndex: number;

View File

@@ -3,7 +3,9 @@
<el-row class="mb-1">
<el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right">
<template #reference>
<el-button :disabled="state.dumpInfo.tables?.length == 0" class="ml-1" type="success" size="small">{{ $t('db.dump') }}</el-button>
<el-button v-auth="'db:data:export'" :disabled="state.dumpInfo.tables?.length == 0" class="ml-1" type="success" size="small">
{{ $t('db.dump') }}
</el-button>
</template>
<el-form-item :label="$t('db.exportContent')">
<el-radio-group v-model="dumpInfo.type">

View File

@@ -86,13 +86,13 @@
@tab-remove="onRemoveTab"
@tab-change="onTabChange"
v-model="state.activeName"
class="!h-full w-full"
class="h-full! w-full"
>
<el-tab-pane class="!h-full" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
<el-tab-pane class="h-full!" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
<template #label>
<el-popover :show-after="1000" placement="bottom-start" trigger="hover" :width="250">
<template #reference>
<span @contextmenu.prevent="onTabContextmenu(dt, $event)" class="!text-[12px]">{{ dt.label }}</span>
<span @contextmenu.prevent="onTabContextmenu(dt, $event)" class="text-[12px]!">{{ dt.label }}</span>
</template>
<template #default>
<el-descriptions :column="1" size="small">

View File

@@ -1,19 +1,19 @@
<template>
<div class="file-manage">
<el-dialog :title="$t('machine.process')" v-model="dialogVisible" :destroy-on-close="true" :show-close="true" :before-close="handleClose" width="65%">
<div class="card !p-1">
<div class="card p-1!">
<el-row>
<el-col :span="4">
<el-input size="small" :placeholder="$t('machine.processName')" v-model="params.name" plain clearable></el-input>
</el-col>
<el-col :span="4" class="ml-1">
<el-select class="!w-full" @change="getProcess" size="small" v-model="params.sortType" :placeholder="$t('machine.selectSortType')">
<el-select @change="getProcess" size="small" v-model="params.sortType" :placeholder="$t('machine.selectSortType')">
<el-option key="cpu" :label="$t('machine.cpuDesc')" value="1"> </el-option>
<el-option key="cpu" :label="$t('machine.memDesc')" value="2"> </el-option>
</el-select>
</el-col>
<el-col :span="4" class="ml-1">
<el-select class="!w-full" @change="getProcess" size="small" v-model="params.count" :placeholder="$t('machine.selectProcessNum')">
<el-select @change="getProcess" size="small" v-model="params.count" :placeholder="$t('machine.selectProcessNum')">
<el-option key="10" label="10" value="10"> </el-option>
<el-option key="15" label="15" value="15"> </el-option>
<el-option key="20" label="20" value="20"> </el-option>
@@ -26,7 +26,7 @@
</el-row>
</div>
<el-table :data="processList" size="small" style="width: 100%">
<el-table :data="processList" size="small" :height="500">
<el-table-column prop="user" label="USER" :min-width="50"> </el-table-column>
<el-table-column prop="pid" label="PID" :min-width="50" show-overflow-tooltip></el-table-column>
<el-table-column prop="cpu" label="%CPU" :min-width="40"> </el-table-column>

View File

@@ -741,7 +741,11 @@ function getParentPath(filePath: string) {
const deleteFile = async (files: any) => {
try {
await useI18nDeleteConfirm(files.map((x: any) => `[${x.path}]`).join('\n'));
let confirmMsg = files.map((x: any) => `[${x.path}]`).join('\n');
if (confirmMsg.length > 400) {
confirmMsg = confirmMsg.substring(0, 400) + '...';
}
await useI18nDeleteConfirm(confirmMsg);
state.loading = true;
await machineApi.rmFile.request({
fileId: props.fileId,

View File

@@ -1,7 +1,7 @@
<template>
<div class="h-full machine-terminal-tabs">
<el-tabs v-if="state.tabs.size > 0" type="card" @tab-remove="onRemoveTab" v-model="state.activeTermName" class="!h-full w-full">
<el-tab-pane class="!h-full flex flex-col" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
<el-tab-pane class="h-full! flex flex-col" closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
<template #label>
<el-popconfirm @confirm="handleReconnect(dt, true)" :title="$t('machine.reConnTips')">
<template #reference>

View File

@@ -49,7 +49,7 @@ func (d *DbConn) Ping() error {
stats := d.db.Stats()
logx.Debugf("[%s] db stats -> open: %d, idle: %d, inUse: %d, maxOpen: %d", d.Info.Name, stats.OpenConnections, stats.Idle, stats.InUse, stats.MaxOpenConnections)
if stats.OpenConnections == 0 {
return errors.New("no open connections")
logx.Info("db stats: no open connections")
}
return d.db.Ping()

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.10.4"
Version = "v1.10.5"
)
func GetAppInfo() string {

View File

@@ -47,10 +47,10 @@ func (r *RedisConn) Ping() error {
}
stats := r.Cli.PoolStats()
if stats.TotalConns == 0 {
return fmt.Errorf("no open connections")
}
logx.Debugf("[%s] redis stats -> open: %d, idle: %d", r.Info.Name, stats.TotalConns, stats.IdleConns)
if stats.TotalConns == 0 {
logx.Info("redis stats: no open connections")
}
cmd := r.Cli.Ping(context.Background())
if cmd == nil {

View File

@@ -352,23 +352,18 @@ func V1_10_4() []*gormigrate.Migration {
return nil
},
},
}
}
func V1_10_5() []*gormigrate.Migration {
return []*gormigrate.Migration{
{
ID: "20251207-v1.10.4.1",
Migrate: func(tx *gorm.DB) error {
config := &sysentity.Config{}
// 查询是否存在该配置
result := tx.Model(config).Where("key = ?", "AiModelConfig").First(config)
result := tx.Model(config).Where("`key` = ?", "AiModelConfig").First(config)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 如果不存在,则创建默认配置
now := time.Now()
aiConfig := &sysentity.Config{
Key: "AiModelConfig",
Name: "system.sysconf.aiModelConf'",
Name: "system.sysconf.aiModelConf",
Value: "{}", // 默认空JSON值
Params: `[{"model":"modelType","name":"system.sysconf.aiModelType","placeholder":"system.sysconf.aiModelTypePlaceholder","options":"openai"},{"model":"model","name":"system.sysconf.aiModel","placeholder":"system.sysconf.aiModelPlaceholder"},{"model":"baseUrl","name":"system.sysconf.aiBaseUrl","placeholder":"system.sysconf.aiBaseUrlPlaceholder"},{"model":"apiKey","name":"ApiKey","placeholder":"api key"}]`,
Permission: "all",
@@ -391,3 +386,35 @@ func V1_10_5() []*gormigrate.Migration {
},
}
}
func V1_10_5() []*gormigrate.Migration {
return []*gormigrate.Migration{
{
ID: "20260107-v1.10.5",
Migrate: func(tx *gorm.DB) error {
resource := &sysentity.Resource{}
// 查询是否存在该配置
result := tx.Model(resource).Where("code = ?", "db:data:export").First(resource)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 如果不存在,则创建默认配置
resource = &sysentity.Resource{
Code: "db:data:export",
Name: "menu.dbDataExport",
Type: sysentity.ResourceTypePermission,
UiPath: "ocdrUNaa/fo59olyi/",
Pid: 1756122788,
Status: sysentity.ResourceStatusEnable,
}
resource.FillBaseInfo(model.IdGenTypeTimestamp, model.SysAccount)
resource.Id = 1767788697
return tx.Create(resource).Error
}
return nil
},
Rollback: func(tx *gorm.DB) error {
return nil
},
},
}
}