Compare commits
5 Commits
70235cc295
...
649cc71680
Author | SHA1 | Date |
---|---|---|
Lykin | 649cc71680 | |
Lykin | c76a0a505f | |
Lykin | ffed68ae4c | |
Lykin | 7fecbc2b53 | |
Lykin | ee57346df6 |
|
@ -2456,8 +2456,8 @@ func (b *browserService) CleanCmdHistory() (resp types.JSResp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSlowLogs get slow log list
|
// GetSlowLogs get slow log list
|
||||||
func (b *browserService) GetSlowLogs(server string, db int, num int64) (resp types.JSResp) {
|
func (b *browserService) GetSlowLogs(server string, num int64) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(server, db)
|
item, err := b.getRedisClient(server, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"tinyrdm/backend/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pubsubItem struct {
|
||||||
|
client *redis.Client
|
||||||
|
pubsub *redis.PubSub
|
||||||
|
mutex sync.Mutex
|
||||||
|
closeCh chan struct{}
|
||||||
|
eventName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type subMessage struct {
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pubsubService struct {
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
mutex sync.Mutex
|
||||||
|
items map[string]*pubsubItem
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubsub *pubsubService
|
||||||
|
var oncePubsub sync.Once
|
||||||
|
|
||||||
|
func Pubsub() *pubsubService {
|
||||||
|
if pubsub == nil {
|
||||||
|
oncePubsub.Do(func() {
|
||||||
|
pubsub = &pubsubService{
|
||||||
|
items: map[string]*pubsubItem{},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pubsub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pubsubService) getItem(server string) (*pubsubItem, error) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
item, ok := p.items[server]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
conf := Connection().getConnection(server)
|
||||||
|
if conf == nil {
|
||||||
|
return nil, fmt.Errorf("no connection profile named: %s", server)
|
||||||
|
}
|
||||||
|
var uniClient redis.UniversalClient
|
||||||
|
if uniClient, err = Connection().createRedisClient(conf.ConnectionConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var client *redis.Client
|
||||||
|
if client, ok = uniClient.(*redis.Client); !ok {
|
||||||
|
return nil, errors.New("create redis client fail")
|
||||||
|
}
|
||||||
|
item = &pubsubItem{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
p.items[server] = item
|
||||||
|
}
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pubsubService) Start(ctx context.Context) {
|
||||||
|
p.ctx, p.ctxCancel = context.WithCancel(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish publish message to channel
|
||||||
|
func (p *pubsubService) Publish(server, channel, payload string) (resp types.JSResp) {
|
||||||
|
rdb, err := Browser().getRedisClient(server, -1)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var received int64
|
||||||
|
received, err = rdb.client.Publish(p.ctx, channel, payload).Result()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = struct {
|
||||||
|
Received int64 `json:"received"`
|
||||||
|
}{
|
||||||
|
Received: received,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartSubscribe start to subscribe a channel
|
||||||
|
func (p *pubsubService) StartSubscribe(server string) (resp types.JSResp) {
|
||||||
|
item, err := p.getItem(server)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.closeCh = make(chan struct{})
|
||||||
|
item.eventName = "sub:" + strconv.Itoa(int(time.Now().Unix()))
|
||||||
|
item.pubsub = item.client.PSubscribe(p.ctx, "*")
|
||||||
|
|
||||||
|
go p.processSubscribe(&item.mutex, item.pubsub.Channel(), item.closeCh, item.eventName)
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = struct {
|
||||||
|
EventName string `json:"eventName"`
|
||||||
|
}{
|
||||||
|
EventName: item.eventName,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pubsubService) processSubscribe(mutex *sync.Mutex, ch <-chan *redis.Message, closeCh <-chan struct{}, eventName string) {
|
||||||
|
lastEmitTime := time.Now().Add(-1 * time.Minute)
|
||||||
|
cache := make([]subMessage, 0, 1000)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case data := <-ch:
|
||||||
|
go func() {
|
||||||
|
timestamp := time.Now().UnixMilli()
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
cache = append(cache, subMessage{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Channel: data.Channel,
|
||||||
|
Message: data.Payload,
|
||||||
|
})
|
||||||
|
if time.Now().Sub(lastEmitTime) > 300*time.Millisecond || len(cache) > 300 {
|
||||||
|
runtime.EventsEmit(p.ctx, eventName, cache)
|
||||||
|
cache = cache[:0:cap(cache)]
|
||||||
|
lastEmitTime = time.Now()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
case <-closeCh:
|
||||||
|
// subscribe stopped
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopSubscribe stop subscribe by server name
|
||||||
|
func (p *pubsubService) StopSubscribe(server string) (resp types.JSResp) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
item, ok := p.items[server]
|
||||||
|
if !ok || item.pubsub == nil {
|
||||||
|
resp.Success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//item.pubsub.Unsubscribe(p.ctx, "*")
|
||||||
|
item.pubsub.Close()
|
||||||
|
close(item.closeCh)
|
||||||
|
delete(p.items, server)
|
||||||
|
resp.Success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopAll stop all subscribe
|
||||||
|
func (p *pubsubService) StopAll() {
|
||||||
|
if p.ctxCancel != nil {
|
||||||
|
p.ctxCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
for server := range p.items {
|
||||||
|
p.StopSubscribe(server)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,13 +8,14 @@
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"chart.js": "^4.4.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.45.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.69.7",
|
||||||
"vue": "^3.4.10",
|
"vue": "^3.4.14",
|
||||||
|
"vue-chartjs": "^5.3.0",
|
||||||
"vue-i18n": "^9.9.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0"
|
"xterm-addon-fit": "^0.8.0"
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.3",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
"naive-ui": "^2.37.3",
|
"naive-ui": "^2.37.3",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.2.4",
|
||||||
"unplugin-auto-import": "^0.17.3",
|
"unplugin-auto-import": "^0.17.3",
|
||||||
"unplugin-icons": "^0.18.2",
|
"unplugin-icons": "^0.18.2",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
@ -623,6 +624,11 @@
|
||||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
@ -877,49 +883,49 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.14.tgz",
|
||||||
"integrity": "sha512-53vxh7K9qbx+JILnGEhrFRyr7H7e4NdT8RuTNU3m6HhJKFvcAqFTNXpYMHnyuAzzRGdsbsYHBgQC3H6xEXTG6w==",
|
"integrity": "sha512-ro4Zzl/MPdWs7XwxT7omHRxAjMbDFRZEEjD+2m3NBf8YzAe3HuoSEZosXQo+m1GQ1G3LQ1LdmNh1RKTYe+ssEg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.6",
|
"@babel/parser": "^7.23.6",
|
||||||
"@vue/shared": "3.4.10",
|
"@vue/shared": "3.4.14",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-dom": {
|
"node_modules/@vue/compiler-dom": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.14.tgz",
|
||||||
"integrity": "sha512-QAALBJksIFpXGYuo74rtMgnwpVZDvd3kYbUa4gYX9s/5QiqEvZSgbKtOdUGydXcxKPt3ifC+0/bhPVHXN2694A==",
|
"integrity": "sha512-nOZTY+veWNa0DKAceNWxorAbWm0INHdQq7cejFaWM1WYnoNSJbSEKYtE7Ir6lR/+mo9fttZpPVI9ZFGJ1juUEQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.4.10",
|
"@vue/compiler-core": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-sfc": {
|
"node_modules/@vue/compiler-sfc": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.14.tgz",
|
||||||
"integrity": "sha512-sTOssaQySgrMjrhZxmAqdp6n+E51VteIVIDaOR537H2P63DyzMmig21U0XXFxiXmMIfrK91lAInnc+bIAYemGw==",
|
"integrity": "sha512-1vHc9Kv1jV+YBZC/RJxQJ9JCxildTI+qrhtDh6tPkR1O8S+olBUekimY0km0ZNn8nG1wjtFAe9XHij+YLR8cRQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.6",
|
"@babel/parser": "^7.23.6",
|
||||||
"@vue/compiler-core": "3.4.10",
|
"@vue/compiler-core": "3.4.14",
|
||||||
"@vue/compiler-dom": "3.4.10",
|
"@vue/compiler-dom": "3.4.14",
|
||||||
"@vue/compiler-ssr": "3.4.10",
|
"@vue/compiler-ssr": "3.4.14",
|
||||||
"@vue/shared": "3.4.10",
|
"@vue/shared": "3.4.14",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.33",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-ssr": {
|
"node_modules/@vue/compiler-ssr": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.14.tgz",
|
||||||
"integrity": "sha512-Y90TL1abretWbUiK5rv+9smS1thCHE5sSuhZgiLh6cxgZ2Pcy3BEvDd3reID0iwNcTdMbTeE6NI3Aq4Mux6hqQ==",
|
"integrity": "sha512-bXT6+oAGlFjTYVOTtFJ4l4Jab1wjsC0cfSfOe2B4Z0N2vD2zOBSQ9w694RsCfhjk+bC2DY5Gubb1rHZVii107Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.10",
|
"@vue/compiler-dom": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/devtools-api": {
|
"node_modules/@vue/devtools-api": {
|
||||||
|
@ -928,29 +934,29 @@
|
||||||
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
|
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.14.tgz",
|
||||||
"integrity": "sha512-SmGGpo37LzPcAFTopHNIJRNVOQfma9YgyPkAzx9/TJ01lbCCYigS28hEcY1hjiJ1PRK8iVX62Ov5yzmUgYH/pQ==",
|
"integrity": "sha512-xRYwze5Q4tK7tT2J4uy4XLhK/AIXdU5EBUu9PLnIHcOKXO0uyXpNNMzlQKuq7B+zwtq6K2wuUL39pHA6ZQzObw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-core": {
|
"node_modules/@vue/runtime-core": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.14.tgz",
|
||||||
"integrity": "sha512-Ri2Cz9sFr66AEUewGUK8IXhIUAhshTHVUGuJR8pqMbtjIds+zPa8QPO5UZImGMQ8HTY7eEpKwztCct9V3+Iqug==",
|
"integrity": "sha512-qu+NMkfujCoZL6cfqK5NOfxgXJROSlP2ZPs4CTcVR+mLrwl4TtycF5Tgo0QupkdBL+2kigc6EsJlTcuuZC1NaQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.4.10",
|
"@vue/reactivity": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-dom": {
|
"node_modules/@vue/runtime-dom": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.14.tgz",
|
||||||
"integrity": "sha512-ROsdi5M2niRDmjXJNZ8KKiGwXyG1FO8l9n6sCN0kaJEHbjWkuigu96YAI3fK/AWUZPSXXEcMEBVPC6rL3mmUuA==",
|
"integrity": "sha512-B85XmcR4E7XsirEHVqhmy4HPbRT9WLFWV9Uhie3OapV9m1MEN9+Er6hmUIE6d8/l2sUygpK9RstFM2bmHEUigA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/runtime-core": "3.4.10",
|
"@vue/runtime-core": "3.4.14",
|
||||||
"@vue/shared": "3.4.10",
|
"@vue/shared": "3.4.14",
|
||||||
"csstype": "^3.1.3"
|
"csstype": "^3.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -960,21 +966,21 @@
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/server-renderer": {
|
"node_modules/@vue/server-renderer": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.14.tgz",
|
||||||
"integrity": "sha512-WpCBAhesLq44JKWfdFqb+Bi4ACUW0d8x1z90GnE0spccsAlEDMXV5nm+pwXLyW0OdP2iPrO/n/QMJh4B1v9Ciw==",
|
"integrity": "sha512-pwSKXQfYdJBTpvWHGEYI+akDE18TXAiLcGn+Q/2Fj8wQSHWztoo7PSvfMNqu6NDhp309QXXbPFEGCU5p85HqkA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-ssr": "3.4.10",
|
"@vue/compiler-ssr": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "3.4.10"
|
"vue": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/shared": {
|
"node_modules/@vue/shared": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.14.tgz",
|
||||||
"integrity": "sha512-C0mIVhwW1xQLMFyqMJxnhq6fWyE02lCgcE+TDdtGpg6B3H6kh/0YcqS54qYc76UJNlWegf3VgsLqgk6D9hBmzQ=="
|
"integrity": "sha512-nmi3BtLpvqXAWoRZ6HQ+pFJOHBU4UnH3vD3opgmwXac7vhaHKA9nj1VeGjMggdB9eLtW83eHyPCmOU1qzdsC7Q=="
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.11.2",
|
"version": "8.11.2",
|
||||||
|
@ -1040,12 +1046,15 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/chart.js": {
|
||||||
"version": "3.1.2",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
|
||||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
"integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"pnpm": ">=7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
|
@ -1755,9 +1764,23 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.32",
|
"version": "8.4.33",
|
||||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.32.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
@ -1768,9 +1791,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.1.1",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
|
||||||
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
|
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
|
@ -2259,15 +2282,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.14.tgz",
|
||||||
"integrity": "sha512-c+O8qGqdWPF9joTCzMGeDDedViooh6c8RY3+eW5+6GCAIY8YjChmU06LsUu0PnMZbIk1oKUoJTqKzmghYtFypw==",
|
"integrity": "sha512-Rop5Al/ZcBbBz+KjPZaZDgHDX0kUP4duEzDbm+1o91uxYUNmJrZSBuegsNIJvUGy+epLevNRNhLjm08VKTgGyw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.10",
|
"@vue/compiler-dom": "3.4.14",
|
||||||
"@vue/compiler-sfc": "3.4.10",
|
"@vue/compiler-sfc": "3.4.14",
|
||||||
"@vue/runtime-dom": "3.4.10",
|
"@vue/runtime-dom": "3.4.14",
|
||||||
"@vue/server-renderer": "3.4.10",
|
"@vue/server-renderer": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
|
@ -2278,6 +2301,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-chartjs": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"chart.js": "^4.1.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-i18n": {
|
"node_modules/vue-i18n": {
|
||||||
"version": "9.9.0",
|
"version": "9.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz",
|
||||||
|
@ -2709,6 +2741,11 @@
|
||||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@kurkle/color": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
|
||||||
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
@ -2865,49 +2902,49 @@
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@vue/compiler-core": {
|
"@vue/compiler-core": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.14.tgz",
|
||||||
"integrity": "sha512-53vxh7K9qbx+JILnGEhrFRyr7H7e4NdT8RuTNU3m6HhJKFvcAqFTNXpYMHnyuAzzRGdsbsYHBgQC3H6xEXTG6w==",
|
"integrity": "sha512-ro4Zzl/MPdWs7XwxT7omHRxAjMbDFRZEEjD+2m3NBf8YzAe3HuoSEZosXQo+m1GQ1G3LQ1LdmNh1RKTYe+ssEg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.23.6",
|
"@babel/parser": "^7.23.6",
|
||||||
"@vue/shared": "3.4.10",
|
"@vue/shared": "3.4.14",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-dom": {
|
"@vue/compiler-dom": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.14.tgz",
|
||||||
"integrity": "sha512-QAALBJksIFpXGYuo74rtMgnwpVZDvd3kYbUa4gYX9s/5QiqEvZSgbKtOdUGydXcxKPt3ifC+0/bhPVHXN2694A==",
|
"integrity": "sha512-nOZTY+veWNa0DKAceNWxorAbWm0INHdQq7cejFaWM1WYnoNSJbSEKYtE7Ir6lR/+mo9fttZpPVI9ZFGJ1juUEQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-core": "3.4.10",
|
"@vue/compiler-core": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-sfc": {
|
"@vue/compiler-sfc": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.14.tgz",
|
||||||
"integrity": "sha512-sTOssaQySgrMjrhZxmAqdp6n+E51VteIVIDaOR537H2P63DyzMmig21U0XXFxiXmMIfrK91lAInnc+bIAYemGw==",
|
"integrity": "sha512-1vHc9Kv1jV+YBZC/RJxQJ9JCxildTI+qrhtDh6tPkR1O8S+olBUekimY0km0ZNn8nG1wjtFAe9XHij+YLR8cRQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.23.6",
|
"@babel/parser": "^7.23.6",
|
||||||
"@vue/compiler-core": "3.4.10",
|
"@vue/compiler-core": "3.4.14",
|
||||||
"@vue/compiler-dom": "3.4.10",
|
"@vue/compiler-dom": "3.4.14",
|
||||||
"@vue/compiler-ssr": "3.4.10",
|
"@vue/compiler-ssr": "3.4.14",
|
||||||
"@vue/shared": "3.4.10",
|
"@vue/shared": "3.4.14",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.33",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-ssr": {
|
"@vue/compiler-ssr": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.14.tgz",
|
||||||
"integrity": "sha512-Y90TL1abretWbUiK5rv+9smS1thCHE5sSuhZgiLh6cxgZ2Pcy3BEvDd3reID0iwNcTdMbTeE6NI3Aq4Mux6hqQ==",
|
"integrity": "sha512-bXT6+oAGlFjTYVOTtFJ4l4Jab1wjsC0cfSfOe2B4Z0N2vD2zOBSQ9w694RsCfhjk+bC2DY5Gubb1rHZVii107Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-dom": "3.4.10",
|
"@vue/compiler-dom": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/devtools-api": {
|
"@vue/devtools-api": {
|
||||||
|
@ -2916,29 +2953,29 @@
|
||||||
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
|
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
|
||||||
},
|
},
|
||||||
"@vue/reactivity": {
|
"@vue/reactivity": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.14.tgz",
|
||||||
"integrity": "sha512-SmGGpo37LzPcAFTopHNIJRNVOQfma9YgyPkAzx9/TJ01lbCCYigS28hEcY1hjiJ1PRK8iVX62Ov5yzmUgYH/pQ==",
|
"integrity": "sha512-xRYwze5Q4tK7tT2J4uy4XLhK/AIXdU5EBUu9PLnIHcOKXO0uyXpNNMzlQKuq7B+zwtq6K2wuUL39pHA6ZQzObw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/runtime-core": {
|
"@vue/runtime-core": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.14.tgz",
|
||||||
"integrity": "sha512-Ri2Cz9sFr66AEUewGUK8IXhIUAhshTHVUGuJR8pqMbtjIds+zPa8QPO5UZImGMQ8HTY7eEpKwztCct9V3+Iqug==",
|
"integrity": "sha512-qu+NMkfujCoZL6cfqK5NOfxgXJROSlP2ZPs4CTcVR+mLrwl4TtycF5Tgo0QupkdBL+2kigc6EsJlTcuuZC1NaQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/reactivity": "3.4.10",
|
"@vue/reactivity": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/runtime-dom": {
|
"@vue/runtime-dom": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.14.tgz",
|
||||||
"integrity": "sha512-ROsdi5M2niRDmjXJNZ8KKiGwXyG1FO8l9n6sCN0kaJEHbjWkuigu96YAI3fK/AWUZPSXXEcMEBVPC6rL3mmUuA==",
|
"integrity": "sha512-B85XmcR4E7XsirEHVqhmy4HPbRT9WLFWV9Uhie3OapV9m1MEN9+Er6hmUIE6d8/l2sUygpK9RstFM2bmHEUigA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/runtime-core": "3.4.10",
|
"@vue/runtime-core": "3.4.14",
|
||||||
"@vue/shared": "3.4.10",
|
"@vue/shared": "3.4.14",
|
||||||
"csstype": "^3.1.3"
|
"csstype": "^3.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -2950,18 +2987,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/server-renderer": {
|
"@vue/server-renderer": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.14.tgz",
|
||||||
"integrity": "sha512-WpCBAhesLq44JKWfdFqb+Bi4ACUW0d8x1z90GnE0spccsAlEDMXV5nm+pwXLyW0OdP2iPrO/n/QMJh4B1v9Ciw==",
|
"integrity": "sha512-pwSKXQfYdJBTpvWHGEYI+akDE18TXAiLcGn+Q/2Fj8wQSHWztoo7PSvfMNqu6NDhp309QXXbPFEGCU5p85HqkA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-ssr": "3.4.10",
|
"@vue/compiler-ssr": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/shared": {
|
"@vue/shared": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.14.tgz",
|
||||||
"integrity": "sha512-C0mIVhwW1xQLMFyqMJxnhq6fWyE02lCgcE+TDdtGpg6B3H6kh/0YcqS54qYc76UJNlWegf3VgsLqgk6D9hBmzQ=="
|
"integrity": "sha512-nmi3BtLpvqXAWoRZ6HQ+pFJOHBU4UnH3vD3opgmwXac7vhaHKA9nj1VeGjMggdB9eLtW83eHyPCmOU1qzdsC7Q=="
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
"version": "8.11.2",
|
"version": "8.11.2",
|
||||||
|
@ -3012,10 +3049,13 @@
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bytes": {
|
"chart.js": {
|
||||||
"version": "3.1.2",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
|
||||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
"integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
|
||||||
|
"requires": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.5.3",
|
"version": "3.5.3",
|
||||||
|
@ -3555,9 +3595,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.4.32",
|
"version": "8.4.33",
|
||||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.32.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
@ -3565,9 +3605,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"version": "3.1.1",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
|
||||||
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
|
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"queue-microtask": {
|
"queue-microtask": {
|
||||||
|
@ -3884,17 +3924,23 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue": {
|
"vue": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.14.tgz",
|
||||||
"integrity": "sha512-c+O8qGqdWPF9joTCzMGeDDedViooh6c8RY3+eW5+6GCAIY8YjChmU06LsUu0PnMZbIk1oKUoJTqKzmghYtFypw==",
|
"integrity": "sha512-Rop5Al/ZcBbBz+KjPZaZDgHDX0kUP4duEzDbm+1o91uxYUNmJrZSBuegsNIJvUGy+epLevNRNhLjm08VKTgGyw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-dom": "3.4.10",
|
"@vue/compiler-dom": "3.4.14",
|
||||||
"@vue/compiler-sfc": "3.4.10",
|
"@vue/compiler-sfc": "3.4.14",
|
||||||
"@vue/runtime-dom": "3.4.10",
|
"@vue/runtime-dom": "3.4.14",
|
||||||
"@vue/server-renderer": "3.4.10",
|
"@vue/server-renderer": "3.4.14",
|
||||||
"@vue/shared": "3.4.10"
|
"@vue/shared": "3.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-chartjs": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"vue-i18n": {
|
"vue-i18n": {
|
||||||
"version": "9.9.0",
|
"version": "9.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz",
|
||||||
|
|
|
@ -9,13 +9,14 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"chart.js": "^4.4.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.45.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.69.7",
|
||||||
"vue": "^3.4.10",
|
"vue": "^3.4.14",
|
||||||
|
"vue-chartjs": "^5.3.0",
|
||||||
"vue-i18n": "^9.9.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0"
|
"xterm-addon-fit": "^0.8.0"
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.3",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
"naive-ui": "^2.37.3",
|
"naive-ui": "^2.37.3",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.2.4",
|
||||||
"unplugin-auto-import": "^0.17.3",
|
"unplugin-auto-import": "^0.17.3",
|
||||||
"unplugin-icons": "^0.18.2",
|
"unplugin-icons": "^0.18.2",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
d8ea7df2e401b3a841c47e94dd1c20db
|
0024a462d0c3d76d35aeb433518d8b5e
|
||||||
|
|
|
@ -31,6 +31,8 @@ const props = defineProps({
|
||||||
buttonStyle: [String, Object],
|
buttonStyle: [String, Object],
|
||||||
buttonClass: [String, Object],
|
buttonClass: [String, Object],
|
||||||
small: Boolean,
|
small: Boolean,
|
||||||
|
secondary: Boolean,
|
||||||
|
tertiary: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasTooltip = computed(() => {
|
const hasTooltip = computed(() => {
|
||||||
|
@ -44,13 +46,15 @@ const hasTooltip = computed(() => {
|
||||||
<n-button
|
<n-button
|
||||||
:class="props.buttonClass"
|
:class="props.buttonClass"
|
||||||
:color="props.color"
|
:color="props.color"
|
||||||
:disabled="disabled"
|
:disabled="props.disabled"
|
||||||
:focusable="false"
|
:focusable="false"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:size="small ? 'small' : ''"
|
:secondary="props.secondary"
|
||||||
|
:size="props.small ? 'small' : ''"
|
||||||
:style="props.buttonStyle"
|
:style="props.buttonStyle"
|
||||||
:text="!border"
|
:tertiary="props.tertiary"
|
||||||
:type="type"
|
:text="!props.border"
|
||||||
|
:type="props.type"
|
||||||
@click.prevent="emit('click')">
|
@click.prevent="emit('click')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot>
|
<slot>
|
||||||
|
@ -67,13 +71,15 @@ const hasTooltip = computed(() => {
|
||||||
v-else
|
v-else
|
||||||
:class="props.buttonClass"
|
:class="props.buttonClass"
|
||||||
:color="props.color"
|
:color="props.color"
|
||||||
:disabled="disabled"
|
:disabled="props.disabled"
|
||||||
:focusable="false"
|
:focusable="false"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:size="small ? 'small' : ''"
|
:secondary="props.secondary"
|
||||||
|
:size="props.small ? 'small' : ''"
|
||||||
:style="props.buttonStyle"
|
:style="props.buttonStyle"
|
||||||
:text="!border"
|
:tertiary="props.tertiary"
|
||||||
:type="type"
|
:text="!props.border"
|
||||||
|
:type="props.type"
|
||||||
@click.prevent="emit('click')">
|
@click.prevent="emit('click')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot>
|
<slot>
|
||||||
|
|
|
@ -17,6 +17,7 @@ import ContentSlog from '@/components/content_value/ContentSlog.vue'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import ContentMonitor from '@/components/content_value/ContentMonitor.vue'
|
import ContentMonitor from '@/components/content_value/ContentMonitor.vue'
|
||||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||||
|
import ContentPubsub from '@/components/content_value/ContentPubsub.vue'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ watch(
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- slow log pane -->
|
<!-- slow log pane -->
|
||||||
<n-tab-pane :name="BrowserTabType.SlowLog.toString()" display-directive="if">
|
<n-tab-pane :name="BrowserTabType.SlowLog.toString()" display-directive="show:lazy">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
@ -171,7 +172,7 @@ watch(
|
||||||
<span>{{ $t('interface.sub_tab.slow_log') }}</span>
|
<span>{{ $t('interface.sub_tab.slow_log') }}</span>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
<content-slog :db="tabContent.db" :server="props.server" />
|
<content-slog :server="props.server" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- command monitor pane -->
|
<!-- command monitor pane -->
|
||||||
|
@ -191,7 +192,7 @@ watch(
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- pub/sub message pane -->
|
<!-- pub/sub message pane -->
|
||||||
<n-tab-pane :disabled="true" :name="BrowserTabType.PubMessage.toString()">
|
<n-tab-pane :name="BrowserTabType.PubMessage.toString()" display-directive="show:lazy">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
|
||||||
<n-icon size="16">
|
<n-icon size="16">
|
||||||
|
@ -203,6 +204,7 @@ watch(
|
||||||
<span>{{ $t('interface.sub_tab.pub_message') }}</span>
|
<span>{{ $t('interface.sub_tab.pub_message') }}</span>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
|
<content-pubsub :server="props.server" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -340,7 +340,6 @@ const changeHistory = (prev) => {
|
||||||
*/
|
*/
|
||||||
const flushTermInput = (flushCmd = false) => {
|
const flushTermInput = (flushCmd = false) => {
|
||||||
const currentLine = getCurrentInput()
|
const currentLine = getCurrentInput()
|
||||||
console.log('===send cmd', currentLine, currentLine.length)
|
|
||||||
EventsEmit(`cmd:input:${props.name}`, currentLine)
|
EventsEmit(`cmd:input:${props.name}`, currentLine)
|
||||||
inputCursor = 0
|
inputCursor = 0
|
||||||
// historyIndex = inputHistory.length
|
// historyIndex = inputHistory.length
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Copy from '@/components/icons/Copy.vue'
|
||||||
import Export from '@/components/icons/Export.vue'
|
import Export from '@/components/icons/Export.vue'
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
import Bottom from '@/components/icons/Bottom.vue'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
@ -118,7 +119,7 @@ const onCleanLog = () => {
|
||||||
<div class="content-log content-container fill-height flex-box-v">
|
<div class="content-log content-container fill-height flex-box-v">
|
||||||
<n-form class="flex-item" label-align="left" label-placement="left" label-width="auto" size="small">
|
<n-form class="flex-item" label-align="left" label-placement="left" label-width="auto" size="small">
|
||||||
<n-form-item :feedback="$t('monitor.warning')" :label="$t('monitor.actions')">
|
<n-form-item :feedback="$t('monitor.warning')" :label="$t('monitor.actions')">
|
||||||
<n-space>
|
<n-space :wrap="false" :wrap-item="false" style="width: 100%">
|
||||||
<n-button
|
<n-button
|
||||||
v-if="!isMonitoring"
|
v-if="!isMonitoring"
|
||||||
:focusable="false"
|
:focusable="false"
|
||||||
|
@ -153,6 +154,16 @@ const onCleanLog = () => {
|
||||||
t-tooltip="monitor.save_log"
|
t-tooltip="monitor.save_log"
|
||||||
@click="onExportLog" />
|
@click="onExportLog" />
|
||||||
</n-button-group>
|
</n-button-group>
|
||||||
|
<icon-button
|
||||||
|
:icon="Bottom"
|
||||||
|
:secondary="data.autoShowLast"
|
||||||
|
:type="data.autoShowLast ? 'primary' : 'default'"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
stroke-width="3.5"
|
||||||
|
t-tooltip="monitor.always_show_last"
|
||||||
|
@click="data.autoShowLast = !data.autoShowLast" />
|
||||||
|
<div class="flex-item-expand" />
|
||||||
<icon-button
|
<icon-button
|
||||||
:icon="Delete"
|
:icon="Delete"
|
||||||
border
|
border
|
||||||
|
@ -165,9 +176,6 @@ const onCleanLog = () => {
|
||||||
<n-form-item :label="$t('monitor.search')">
|
<n-form-item :label="$t('monitor.search')">
|
||||||
<n-input v-model:value="data.keyword" clearable placeholder="" />
|
<n-input v-model:value="data.keyword" clearable placeholder="" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item :label="$t('monitor.always_show_last')">
|
|
||||||
<n-switch v-model:value="data.autoShowLast" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
</n-form>
|
||||||
<n-virtual-list ref="listRef" :item-size="25" :items="displayList" class="list-wrapper">
|
<n-virtual-list ref="listRef" :item-size="25" :items="displayList" class="list-wrapper">
|
||||||
<template #default="{ item }">
|
<template #default="{ item }">
|
||||||
|
|
|
@ -0,0 +1,294 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||||
|
import { debounce, get, isEmpty, size, uniq } from 'lodash'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useThemeVars } from 'naive-ui'
|
||||||
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import { EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import Publish from '@/components/icons/Publish.vue'
|
||||||
|
import Subscribe from '@/components/icons/Subscribe.vue'
|
||||||
|
import Pause from '@/components/icons/Pause.vue'
|
||||||
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
|
import { Publish as PublishSend, StartSubscribe, StopSubscribe } from 'wailsjs/go/services/pubsubService.js'
|
||||||
|
import Checked from '@/components/icons/Checked.vue'
|
||||||
|
import Bottom from '@/components/icons/Bottom.vue'
|
||||||
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
|
||||||
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
const browserStore = useBrowserStore()
|
||||||
|
const i18n = useI18n()
|
||||||
|
const props = defineProps({
|
||||||
|
server: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
subscribeEvent: '',
|
||||||
|
list: [],
|
||||||
|
keyword: '',
|
||||||
|
autoShowLast: true,
|
||||||
|
ellipsisMessage: false,
|
||||||
|
channelHistory: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
const publishData = reactive({
|
||||||
|
channel: '',
|
||||||
|
message: '',
|
||||||
|
received: 0,
|
||||||
|
lastShowReceived: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableRef = ref(null)
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
title: () => i18n.t('pubsub.time'),
|
||||||
|
key: 'timestamp',
|
||||||
|
width: 180,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
render: ({ timestamp }, index) => {
|
||||||
|
return dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: () => i18n.t('pubsub.channel'),
|
||||||
|
key: 'channel',
|
||||||
|
filterOptionValue: data.client,
|
||||||
|
resizable: true,
|
||||||
|
filter: (value, row) => {
|
||||||
|
return value === '' || row.client === value.toString() || row.addr === value.toString()
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: {
|
||||||
|
style: {
|
||||||
|
maxWidth: '50vw',
|
||||||
|
maxHeight: '50vh',
|
||||||
|
},
|
||||||
|
scrollable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: () => i18n.t('pubsub.message'),
|
||||||
|
key: 'message',
|
||||||
|
titleAlign: 'center',
|
||||||
|
filterOptionValue: data.keyword,
|
||||||
|
resizable: true,
|
||||||
|
className: 'content-value',
|
||||||
|
ellipsis: data.ellipsisMessage
|
||||||
|
? {
|
||||||
|
tooltip: {
|
||||||
|
style: {
|
||||||
|
maxWidth: '50vw',
|
||||||
|
maxHeight: '50vh',
|
||||||
|
},
|
||||||
|
scrollable: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
filter: (value, row) => {
|
||||||
|
return value === '' || !!~row.cmd.indexOf(value.toString())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// try to stop prev subscribe first
|
||||||
|
onStopSubscribe()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
onStopSubscribe()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSubscribing = computed(() => {
|
||||||
|
return !isEmpty(data.subscribeEvent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const publishEnable = computed(() => {
|
||||||
|
return !isEmpty(publishData.channel)
|
||||||
|
})
|
||||||
|
|
||||||
|
const _scrollToBottom = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
tableRef.value?.scrollTo({ position: 'bottom' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const scrollToBottom = debounce(_scrollToBottom, 300, { leading: true, trailing: true })
|
||||||
|
|
||||||
|
const onStartSubscribe = async () => {
|
||||||
|
if (isSubscribing.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: ret, success, msg } = await StartSubscribe(props.server)
|
||||||
|
if (!success) {
|
||||||
|
$message.error(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.subscribeEvent = get(ret, 'eventName')
|
||||||
|
EventsOn(data.subscribeEvent, (content) => {
|
||||||
|
if (content instanceof Array) {
|
||||||
|
data.list.push(...content)
|
||||||
|
} else {
|
||||||
|
data.list.push(content)
|
||||||
|
}
|
||||||
|
if (data.autoShowLast) {
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const onStopSubscribe = async () => {
|
||||||
|
const { success, msg } = await StopSubscribe(props.server)
|
||||||
|
if (!success) {
|
||||||
|
$message.error(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
EventsOff(data.subscribeEvent)
|
||||||
|
data.subscribeEvent = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCleanLog = () => {
|
||||||
|
data.list = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPublish = async () => {
|
||||||
|
if (isEmpty(publishData.channel)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
success,
|
||||||
|
msg,
|
||||||
|
data: { received = 0 },
|
||||||
|
} = await PublishSend(props.server, publishData.channel, publishData.message || '')
|
||||||
|
if (!success) {
|
||||||
|
publishData.received = 0
|
||||||
|
if (!isEmpty(msg)) {
|
||||||
|
$message.error(msg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
publishData.message = ''
|
||||||
|
publishData.received = received
|
||||||
|
publishData.lastShowReceived = Date.now()
|
||||||
|
// save channel history
|
||||||
|
data.channelHistory = uniq(data.channelHistory.concat(publishData.channel))
|
||||||
|
|
||||||
|
// hide send status after 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (publishData.lastShowReceived > 0 && Date.now() - publishData.lastShowReceived > 2000) {
|
||||||
|
publishData.lastShowReceived = -1
|
||||||
|
}
|
||||||
|
}, 2100)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="content-log content-container fill-height flex-box-v">
|
||||||
|
<n-form class="flex-item" label-align="left" label-placement="left" label-width="auto" size="small">
|
||||||
|
<n-form-item :show-label="false">
|
||||||
|
<n-space :wrap="false" :wrap-item="false" style="width: 100%">
|
||||||
|
<n-button
|
||||||
|
v-if="!isSubscribing"
|
||||||
|
:focusable="false"
|
||||||
|
secondary
|
||||||
|
strong
|
||||||
|
type="success"
|
||||||
|
@click="onStartSubscribe">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="Subscribe" size="18" />
|
||||||
|
</template>
|
||||||
|
{{ $t('pubsub.subscribe') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button v-else :focusable="false" secondary strong type="warning" @click="onStopSubscribe">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="Pause" size="18" />
|
||||||
|
</template>
|
||||||
|
{{ $t('pubsub.unsubscribe') }}
|
||||||
|
</n-button>
|
||||||
|
<icon-button
|
||||||
|
:icon="Bottom"
|
||||||
|
:secondary="data.autoShowLast"
|
||||||
|
:type="data.autoShowLast ? 'primary' : 'default'"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
stroke-width="3.5"
|
||||||
|
t-tooltip="monitor.always_show_last"
|
||||||
|
@click="data.autoShowLast = !data.autoShowLast" />
|
||||||
|
<div class="flex-item-expand" />
|
||||||
|
<icon-button
|
||||||
|
:icon="Delete"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
stroke-width="3.5"
|
||||||
|
t-tooltip="pubsub.clear"
|
||||||
|
@click="onCleanLog" />
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<n-data-table
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:data="data.list"
|
||||||
|
:loading="data.loading"
|
||||||
|
class="flex-item-expand"
|
||||||
|
flex-height
|
||||||
|
size="small"
|
||||||
|
virtual-scroll />
|
||||||
|
<div class="total-message">{{ $t('pubsub.receive_message', { total: size(data.list) }) }}</div>
|
||||||
|
<div class="flex-box-h publish-input">
|
||||||
|
<n-input-group>
|
||||||
|
<n-auto-complete
|
||||||
|
v-model:value="publishData.channel"
|
||||||
|
:get-show="() => true"
|
||||||
|
:options="data.channelHistory"
|
||||||
|
:placeholder="$t('pubsub.channel')"
|
||||||
|
style="width: 35%; max-width: 200px"
|
||||||
|
@keydown.enter="onPublish" />
|
||||||
|
<n-input
|
||||||
|
v-model:value="publishData.message"
|
||||||
|
:placeholder="$t('pubsub.message')"
|
||||||
|
@keydown.enter="onPublish">
|
||||||
|
<template #suffix>
|
||||||
|
<transition mode="out-in" name="fade">
|
||||||
|
<n-tag v-show="publishData.lastShowReceived > 0" bordered size="small" type="success">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="Checked" size="16" />
|
||||||
|
</template>
|
||||||
|
{{ publishData.received }}
|
||||||
|
</n-tag>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</n-input-group>
|
||||||
|
<n-button :disabled="!publishEnable" type="info" @click="onPublish">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="Publish" size="18" />
|
||||||
|
</template>
|
||||||
|
{{ $t('pubsub.publish') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/styles/content';
|
||||||
|
|
||||||
|
.total-message {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-input {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { get, isEmpty, map, mapValues, pickBy, split, sum, toArray, toNumber } from 'lodash'
|
import { cloneDeep, flatMap, get, isEmpty, map, mapValues, pickBy, slice, split, sum, toArray, toNumber } from 'lodash'
|
||||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, reactive, ref, toRaw } from 'vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import Filter from '@/components/icons/Filter.vue'
|
import Filter from '@/components/icons/Filter.vue'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
|
@ -8,6 +8,10 @@ import useBrowserStore from 'stores/browser.js'
|
||||||
import { timeout } from '@/utils/promise.js'
|
import { timeout } from '@/utils/promise.js'
|
||||||
import AutoRefreshForm from '@/components/common/AutoRefreshForm.vue'
|
import AutoRefreshForm from '@/components/common/AutoRefreshForm.vue'
|
||||||
import { NIcon, useThemeVars } from 'naive-ui'
|
import { NIcon, useThemeVars } from 'naive-ui'
|
||||||
|
import { Line } from 'vue-chartjs'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { convertBytes, formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
import { i18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -22,6 +26,24 @@ const pageState = reactive({
|
||||||
loading: false, // loading status for refresh
|
loading: false, // loading status for refresh
|
||||||
autoLoading: false, // loading status for auto refresh
|
autoLoading: false, // loading status for auto refresh
|
||||||
})
|
})
|
||||||
|
const statusHistory = 5
|
||||||
|
const cmdRateRef = ref(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param origin
|
||||||
|
* @param {string[]} labels
|
||||||
|
* @param {number[][]} datalist
|
||||||
|
* @return {unknown}
|
||||||
|
*/
|
||||||
|
const generateData = (origin, labels, datalist) => {
|
||||||
|
let ret = toRaw(origin)
|
||||||
|
ret.labels = labels
|
||||||
|
for (let i = 0; i < datalist.length; i++) {
|
||||||
|
ret.datasets[i].data = datalist[i]
|
||||||
|
}
|
||||||
|
return cloneDeep(ret)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* refresh server status info
|
* refresh server status info
|
||||||
|
@ -36,7 +58,9 @@ const refreshInfo = async (force) => {
|
||||||
}
|
}
|
||||||
if (!isEmpty(props.server) && browserStore.isConnected(props.server)) {
|
if (!isEmpty(props.server) && browserStore.isConnected(props.server)) {
|
||||||
try {
|
try {
|
||||||
serverInfo.value = await browserStore.getServerInfo(props.server)
|
const info = await browserStore.getServerInfo(props.server)
|
||||||
|
serverInfo.value = info
|
||||||
|
_updateChart(info)
|
||||||
} finally {
|
} finally {
|
||||||
pageState.loading = false
|
pageState.loading = false
|
||||||
pageState.autoLoading = false
|
pageState.autoLoading = false
|
||||||
|
@ -44,6 +68,54 @@ const refreshInfo = async (force) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _updateChart = (info) => {
|
||||||
|
let timeLabels = toRaw(cmdRate.value.labels)
|
||||||
|
timeLabels = timeLabels.concat(dayjs().format('hh:mm:ss'))
|
||||||
|
timeLabels = slice(timeLabels, Math.max(0, timeLabels.length - statusHistory))
|
||||||
|
|
||||||
|
// commands per seconds
|
||||||
|
{
|
||||||
|
let dataset = toRaw(cmdRate.value.datasets[0].data)
|
||||||
|
const cmd = parseInt(get(info, 'Stats.instantaneous_ops_per_sec', '0'))
|
||||||
|
dataset = dataset.concat(cmd)
|
||||||
|
dataset = slice(dataset, Math.max(0, dataset.length - statusHistory))
|
||||||
|
cmdRate.value = generateData(cmdRate.value, timeLabels, [dataset])
|
||||||
|
}
|
||||||
|
|
||||||
|
// connected clients
|
||||||
|
{
|
||||||
|
let dataset = toRaw(connectedClients.value.datasets[0].data)
|
||||||
|
const count = parseInt(get(info, 'Clients.connected_clients', '0'))
|
||||||
|
dataset = dataset.concat(count)
|
||||||
|
dataset = slice(dataset, Math.max(0, dataset.length - statusHistory))
|
||||||
|
connectedClients.value = generateData(connectedClients.value, timeLabels, [dataset])
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory usage
|
||||||
|
{
|
||||||
|
let dataset = toRaw(memoryUsage.value.datasets[0].data)
|
||||||
|
let size = parseInt(get(info, 'Memory.used_memory', '0'))
|
||||||
|
dataset = dataset.concat(size)
|
||||||
|
dataset = slice(dataset, Math.max(0, dataset.length - statusHistory))
|
||||||
|
memoryUsage.value = generateData(memoryUsage.value, timeLabels, [dataset])
|
||||||
|
}
|
||||||
|
|
||||||
|
// network input/output rate
|
||||||
|
{
|
||||||
|
let dataset1 = toRaw(networkRate.value.datasets[0].data)
|
||||||
|
const input = parseInt(get(info, 'Stats.instantaneous_input_kbps', '0'))
|
||||||
|
dataset1 = dataset1.concat(input)
|
||||||
|
dataset1 = slice(dataset1, Math.max(0, dataset1.length - statusHistory))
|
||||||
|
|
||||||
|
let dataset2 = toRaw(networkRate.value.datasets[1].data)
|
||||||
|
const output = parseInt(get(info, 'Stats.instantaneous_output_kbps', '0'))
|
||||||
|
dataset2 = dataset2.concat(output)
|
||||||
|
dataset2 = slice(dataset2, Math.max(0, dataset2.length - statusHistory))
|
||||||
|
networkRate.value = generateData(networkRate.value, timeLabels, [dataset1, dataset2])
|
||||||
|
// console.log(dataset1, dataset2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isLoading = computed(() => {
|
const isLoading = computed(() => {
|
||||||
return pageState.loading || pageState.autoLoading
|
return pageState.loading || pageState.autoLoading
|
||||||
})
|
})
|
||||||
|
@ -70,6 +142,7 @@ const stopAutoRefresh = () => {
|
||||||
|
|
||||||
const onToggleRefresh = (on) => {
|
const onToggleRefresh = (on) => {
|
||||||
if (on) {
|
if (on) {
|
||||||
|
tabVal.value = 'activity'
|
||||||
startAutoRefresh()
|
startAutoRefresh()
|
||||||
} else {
|
} else {
|
||||||
stopAutoRefresh()
|
stopAutoRefresh()
|
||||||
|
@ -84,7 +157,6 @@ onUnmounted(() => {
|
||||||
stopAutoRefresh()
|
stopAutoRefresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
const scrollRef = ref(null)
|
|
||||||
const redisVersion = computed(() => {
|
const redisVersion = computed(() => {
|
||||||
return get(serverInfo.value, 'Server.redis_version', '')
|
return get(serverInfo.value, 'Server.redis_version', '')
|
||||||
})
|
})
|
||||||
|
@ -99,31 +171,24 @@ const role = computed(() => {
|
||||||
|
|
||||||
const timeUnit = ['common.unit_minute', 'common.unit_hour', 'common.unit_day']
|
const timeUnit = ['common.unit_minute', 'common.unit_hour', 'common.unit_day']
|
||||||
const uptime = computed(() => {
|
const uptime = computed(() => {
|
||||||
let seconds = get(serverInfo.value, 'Server.uptime_in_seconds', 0)
|
let seconds = parseInt(get(serverInfo.value, 'Server.uptime_in_seconds', '0'))
|
||||||
seconds /= 60
|
seconds /= 60
|
||||||
if (seconds < 60) {
|
if (seconds < 60) {
|
||||||
// minutes
|
// minutes
|
||||||
return [Math.floor(seconds), timeUnit[0]]
|
return { value: Math.floor(seconds), unit: timeUnit[0] }
|
||||||
}
|
}
|
||||||
seconds /= 60
|
seconds /= 60
|
||||||
if (seconds < 60) {
|
if (seconds < 60) {
|
||||||
// hours
|
// hours
|
||||||
return [Math.floor(seconds), timeUnit[1]]
|
return { value: Math.floor(seconds), unit: timeUnit[1] }
|
||||||
}
|
}
|
||||||
return [Math.floor(seconds / 24), timeUnit[2]]
|
return { value: Math.floor(seconds / 24), unit: timeUnit[2] }
|
||||||
})
|
})
|
||||||
|
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
||||||
const usedMemory = computed(() => {
|
const usedMemory = computed(() => {
|
||||||
let size = get(serverInfo.value, 'Memory.used_memory', 0)
|
let size = parseInt(get(serverInfo.value, 'Memory.used_memory', '0'))
|
||||||
let unitIndex = 0
|
const { value, unit } = convertBytes(size)
|
||||||
|
return [value, unit]
|
||||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
||||||
size /= 1024
|
|
||||||
unitIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
return [size.toFixed(2), units[unitIndex]]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const totalKeys = computed(() => {
|
const totalKeys = computed(() => {
|
||||||
|
@ -138,13 +203,158 @@ const totalKeys = computed(() => {
|
||||||
})
|
})
|
||||||
return sum(toArray(nums))
|
return sum(toArray(nums))
|
||||||
})
|
})
|
||||||
const infoFilter = ref('')
|
|
||||||
|
const tabVal = ref('info')
|
||||||
|
const envFilter = reactive({
|
||||||
|
keyword: '',
|
||||||
|
group: 'CPU',
|
||||||
|
})
|
||||||
|
|
||||||
|
const env = computed(() => {
|
||||||
|
if (!isEmpty(envFilter.group)) {
|
||||||
|
const val = serverInfo.value[envFilter.group]
|
||||||
|
if (!isEmpty(val)) {
|
||||||
|
return map(val, (v, k) => ({
|
||||||
|
key: k,
|
||||||
|
value: v,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flatMap(serverInfo.value, (value, key) => {
|
||||||
|
return map(value, (v, k) => ({
|
||||||
|
group: key,
|
||||||
|
key: k,
|
||||||
|
value: v,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const onFilterGroup = (group) => {
|
||||||
|
if (group === envFilter.group) {
|
||||||
|
envFilter.group = ''
|
||||||
|
} else {
|
||||||
|
envFilter.group = group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartBGColor = [
|
||||||
|
'rgba(255, 99, 132, 0.2)',
|
||||||
|
'rgba(255, 159, 64, 0.2)',
|
||||||
|
'rgba(153, 102, 255, 0.2)',
|
||||||
|
'rgba(75, 192, 192, 0.2)',
|
||||||
|
'rgba(54, 162, 235, 0.2)',
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartBorderColor = [
|
||||||
|
'rgb(255, 99, 132)',
|
||||||
|
'rgb(255, 159, 64)',
|
||||||
|
'rgb(153, 102, 255)',
|
||||||
|
'rgb(75, 192, 192)',
|
||||||
|
'rgb(54, 162, 235)',
|
||||||
|
]
|
||||||
|
|
||||||
|
const cmdRate = ref({
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('status.act_cmd'),
|
||||||
|
data: [],
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: chartBGColor[0],
|
||||||
|
borderColor: chartBorderColor[0],
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const connectedClients = ref({
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('status.connected_clients'),
|
||||||
|
data: [],
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: chartBGColor[1],
|
||||||
|
borderColor: chartBorderColor[1],
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const memoryUsage = ref({
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('status.memory_used'),
|
||||||
|
data: [],
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: chartBGColor[2],
|
||||||
|
borderColor: chartBorderColor[2],
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const networkRate = ref({
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('status.act_network_input'),
|
||||||
|
data: [],
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: chartBGColor[3],
|
||||||
|
borderColor: chartBorderColor[3],
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('status.act_network_output'),
|
||||||
|
data: [],
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: chartBGColor[4],
|
||||||
|
borderColor: chartBorderColor[4],
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartOption = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
events: [],
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
suggestedMin: 0,
|
||||||
|
ticks: {
|
||||||
|
precision: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteChartOption = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
events: [],
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
suggestedMin: 0,
|
||||||
|
ticks: {
|
||||||
|
precision: 0,
|
||||||
|
// format display y axios tag
|
||||||
|
callback: function (value, index, values) {
|
||||||
|
return formatBytes(value, 0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-scrollbar ref="scrollRef">
|
<n-space :size="5" :wrap-item="false" style="padding: 5px; box-sizing: border-box; height: 100%" vertical>
|
||||||
<n-back-top :listen-to="scrollRef" />
|
|
||||||
<n-space :size="5" :wrap-item="false" style="padding: 5px" vertical>
|
|
||||||
<n-card embedded>
|
<n-card embedded>
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-space :wrap-item="false" align="center" inline size="small">
|
<n-space :wrap-item="false" align="center" inline size="small">
|
||||||
|
@ -202,14 +412,14 @@ const infoFilter = ref('')
|
||||||
<n-spin :show="pageState.loading">
|
<n-spin :show="pageState.loading">
|
||||||
<n-grid style="min-width: 500px" x-gap="5">
|
<n-grid style="min-width: 500px" x-gap="5">
|
||||||
<n-gi :span="6">
|
<n-gi :span="6">
|
||||||
<n-statistic :label="$t('status.uptime')" :value="uptime[0]">
|
<n-statistic :label="$t('status.uptime')" :value="uptime.value">
|
||||||
<template #suffix>{{ $t(uptime[1]) }}</template>
|
<template #suffix>{{ $t(uptime.unit) }}</template>
|
||||||
</n-statistic>
|
</n-statistic>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi :span="6">
|
<n-gi :span="6">
|
||||||
<n-statistic
|
<n-statistic
|
||||||
:label="$t('status.connected_clients')"
|
:label="$t('status.connected_clients')"
|
||||||
:value="get(serverInfo, 'Clients.connected_clients', 0)" />
|
:value="get(serverInfo, 'Clients.connected_clients', '0')" />
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi :span="6">
|
<n-gi :span="6">
|
||||||
<n-statistic :value="totalKeys">
|
<n-statistic :value="totalKeys">
|
||||||
|
@ -226,39 +436,108 @@ const infoFilter = ref('')
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</n-spin>
|
</n-spin>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card :title="$t('status.all_info')" embedded>
|
<n-card class="flex-item-expand" content-style="padding: 0; height: 100%;" embedded style="overflow: hidden">
|
||||||
<template #header-extra>
|
<n-tabs
|
||||||
<n-input v-model:value="infoFilter" clearable placeholder="">
|
v-model:value="tabVal"
|
||||||
|
:tabs-padding="20"
|
||||||
|
pane-style="padding: 10px; box-sizing: border-box; display: flex; flex-direction: column; flex-grow: 1;"
|
||||||
|
size="large"
|
||||||
|
style="height: 100%; overflow: hidden"
|
||||||
|
type="line">
|
||||||
|
<template #suffix>
|
||||||
|
<div v-if="tabVal === 'info'" style="padding-right: 10px">
|
||||||
|
<n-input v-model:value="envFilter.keyword" clearable placeholder="">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<icon-button :icon="Filter" size="18" />
|
<icon-button :icon="Filter" size="18" />
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<n-spin :show="pageState.loading">
|
|
||||||
<n-tabs default-value="CPU" placement="left" type="line">
|
<!-- environment tab pane -->
|
||||||
<n-tab-pane v-for="(v, k) in serverInfo" :key="k" :disabled="isEmpty(v)" :name="k">
|
<n-tab-pane :tab="$t('status.env_info')" name="info">
|
||||||
|
<n-space :wrap="false" :wrap-item="false" class="flex-item-expand">
|
||||||
|
<n-space align="end" item-style="padding: 0 5px;" vertical>
|
||||||
|
<n-button
|
||||||
|
v-for="(v, k) in serverInfo"
|
||||||
|
:key="k"
|
||||||
|
:disabled="isEmpty(v)"
|
||||||
|
:focusable="false"
|
||||||
|
:type="envFilter.group === k ? 'primary' : 'default'"
|
||||||
|
secondary
|
||||||
|
size="small"
|
||||||
|
@click="onFilterGroup(k)">
|
||||||
|
<span style="min-width: 80px">{{ k }}</span>
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:columns="[
|
:columns="[
|
||||||
{
|
{
|
||||||
title: $t('common.key'),
|
title: $t('common.key'),
|
||||||
key: 'key',
|
key: 'key',
|
||||||
defaultSortOrder: 'ascend',
|
defaultSortOrder: 'ascend',
|
||||||
sorter: 'default',
|
minWidth: 80,
|
||||||
minWidth: 100,
|
titleAlign: 'center',
|
||||||
filterOptionValue: infoFilter,
|
filterOptionValue: envFilter.keyword,
|
||||||
filter(value, row) {
|
filter(value, row) {
|
||||||
return !!~row.key.indexOf(value.toString())
|
return !!~row.key.indexOf(value.toString())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ title: $t('common.value'), key: 'value' },
|
{ title: $t('common.value'), titleAlign: 'center', key: 'value' },
|
||||||
]"
|
]"
|
||||||
:data="map(v, (value, key) => ({ value, key }))" />
|
:data="env"
|
||||||
|
:loading="pageState.loading"
|
||||||
|
:single-line="false"
|
||||||
|
class="flex-item-expand"
|
||||||
|
flex-height
|
||||||
|
striped />
|
||||||
|
</n-space>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<!-- activity tab pane -->
|
||||||
|
<n-tab-pane
|
||||||
|
:tab="$t('status.activity_status')"
|
||||||
|
class="line-chart"
|
||||||
|
display-directive="show:lazy"
|
||||||
|
name="activity">
|
||||||
|
<div class="line-chart">
|
||||||
|
<div class="line-chart-item">
|
||||||
|
<Line ref="cmdRateRef" :data="cmdRate" :options="chartOption" />
|
||||||
|
</div>
|
||||||
|
<div class="line-chart-item">
|
||||||
|
<Line :data="connectedClients" :options="chartOption" />
|
||||||
|
</div>
|
||||||
|
<div class="line-chart-item">
|
||||||
|
<Line :data="memoryUsage" :options="byteChartOption" />
|
||||||
|
</div>
|
||||||
|
<div class="line-chart-item">
|
||||||
|
<Line :data="networkRate" :options="byteChartOption" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</n-spin>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-scrollbar>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
@import '@/styles/content';
|
||||||
|
|
||||||
|
.line-chart {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { computed, h, nextTick, onMounted, onUnmounted, reactive, ref } from 'vu
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
import { debounce, isEmpty, map, size, split } from 'lodash'
|
import { debounce, isEmpty, map, size, split } from 'lodash'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { NIcon, useThemeVars } from 'naive-ui'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { timeout } from '@/utils/promise.js'
|
import { timeout } from '@/utils/promise.js'
|
||||||
|
@ -17,10 +17,6 @@ const props = defineProps({
|
||||||
server: {
|
server: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
db: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const autoRefresh = reactive({
|
const autoRefresh = reactive({
|
||||||
|
@ -127,7 +123,7 @@ const columns = computed(() => [
|
||||||
const _loadSlowLog = () => {
|
const _loadSlowLog = () => {
|
||||||
data.loading = true
|
data.loading = true
|
||||||
browserStore
|
browserStore
|
||||||
.getSlowLog(props.server, props.db, data.listLimit)
|
.getSlowLog(props.server, data.listLimit)
|
||||||
.then((list) => {
|
.then((list) => {
|
||||||
data.list = list || []
|
data.list = list || []
|
||||||
})
|
})
|
||||||
|
@ -194,10 +190,12 @@ const onListLimitChanged = (limit) => {
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button :loading="data.loading" circle size="small" tertiary @click="_loadSlowLog">
|
<n-button :loading="data.loading" circle size="small" tertiary @click="_loadSlowLog">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
<n-icon :size="props.size">
|
||||||
<refresh
|
<refresh
|
||||||
:class="{ 'auto-rotate': autoRefresh.on }"
|
:class="{ 'auto-rotate': autoRefresh.on }"
|
||||||
:color="autoRefresh.on ? themeVars.primaryColor : undefined"
|
:color="autoRefresh.on ? themeVars.primaryColor : undefined"
|
||||||
:stroke-width="autoRefresh.on ? 6 : 3" />
|
:stroke-width="autoRefresh.on ? 6 : 3" />
|
||||||
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -217,6 +215,7 @@ const onListLimitChanged = (limit) => {
|
||||||
:loading="data.loading"
|
:loading="data.loading"
|
||||||
class="flex-item-expand"
|
class="flex-item-expand"
|
||||||
flex-height
|
flex-height
|
||||||
|
striped
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
@update:sorter="({ order }) => (data.sortOrder = order)" />
|
@update:sorter="({ order }) => (data.sortOrder = order)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import { isEmpty, size } from 'lodash'
|
import { isEmpty, size } from 'lodash'
|
||||||
import bytes from 'bytes'
|
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
@ -19,6 +18,7 @@ import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -420,7 +420,7 @@ defineExpose({
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="showMemoryUsage" vertical />
|
<n-divider v-if="showMemoryUsage" vertical />
|
||||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<format-selector
|
<format-selector
|
||||||
v-show="!inEdit"
|
v-show="!inEdit"
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { isEmpty, size } from 'lodash'
|
||||||
import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import bytes from 'bytes'
|
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
@ -18,6 +17,7 @@ import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import Edit from '@/components/icons/Edit.vue'
|
import Edit from '@/components/icons/Edit.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -388,7 +388,7 @@ defineExpose({
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="showMemoryUsage" vertical />
|
<n-divider v-if="showMemoryUsage" vertical />
|
||||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<format-selector
|
<format-selector
|
||||||
v-show="!inEdit"
|
v-show="!inEdit"
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { isEmpty, size } from 'lodash'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
||||||
import bytes from 'bytes'
|
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
@ -18,6 +17,7 @@ import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vu
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -385,7 +385,7 @@ defineExpose({
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="showMemoryUsage" vertical />
|
<n-divider v-if="showMemoryUsage" vertical />
|
||||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<format-selector
|
<format-selector
|
||||||
v-show="!inEdit"
|
v-show="!inEdit"
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import { includes, isEmpty, size } from 'lodash'
|
import { includes, isEmpty, size } from 'lodash'
|
||||||
import bytes from 'bytes'
|
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
@ -15,6 +14,7 @@ import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -243,7 +243,7 @@ defineExpose({
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="showMemoryUsage" vertical />
|
<n-divider v-if="showMemoryUsage" vertical />
|
||||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { formatTypes } from '@/consts/value_view_type.js'
|
||||||
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { isEmpty, toLower } from 'lodash'
|
import { isEmpty, toLower } from 'lodash'
|
||||||
import bytes from 'bytes'
|
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
||||||
import usePreferencesStore from 'stores/preferences.js'
|
import usePreferencesStore from 'stores/preferences.js'
|
||||||
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -215,7 +215,7 @@ defineExpose({
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.length') }}: {{ props.length }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.length') }}: {{ props.length }}</n-text>
|
||||||
<n-divider v-if="showMemoryUsage" vertical />
|
<n-divider v-if="showMemoryUsage" vertical />
|
||||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand" />
|
<div class="flex-item-expand" />
|
||||||
<format-selector
|
<format-selector
|
||||||
:decode="viewAs.decode"
|
:decode="viewAs.decode"
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { types, types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
||||||
import { isEmpty, size } from 'lodash'
|
import { isEmpty, size } from 'lodash'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import bytes from 'bytes'
|
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import LoadList from '@/components/icons/LoadList.vue'
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
@ -18,6 +17,7 @@ import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import Edit from '@/components/icons/Edit.vue'
|
import Edit from '@/components/icons/Edit.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -420,7 +420,7 @@ defineExpose({
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="showMemoryUsage" vertical />
|
<n-divider v-if="showMemoryUsage" vertical />
|
||||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<format-selector
|
<format-selector
|
||||||
v-show="!inEdit"
|
v-show="!inEdit"
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
strokeWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M24.0083 33.8995V6"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M36 22L24 34L12 22"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M36 42H12"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -11,17 +11,17 @@ const props = defineProps({
|
||||||
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#icon-25e88d94353e4f38)">
|
<g clip-path="url(#icon-25e88d94353e4f38)">
|
||||||
<path
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
d="M42 20V39C42 40.6569 40.6569 42 39 42H9C7.34315 42 6 40.6569 6 39V9C6 7.34315 7.34315 6 9 6H30"
|
d="M42 20V39C42 40.6569 40.6569 42 39 42H9C7.34315 42 6 40.6569 6 39V9C6 7.34315 7.34315 6 9 6H30"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round" />
|
||||||
stroke-width="4" />
|
|
||||||
<path
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
d="M16 20L26 28L41 7"
|
d="M16 20L26 28L41 7"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round" />
|
||||||
stroke-width="4" />
|
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="icon-25e88d94353e4f38">
|
<clipPath id="icon-25e88d94353e4f38">
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
strokeWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M14 24L15.25 25.25M44 14L24 34L22.75 32.75"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M4 24L14 34L34 14"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
strokeWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M43 5L29.7 43L22.1 25.9L5 18.3L43 5Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M43.0001 5L22.1001 25.9"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
strokeWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M24 28.6292C26.5104 28.6292 28.5455 26.6004 28.5455 24.0979C28.5455 21.5954 26.5104 19.5667 24 19.5667C21.4897 19.5667 19.4546 21.5954 19.4546 24.0979C19.4546 26.6004 21.4897 28.6292 24 28.6292Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M16 15C10.6667 19.9706 10.6667 28.0294 16 33"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M32 33C37.3333 28.0294 37.3333 19.9706 32 15"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M9.85786 10C2.04738 17.7861 2.04738 30.4098 9.85786 38.1959"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
d="M38.1421 38.1959C45.9526 30.4098 45.9526 17.7861 38.1421 10"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -359,7 +359,12 @@
|
||||||
"connected_clients": "Clients",
|
"connected_clients": "Clients",
|
||||||
"total_keys": "Keys",
|
"total_keys": "Keys",
|
||||||
"memory_used": "Memory",
|
"memory_used": "Memory",
|
||||||
"all_info": "Information"
|
"all_info": "Information",
|
||||||
|
"env_info": "Environment",
|
||||||
|
"activity_status": "Activity",
|
||||||
|
"act_cmd": "Commands Per Second",
|
||||||
|
"act_network_input": "Network Input",
|
||||||
|
"act_network_output": "Network Output"
|
||||||
},
|
},
|
||||||
"slog": {
|
"slog": {
|
||||||
"title": "Slow Log",
|
"title": "Slow Log",
|
||||||
|
@ -381,5 +386,18 @@
|
||||||
"save_log": "Save Log",
|
"save_log": "Save Log",
|
||||||
"clean_log": "Clean Log",
|
"clean_log": "Clean Log",
|
||||||
"always_show_last": "Always To Last Line"
|
"always_show_last": "Always To Last Line"
|
||||||
|
},
|
||||||
|
"pubsub": {
|
||||||
|
"title": "Pub/Sub",
|
||||||
|
"publish": "Publish",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"unsubscribe": "Unsubscribe",
|
||||||
|
"clear": "Clear Message",
|
||||||
|
"time": "Time",
|
||||||
|
"filter": "Filter",
|
||||||
|
"channel": "Channel",
|
||||||
|
"message": "Message",
|
||||||
|
"receive_message": "Received Messages {total}",
|
||||||
|
"always_show_last": "Always To Last Line"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,8 +290,7 @@
|
||||||
"uptime": "Tempo de Atividade",
|
"uptime": "Tempo de Atividade",
|
||||||
"connected_clients": "Clientes Conectados",
|
"connected_clients": "Clientes Conectados",
|
||||||
"total_keys": "Chaves Totais",
|
"total_keys": "Chaves Totais",
|
||||||
"memory_used": "Memória Usada",
|
"memory_used": "Memória Usada"
|
||||||
"all_info": "Informações"
|
|
||||||
},
|
},
|
||||||
"slog": {
|
"slog": {
|
||||||
"title": "Registro de Operações Lentas",
|
"title": "Registro de Operações Lentas",
|
||||||
|
|
|
@ -136,7 +136,7 @@
|
||||||
"cli": "命令行",
|
"cli": "命令行",
|
||||||
"slow_log": "慢日志",
|
"slow_log": "慢日志",
|
||||||
"cmd_monitor": "监控命令",
|
"cmd_monitor": "监控命令",
|
||||||
"pub_message": "推送/订阅"
|
"pub_message": "发布/订阅"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ribbon": {
|
"ribbon": {
|
||||||
|
@ -359,7 +359,12 @@
|
||||||
"connected_clients": "已连客户端",
|
"connected_clients": "已连客户端",
|
||||||
"total_keys": "键总数",
|
"total_keys": "键总数",
|
||||||
"memory_used": "内存使用",
|
"memory_used": "内存使用",
|
||||||
"all_info": "全部信息"
|
"all_info": "全部信息",
|
||||||
|
"env_info": "运行环境",
|
||||||
|
"activity_status": "活动状态",
|
||||||
|
"act_cmd": "命令执行数/秒",
|
||||||
|
"act_network_input": "网络输入",
|
||||||
|
"act_network_output": "网络输出"
|
||||||
},
|
},
|
||||||
"slog": {
|
"slog": {
|
||||||
"title": "慢日志",
|
"title": "慢日志",
|
||||||
|
@ -381,5 +386,18 @@
|
||||||
"save_log": "保存日志",
|
"save_log": "保存日志",
|
||||||
"clean_log": "清空日志",
|
"clean_log": "清空日志",
|
||||||
"always_show_last": "总是显示最新"
|
"always_show_last": "总是显示最新"
|
||||||
|
},
|
||||||
|
"pubsub": {
|
||||||
|
"title": "发布订阅",
|
||||||
|
"publish": "发布",
|
||||||
|
"subscribe": "开启订阅",
|
||||||
|
"unsubscribe": "取消订阅",
|
||||||
|
"clear": "清空消息",
|
||||||
|
"time": "时间",
|
||||||
|
"filter": "筛选",
|
||||||
|
"channel": "频道",
|
||||||
|
"message": "消息",
|
||||||
|
"receive_message": "已接收消息 {total} 条",
|
||||||
|
"always_show_last": "总是显示最新"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { setupDiscreteApi } from '@/utils/discrete.js'
|
||||||
import usePreferencesStore from 'stores/preferences.js'
|
import usePreferencesStore from 'stores/preferences.js'
|
||||||
import { loadEnvironment } from '@/utils/platform.js'
|
import { loadEnvironment } from '@/utils/platform.js'
|
||||||
import { setupMonaco } from '@/utils/monaco.js'
|
import { setupMonaco } from '@/utils/monaco.js'
|
||||||
|
import { setupChart } from '@/utils/chart.js'
|
||||||
|
|
||||||
dayjs.extend(duration)
|
dayjs.extend(duration)
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
@ -21,6 +22,7 @@ async function setupApp() {
|
||||||
|
|
||||||
await loadEnvironment()
|
await loadEnvironment()
|
||||||
setupMonaco()
|
setupMonaco()
|
||||||
|
setupChart()
|
||||||
const prefStore = usePreferencesStore()
|
const prefStore = usePreferencesStore()
|
||||||
await prefStore.loadPreferences()
|
await prefStore.loadPreferences()
|
||||||
await setupDiscreteApi()
|
await setupDiscreteApi()
|
||||||
|
|
|
@ -1884,13 +1884,12 @@ const useBrowserStore = defineStore('browser', {
|
||||||
/**
|
/**
|
||||||
* get slow log list
|
* get slow log list
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
* @param {number} db
|
|
||||||
* @param {number} num
|
* @param {number} num
|
||||||
* @return {Promise<[]>}
|
* @return {Promise<[]>}
|
||||||
*/
|
*/
|
||||||
async getSlowLog(server, db, num) {
|
async getSlowLog(server, num) {
|
||||||
try {
|
try {
|
||||||
const { success, data = { list: [] } } = await GetSlowLogs(server, db, num)
|
const { success, data = { list: [] } } = await GetSlowLogs(server, num)
|
||||||
const { list } = data
|
const { list } = data
|
||||||
return list
|
return list
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
.content-value {
|
.content-value {
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content {
|
.tab-content {
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
/**
|
||||||
|
* convert byte value
|
||||||
|
* @param {number} bytes
|
||||||
|
* @param {number} decimals
|
||||||
|
* @return {{unit: string, value: number}}
|
||||||
|
*/
|
||||||
|
export const convertBytes = (bytes, decimals = 2) => {
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return {
|
||||||
|
value: 0,
|
||||||
|
unit: sizes[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const k = 1024
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
const j = Math.min(i, sizes.length - 1)
|
||||||
|
return {
|
||||||
|
value: parseFloat((bytes / Math.pow(k, j)).toFixed(decimals)),
|
||||||
|
unit: sizes[j],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} bytes
|
||||||
|
* @param {number} decimals
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export const formatBytes = (bytes, decimals = 2) => {
|
||||||
|
const res = convertBytes(bytes, decimals)
|
||||||
|
return res.value + res.unit
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import {
|
||||||
|
CategoryScale,
|
||||||
|
Chart as ChartJS,
|
||||||
|
Filler,
|
||||||
|
Legend,
|
||||||
|
LinearScale,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from 'chart.js'
|
||||||
|
|
||||||
|
export const setupChart = () => {
|
||||||
|
ChartJS.register(Title, Tooltip, LineElement, CategoryScale, LinearScale, PointElement, Legend, Filler)
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/sysfont v0.1.2
|
github.com/adrg/sysfont v0.1.2
|
||||||
github.com/andybalholm/brotli v1.0.6
|
github.com/andybalholm/brotli v1.1.0
|
||||||
github.com/google/uuid v1.5.0
|
github.com/google/uuid v1.5.0
|
||||||
github.com/klauspost/compress v1.17.4
|
github.com/klauspost/compress v1.17.4
|
||||||
github.com/redis/go-redis/v9 v9.4.0
|
github.com/redis/go-redis/v9 v9.4.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -6,8 +6,8 @@ github.com/adrg/sysfont v0.1.2/go.mod h1:6d3l7/BSjX9VaeXWJt9fcrftFaD/t7l11xgSywC
|
||||||
github.com/adrg/xdg v0.3.0/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
github.com/adrg/xdg v0.3.0/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
|
4
main.go
4
main.go
|
@ -32,6 +32,7 @@ func main() {
|
||||||
browserSvc := services.Browser()
|
browserSvc := services.Browser()
|
||||||
cliSvc := services.Cli()
|
cliSvc := services.Cli()
|
||||||
monitorSvc := services.Monitor()
|
monitorSvc := services.Monitor()
|
||||||
|
pubsubSvc := services.Pubsub()
|
||||||
prefSvc := services.Preferences()
|
prefSvc := services.Preferences()
|
||||||
prefSvc.SetAppVersion(version)
|
prefSvc.SetAppVersion(version)
|
||||||
windowWidth, windowHeight, maximised := prefSvc.GetWindowSize()
|
windowWidth, windowHeight, maximised := prefSvc.GetWindowSize()
|
||||||
|
@ -70,6 +71,7 @@ func main() {
|
||||||
browserSvc.Start(ctx)
|
browserSvc.Start(ctx)
|
||||||
cliSvc.Start(ctx)
|
cliSvc.Start(ctx)
|
||||||
monitorSvc.Start(ctx)
|
monitorSvc.Start(ctx)
|
||||||
|
pubsubSvc.Start(ctx)
|
||||||
|
|
||||||
services.GA().SetSecretKey(gaMeasurementID, gaSecretKey)
|
services.GA().SetSecretKey(gaMeasurementID, gaSecretKey)
|
||||||
services.GA().Startup(version)
|
services.GA().Startup(version)
|
||||||
|
@ -88,6 +90,7 @@ func main() {
|
||||||
browserSvc.Stop()
|
browserSvc.Stop()
|
||||||
cliSvc.CloseAll()
|
cliSvc.CloseAll()
|
||||||
monitorSvc.StopAll()
|
monitorSvc.StopAll()
|
||||||
|
pubsubSvc.StopAll()
|
||||||
},
|
},
|
||||||
Bind: []interface{}{
|
Bind: []interface{}{
|
||||||
sysSvc,
|
sysSvc,
|
||||||
|
@ -95,6 +98,7 @@ func main() {
|
||||||
browserSvc,
|
browserSvc,
|
||||||
cliSvc,
|
cliSvc,
|
||||||
monitorSvc,
|
monitorSvc,
|
||||||
|
pubsubSvc,
|
||||||
prefSvc,
|
prefSvc,
|
||||||
},
|
},
|
||||||
Mac: &mac.Options{
|
Mac: &mac.Options{
|
||||||
|
|
Loading…
Reference in New Issue