mirror of
https://gitee.com/fit2cloud-feizhiyun/1Panel.git
synced 2024-12-01 19:37:48 +08:00
feat: Redis 支持远程数据库 (#5014)
This commit is contained in:
parent
c56435970a
commit
94cebfd8cc
@ -16,7 +16,7 @@ import (
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.RedisStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/redis/status [get]
|
||||
// @Router /databases/redis/status [post]
|
||||
func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBind(&req, c); err != nil {
|
||||
@ -38,7 +38,7 @@ func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.RedisConf
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/redis/conf [get]
|
||||
// @Router /databases/redis/conf [post]
|
||||
func (b *BaseApi) LoadRedisConf(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBind(&req, c); err != nil {
|
||||
@ -60,7 +60,7 @@ func (b *BaseApi) LoadRedisConf(c *gin.Context) {
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.RedisPersistence
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/redis/persistence/conf [get]
|
||||
// @Router /databases/redis/persistence/conf [post]
|
||||
func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBind(&req, c); err != nil {
|
||||
@ -75,6 +75,25 @@ func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
||||
helper.SuccessWithData(c, redisService.CheckHasCli())
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Install redis-cli
|
||||
// @Description 安装 redis cli
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/redis/install/cli [post]
|
||||
func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||
if err := redisService.InstallCli(); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Update redis conf
|
||||
// @Description 更新 redis 配置信息
|
||||
|
@ -3,6 +3,7 @@ package v1
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -88,23 +89,38 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
name := c.Query("name")
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Type: "redis", Name: name})
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
|
||||
from := c.Query("from")
|
||||
defer wsConn.Close()
|
||||
commands := []string{"redis-cli"}
|
||||
if len(redisInfo.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-a", redisInfo.Password, "--no-auth-warning"}
|
||||
database, err := databaseService.Get(name)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
return
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(redisInfo.Password)
|
||||
itemCmds := append([]string{"exec", "-it", redisInfo.ContainerName}, commands...)
|
||||
if from == "local" {
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
return
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
|
||||
}
|
||||
} else {
|
||||
itemPort := fmt.Sprintf("%v", database.Port)
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort}
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"}
|
||||
}
|
||||
name = "1Panel-redis-cli-tools"
|
||||
}
|
||||
|
||||
pidMap := loadMapFromDockerTop(name)
|
||||
itemCmds := append([]string{"exec", "-it", name}, commands...)
|
||||
slave, err := terminal.NewCommand(itemCmds)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(redisInfo.ContainerName, strings.Join(commands, " "), pidMap)
|
||||
defer killBash(name, strings.Join(commands, " "), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
|
@ -7,7 +7,8 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/postgresql"
|
||||
client2 "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
pg_client "github.com/1Panel-dev/1Panel/backend/utils/postgresql/client"
|
||||
redis_client "github.com/1Panel-dev/1Panel/backend/utils/redis"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
@ -113,7 +114,7 @@ func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
|
||||
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
_, err := postgresql.NewPostgresqlClient(client2.DBInfo{
|
||||
_, err := postgresql.NewPostgresqlClient(pg_client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
@ -122,6 +123,13 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
Timeout: 6,
|
||||
})
|
||||
return err == nil
|
||||
case constant.AppRedis:
|
||||
_, err := redis_client.NewRedisClient(redis_client.DBInfo{
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Password: req.Password,
|
||||
})
|
||||
return err == nil
|
||||
case "mysql", "mariadb":
|
||||
_, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
@ -153,7 +161,7 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
}
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
|
||||
if _, err := postgresql.NewPostgresqlClient(pg_client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
@ -163,6 +171,14 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.AppRedis:
|
||||
if _, err := redis_client.NewRedisClient(redis_client.DBInfo{
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Password: req.Password,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
case "mysql", "mariadb":
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
@ -249,7 +265,7 @@ func (u *DatabaseService) Delete(req dto.DatabaseDelete) error {
|
||||
func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(client2.DBInfo{
|
||||
if _, err := postgresql.NewPostgresqlClient(pg_client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -11,6 +12,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
@ -24,6 +27,9 @@ type IRedisService interface {
|
||||
LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error)
|
||||
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
|
||||
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
|
||||
|
||||
CheckHasCli() bool
|
||||
InstallCli() error
|
||||
}
|
||||
|
||||
func NewIRedisService() IRedisService {
|
||||
@ -50,6 +56,33 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *RedisService) CheckHasCli() bool {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer client.Close()
|
||||
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, item := range containerLists {
|
||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *RedisService) InstallCli() error {
|
||||
item := dto.ContainerOperate{
|
||||
Name: "1Panel-redis-cli-tools",
|
||||
Image: "redis:7.2.4",
|
||||
Network: "1panel-network",
|
||||
}
|
||||
return NewIContainerService().ContainerCreate(item)
|
||||
}
|
||||
|
||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||
if err := updateInstallInfoInDB("redis", req.Database, "password", req.Value); err != nil {
|
||||
return err
|
||||
|
@ -39,6 +39,8 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
||||
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
||||
cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh)
|
||||
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
||||
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
||||
cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf)
|
||||
|
26
backend/utils/redis/redis.go
Normal file
26
backend/utils/redis/redis.go
Normal file
@ -0,0 +1,26 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
type DBInfo struct {
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func NewRedisClient(conn DBInfo) (*redis.Client, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%v", conn.Address, conn.Port),
|
||||
Password: conn.Password,
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
if _, err := client.Ping().Result(); err != nil {
|
||||
return client, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
@ -5110,7 +5110,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/databases/redis/conf": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@ -5185,6 +5185,25 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/redis/install/cli": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "安装 redis cli",
|
||||
"tags": [
|
||||
"Database Redis"
|
||||
],
|
||||
"summary": "Install redis-cli",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/redis/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -5226,7 +5245,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/databases/redis/persistence/conf": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@ -5302,7 +5321,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/databases/redis/status": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
|
@ -5103,7 +5103,7 @@
|
||||
}
|
||||
},
|
||||
"/databases/redis/conf": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@ -5178,6 +5178,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/redis/install/cli": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "安装 redis cli",
|
||||
"tags": [
|
||||
"Database Redis"
|
||||
],
|
||||
"summary": "Install redis-cli",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/redis/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -5219,7 +5238,7 @@
|
||||
}
|
||||
},
|
||||
"/databases/redis/persistence/conf": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
@ -5295,7 +5314,7 @@
|
||||
}
|
||||
},
|
||||
"/databases/redis/status": {
|
||||
"get": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
|
@ -8349,7 +8349,7 @@ paths:
|
||||
tags:
|
||||
- Database Postgresql
|
||||
/databases/redis/conf:
|
||||
get:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取 redis 配置信息
|
||||
@ -8396,6 +8396,17 @@ paths:
|
||||
formatEN: update the redis database configuration information
|
||||
formatZH: 更新 redis 数据库配置信息
|
||||
paramKeys: []
|
||||
/databases/redis/install/cli:
|
||||
post:
|
||||
description: 安装 redis cli
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Install redis-cli
|
||||
tags:
|
||||
- Database Redis
|
||||
/databases/redis/password:
|
||||
post:
|
||||
consumes:
|
||||
@ -8423,7 +8434,7 @@ paths:
|
||||
formatZH: 修改 redis 数据库密码
|
||||
paramKeys: []
|
||||
/databases/redis/persistence/conf:
|
||||
get:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取 redis 持久化配置
|
||||
@ -8471,7 +8482,7 @@ paths:
|
||||
formatZH: redis 数据库持久化配置更新
|
||||
paramKeys: []
|
||||
/databases/redis/status:
|
||||
get:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取 redis 状态信息
|
||||
|
@ -117,6 +117,12 @@ export const loadRedisConf = (database: string) => {
|
||||
export const redisPersistenceConf = (database: string) => {
|
||||
return http.post<Database.RedisPersistenceConf>(`/databases/redis/persistence/conf`, { name: database });
|
||||
};
|
||||
export const checkRedisCli = () => {
|
||||
return http.get<boolean>(`/databases/redis/check`);
|
||||
};
|
||||
export const installRedisCli = () => {
|
||||
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M);
|
||||
};
|
||||
export const changeRedisPassword = (database: string, password: string) => {
|
||||
if (password) {
|
||||
password = Base64.encode(password);
|
||||
|
@ -514,6 +514,7 @@ const message = {
|
||||
keyspaceMisses: 'Number of failed attempts to find the database key',
|
||||
hit: 'Find the database key hit ratio',
|
||||
latestForkUsec: 'The number of microseconds spent on the last fork() operation',
|
||||
redisCliHelper: 'redis-cli service not detected, please enable the service first!',
|
||||
|
||||
recoverHelper: 'Data is about to be overwritten with [{0}]. Do you want to continue?',
|
||||
submitIt: 'Overwrite the data',
|
||||
|
@ -502,6 +502,7 @@ const message = {
|
||||
keyspaceMisses: '查找數據庫鍵失敗的次數',
|
||||
hit: '查找數據庫鍵命中率',
|
||||
latestForkUsec: '最近一次 fork() 操作耗費的微秒數',
|
||||
redisCliHelper: '未檢測到 redis-cli 服務,請先啟用服務!',
|
||||
|
||||
recoverHelper: '即將使用 [{0}] 對數據進行覆蓋,是否繼續?',
|
||||
submitIt: '覆蓋數據',
|
||||
|
@ -502,6 +502,7 @@ const message = {
|
||||
keyspaceMisses: '查找数据库键失败的次数',
|
||||
hit: '查找数据库键命中率',
|
||||
latestForkUsec: '最近一次 fork() 操作耗费的微秒数',
|
||||
redisCliHelper: '未检测到 redis-cli 服务,请先启用服务!',
|
||||
|
||||
recoverHelper: '即将使用 [{0}] 对数据进行覆盖,是否继续?',
|
||||
submitIt: '覆盖数据',
|
||||
|
@ -89,6 +89,16 @@ const databaseRouter = {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'redis/remote',
|
||||
name: 'Redis-Remote',
|
||||
component: () => import('@/views/database/redis/remote/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/databases',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -28,6 +28,7 @@ export interface GlobalState {
|
||||
device: DeviceType;
|
||||
lastFilePath: string;
|
||||
currentDB: string;
|
||||
currentRedisDB: string;
|
||||
showEntranceWarn: boolean;
|
||||
defaultNetwork: string;
|
||||
|
||||
|
@ -32,6 +32,7 @@ const GlobalStore = defineStore({
|
||||
device: DeviceType.Desktop,
|
||||
lastFilePath: '',
|
||||
currentDB: '',
|
||||
currentRedisDB: '',
|
||||
showEntranceWarn: true,
|
||||
defaultNetwork: 'all',
|
||||
|
||||
@ -80,6 +81,9 @@ const GlobalStore = defineStore({
|
||||
setCurrentDB(name: string) {
|
||||
this.currentDB = name;
|
||||
},
|
||||
setCurrentRedisDB(name: string) {
|
||||
this.currentRedisDB = name;
|
||||
},
|
||||
setShowEntranceWarn(show: boolean) {
|
||||
this.showEntranceWarn = show;
|
||||
},
|
||||
|
54
frontend/src/views/database/redis/check/index.vue
Normal file
54
frontend/src/views/database/redis/check/index.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('app.checkTitle')"
|
||||
width="50%"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2" v-if="open">
|
||||
<el-alert
|
||||
type="error"
|
||||
:description="$t('app.deleteHelper', [$t('app.database')])"
|
||||
center
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<br />
|
||||
<el-descriptions border :column="1">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<a href="javascript:void(0);" @click="toApp()">{{ $t('app.app') }}</a>
|
||||
</template>
|
||||
{{ installData.join(',') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
|
||||
interface InstallProps {
|
||||
items: Array<string>;
|
||||
}
|
||||
const installData = ref();
|
||||
let open = ref(false);
|
||||
|
||||
const acceptParams = (props: InstallProps) => {
|
||||
installData.value = props.items;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const toApp = () => {
|
||||
router.push({ name: 'AppInstalled' });
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="app-status" style="margin-top: 20px" v-if="currentDB?.from === 'remote'">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">Redis</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<LayoutContent :title="'Redis ' + $t('menu.database')">
|
||||
<template #app v-if="currentDB?.from === 'local'">
|
||||
<AppStatus
|
||||
@ -35,9 +43,6 @@
|
||||
<el-tooltip v-else :content="item.database" placement="top">
|
||||
<span>{{ item.database.substring(0, 25) }}...</span>
|
||||
</el-tooltip>
|
||||
<el-tag class="tagClass">
|
||||
{{ item.type === 'mysql' ? 'MySQL' : 'MariaDB' }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</div>
|
||||
<el-button link type="primary" class="jumpAdd" @click="goRouter('remote')" icon="Position">
|
||||
@ -51,6 +56,9 @@
|
||||
<el-button type="primary" plain @click="onChangePassword">
|
||||
{{ $t('database.databaseConnInfo') }}
|
||||
</el-button>
|
||||
<el-button @click="goRemoteDB" type="primary" plain>
|
||||
{{ $t('database.remoteDB') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="goDashboard" icon="Position">Redis-Commander</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@ -59,13 +67,21 @@
|
||||
:style="{ height: `calc(100vh - ${loadHeight()})` }"
|
||||
:key="isRefresh"
|
||||
ref="terminalRef"
|
||||
v-show="terminalShow && redisStatus === 'Running'"
|
||||
v-show="redisStatus === 'Running' && terminalShow"
|
||||
/>
|
||||
<el-empty
|
||||
v-if="redisStatus !== 'Running'"
|
||||
v-if="redisStatus !== 'Running' || (currentDB.from === 'remote' && !redisCliExist)"
|
||||
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
|
||||
:description="$t('commons.service.serviceNotStarted', ['Redis'])"
|
||||
></el-empty>
|
||||
:description="
|
||||
currentDB.from !== 'remote'
|
||||
? $t('commons.service.serviceNotStarted', ['Redis'])
|
||||
: $t('database.redisCliHelper')
|
||||
"
|
||||
>
|
||||
<el-button v-if="currentDB.from === 'remote'" type="primary" @click="installCli">
|
||||
{{ $t('commons.button.enable') }}
|
||||
</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
@ -104,8 +120,10 @@ import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { CheckAppInstalled, GetAppPort } from '@/api/modules/app';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { listDatabases } from '@/api/modules/database';
|
||||
import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/database';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
@ -121,6 +139,8 @@ const terminalShow = ref(false);
|
||||
const redisCommandPort = ref();
|
||||
const commandVisible = ref(false);
|
||||
|
||||
const redisCliExist = ref();
|
||||
|
||||
const appKey = ref('redis');
|
||||
const appName = ref();
|
||||
const dbOptionsLocal = ref<Array<Database.DatabaseOption>>([]);
|
||||
@ -153,6 +173,12 @@ const goDashboard = async () => {
|
||||
const getAppDetail = (key: string) => {
|
||||
router.push({ name: 'AppAll', query: { install: key } });
|
||||
};
|
||||
const goRemoteDB = async () => {
|
||||
if (currentDB.value) {
|
||||
globalStore.setCurrentRedisDB(currentDBName.value);
|
||||
}
|
||||
router.push({ name: 'Redis-Remote' });
|
||||
};
|
||||
|
||||
const loadDashboardPort = async () => {
|
||||
const res = await GetAppPort('redis-commander', '');
|
||||
@ -237,17 +263,39 @@ const reOpenTerminal = async () => {
|
||||
|
||||
const initTerminal = async () => {
|
||||
loading.value = true;
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (!redisCliExist.value) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
isRefresh.value = !isRefresh.value;
|
||||
loading.value = false;
|
||||
redisIsExist.value = true;
|
||||
nextTick(() => {
|
||||
terminalShow.value = true;
|
||||
redisStatus.value = 'Running';
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v1/databases/redis/exec',
|
||||
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
});
|
||||
});
|
||||
isRefresh.value = !isRefresh.value;
|
||||
return;
|
||||
}
|
||||
await CheckAppInstalled('redis', currentDBName.value)
|
||||
.then((res) => {
|
||||
redisIsExist.value = res.data.isExist;
|
||||
redisStatus.value = res.data.status;
|
||||
console.log(redisStatus.value);
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
if (res.data.status === 'Running') {
|
||||
terminalShow.value = true;
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v1/databases/redis/exec',
|
||||
args: `name=${currentDBName.value}`,
|
||||
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
});
|
||||
@ -266,9 +314,27 @@ const closeTerminal = async (isKeepShow: boolean) => {
|
||||
terminalShow.value = isKeepShow;
|
||||
};
|
||||
|
||||
const checkCliValid = async () => {
|
||||
const res = await checkRedisCli();
|
||||
redisCliExist.value = res.data;
|
||||
};
|
||||
const installCli = async () => {
|
||||
loading.value = true;
|
||||
await installRedisCli()
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
redisCliExist.value = true;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadDBOptions();
|
||||
loadDashboardPort();
|
||||
checkCliValid();
|
||||
});
|
||||
const onBefore = () => {
|
||||
closeTerminal(false);
|
||||
|
100
frontend/src/views/database/redis/remote/delete/index.vue
Normal file
100
frontend/src/views/database/redis/remote/delete/index.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('commons.button.delete') + ' - ' + deleteMysqlReq.database"
|
||||
width="30%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('app.forceDelete')" />
|
||||
<span class="input-help">
|
||||
{{ $t('app.forceDeleteHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteMysqlReq.deleteBackup" :label="$t('app.deleteBackup')" />
|
||||
<span class="input-help">
|
||||
{{ $t('database.deleteBackupHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteMysqlReq.database }}</span>
|
||||
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
|
||||
</div>
|
||||
<el-input v-model="delMysqlInfo" :placeholder="deleteMysqlReq.database"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submit"
|
||||
:disabled="delMysqlInfo != deleteMysqlReq.database || loading"
|
||||
>
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { deleteDatabase } from '@/api/modules/database';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let deleteMysqlReq = ref({
|
||||
id: 0,
|
||||
database: '',
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
});
|
||||
let dialogVisible = ref(false);
|
||||
let loading = ref(false);
|
||||
let delMysqlInfo = ref('');
|
||||
|
||||
const deleteForm = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
id: number;
|
||||
name: string;
|
||||
database: string;
|
||||
}
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const acceptParams = async (prop: DialogProps) => {
|
||||
delMysqlInfo.value = '';
|
||||
deleteMysqlReq.value = {
|
||||
id: prop.id,
|
||||
database: prop.database,
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
};
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
deleteDatabase(deleteMysqlReq.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
dialogVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
176
frontend/src/views/database/redis/remote/index.vue
Normal file
176
frontend/src/views/database/redis/remote/index.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<LayoutContent>
|
||||
<template #title>
|
||||
<back-button name="Redis" :header="$t('database.remoteDB')" />
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('database.createRemoteDB') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
<el-table-column show-overflow-tooltip :label="$t('commons.table.name')" prop="name" sortable />
|
||||
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
|
||||
<el-table-column :label="$t('commons.login.password')" prop="password">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<div class="star-center">
|
||||
<span v-if="!row.showPassword">**********</span>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="row.showPassword">
|
||||
{{ row.password }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="!row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = true"
|
||||
icon="View"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<el-button
|
||||
v-if="row.showPassword"
|
||||
link
|
||||
@click="row.showPassword = false"
|
||||
icon="Hide"
|
||||
class="ml-1.5"
|
||||
></el-button>
|
||||
<div>
|
||||
<CopyButton :content="row.password" type="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
:label="$t('commons.table.description')"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="170px"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<AppResources ref="checkRef"></AppResources>
|
||||
<OperateDialog ref="dialogRef" @search="search" />
|
||||
<DeleteDialog ref="deleteRef" @search="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
import AppResources from '@/views/database/redis/check/index.vue';
|
||||
import OperateDialog from '@/views/database/redis/remote/operate/index.vue';
|
||||
import DeleteDialog from '@/views/database/redis/remote/delete/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { Database } from '@/api/interface/database';
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const dialogRef = ref();
|
||||
const checkRef = ref();
|
||||
const deleteRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'redis-remote-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
orderBy: 'created_at',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
info: searchName.value,
|
||||
type: 'redis',
|
||||
orderBy: paginationConfig.orderBy,
|
||||
order: paginationConfig.order,
|
||||
};
|
||||
const res = await searchDatabases(params);
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const onOpenDialog = async (
|
||||
title: string,
|
||||
rowData: Partial<Database.DatabaseInfo> = {
|
||||
name: '',
|
||||
type: 'redis',
|
||||
from: 'remote',
|
||||
version: '7.2.x',
|
||||
address: '',
|
||||
port: 6379,
|
||||
username: '-',
|
||||
password: '',
|
||||
description: '',
|
||||
},
|
||||
) => {
|
||||
let params = {
|
||||
title,
|
||||
rowData: { ...rowData },
|
||||
};
|
||||
dialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const onDelete = async (row: Database.DatabaseInfo) => {
|
||||
const res = await deleteCheckDatabase(row.id);
|
||||
if (res.data && res.data.length > 0) {
|
||||
checkRef.value.acceptParams({ items: res.data });
|
||||
} else {
|
||||
deleteRef.value.acceptParams({
|
||||
id: row.id,
|
||||
database: row.name,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onOpenDialog('edit', row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: Database.DatabaseInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
174
frontend/src/views/database/redis/remote/operate/index.vue
Normal file
174
frontend/src/views/database/redis/remote/operate/index.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:hideResource="dialogData.title === 'create'"
|
||||
:header="title"
|
||||
:resource="dialogData.rowData?.name"
|
||||
:back="handleClose"
|
||||
/>
|
||||
</template>
|
||||
<el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input
|
||||
v-if="dialogData.title === 'create'"
|
||||
clearable
|
||||
v-model.trim="dialogData.rowData!.name"
|
||||
/>
|
||||
<el-tag v-else>{{ dialogData.rowData!.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.version')" prop="version">
|
||||
<el-radio-group v-model="dialogData.rowData!.version" @change="isOK = false">
|
||||
<el-radio label="6.x" value="6.x" />
|
||||
<el-radio label="7.x" value="7.x" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.address')" prop="address">
|
||||
<el-input @change="isOK = false" clearable v-model.trim="dialogData.rowData!.address" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.port')" prop="port">
|
||||
<el-input @change="isOK = false" clearable v-model.number="dialogData.rowData!.port" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input
|
||||
@change="isOK = false"
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model.trim="dialogData.rowData!.password"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.description" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button @click="onSubmit(formRef, 'check')">
|
||||
{{ $t('terminal.testConn') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :disabled="!isOK" @click="onSubmit(formRef, dialogData.title)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { addDatabase, checkDatabase, editDatabase } from '@/api/modules/database';
|
||||
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Database.DatabaseInfo;
|
||||
getTableList?: () => Promise<any>;
|
||||
}
|
||||
const title = ref<string>('');
|
||||
const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
const isOK = ref(false);
|
||||
const loading = ref();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
if (dialogData.value.rowData.version.startsWith('6.')) {
|
||||
dialogData.value.rowData.version = '6.x';
|
||||
}
|
||||
if (dialogData.value.rowData.version.startsWith('7.')) {
|
||||
dialogData.value.rowData.version = '7,x';
|
||||
}
|
||||
title.value = i18n.global.t('database.' + dialogData.value.title + 'RemoteDB');
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput],
|
||||
type: [Rules.requiredSelect],
|
||||
version: [Rules.requiredSelect],
|
||||
address: [Rules.ipV4V6OrDomain],
|
||||
port: [Rules.port],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined, operation: string) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
dialogData.value.rowData.from = 'remote';
|
||||
loading.value = true;
|
||||
dialogData.value.rowData.rootCert = dialogData.value.rowData.hasCA ? dialogData.value.rowData.rootCert : '';
|
||||
if (operation === 'check') {
|
||||
await checkDatabase(dialogData.value.rowData)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.data) {
|
||||
isOK.value = true;
|
||||
MsgSuccess(i18n.global.t('terminal.connTestOk'));
|
||||
} else {
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
});
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
await addDatabase(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
if (operation === 'edit') {
|
||||
await editDatabase(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
1
go.mod
1
go.mod
@ -19,6 +19,7 @@ require (
|
||||
github.com/go-acme/lego/v4 v4.15.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.1
|
||||
github.com/go-playground/validator/v10 v10.18.0
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
|
5
go.sum
5
go.sum
@ -328,6 +328,8 @@ github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiR
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
||||
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
@ -474,6 +476,7 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
@ -1256,6 +1259,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
@ -1265,6 +1269,7 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
|
||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
Loading…
Reference in New Issue
Block a user