mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5610c73d67 | ||
|
|
51fe02661f | ||
|
|
8f491b2933 | ||
|
|
b7e16bd6d5 | ||
|
|
38ca2ddd01 | ||
|
|
402b41100b | ||
|
|
766beae4e1 | ||
|
|
05f72e9dcf | ||
|
|
23553c68ae | ||
|
|
4dd1eb7a1e | ||
|
|
db307732c6 | ||
|
|
33d4ade1d4 | ||
|
|
186075ff9a | ||
|
|
34df745d1c | ||
|
|
ec363ac157 |
+1
-1
@@ -33,7 +33,7 @@ builds:
|
||||
- s390x
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: [ 'tar.gz' ]
|
||||
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
|
||||
@@ -61,7 +61,9 @@ Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for mo
|
||||
|
||||
## Pro Edition
|
||||
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, website tamper protection, website monitoring, GPU monitoring, custom logo and theme color, etc. [Click to view the detailed introduction of the Pro Edition](https://1panel.pro/pricing).
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, Website monitoring, Mobile APP, custom logo and theme, etc.
|
||||
|
||||
[Click to see Pro Edition details](https://1panel.pro/pricing).
|
||||
|
||||
## Security Information
|
||||
|
||||
|
||||
+68
-157
@@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
@@ -74,7 +73,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
@@ -96,193 +95,105 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
source := c.Query("source")
|
||||
var containerID string
|
||||
var initCmd string
|
||||
switch source {
|
||||
case "redis":
|
||||
containerID, initCmd, err = loadRedisInitCmd(c)
|
||||
case "ollama":
|
||||
containerID, initCmd, err = loadOllamaInitCmd(c)
|
||||
case "container":
|
||||
containerID, initCmd, err = loadContainerInitCmd(c)
|
||||
default:
|
||||
if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand("clear && " + initCmd)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, strings.ReplaceAll(initCmd, fmt.Sprintf("docker exec -it %s ", containerID), ""), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func loadRedisInitCmd(c *gin.Context) (string, string, error) {
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
commands := []string{"redis-cli"}
|
||||
commands := "redis-cli"
|
||||
database, err := databaseService.Get(name)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
return
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such database in db, err: %v", err)
|
||||
}
|
||||
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
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
|
||||
commands = "redis-cli -a " + database.Password + " --no-auth-warning"
|
||||
}
|
||||
} else {
|
||||
itemPort := fmt.Sprintf("%v", database.Port)
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort}
|
||||
commands = fmt.Sprintf("redis-cli -h %s -p %v", database.Address, database.Port)
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"}
|
||||
commands = fmt.Sprintf("redis-cli -h %s -p %v -a %s --no-auth-warning", database.Address, database.Port, database.Password)
|
||||
}
|
||||
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(name, strings.Join(commands, " "), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
return name, fmt.Sprintf("docker exec -it %s %s", name, commands), nil
|
||||
}
|
||||
|
||||
func (b *BaseApi) OllamaWsSsh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.System.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, string, error) {
|
||||
name := c.Query("name")
|
||||
if cmd.CheckIllegal(name) {
|
||||
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
|
||||
return
|
||||
}
|
||||
return "", "", fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
}
|
||||
container, err := service.LoadContainerName()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) {
|
||||
return
|
||||
}
|
||||
commands := []string{"ollama", "run", name}
|
||||
|
||||
pidMap := loadMapFromDockerTop(container)
|
||||
fmt.Println("pidMap")
|
||||
for k, v := range pidMap {
|
||||
fmt.Println(k, v)
|
||||
}
|
||||
itemCmds := append([]string{"exec", "-it", container}, commands...)
|
||||
slave, err := terminal.NewCommand(itemCmds)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(container, strings.Join(commands, " "), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
containerName := ollamaInfo.ContainerName
|
||||
return containerName, fmt.Sprintf("docker exec -it %s ollama run %s", containerName, name), nil
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.System.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func loadContainerInitCmd(c *gin.Context) (string, string, error) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
|
||||
cmds := []string{"exec", containerID, command}
|
||||
if len(user) != 0 {
|
||||
cmds = []string{"exec", "-u", user, containerID, command}
|
||||
}
|
||||
if cmd.CheckIllegal(user, containerID, command) {
|
||||
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
|
||||
return
|
||||
}
|
||||
return "", "", fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
}
|
||||
stdout, err := cmd.ExecWithCheck("docker", cmds...)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
|
||||
return
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
return "", "", fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
}
|
||||
|
||||
commands := []string{"exec", "-it", containerID, command}
|
||||
command = fmt.Sprintf("docker exec -it %s %s", containerID, command)
|
||||
if len(user) != 0 {
|
||||
commands = []string{"exec", "-it", "-u", user, containerID, command}
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand(commands)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, command, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
command = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command)
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
return containerID, command, nil
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
|
||||
@@ -71,6 +71,7 @@ type SettingInfo struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
IpWhiteList string `json:"ipWhiteList"`
|
||||
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
|
||||
LicenseVerify string `json:"licenseVerify"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
||||
@@ -255,14 +255,14 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []dto.ContainerListStats
|
||||
datas := make([]dto.ContainerListStats, len(list))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(list))
|
||||
for i := 0; i < len(list); i++ {
|
||||
go func(item types.Container) {
|
||||
datas = append(datas, loadCpuAndMem(client, item.ID))
|
||||
go func(index int, item types.Container) {
|
||||
datas[index] = loadCpuAndMem(client, item.ID)
|
||||
wg.Done()
|
||||
}(list[i])
|
||||
}(i, list[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
@@ -937,15 +937,27 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
|
||||
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
|
||||
memPercent := 0.0
|
||||
memUsage := float64(memStats.Usage)
|
||||
memLimit := float64(memStats.Limit)
|
||||
memUsage := calculateMemUsageUnixNoCache(memStats)
|
||||
memLimit := memStats.Limit
|
||||
if memUsage > 0.0 && memLimit > 0.0 {
|
||||
memPercent = (memUsage / memLimit) * 100.0
|
||||
memPercent = (float64(memUsage) / float64(memLimit)) * 100.0
|
||||
}
|
||||
return memPercent
|
||||
}
|
||||
|
||||
func calculateMemUsageUnixNoCache(mem types.MemoryStats) uint64 {
|
||||
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
|
||||
return mem.Usage - v
|
||||
}
|
||||
if v := mem.Stats["inactive_file"]; v < mem.Usage {
|
||||
return mem.Usage - v
|
||||
}
|
||||
return mem.Usage
|
||||
}
|
||||
|
||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
|
||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||
switch strings.ToLower(bioEntry.Op) {
|
||||
@@ -1063,7 +1075,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
|
||||
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)
|
||||
|
||||
data.MemoryCache = stats.MemoryStats.Stats["cache"]
|
||||
data.MemoryUsage = stats.MemoryStats.Usage
|
||||
data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats)
|
||||
data.MemoryLimit = stats.MemoryStats.Limit
|
||||
|
||||
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
|
||||
@@ -1183,9 +1195,10 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
for _, volume := range req.Volumes {
|
||||
if volume.Type == "volume" {
|
||||
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
ReadOnly: volume.Mode == "ro",
|
||||
})
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
} else {
|
||||
|
||||
@@ -23,6 +23,7 @@ type System struct {
|
||||
Language string `mapstructure:"language"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
IsIntl bool `mapstructure:"is_intl"`
|
||||
LicenseVerify string `mapstructure:"license_verify"`
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
ChangeUserInfo string `mapstructure:"change_user_info"`
|
||||
OneDriveID string `mapstructure:"one_drive_id"`
|
||||
|
||||
@@ -84,6 +84,15 @@ func Init() {
|
||||
global.CONF.System.ApiKeyValidityTime = apiKeyValidityTimeSetting.Value
|
||||
}
|
||||
|
||||
if global.CONF.System.LicenseVerify == "" {
|
||||
licenseVerify, err := settingRepo.Get(settingRepo.WithByKey("LicenseVerify"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service license verify from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.LicenseVerify = licenseVerify.Value
|
||||
}
|
||||
handleLicenseVerify(global.CONF.System.LicenseVerify, settingRepo)
|
||||
|
||||
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
||||
|
||||
handleCronjobStatus()
|
||||
@@ -207,6 +216,12 @@ func loadLocalDir() {
|
||||
global.LOG.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
|
||||
func handleLicenseVerify(licenseVerify string, settingRepo repo.ISettingRepo) {
|
||||
if err := settingRepo.Update("LicenseVerify", licenseVerify); err != nil {
|
||||
global.LOG.Fatalf("init license verify before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
||||
if len(tags) == 0 {
|
||||
return
|
||||
|
||||
@@ -105,6 +105,7 @@ func Init() {
|
||||
migrations.AddOllamaModel,
|
||||
migrations.AddAppMenu,
|
||||
migrations.AddAppPanelName,
|
||||
migrations.AddLicenseVerify,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -448,3 +448,14 @@ var AddAppPanelName = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddLicenseVerify = &gormigrate.Migration{
|
||||
ID: "20250226-add-license-verify",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
if err := tx.Create(&model.Setting{Key: "LicenseVerify", Value: "LX"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -188,7 +188,12 @@ func Routers() *gin.Engine {
|
||||
|
||||
swaggerRouter := Router.Group("1panel")
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
swaggerRouter.GET("/swagger/*any", func(c *gin.Context) {
|
||||
if !checkSession(c) {
|
||||
handleNoRoute(c)
|
||||
return
|
||||
}
|
||||
}, ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
PublicGroup := Router.Group("")
|
||||
{
|
||||
PublicGroup.GET("/health", func(c *gin.Context) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package viper
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
@@ -103,6 +104,7 @@ func Init() {
|
||||
global.CONF.System.Entrance = entrance
|
||||
global.CONF.System.Language = language
|
||||
global.CONF.System.ChangeUserInfo = loadChangeInfo()
|
||||
global.CONF.System.LicenseVerify = os.Getenv("LXWARE_LICENSE_VERIFY")
|
||||
global.Viper = v
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh)
|
||||
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
|
||||
|
||||
@@ -15,7 +15,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
baRouter.GET("/exec", baseApi.ContainerWsSsh)
|
||||
baRouter.GET("/exec", baseApi.ContainerWsSSH)
|
||||
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||
|
||||
baRouter.POST("", baseApi.ContainerCreate)
|
||||
|
||||
@@ -38,7 +38,6 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf)
|
||||
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)
|
||||
|
||||
@@ -47,9 +47,9 @@ func Start() {
|
||||
session.Init()
|
||||
gin.SetMode(gin.DebugMode)
|
||||
cron.Run()
|
||||
hook.Init()
|
||||
InitOthers()
|
||||
business.Init()
|
||||
hook.Init()
|
||||
|
||||
rootRouter := router.Routers()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
cosSDK "github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
@@ -18,6 +19,7 @@ type cosClient struct {
|
||||
|
||||
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
region := loadParamFromVars("region", vars)
|
||||
endpoint := loadParamFromVars("endpoint", vars)
|
||||
accessKey := loadParamFromVars("accessKey", vars)
|
||||
secretKey := loadParamFromVars("secretKey", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
@@ -26,7 +28,16 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
scType = "Standard"
|
||||
}
|
||||
|
||||
u, _ := url.Parse(fmt.Sprintf("https://cos.%s.myqcloud.com", region))
|
||||
// 允许使用对象存储双栈域名
|
||||
endpointType := "cos"
|
||||
if len(endpoint) != 0 {
|
||||
re := regexp.MustCompile(`.*cos-dualstack\..*`)
|
||||
if re.MatchString(endpoint) {
|
||||
endpointType = "cos-dualstack"
|
||||
}
|
||||
}
|
||||
|
||||
u, _ := url.Parse(fmt.Sprintf("https://%s.%s.myqcloud.com", endpointType, region))
|
||||
b := &cosSDK.BaseURL{BucketURL: u}
|
||||
client := cosSDK.NewClient(b, &http.Client{
|
||||
Transport: &cosSDK.AuthorizationTransport{
|
||||
@@ -36,7 +47,7 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
})
|
||||
|
||||
if len(bucket) != 0 {
|
||||
u2, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucket, region))
|
||||
u2, _ := url.Parse(fmt.Sprintf("https://%s.%s.%s.myqcloud.com", bucket, endpointType, region))
|
||||
b2 := &cosSDK.BaseURL{BucketURL: u2}
|
||||
clientWithBucket := cosSDK.NewClient(b2, &http.Client{
|
||||
Transport: &cosSDK.AuthorizationTransport{
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
@@ -82,58 +79,6 @@ func (c *ConnInfo) Close() {
|
||||
_ = c.Client.Close()
|
||||
}
|
||||
|
||||
type SshConn struct {
|
||||
StdinPipe io.WriteCloser
|
||||
ComboOutput *wsBufferWriter
|
||||
Session *gossh.Session
|
||||
}
|
||||
|
||||
func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) {
|
||||
sshSession, err := c.Client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdinP, err := sshSession.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comboWriter := new(wsBufferWriter)
|
||||
sshSession.Stdout = comboWriter
|
||||
sshSession.Stderr = comboWriter
|
||||
|
||||
modes := gossh.TerminalModes{
|
||||
gossh.ECHO: 1,
|
||||
gossh.TTY_OP_ISPEED: 14400,
|
||||
gossh.TTY_OP_OSPEED: 14400,
|
||||
}
|
||||
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := sshSession.Shell(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil
|
||||
}
|
||||
|
||||
func (s *SshConn) Close() {
|
||||
if s.Session != nil {
|
||||
s.Session.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type wsBufferWriter struct {
|
||||
buffer bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *wsBufferWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.buffer.Write(p)
|
||||
}
|
||||
|
||||
func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) {
|
||||
if len(passPhrase) != 0 {
|
||||
return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase)
|
||||
|
||||
@@ -25,14 +25,24 @@ type LocalCommand struct {
|
||||
pty *os.File
|
||||
}
|
||||
|
||||
func NewCommand(commands []string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("docker", commands...)
|
||||
func NewCommand(initCmd string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("bash")
|
||||
if term := os.Getenv("TERM"); term != "" {
|
||||
cmd.Env = append(os.Environ(), "TERM="+term)
|
||||
} else {
|
||||
cmd.Env = append(os.Environ(), "TERM=xterm")
|
||||
}
|
||||
|
||||
pty, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to start command")
|
||||
}
|
||||
|
||||
if len(initCmd) != 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, _ = pty.Write([]byte(initCmd + "\n"))
|
||||
}
|
||||
|
||||
lcmd := &LocalCommand{
|
||||
closeSignal: DefaultCloseSignal,
|
||||
closeTimeout: DefaultCloseTimeout,
|
||||
|
||||
@@ -53,6 +53,11 @@ func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) {
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) masterWrite(data []byte) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r)
|
||||
}
|
||||
}()
|
||||
sws.writeMutex.Lock()
|
||||
defer sws.writeMutex.Unlock()
|
||||
wsData, err := json.Marshal(WsMsg{
|
||||
@@ -72,6 +77,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error {
|
||||
func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
setQuit(exitCh)
|
||||
global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -5,7 +5,8 @@ system:
|
||||
repo_url: https://resource.1panel.pro
|
||||
app_repo: https://apps.1panel.pro
|
||||
is_demo: false
|
||||
port: 8888
|
||||
is_intl: true
|
||||
port: 9999
|
||||
username: admin
|
||||
password: admin123
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export namespace Setting {
|
||||
apiKey: string;
|
||||
ipWhiteList: string;
|
||||
apiKeyValidityTime: number;
|
||||
licenseVerify: string;
|
||||
}
|
||||
export interface SettingUpdate {
|
||||
key: string;
|
||||
|
||||
@@ -123,8 +123,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: 'No data available',
|
||||
disConn:
|
||||
'Please click the disconnect button directly to terminate the terminal connection, avoiding the use of exit commands like {0}.',
|
||||
delete: `This operation delete can't be undone. Do you want to continue?`,
|
||||
clean: `This operation clean can't be undone. Do you want to continue?`,
|
||||
deleteTitle: 'Delete',
|
||||
|
||||
@@ -122,8 +122,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: '利用可能なデータはありません',
|
||||
disConn:
|
||||
'端末接続を切断するには、{0} のような終了コマンドを使用せずに、直接切断ボタンをクリックしてください',
|
||||
delete: `この操作削除は元に戻すことはできません。続けたいですか?`,
|
||||
clean: `この操作は取り消すことはできません。続けたいですか?`,
|
||||
deleteTitle: '消去',
|
||||
|
||||
@@ -123,8 +123,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: '데이터가 없습니다',
|
||||
disConn:
|
||||
'종료 명령어인 {0} 등을 사용하지 않고 직접 연결 끊기 버튼을 클릭하여 터미널 연결을 종료해 주십시오.',
|
||||
delete: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
|
||||
clean: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`,
|
||||
deleteTitle: '삭제',
|
||||
|
||||
@@ -123,8 +123,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: 'Tiada data tersedia',
|
||||
disConn:
|
||||
'Sila klik butang putus sambungan secara langsung untuk menamatkan sambungan terminal, mengelakkan penggunaan arahan keluar seperti {0}.',
|
||||
delete: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
|
||||
clean: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?',
|
||||
deleteTitle: 'Padam',
|
||||
|
||||
@@ -123,8 +123,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: 'Nenhum dado disponível',
|
||||
disConn:
|
||||
'Por favor, clique diretamente no botão de desconexão para encerrar a conexão do terminal, evitando o uso de comandos de saída como {0}.',
|
||||
delete: 'Esta operação de exclusão não pode ser desfeita. Deseja continuar?',
|
||||
clean: 'Esta operação de limpeza não pode ser desfeita. Deseja continuar?',
|
||||
deleteTitle: 'Excluir',
|
||||
|
||||
@@ -124,8 +124,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: 'Нет данных',
|
||||
disConn:
|
||||
'Пожалуйста, нажмите кнопку отключения, чтобы разорвать соединение с терминалом, избегая использования команд выхода, таких как {0}.',
|
||||
delete: 'Эта операция удаления не может быть отменена. Хотите продолжить?',
|
||||
clean: 'Эта операция очистки не может быть отменена. Хотите продолжить?',
|
||||
deleteTitle: 'Удалить',
|
||||
|
||||
@@ -123,7 +123,6 @@ const message = {
|
||||
},
|
||||
msg: {
|
||||
noneData: '暫無資料',
|
||||
disConn: '請直接點選斷開按鈕斷開終端連接,避免使用 {0} 等退出指令。',
|
||||
delete: '刪除 操作不可復原,是否繼續?',
|
||||
clean: '清空 操作不可復原,是否繼續?',
|
||||
deleteTitle: '刪除',
|
||||
|
||||
@@ -121,7 +121,6 @@ const message = {
|
||||
Rollbacking: '快照回滚中,请稍候...',
|
||||
},
|
||||
msg: {
|
||||
disConn: '请直接点击断开按钮断开终端连接,避免使用 {0} 等退出命令',
|
||||
noneData: '暂无数据',
|
||||
delete: '删除 操作不可回滚,是否继续?',
|
||||
clean: '清空 操作不可回滚,是否继续?',
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface GlobalState {
|
||||
isIntl: boolean;
|
||||
isTrial: boolean;
|
||||
productProExpires: number;
|
||||
licenseVerify: string;
|
||||
|
||||
errStatus: string;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ const GlobalStore = defineStore({
|
||||
isIntl: false,
|
||||
isTrial: false,
|
||||
productProExpires: 0,
|
||||
licenseVerify: '',
|
||||
|
||||
errStatus: '',
|
||||
}),
|
||||
|
||||
@@ -16,12 +16,7 @@
|
||||
</template>
|
||||
</DrawerHeader>
|
||||
</template>
|
||||
<el-alert type="error" :closable="false">
|
||||
<template #title>
|
||||
<span>{{ $t('commons.msg.disConn', ['/bye exit']) }}</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<Terminal class="mt-2" style="height: calc(100vh - 225px)" ref="terminalRef"></Terminal>
|
||||
<Terminal class="mt-2" style="height: calc(100vh - 175px)" ref="terminalRef"></Terminal>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -67,8 +62,8 @@ const loadTooltip = () => {
|
||||
const initTerm = () => {
|
||||
nextTick(() => {
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v1/ai/ollama/exec',
|
||||
args: `name=${itemName.value}`,
|
||||
endpoint: '/api/v1/containers/exec',
|
||||
args: `source=ollama&name=${itemName.value}`,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
});
|
||||
|
||||
@@ -10,12 +10,7 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('container.containerTerminal')" :resource="title" :back="handleClose" />
|
||||
</template>
|
||||
<el-alert type="error" :closable="false">
|
||||
<template #title>
|
||||
<span>{{ $t('commons.msg.disConn', ['exit']) }}</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form ref="formRef" class="mt-2" :model="form" label-position="top">
|
||||
<el-form ref="formRef" :model="form" label-position="top">
|
||||
<el-form-item :label="$t('commons.table.user')" prop="user">
|
||||
<el-input placeholder="root" clearable v-model="form.user" />
|
||||
</el-form-item>
|
||||
@@ -51,7 +46,7 @@
|
||||
</el-button>
|
||||
<el-button v-else @click="onClose()">{{ $t('commons.button.disconnect') }}</el-button>
|
||||
<Terminal
|
||||
style="height: calc(100vh - 355px); margin-top: 18px"
|
||||
style="height: calc(100vh - 312px); margin-top: 18px"
|
||||
ref="terminalRef"
|
||||
v-if="terminalOpen"
|
||||
></Terminal>
|
||||
@@ -104,7 +99,7 @@ const initTerm = (formEl: FormInstance | undefined) => {
|
||||
await nextTick();
|
||||
terminalRef.value!.acceptParams({
|
||||
endpoint: '/api/v1/containers/exec',
|
||||
args: `containerid=${form.containerID}&user=${form.user}&command=${form.command}`,
|
||||
args: `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}`,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
});
|
||||
|
||||
@@ -309,8 +309,8 @@ const initTerminal = async () => {
|
||||
terminalShow.value = true;
|
||||
redisStatus.value = 'Running';
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v1/databases/redis/exec',
|
||||
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
endpoint: '/api/v1/containers/exec',
|
||||
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
});
|
||||
@@ -327,8 +327,8 @@ const initTerminal = async () => {
|
||||
if (res.data.status === 'Running') {
|
||||
terminalShow.value = true;
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v1/databases/redis/exec',
|
||||
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
endpoint: '/api/v1/containers/exec',
|
||||
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
error: '',
|
||||
initCmd: '',
|
||||
});
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
>
|
||||
<template #header>
|
||||
<div ref="dialogHeader" class="flex items-center justify-between">
|
||||
<span>{{ $t('commons.button.edit') + ' - ' + form.path }}</span>
|
||||
<el-space alignment="center" :size="1" class="dialog-header-icon">
|
||||
<div class="truncate flex-1 mr-2">
|
||||
<span>{{ $t('commons.button.edit') + ' - ' + form.path }}</span>
|
||||
</div>
|
||||
<el-space alignment="center" :size="1" class="dialog-header-icon flex-shrink-0">
|
||||
<el-tooltip :content="loadTooltip()" placement="top">
|
||||
<el-button
|
||||
@click="toggleFullscreen"
|
||||
|
||||
@@ -6,15 +6,47 @@
|
||||
:before-close="handleClose"
|
||||
:title="$t('file.downloadProcess')"
|
||||
>
|
||||
<div v-for="(value, index) in res" :key="index">
|
||||
<span>{{ value['percent'] === 100 ? $t('file.downloadSuccess') : $t('file.downloading') }}</span>
|
||||
<MsgInfo :info="value['name']" width="250" />
|
||||
<el-progress v-if="value['total'] == 0" :percentage="100" :indeterminate="true" :duration="1" />
|
||||
<el-progress v-else :text-inside="true" :stroke-width="15" :percentage="value['percent']"></el-progress>
|
||||
<span>
|
||||
{{ getFileSize(value['written']) }}/
|
||||
<span v-if="value['total'] > 0">{{ getFileSize(value['total']) }}</span>
|
||||
</span>
|
||||
<div class="space-y-4 p-4">
|
||||
<div
|
||||
v-for="(value, index) in res"
|
||||
:key="index"
|
||||
class="bg-white rounded-lg p-4 shadow-sm border border-gray-100 transition-all duration-200 hover:shadow-md"
|
||||
:class="{ completed: value.percent === 100 }"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-1">
|
||||
<MsgInfo :info="value.name" class="text-gray-700" />
|
||||
<div class="text-gray-500">
|
||||
{{ value.percent === 100 ? $t('file.downloadSuccess') : $t('file.downloading') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-end text-gray-500 mb-1">
|
||||
<span>{{ getFileSize(value.written) }}</span>
|
||||
<span v-if="value.total > 0" class="text-gray-400">/{{ getFileSize(value.total) }}</span>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<el-progress
|
||||
v-if="value.total === 0 && value.percent != 100"
|
||||
:percentage="100"
|
||||
:indeterminate="true"
|
||||
:duration="1"
|
||||
class="progress-bar"
|
||||
:stroke-width="8"
|
||||
:show-text="false"
|
||||
/>
|
||||
<el-progress
|
||||
v-else
|
||||
:percentage="value.percent"
|
||||
:stroke-width="8"
|
||||
class="progress-bar"
|
||||
:status="value.percent === 100 ? 'success' : ''"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -108,3 +140,30 @@ const onOpen = () => {
|
||||
getKeys();
|
||||
};
|
||||
</script>
|
||||
<style type="scss" scoped>
|
||||
.download-item.completed {
|
||||
@apply bg-green-50/50;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
:deep(.el-progress-bar__outer) {
|
||||
@apply rounded-full bg-gray-100;
|
||||
}
|
||||
|
||||
:deep(.el-progress-bar__inner) {
|
||||
@apply rounded-full transition-all duration-300;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-10%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(0);
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -412,6 +412,7 @@ const loadDataFromDB = async () => {
|
||||
i18n.locale.value = res.data.language;
|
||||
i18n.warnHtmlMessage = false;
|
||||
globalStore.entrance = res.data.securityEntrance;
|
||||
globalStore.licenseVerify = res.data.licenseVerify;
|
||||
globalStore.setDefaultNetwork(res.data.defaultNetwork);
|
||||
globalStore.setOpenMenuTabs(res.data.menuTabs === 'enable');
|
||||
globalStore.updateLanguage(res.data.language);
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<el-col :xs="24" :sm="24" :md="9" :lg="9" :xl="9">
|
||||
<CardWithHeader :header="$t('license.quickUpdate')" height="160px">
|
||||
<template #body>
|
||||
<div class="h-app-card">
|
||||
<div class="h-app-card" v-if="globalStore.licenseVerify != 'TC' || !hasLicense">
|
||||
<el-row>
|
||||
<el-col :span="15">
|
||||
<div class="h-app-content">{{ $t('license.importLicense') }}</div>
|
||||
|
||||
@@ -84,7 +84,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
await updateFail2ban({ key: 'logPath', value: form.logPath })
|
||||
await updateFail2ban({ key: 'logpath', value: form.logPath })
|
||||
.then(async () => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loading.value = false;
|
||||
|
||||
@@ -58,6 +58,7 @@ require (
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/term v0.28.0
|
||||
golang.org/x/text v0.21.0
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.7
|
||||
@@ -218,7 +219,7 @@ require (
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1105 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
@@ -258,7 +259,6 @@ require (
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
|
||||
@@ -961,8 +961,9 @@ github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRd
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 h1:krcqtAmexnHHBm/4ge4tr2b1cn/a7JGBESVGoZYXQAE=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1105 h1:lCs0dmezU6/8JcfNwEaam1Pm1RS/5MhXvNhf/X2y65s=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1105/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
|
||||
Reference in New Issue
Block a user