Skip to content

Commit

Permalink
feat: support real-time command monitoring
Browse files Browse the repository at this point in the history
  • Loading branch information
tiny-craft committed Dec 28, 2023
1 parent 665f880 commit 10ec866
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 41 deletions.
174 changes: 174 additions & 0 deletions backend/services/monitor_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package services

import (
"bufio"
"context"
"errors"
"fmt"
"github.com/redis/go-redis/v9"
"github.com/wailsapp/wails/v2/pkg/runtime"
"os"
"strconv"
"sync"
"time"
"tinyrdm/backend/types"
)

type monitorItem struct {
client *redis.Client
cmd *redis.MonitorCmd
ch chan string
closeCh chan struct{}
eventName string
}

type monitorService struct {
ctx context.Context
ctxCancel context.CancelFunc
mutex sync.Mutex
items map[string]*monitorItem
}

var monitor *monitorService
var onceMonitor sync.Once

func Monitor() *monitorService {
if monitor == nil {
onceMonitor.Do(func() {
monitor = &monitorService{
items: map[string]*monitorItem{},
}
})
}
return monitor
}

func (c *monitorService) getItem(server string) (*monitorItem, error) {
c.mutex.Lock()
defer c.mutex.Unlock()

item, ok := c.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 = &monitorItem{
client: client,
}
c.items[server] = item
}
return item, nil
}

func (c *monitorService) Start(ctx context.Context) {
c.ctx, c.ctxCancel = context.WithCancel(ctx)
}

// StartMonitor start a monitor by server name
func (c *monitorService) StartMonitor(server string) (resp types.JSResp) {
item, err := c.getItem(server)
if err != nil {
resp.Msg = err.Error()
return
}

item.ch = make(chan string)
item.closeCh = make(chan struct{})
item.eventName = "monitor:" + strconv.Itoa(int(time.Now().Unix()))
item.cmd = item.client.Monitor(c.ctx, item.ch)
item.cmd.Start()

go c.processMonitor(item.ch, item.closeCh, item.eventName)
resp.Success = true
resp.Data = struct {
EventName string `json:"eventName"`
}{
EventName: item.eventName,
}
return
}

func (c *monitorService) processMonitor(ch <-chan string, closeCh <-chan struct{}, eventName string) {
for {
select {
case data := <-ch:
if data != "OK" {
runtime.EventsEmit(c.ctx, eventName, data)
}

case <-closeCh:
// monitor stopped
return
}
}
}

// StopMonitor stop monitor by server name
func (c *monitorService) StopMonitor(server string) (resp types.JSResp) {
c.mutex.Lock()
defer c.mutex.Unlock()

item, ok := c.items[server]
if !ok || item.cmd == nil {
resp.Success = true
return
}

item.cmd.Stop()
//close(item.ch)
close(item.closeCh)
delete(c.items, server)
resp.Success = true
return
}

// StopAll stop all monitor
func (c *monitorService) StopAll() {
if c.ctxCancel != nil {
c.ctxCancel()
}

for server := range c.items {
c.StopMonitor(server)
}
}

func (c *monitorService) ExportLog(logs []string) (resp types.JSResp) {
filepath, err := runtime.SaveFileDialog(c.ctx, runtime.SaveDialogOptions{
ShowHiddenFiles: false,
DefaultFilename: fmt.Sprintf("monitor_log_%s.txt", time.Now().Format("20060102150405")),
Filters: []runtime.FileFilter{
{Pattern: "*.txt"},
},
})
if err != nil {
resp.Msg = err.Error()
return
}

file, err := os.Create(filepath)
if err != nil {
resp.Msg = err.Error()
return
}
defer file.Close()

writer := bufio.NewWriter(file)
for _, line := range logs {
_, _ = writer.WriteString(line + "\n")
}
writer.Flush()

resp.Success = true
return
}
33 changes: 11 additions & 22 deletions frontend/src/components/content/ContentLogPane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,8 @@ defineExpose({
</script>
<template>
<n-card
:bordered="false"
:theme-overrides="{ borderRadius: '0px' }"
:title="$t('log.title')"
class="content-container flex-box-v"
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray">
<div class="content-log content-container content-value fill-height flex-box-v">
<n-h3>{{ $t('log.title') }}</n-h3>
<n-form :disabled="data.loading" class="flex-item" inline>
<n-form-item :label="$t('log.filter_server')">
<n-select
Expand All @@ -157,24 +153,17 @@ defineExpose({
<icon-button :icon="Delete" border t-tooltip="log.clean_log" @click="cleanHistory" />
</n-form-item>
</n-form>
<div class="content-value fill-height flex-box-h">
<n-data-table
ref="tableRef"
:columns="columns"
:data="data.history"
:loading="data.loading"
class="flex-item-expand"
flex-height
virtual-scroll />
</div>
</n-card>
<n-data-table
ref="tableRef"
:columns="columns"
:data="data.history"
:loading="data.loading"
class="flex-item-expand"
flex-height
virtual-scroll />
</div>
</template>
<style lang="scss" scoped>
@import '@/styles/content';
.content-container {
padding: 5px;
box-sizing: border-box;
}
</style>
4 changes: 3 additions & 1 deletion frontend/src/components/content/ContentPane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Monitor from '@/components/icons/Monitor.vue'
import Pub from '@/components/icons/Pub.vue'
import ContentSlog from '@/components/content_value/ContentSlog.vue'
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
import ContentMonitor from '@/components/content_value/ContentMonitor.vue'
const themeVars = useThemeVars()
Expand Down Expand Up @@ -173,7 +174,7 @@ watch(
</n-tab-pane>
<!-- command monitor pane -->
<n-tab-pane :disabled="true" :name="BrowserTabType.CmdMonitor.toString()" display-directive="if">
<n-tab-pane :name="BrowserTabType.CmdMonitor.toString()" display-directive="show:lazy">
<template #tab>
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
<n-icon size="16">
Expand All @@ -185,6 +186,7 @@ watch(
<span>{{ $t('interface.sub_tab.cmd_monitor') }}</span>
</n-space>
</template>
<content-monitor :server="props.server" />
</n-tab-pane>
<!-- pub/sub message pane -->
Expand Down
Loading

0 comments on commit 10ec866

Please sign in to comment.