feat: add app launch log page
This commit is contained in:
parent
15c80bc9f7
commit
4879901a33
|
@ -12,13 +12,22 @@ import (
|
||||||
. "tinyrdm/backend/storage"
|
. "tinyrdm/backend/storage"
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
maputil "tinyrdm/backend/utils/map"
|
maputil "tinyrdm/backend/utils/map"
|
||||||
|
mathutil "tinyrdm/backend/utils/math"
|
||||||
redis2 "tinyrdm/backend/utils/redis"
|
redis2 "tinyrdm/backend/utils/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type cmdHistoryItem struct {
|
||||||
|
timestamp int64
|
||||||
|
Time string `json:"time"`
|
||||||
|
Server string `json:"server"`
|
||||||
|
Cmd string `json:"cmd"`
|
||||||
|
}
|
||||||
|
|
||||||
type connectionService struct {
|
type connectionService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
conns *ConnectionsStorage
|
conns *ConnectionsStorage
|
||||||
connMap map[string]connectionItem
|
connMap map[string]connectionItem
|
||||||
|
cmdHistory []cmdHistoryItem
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectionItem struct {
|
type connectionItem struct {
|
||||||
|
@ -230,7 +239,7 @@ func (c *connectionService) CloseConnection(name string) (resp types.JSResp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get redis client from local cache or create a new open
|
// get redis client from local cache or create a new open
|
||||||
// if db >= 0, also switch to db index
|
// if db >= 0, will also switch to db index
|
||||||
func (c *connectionService) getRedisClient(connName string, db int) (*redis.Client, context.Context, error) {
|
func (c *connectionService) getRedisClient(connName string, db int) (*redis.Client, context.Context, error) {
|
||||||
item, ok := c.connMap[connName]
|
item, ok := c.connMap[connName]
|
||||||
var rdb *redis.Client
|
var rdb *redis.Client
|
||||||
|
@ -249,7 +258,19 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
||||||
Password: selConn.Password,
|
Password: selConn.Password,
|
||||||
ReadTimeout: -1,
|
ReadTimeout: -1,
|
||||||
})
|
})
|
||||||
rdb.AddHook(redis2.NewHook(connName))
|
rdb.AddHook(redis2.NewHook(connName, func(cmd string) {
|
||||||
|
now := time.Now()
|
||||||
|
last := strings.LastIndex(cmd, ":")
|
||||||
|
if last != -1 {
|
||||||
|
cmd = cmd[:last]
|
||||||
|
}
|
||||||
|
c.cmdHistory = append(c.cmdHistory, cmdHistoryItem{
|
||||||
|
timestamp: now.UnixMilli(),
|
||||||
|
Time: now.Format("2006-01-02 15:04:05"),
|
||||||
|
Server: connName,
|
||||||
|
Cmd: cmd,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
if _, err := rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
if _, err := rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
||||||
return nil, nil, errors.New("can not connect to redis server:" + err.Error())
|
return nil, nil, errors.New("can not connect to redis server:" + err.Error())
|
||||||
|
@ -960,6 +981,28 @@ func (c *connectionService) RenameKey(connName string, db int, key, newKey strin
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *connectionService) GetCmdHistory(pageNo, pageSize int) (resp types.JSResp) {
|
||||||
|
resp.Success = true
|
||||||
|
if pageSize <= 0 || pageNo <= 0 {
|
||||||
|
// return all history
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"list": c.cmdHistory,
|
||||||
|
"pageNo": 1,
|
||||||
|
"pageSize": -1,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
total := len(c.cmdHistory)
|
||||||
|
startIndex := total / pageSize * (pageNo - 1)
|
||||||
|
endIndex := mathutil.Min(startIndex+pageSize, total)
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"list": c.cmdHistory[startIndex:endIndex],
|
||||||
|
"pageNo": pageNo,
|
||||||
|
"pageSize": pageSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// update or insert key info to database
|
// update or insert key info to database
|
||||||
//func (c *connectionService) updateDBKey(connName string, db int, keys []string, separator string) {
|
//func (c *connectionService) updateDBKey(connName string, db int, keys []string, separator string) {
|
||||||
// dbStruct := map[string]any{}
|
// dbStruct := map[string]any{}
|
||||||
|
|
|
@ -102,7 +102,7 @@ func (c *ConnectionsStorage) GetConnection(name string) *types.Connection {
|
||||||
return findConn(name, "", conns)
|
return findConn(name, "", conns)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroup get connection group by name
|
// GetGroup get one connection group by name
|
||||||
func (c *ConnectionsStorage) GetGroup(name string) *types.Connection {
|
func (c *ConnectionsStorage) GetGroup(name string) *types.Connection {
|
||||||
conns := c.getConnections()
|
conns := c.getConnections()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package mathutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
. "tinyrdm/backend/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxWithIndex 查找所有元素中的最大值
|
||||||
|
func MaxWithIndex[T Hashable](items ...T) (T, int) {
|
||||||
|
selIndex := -1
|
||||||
|
for i, t := range items {
|
||||||
|
if selIndex < 0 {
|
||||||
|
selIndex = i
|
||||||
|
} else {
|
||||||
|
if t > items[selIndex] {
|
||||||
|
selIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items[selIndex], selIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func Max[T Hashable](items ...T) T {
|
||||||
|
val, _ := MaxWithIndex(items...)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinWithIndex 查找所有元素中的最小值
|
||||||
|
func MinWithIndex[T Hashable](items ...T) (T, int) {
|
||||||
|
selIndex := -1
|
||||||
|
for i, t := range items {
|
||||||
|
if selIndex < 0 {
|
||||||
|
selIndex = i
|
||||||
|
} else {
|
||||||
|
if t < items[selIndex] {
|
||||||
|
selIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items[selIndex], selIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func Min[T Hashable](items ...T) T {
|
||||||
|
val, _ := MinWithIndex(items...)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp 返回限制在minVal和maxVal范围内的value
|
||||||
|
func Clamp[T Hashable](value T, minVal T, maxVal T) T {
|
||||||
|
if minVal > maxVal {
|
||||||
|
minVal, maxVal = maxVal, minVal
|
||||||
|
}
|
||||||
|
if value < minVal {
|
||||||
|
value = minVal
|
||||||
|
} else if value > maxVal {
|
||||||
|
value = maxVal
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abs 计算绝对值
|
||||||
|
func Abs[T SignedNumber](val T) T {
|
||||||
|
return T(math.Abs(float64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floor 向下取整
|
||||||
|
func Floor[T SignedNumber | UnsignedNumber](val T) T {
|
||||||
|
return T(math.Floor(float64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ceil 向上取整
|
||||||
|
func Ceil[T SignedNumber | UnsignedNumber](val T) T {
|
||||||
|
return T(math.Ceil(float64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round 四舍五入取整
|
||||||
|
func Round[T SignedNumber | UnsignedNumber](val T) T {
|
||||||
|
return T(math.Round(float64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum 计算所有元素总和
|
||||||
|
func Sum[T SignedNumber | UnsignedNumber](items ...T) T {
|
||||||
|
var sum T
|
||||||
|
for _, item := range items {
|
||||||
|
sum += item
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Average 计算所有元素的平均值
|
||||||
|
func Average[T SignedNumber | UnsignedNumber](items ...T) T {
|
||||||
|
return Sum(items...) / T(len(items))
|
||||||
|
}
|
|
@ -7,31 +7,41 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type execCallback func(string)
|
||||||
|
|
||||||
type LogHook struct {
|
type LogHook struct {
|
||||||
name string
|
name string
|
||||||
|
cmdExec execCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHook(name string) LogHook {
|
func NewHook(name string, cmdExec execCallback) *LogHook {
|
||||||
return LogHook{
|
return &LogHook{
|
||||||
name: name,
|
name: name,
|
||||||
|
cmdExec: cmdExec,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (LogHook) DialHook(next redis.DialHook) redis.DialHook {
|
func (l *LogHook) DialHook(next redis.DialHook) redis.DialHook {
|
||||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return next(ctx, network, addr)
|
return next(ctx, network, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||||
log.Println(cmd.String())
|
log.Println(cmd)
|
||||||
|
if l.cmdExec != nil {
|
||||||
|
l.cmdExec(cmd.String())
|
||||||
|
}
|
||||||
return next(ctx, cmd)
|
return next(ctx, cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
||||||
return func(ctx context.Context, cmds []redis.Cmder) error {
|
return func(ctx context.Context, cmds []redis.Cmder) error {
|
||||||
for _, cmd := range cmds {
|
for _, cmd := range cmds {
|
||||||
log.Println(cmd.String())
|
log.Println("pipeline: ", cmd)
|
||||||
|
if l.cmdExec != nil {
|
||||||
|
l.cmdExec(cmd.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return next(ctx, cmds)
|
return next(ctx, cmds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import ContentServerPane from './components/content/ContentServerPane.vue'
|
||||||
import useTabStore from './stores/tab.js'
|
import useTabStore from './stores/tab.js'
|
||||||
import usePreferencesStore from './stores/preferences.js'
|
import usePreferencesStore from './stores/preferences.js'
|
||||||
import useConnectionStore from './stores/connections.js'
|
import useConnectionStore from './stores/connections.js'
|
||||||
|
import ContentLogPane from './components/content/ContentLogPane.vue'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
@ -60,8 +61,8 @@ const dragging = computed(() => {
|
||||||
<!-- app content-->
|
<!-- app content-->
|
||||||
<div id="app-content-wrapper" :class="{ dragging }" class="flex-box-h" :style="prefStore.generalFont">
|
<div id="app-content-wrapper" :class="{ dragging }" class="flex-box-h" :style="prefStore.generalFont">
|
||||||
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
||||||
<!-- structure page-->
|
<!-- browser page-->
|
||||||
<div v-show="tabStore.nav === 'structure'" class="flex-box-h flex-item-expand">
|
<div v-show="tabStore.nav === 'browser'" class="flex-box-h flex-item-expand">
|
||||||
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
|
||||||
<browser-pane
|
<browser-pane
|
||||||
v-for="t in tabStore.tabs"
|
v-for="t in tabStore.tabs"
|
||||||
|
@ -102,7 +103,11 @@ const dragging = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- log page -->
|
<!-- log page -->
|
||||||
<div v-show="tabStore.nav === 'log'">display log</div>
|
<div v-if="tabStore.nav === 'log'" class="flex-box-h flex-item-expand">
|
||||||
|
<keep-alive>
|
||||||
|
<content-log-pane class="flex-item-expand" />
|
||||||
|
</keep-alive>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, nextTick, onActivated, reactive, ref } from 'vue'
|
||||||
|
import IconButton from '../common/IconButton.vue'
|
||||||
|
import Refresh from '../icons/Refresh.vue'
|
||||||
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
import { map, uniqBy } from 'lodash'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
const i18n = useI18n()
|
||||||
|
const data = reactive({
|
||||||
|
loading: false,
|
||||||
|
server: '',
|
||||||
|
keyword: '',
|
||||||
|
history: [],
|
||||||
|
})
|
||||||
|
const filterServerOption = computed(() => {
|
||||||
|
const serverSet = uniqBy(data.history, 'server')
|
||||||
|
const options = map(serverSet, ({ server }) => ({
|
||||||
|
label: server,
|
||||||
|
value: server,
|
||||||
|
}))
|
||||||
|
options.splice(0, 0, {
|
||||||
|
label: i18n.t('all'),
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableRef = ref(null)
|
||||||
|
|
||||||
|
const loadHistory = () => {
|
||||||
|
data.loading = true
|
||||||
|
connectionStore
|
||||||
|
.getCmdHistory()
|
||||||
|
.then((list) => {
|
||||||
|
data.history = list
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
data.loading = false
|
||||||
|
tableRef.value?.scrollTo({ top: 999999 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onActivated(() => {
|
||||||
|
nextTick().then(loadHistory)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-card
|
||||||
|
:title="$t('launch_log')"
|
||||||
|
class="content-container flex-box-v"
|
||||||
|
content-style="display: flex;flex-direction: column; overflow: hidden;"
|
||||||
|
>
|
||||||
|
<n-form inline :disabled="data.loading" class="flex-item">
|
||||||
|
<n-form-item :label="$t('filter_server')">
|
||||||
|
<n-select
|
||||||
|
style="min-width: 100px"
|
||||||
|
v-model:value="data.server"
|
||||||
|
:options="filterServerOption"
|
||||||
|
:consistent-menu-width="false"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('filter_keyword')">
|
||||||
|
<n-input v-model:value="data.keyword" placeholder="" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<icon-button :icon="Refresh" border t-tooltip="refresh" @click="loadHistory" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<div class="fill-height flex-box-h" style="user-select: text">
|
||||||
|
<n-data-table
|
||||||
|
ref="tableRef"
|
||||||
|
class="flex-item-expand"
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
title: $t('exec_time'),
|
||||||
|
key: 'time',
|
||||||
|
defaultSortOrder: 'ascend',
|
||||||
|
sorter: 'default',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: $t('server'),
|
||||||
|
key: 'server',
|
||||||
|
filterOptionValue: data.server,
|
||||||
|
filter(value, row) {
|
||||||
|
return value === '' || row.server === value.toString()
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: $t('cmd'),
|
||||||
|
key: 'cmd',
|
||||||
|
filterOptionValue: data.keyword,
|
||||||
|
filter(value, row) {
|
||||||
|
return value === '' || !!~row.cmd.indexOf(value.toString())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:data="data.history"
|
||||||
|
flex-height
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import 'content';
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -63,7 +63,7 @@ const tab = computed(() =>
|
||||||
watch(
|
watch(
|
||||||
() => tabStore.nav,
|
() => tabStore.nav,
|
||||||
(nav) => {
|
(nav) => {
|
||||||
if (nav === 'structure') {
|
if (nav === 'browser') {
|
||||||
refreshInfo()
|
refreshInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { get, map, mapValues, pickBy, split, sum, toArray, toNumber } from 'lodash'
|
import { get, map, mapValues, pickBy, split, sum, toArray, toNumber } from 'lodash'
|
||||||
import { computed, reactive, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import Help from '../icons/Help.vue'
|
import Help from '../icons/Help.vue'
|
||||||
import IconButton from '../common/IconButton.vue'
|
import IconButton from '../common/IconButton.vue'
|
||||||
import Filter from '../icons/Filter.vue'
|
import Filter from '../icons/Filter.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import Refresh from '../icons/Refresh.vue'
|
import Refresh from '../icons/Refresh.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -70,26 +69,7 @@ const totalKeys = computed(() => {
|
||||||
return sum(toArray(nums))
|
return sum(toArray(nums))
|
||||||
})
|
})
|
||||||
const infoList = computed(() => map(props.info, (value, key) => ({ value, key })))
|
const infoList = computed(() => map(props.info, (value, key) => ({ value, key })))
|
||||||
|
|
||||||
const i18n = useI18n()
|
|
||||||
const infoColumns = [
|
|
||||||
reactive({
|
|
||||||
title: i18n.t('key'),
|
|
||||||
key: 'key',
|
|
||||||
defaultSortOrder: 'ascend',
|
|
||||||
sorter: 'default',
|
|
||||||
minWidth: 100,
|
|
||||||
filterOptionValue: null,
|
|
||||||
filter(value, row) {
|
|
||||||
return !!~row.key.indexOf(value.toString())
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ title: i18n.t('value'), key: 'value' },
|
|
||||||
]
|
|
||||||
const infoFilter = ref('')
|
const infoFilter = ref('')
|
||||||
const onFilterInfo = (val) => {
|
|
||||||
infoColumns[0].filterOptionValue = val
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -165,13 +145,29 @@ const onFilterInfo = (val) => {
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card :title="$t('all_info')">
|
<n-card :title="$t('all_info')">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-input v-model:value="infoFilter" @update:value="onFilterInfo" placeholder="" clearable>
|
<n-input v-model:value="infoFilter" placeholder="" clearable>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<icon-button :icon="Filter" size="18" />
|
<icon-button :icon="Filter" size="18" />
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</template>
|
</template>
|
||||||
<n-data-table :columns="infoColumns" :data="infoList" />
|
<n-data-table
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
title: $t('key'),
|
||||||
|
key: 'key',
|
||||||
|
defaultSortOrder: 'ascend',
|
||||||
|
sorter: 'default',
|
||||||
|
minWidth: 100,
|
||||||
|
filterOptionValue: infoFilter,
|
||||||
|
filter(value, row) {
|
||||||
|
return !!~row.key.indexOf(value.toString())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: $t('value'), key: 'value' },
|
||||||
|
]"
|
||||||
|
:data="infoList"
|
||||||
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import useDialog from '../../stores/dialog'
|
import useDialog from '../../stores/dialog'
|
||||||
import useTabStore from '../../stores/tab.js'
|
import useTabStore from '../../stores/tab.js'
|
||||||
import { useMessage } from 'naive-ui'
|
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
|
|
||||||
const ttlForm = reactive({
|
const ttlForm = reactive({
|
||||||
|
@ -42,7 +41,6 @@ const onClose = () => {
|
||||||
dialogStore.closeTTLDialog()
|
dialogStore.closeTTLDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
const onConfirm = async () => {
|
const onConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
const tab = tabStore.currentTab
|
const tab = tabStore.currentTab
|
||||||
|
|
|
@ -23,7 +23,7 @@ const props = defineProps({
|
||||||
:stroke="props.fillColor"
|
:stroke="props.fillColor"
|
||||||
:stroke-width="props.strokeWidth"
|
:stroke-width="props.strokeWidth"
|
||||||
d="M41 4H7C5.34315 4 4 5.34315 4 7V41C4 42.6569 5.34315 44 7 44H41C42.6569 44 44 42.6569 44 41V7C44 5.34315 42.6569 4 41 4Z"
|
d="M41 4H7C5.34315 4 4 5.34315 4 7V41C4 42.6569 5.34315 44 7 44H41C42.6569 44 44 42.6569 44 41V7C44 5.34315 42.6569 4 41 4Z"
|
||||||
fill="#dc423c"
|
:fill="props.fillColor"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import useDialogStore from '../../stores/dialog.js'
|
import useDialogStore from '../../stores/dialog.js'
|
||||||
import { h, nextTick, reactive, ref, watch } from 'vue'
|
import { h, nextTick, reactive, ref, watch } from 'vue'
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
import { NIcon, useDialog, useMessage } from 'naive-ui'
|
import { NIcon, useDialog, useMessage, useThemeVars } from 'naive-ui'
|
||||||
import { ConnectionType } from '../../consts/connection_type.js'
|
import { ConnectionType } from '../../consts/connection_type.js'
|
||||||
import ToggleFolder from '../icons/ToggleFolder.vue'
|
import ToggleFolder from '../icons/ToggleFolder.vue'
|
||||||
import ToggleServer from '../icons/ToggleServer.vue'
|
import ToggleServer from '../icons/ToggleServer.vue'
|
||||||
|
@ -17,6 +17,7 @@ import useTabStore from '../../stores/tab.js'
|
||||||
import Edit from '../icons/Edit.vue'
|
import Edit from '../icons/Edit.vue'
|
||||||
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
||||||
|
|
||||||
|
const themeVars = useThemeVars()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const openingConnection = ref(false)
|
const openingConnection = ref(false)
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
|
|
|
@ -38,8 +38,8 @@ const i18n = useI18n()
|
||||||
const menuOptions = computed(() => {
|
const menuOptions = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: i18n.t('structure'),
|
label: i18n.t('browser'),
|
||||||
key: 'structure',
|
key: 'browser',
|
||||||
icon: renderIcon(ToggleDb),
|
icon: renderIcon(ToggleDb),
|
||||||
show: connectionStore.anyConnectionOpened,
|
show: connectionStore.anyConnectionOpened,
|
||||||
},
|
},
|
||||||
|
|
|
@ -127,7 +127,7 @@
|
||||||
"empty_server_content": "Connect server from left list",
|
"empty_server_content": "Connect server from left list",
|
||||||
"reload_when_succ": "Reload immediately after success",
|
"reload_when_succ": "Reload immediately after success",
|
||||||
"server": "Server",
|
"server": "Server",
|
||||||
"structure": "Structure",
|
"browser": "Browser",
|
||||||
"log": "Log",
|
"log": "Log",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
|
@ -141,5 +141,11 @@
|
||||||
"unit_day": "D",
|
"unit_day": "D",
|
||||||
"unit_hour": "H",
|
"unit_hour": "H",
|
||||||
"unit_minute": "M",
|
"unit_minute": "M",
|
||||||
"all_info": "All Info"
|
"all_info": "All Info",
|
||||||
|
"all": "All",
|
||||||
|
"launch_log": "Launch Log",
|
||||||
|
"filter_server": "Filter Server",
|
||||||
|
"filter_keyword": "Filter Keyword",
|
||||||
|
"exec_time": "Exec Time",
|
||||||
|
"cmd": "Command"
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
"empty_server_content": "可以从左边选择并打开连接",
|
"empty_server_content": "可以从左边选择并打开连接",
|
||||||
"reload_when_succ": "操作成功后立即重新加载",
|
"reload_when_succ": "操作成功后立即重新加载",
|
||||||
"server": "服务器",
|
"server": "服务器",
|
||||||
"structure": "结构",
|
"browser": "浏览器",
|
||||||
"log": "日志",
|
"log": "日志",
|
||||||
"about": "关于",
|
"about": "关于",
|
||||||
"help": "帮助",
|
"help": "帮助",
|
||||||
|
@ -144,5 +144,11 @@
|
||||||
"unit_day": "天",
|
"unit_day": "天",
|
||||||
"unit_hour": "小时",
|
"unit_hour": "小时",
|
||||||
"unit_minute": "分钟",
|
"unit_minute": "分钟",
|
||||||
"all_info": "所有信息"
|
"all_info": "所有信息",
|
||||||
|
"all": "所有",
|
||||||
|
"launch_log": "运行日志",
|
||||||
|
"filter_server": "筛选服务器",
|
||||||
|
"filter_keyword": "筛选关键字",
|
||||||
|
"exec_time": "执行时间",
|
||||||
|
"cmd": "命令"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
DeleteConnection,
|
DeleteConnection,
|
||||||
DeleteGroup,
|
DeleteGroup,
|
||||||
DeleteKey,
|
DeleteKey,
|
||||||
|
GetCmdHistory,
|
||||||
GetConnection,
|
GetConnection,
|
||||||
GetKeyValue,
|
GetKeyValue,
|
||||||
ListConnection,
|
ListConnection,
|
||||||
|
@ -64,6 +65,13 @@ const useConnectionStore = defineStore('connections', {
|
||||||
* @property {Object.<string, Map<string, DatabaseItem>>} nodeMap key format likes 'server#db', children key format likes 'key#type'
|
* @property {Object.<string, Map<string, DatabaseItem>>} nodeMap key format likes 'server#db', children key format likes 'key#type'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} HistoryItem
|
||||||
|
* @property {string} time
|
||||||
|
* @property {string} server
|
||||||
|
* @property {string} cmd
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns {ConnectionState}
|
* @returns {ConnectionState}
|
||||||
|
@ -1149,6 +1157,26 @@ const useConnectionStore = defineStore('connections', {
|
||||||
return { success: false, msg }
|
return { success: false, msg }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get command history
|
||||||
|
* @param {number} [pageNo]
|
||||||
|
* @param {number} [pageSize]
|
||||||
|
* @returns {Promise<HistoryItem[]>}
|
||||||
|
*/
|
||||||
|
async getCmdHistory(pageNo, pageSize) {
|
||||||
|
if (pageNo === undefined || pageSize === undefined) {
|
||||||
|
pageNo = -1
|
||||||
|
pageSize = -1
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { success, data = { list: [] } } = await GetCmdHistory(pageNo, pageSize)
|
||||||
|
const { list } = data
|
||||||
|
return list
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ const useTabStore = defineStore('tab', {
|
||||||
_setActivatedIndex(idx, switchNav) {
|
_setActivatedIndex(idx, switchNav) {
|
||||||
this.activatedIndex = idx
|
this.activatedIndex = idx
|
||||||
if (switchNav === true) {
|
if (switchNav === true) {
|
||||||
this.nav = idx >= 0 ? 'structure' : 'server'
|
this.nav = idx >= 0 ? 'browser' : 'server'
|
||||||
} else {
|
} else {
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
this.nav = 'server'
|
this.nav = 'server'
|
||||||
|
|
Loading…
Reference in New Issue