mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
chore: optimize runtime environment container configuration (#13071)
* chore: optimize runtime environment container configuration * chore: optimize runtime environment container configuration
This commit is contained in:
@@ -42,12 +42,14 @@ type Environment struct {
|
||||
type Volume struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
type ExposedPort struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
HostIP string `json:"hostIP"`
|
||||
Protocol string `json:"protocol"`
|
||||
}
|
||||
|
||||
type ExtraHost struct {
|
||||
|
||||
@@ -89,7 +89,15 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func isPortInUse(port int, protocol string) bool {
|
||||
return common.ScanPortWithProto(port, normalizeComposeProtocol(protocol))
|
||||
}
|
||||
|
||||
func checkPortExist(port int) error {
|
||||
return checkPortExistWithProtocol(port, "")
|
||||
}
|
||||
|
||||
func checkPortExistWithProtocol(port int, protocol string) error {
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["port"] = port
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||
@@ -110,7 +118,7 @@ func checkPortExist(port int) error {
|
||||
errMap["name"] = domain.Domain
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
if common.ScanPort(port) {
|
||||
if isPortInUse(port, protocol) {
|
||||
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||
}
|
||||
return nil
|
||||
|
||||
84
agent/app/service/docker_compose_config.go
Normal file
84
agent/app/service/docker_compose_config.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
)
|
||||
|
||||
func composePortEnvKeys(index int) (containerPort, hostPort, hostIP, protocol string) {
|
||||
return fmt.Sprintf("CONTAINER_PORT_%d", index),
|
||||
fmt.Sprintf("HOST_PORT_%d", index),
|
||||
fmt.Sprintf("HOST_IP_%d", index),
|
||||
fmt.Sprintf("PORT_PROTOCOL_%d", index)
|
||||
}
|
||||
|
||||
func isComposePortEnvKey(key string) bool {
|
||||
return strings.HasPrefix(key, "CONTAINER_PORT_") ||
|
||||
strings.HasPrefix(key, "HOST_PORT_") ||
|
||||
strings.HasPrefix(key, "HOST_IP_") ||
|
||||
strings.HasPrefix(key, "PORT_PROTOCOL_")
|
||||
}
|
||||
|
||||
func formatComposePortMapping(hostIP, hostPort, containerPort, protocol string) string {
|
||||
return fmt.Sprintf("${%s}:${%s}:${%s}/%s", hostIP, hostPort, containerPort, normalizeComposeProtocol(protocol))
|
||||
}
|
||||
|
||||
func normalizeComposeProtocol(protocol string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(protocol)) {
|
||||
case "udp":
|
||||
return "udp"
|
||||
default:
|
||||
return "tcp"
|
||||
}
|
||||
}
|
||||
|
||||
func formatComposeVolume(source, target, mode string) string {
|
||||
return fmt.Sprintf("%s:%s:%s", source, target, normalizeComposeVolumeMode(mode))
|
||||
}
|
||||
|
||||
func normalizeComposeVolumeMode(mode string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case "ro":
|
||||
return "ro"
|
||||
default:
|
||||
return "rw"
|
||||
}
|
||||
}
|
||||
|
||||
func loadComposeExposedPortsFromEnv(envs map[string]string, defaultHostIP string, strict bool) ([]request.ExposedPort, error) {
|
||||
var ports []request.ExposedPort
|
||||
for key, value := range envs {
|
||||
if !strings.HasPrefix(key, "CONTAINER_PORT_") {
|
||||
continue
|
||||
}
|
||||
index := strings.TrimPrefix(key, "CONTAINER_PORT_")
|
||||
containerPort, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
if strict {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
hostPort, err := strconv.Atoi(envs["HOST_PORT_"+index])
|
||||
if err != nil {
|
||||
if strict {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
hostIP := envs["HOST_IP_"+index]
|
||||
if hostIP == "" {
|
||||
hostIP = defaultHostIP
|
||||
}
|
||||
ports = append(ports, request.ExposedPort{
|
||||
ContainerPort: containerPort,
|
||||
HostPort: hostPort,
|
||||
HostIP: hostIP,
|
||||
Protocol: normalizeComposeProtocol(envs["PORT_PROTOCOL_"+index]),
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
@@ -792,9 +792,14 @@ func loadMcpServerComposeConfig(serverDTO *response.McpServerDTO) error {
|
||||
}
|
||||
if service.Volumes != nil {
|
||||
for _, volume := range service.Volumes {
|
||||
mode := "rw"
|
||||
if volume.ReadOnly {
|
||||
mode = "ro"
|
||||
}
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{
|
||||
Source: volume.Source,
|
||||
Target: volume.Target,
|
||||
Mode: mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -850,6 +855,9 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
|
||||
normalizeMcpServerGateway(mcpServer)
|
||||
serviceValue["command"] = buildSupergatewayCommand(mcpServer)
|
||||
serviceValue["image"] = mcpServer.GatewayImage
|
||||
serviceValue["ports"] = []string{
|
||||
formatComposePortMapping("HOST_IP", "PANEL_APP_PORT_HTTP", "PANEL_APP_PORT_HTTP", ""),
|
||||
}
|
||||
oldEnv, hasOldEnv := serviceValue["environment"]
|
||||
delete(serviceValue, "environment")
|
||||
if environments != nil && len(environments) > 0 {
|
||||
@@ -869,7 +877,7 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
|
||||
if volumes != nil && len(volumes) > 0 {
|
||||
volumeList := make([]string, 0)
|
||||
for _, volume := range volumes {
|
||||
volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
volumeList = append(volumeList, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
|
||||
}
|
||||
serviceValue["volumes"] = volumeList
|
||||
} else if volumes == nil {
|
||||
|
||||
@@ -137,7 +137,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
|
||||
create.Install = true
|
||||
for _, export := range create.ExposedPorts {
|
||||
hostPorts = append(hostPorts, strconv.Itoa(export.HostPort))
|
||||
if err := checkPortExist(export.HostPort); err != nil {
|
||||
if err := checkPortExistWithProtocol(export.HostPort, export.Protocol); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -241,30 +241,11 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
|
||||
runtimeDTO.AppID = detail.AppId
|
||||
}
|
||||
for k, v := range envs {
|
||||
runtimeDTO.Params[k] = v
|
||||
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
|
||||
if strings.Contains(k, "CONTAINER_PORT") {
|
||||
matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k)
|
||||
if len(matches) < 2 {
|
||||
continue
|
||||
}
|
||||
containerPort, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
|
||||
runtimeDTO.ExposedPorts = append(runtimeDTO.ExposedPorts, request.ExposedPort{
|
||||
ContainerPort: containerPort,
|
||||
HostPort: hostPort,
|
||||
HostIP: hostIP,
|
||||
})
|
||||
}
|
||||
if !isComposePortEnvKey(k) {
|
||||
runtimeDTO.Params[k] = v
|
||||
}
|
||||
}
|
||||
runtimeDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "", false)
|
||||
res = append(res, runtimeDTO)
|
||||
}
|
||||
return total, res, nil
|
||||
@@ -435,9 +416,11 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
|
||||
return buserr.New("ErrImageExist")
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
|
||||
ownedPortKeys := runtimeOwnedPortKeys(runtime.Env)
|
||||
for _, export := range req.ExposedPorts {
|
||||
hostPorts = append(hostPorts, strconv.Itoa(export.HostPort))
|
||||
if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil {
|
||||
_, owned := ownedPortKeys[composePortCheckKey(export.HostPort, export.Protocol)]
|
||||
if err = checkRuntimePortExistWithProtocol(export.HostPort, export.Protocol, !owned, runtime.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1058,6 +1041,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
|
||||
var (
|
||||
composeContent []byte
|
||||
)
|
||||
ownedPortKeys := runtimeOwnedPortKeys(runtime.Env)
|
||||
for _, export := range req.ExposedPorts {
|
||||
if strconv.Itoa(export.HostPort) == runtime.Port {
|
||||
return buserr.WithName("ErrPHPRuntimePortFailed", strconv.Itoa(export.HostPort))
|
||||
@@ -1065,7 +1049,8 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
|
||||
if export.ContainerPort == 9000 {
|
||||
return buserr.New("ErrPHPPortIsDefault")
|
||||
}
|
||||
if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil {
|
||||
_, owned := ownedPortKeys[composePortCheckKey(export.HostPort, export.Protocol)]
|
||||
if err = checkRuntimePortExistWithProtocol(export.HostPort, export.Protocol, !owned, runtime.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1090,7 +1075,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
|
||||
return err
|
||||
}
|
||||
for k := range envs {
|
||||
if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") {
|
||||
if isComposePortEnvKey(k) || strings.Contains(k, "APP_PORT") {
|
||||
delete(envs, k)
|
||||
}
|
||||
}
|
||||
@@ -1102,6 +1087,7 @@ func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) erro
|
||||
ExposedPorts: req.ExposedPorts,
|
||||
Environments: req.Environments,
|
||||
Volumes: req.Volumes,
|
||||
ExtraHosts: req.ExtraHosts,
|
||||
},
|
||||
}
|
||||
composeContent, err = handleCompose(envs, composeContent, create, projectDir)
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
@@ -33,7 +32,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -464,7 +462,7 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
|
||||
return
|
||||
}
|
||||
for k := range env {
|
||||
if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") {
|
||||
if isComposePortEnvKey(k) || strings.Contains(k, "APP_PORT") {
|
||||
delete(env, k)
|
||||
}
|
||||
}
|
||||
@@ -621,15 +619,16 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
|
||||
if len(create.ExposedPorts) > 0 {
|
||||
var ports []interface{}
|
||||
for i, port := range create.ExposedPorts {
|
||||
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
|
||||
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
|
||||
containerPortStr, hostPortStr, hostIPStr, protocolStr := composePortEnvKeys(i)
|
||||
existMap[containerPortStr] = struct{}{}
|
||||
existMap[hostPortStr] = struct{}{}
|
||||
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
|
||||
ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr))
|
||||
existMap[hostIPStr] = struct{}{}
|
||||
existMap[protocolStr] = struct{}{}
|
||||
ports = append(ports, formatComposePortMapping(hostIPStr, hostPortStr, containerPortStr, port.Protocol))
|
||||
create.Params[containerPortStr] = port.ContainerPort
|
||||
create.Params[hostPortStr] = port.HostPort
|
||||
create.Params[hostIPStr] = port.HostIP
|
||||
create.Params[protocolStr] = normalizeComposeProtocol(port.Protocol)
|
||||
}
|
||||
if create.Type == constant.RuntimePHP {
|
||||
ports = append(ports, "127.0.0.1:${PANEL_APP_PORT_HTTP}:9000")
|
||||
@@ -662,7 +661,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", k, v))
|
||||
}
|
||||
for _, volume := range create.Volumes {
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
volumes = append(volumes, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
|
||||
}
|
||||
|
||||
var extraHosts []interface{}
|
||||
@@ -678,7 +677,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
|
||||
break
|
||||
}
|
||||
for k := range env {
|
||||
if strings.Contains(k, "CONTAINER_PORT_") || strings.Contains(k, "HOST_PORT_") {
|
||||
if isComposePortEnvKey(k) {
|
||||
if _, ok := existMap[k]; !ok {
|
||||
delete(env, k)
|
||||
}
|
||||
@@ -872,9 +871,14 @@ func getDockerComposeVolumes(yml []byte) ([]request.Volume, error) {
|
||||
if len(envArray) > 1 {
|
||||
target = envArray[1]
|
||||
}
|
||||
mode := ""
|
||||
if len(envArray) > 2 {
|
||||
mode = envArray[2]
|
||||
}
|
||||
res = append(res, request.Volume{
|
||||
Source: source,
|
||||
Target: target,
|
||||
Mode: normalizeComposeVolumeMode(mode),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -908,7 +912,31 @@ func getDockerComposeExtraHosts(yml []byte) ([]request.ExtraHost, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func composePortCheckKey(port int, protocol string) string {
|
||||
return fmt.Sprintf("%d/%s", port, normalizeComposeProtocol(protocol))
|
||||
}
|
||||
|
||||
func runtimeOwnedPortKeys(envStr string) map[string]struct{} {
|
||||
portKeys := make(map[string]struct{})
|
||||
envs, err := gotenv.Unmarshal(envStr)
|
||||
if err != nil {
|
||||
return portKeys
|
||||
}
|
||||
exposedPorts, err := loadComposeExposedPortsFromEnv(envs, "", false)
|
||||
if err != nil {
|
||||
return portKeys
|
||||
}
|
||||
for _, port := range exposedPorts {
|
||||
portKeys[composePortCheckKey(port.HostPort, port.Protocol)] = struct{}{}
|
||||
}
|
||||
return portKeys
|
||||
}
|
||||
|
||||
func checkRuntimePortExist(port int, scanPort bool, runtimeID uint) error {
|
||||
return checkRuntimePortExistWithProtocol(port, "", scanPort, runtimeID)
|
||||
}
|
||||
|
||||
func checkRuntimePortExistWithProtocol(port int, protocol string, scanPort bool, runtimeID uint) error {
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["port"] = port
|
||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
|
||||
@@ -933,7 +961,7 @@ func checkRuntimePortExist(port int, scanPort bool, runtimeID uint) error {
|
||||
errMap["name"] = domain.Domain
|
||||
return buserr.WithMap("ErrPortExist", errMap, nil)
|
||||
}
|
||||
if scanPort && common.ScanPort(port) {
|
||||
if scanPort && isPortInUse(port, protocol) {
|
||||
return buserr.WithDetail("ErrPortInUsed", port, nil)
|
||||
}
|
||||
return nil
|
||||
@@ -1006,31 +1034,16 @@ func handleRuntimeDTO(res *response.RuntimeDTO, runtime model.Runtime) error {
|
||||
return err
|
||||
}
|
||||
for k, v := range envs {
|
||||
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
|
||||
if strings.Contains(k, "CONTAINER_PORT") {
|
||||
matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k)
|
||||
if len(matches) < 2 {
|
||||
return fmt.Errorf("invalid container port key: %s", k)
|
||||
}
|
||||
containerPort, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
|
||||
res.ExposedPorts = append(res.ExposedPorts, request.ExposedPort{
|
||||
ContainerPort: containerPort,
|
||||
HostPort: hostPort,
|
||||
HostIP: hostIP,
|
||||
})
|
||||
}
|
||||
if isComposePortEnvKey(k) {
|
||||
continue
|
||||
} else {
|
||||
res.Params[k] = v
|
||||
}
|
||||
}
|
||||
res.ExposedPorts, err = loadComposeExposedPortsFromEnv(envs, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
|
||||
res.Source = v
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/env"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
)
|
||||
|
||||
type TensorRTLLMService struct{}
|
||||
@@ -59,33 +58,7 @@ func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorR
|
||||
serverDTO.Image = envs["IMAGE"]
|
||||
serverDTO.Command = getCommand(item.Env)
|
||||
|
||||
for k, v := range envs {
|
||||
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
|
||||
if strings.Contains(k, "CONTAINER_PORT") {
|
||||
matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k)
|
||||
if len(matches) < 2 {
|
||||
continue
|
||||
}
|
||||
containerPort, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
|
||||
if hostIP == "" {
|
||||
hostIP = "0.0.0.0"
|
||||
}
|
||||
serverDTO.ExposedPorts = append(serverDTO.ExposedPorts, request.ExposedPort{
|
||||
ContainerPort: containerPort,
|
||||
HostPort: hostPort,
|
||||
HostIP: hostIP,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
serverDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "0.0.0.0", false)
|
||||
|
||||
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
|
||||
if err == nil {
|
||||
@@ -149,11 +122,9 @@ func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) e
|
||||
delete(serviceValue, "ports")
|
||||
if len(create.ExposedPorts) > 0 {
|
||||
var ports []interface{}
|
||||
for i := range create.ExposedPorts {
|
||||
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
|
||||
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
|
||||
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
|
||||
ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr))
|
||||
for i, port := range create.ExposedPorts {
|
||||
containerPortStr, hostPortStr, hostIPStr, _ := composePortEnvKeys(i)
|
||||
ports = append(ports, formatComposePortMapping(hostIPStr, hostPortStr, containerPortStr, port.Protocol))
|
||||
}
|
||||
serviceValue["ports"] = ports
|
||||
}
|
||||
@@ -174,7 +145,7 @@ func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) e
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", k, v))
|
||||
}
|
||||
for _, volume := range create.Volumes {
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
volumes = append(volumes, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
|
||||
}
|
||||
serviceValue["volumes"] = volumes
|
||||
|
||||
@@ -195,12 +166,11 @@ func handleLLMEnv(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) gote
|
||||
envMap["IMAGE"] = create.Image
|
||||
envMap["COMMAND"] = create.Command
|
||||
for i, port := range create.ExposedPorts {
|
||||
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
|
||||
hostPortStr := fmt.Sprintf("HOST_PORT_%d", i)
|
||||
hostIPStr := fmt.Sprintf("HOST_IP_%d", i)
|
||||
containerPortStr, hostPortStr, hostIPStr, protocolStr := composePortEnvKeys(i)
|
||||
envMap[containerPortStr] = strconv.Itoa(port.ContainerPort)
|
||||
envMap[hostPortStr] = strconv.Itoa(port.HostPort)
|
||||
envMap[hostIPStr] = port.HostIP
|
||||
envMap[protocolStr] = normalizeComposeProtocol(port.Protocol)
|
||||
}
|
||||
orders := []string{"MODEL_PATH", "COMMAND"}
|
||||
envStr, _ := env.MarshalWithOrder(envMap, orders)
|
||||
@@ -219,7 +189,7 @@ func (t TensorRTLLMService) Create(create request.TensorRTLLMCreate) error {
|
||||
}
|
||||
}
|
||||
for _, export := range create.ExposedPorts {
|
||||
if err := checkPortExist(export.HostPort); err != nil {
|
||||
if err := checkPortExistWithProtocol(export.HostPort, export.Protocol); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,6 +1247,33 @@
|
||||
"formatZH": "更新 AI 网关分组 [name]",
|
||||
"formatEN": "update AI proxy group [name]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/model-groups/create": {
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "创建 AI 网关模型组 [name]",
|
||||
"formatEN": "create AI proxy model group [name]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/model-groups/delete": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "删除 AI 网关模型组 [id]",
|
||||
"formatEN": "delete AI proxy model group [id]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/model-groups/update": {
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新 AI 网关模型组 [name]",
|
||||
"formatEN": "update AI proxy model group [name]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/service/operate": {
|
||||
"bodyKeys": [
|
||||
"operate"
|
||||
@@ -2166,6 +2193,15 @@
|
||||
"formatZH": "更新自定义应用配置 [status]",
|
||||
"formatEN": "update custom app config [status]"
|
||||
},
|
||||
"/core/xpack/settings/app/image-prefix": {
|
||||
"bodyKeys": [
|
||||
"imagePrefix"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新应用镜像前缀 [imagePrefix]",
|
||||
"formatEN": "update app image prefix [imagePrefix]"
|
||||
},
|
||||
"/core/xpack/settings/reset": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
@@ -2193,25 +2229,13 @@
|
||||
"/core/xpack/sync/app": {
|
||||
"bodyKeys": [
|
||||
"sourceNode",
|
||||
"targetNode",
|
||||
"appInstallID",
|
||||
"appBackupID"
|
||||
"targetNodes",
|
||||
"appInstallID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "同步应用备份 [appInstallID] 到节点 [targetNode]",
|
||||
"formatEN": "sync app backup [appInstallID] to node [targetNode]"
|
||||
},
|
||||
"/core/xpack/sync/app/install": {
|
||||
"bodyKeys": [
|
||||
"appKey",
|
||||
"version",
|
||||
"nodes"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "安装应用 [appKey] [version] 到节点 [nodes]",
|
||||
"formatEN": "install app [appKey] [version] to nodes [nodes]"
|
||||
"formatZH": "同步应用 [appInstallID] 到节点 [targetNodes]",
|
||||
"formatEN": "sync app [appInstallID] to nodes [targetNodes]"
|
||||
},
|
||||
"/core/xpack/sync/ssl": {
|
||||
"bodyKeys": [
|
||||
@@ -2352,6 +2376,15 @@
|
||||
"formatZH": "更新自定义应用配置 [status]",
|
||||
"formatEN": "update custom app config [status]"
|
||||
},
|
||||
"/custom/app/image-prefix": {
|
||||
"bodyKeys": [
|
||||
"imagePrefix"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新应用镜像前缀 [imagePrefix]",
|
||||
"formatEN": "update app image prefix [imagePrefix]"
|
||||
},
|
||||
"/custom/app/install": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
@@ -2379,6 +2412,15 @@
|
||||
"formatZH": "同步自定义应用商店",
|
||||
"formatEN": "sync custom app store"
|
||||
},
|
||||
"/custom/app/sync/backup": {
|
||||
"bodyKeys": [
|
||||
"appInstallID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "创建应用同步备份 [appInstallID]",
|
||||
"formatEN": "create app sync backup [appInstallID]"
|
||||
},
|
||||
"/dashboard/app/launcher/show": {
|
||||
"bodyKeys": [
|
||||
"key",
|
||||
@@ -4431,6 +4473,24 @@
|
||||
"formatZH": "申请证书 [domain]",
|
||||
"formatEN": "apply ssl [domain]"
|
||||
},
|
||||
"/websites/ssl/push": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [
|
||||
{
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"db": "website_ssls",
|
||||
"output_column": "primary_domain",
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"formatZH": "推送证书到节点 [domain]",
|
||||
"formatEN": "Push ssl to nodes [domain]"
|
||||
},
|
||||
"/websites/ssl/update": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
@@ -5083,4 +5143,4 @@
|
||||
"formatZH": "从主节点同步设置",
|
||||
"formatEN": "sync settings from master"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ require (
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.10
|
||||
github.com/qiniu/go-sdk/v7 v7.26.12
|
||||
github.com/qiniu/go-sdk/v7 v7.26.13
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.26.5
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
@@ -46,12 +46,12 @@ require (
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/subosito/gotenv v1.6.0
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.73
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.74
|
||||
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19
|
||||
github.com/upyun/go-sdk v2.1.0+incompatible
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.1
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sys v0.46.0
|
||||
|
||||
16
agent/go.sum
16
agent/go.sum
@@ -746,8 +746,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.12 h1:AnWiKjBY62XpULoB/MySKdNmlUo9S9pQB7/0s7QueCo=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.12/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.13 h1:U4usDKoLldAqJntLN4wv4pkAjwvM9dueOUY/wbbO/yM=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.13/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
@@ -833,8 +833,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.24/go.mod h
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.106 h1:YjsxDQwoiB+C0ISjnVk22Wbks02Ir+7Z5wmujcz69Vg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.106/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.73 h1:uFfgp1A7cQaAGR6QP9DsIkoEQ67b8ewj5r1RV6XB540=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.73/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.74 h1:YCsGhIK+Q5vMcmAiQqPuMyhSQ3zN1p7U+dhlHQD++6c=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.74/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
|
||||
github.com/tencentyun/qcloud-cos-sts-sdk v0.0.0-20250515025012-e0eec8a5d123/go.mod h1:b18KQa4IxHbxeseW1GcZox53d7J0z39VNONTxvvlkXw=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
@@ -898,8 +898,8 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.1 h1:YyGZ2lt+4Nv+dWuiGMKoyWuxmBSlGLH5jllheKiQGu0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -1073,8 +1073,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
||||
@@ -2883,6 +2883,44 @@ const docTemplate = `{
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/connection/test": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.McpServerConnectionTest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.McpServerConnectionTestRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Test mcp server connection",
|
||||
"tags": [
|
||||
"McpServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/del": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -2918,6 +2956,44 @@ const docTemplate = `{
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/detail": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.McpServerDetail"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.McpServerDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Load mcp server detail",
|
||||
"tags": [
|
||||
"McpServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/op": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -2953,6 +3029,47 @@ const docTemplate = `{
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/status/sync": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.McpServerStatusSync"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/response.McpServerStatusDTO"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Sync mcp server status",
|
||||
"tags": [
|
||||
"McpServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/update": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -27588,6 +27705,59 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/push": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteSSLPush"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Push ssl to nodes",
|
||||
"tags": [
|
||||
"Website SSL"
|
||||
],
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_ssls",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "primary_domain",
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Push ssl to nodes [domain]",
|
||||
"formatZH": "推送证书到节点 [domain]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/resolve": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -31531,6 +31701,9 @@ const docTemplate = `{
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isImmediate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -39396,6 +39569,9 @@ const docTemplate = `{
|
||||
},
|
||||
"hostPort": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocol": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -40282,6 +40458,17 @@ const docTemplate = `{
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerConnectionTest": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerCreate": {
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
@@ -40299,6 +40486,9 @@ const docTemplate = `{
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gatewayImage": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostIP": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -40311,6 +40501,9 @@ const docTemplate = `{
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -40347,6 +40540,17 @@ const docTemplate = `{
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerDetail": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerOperate": {
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -40383,6 +40587,17 @@ const docTemplate = `{
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerStatusSync": {
|
||||
"properties": {
|
||||
"ids": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerUpdate": {
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
@@ -40400,6 +40615,9 @@ const docTemplate = `{
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gatewayImage": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostIP": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -40415,6 +40633,9 @@ const docTemplate = `{
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -41691,6 +41912,9 @@ const docTemplate = `{
|
||||
},
|
||||
"request.Volume": {
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -41852,9 +42076,15 @@ const docTemplate = `{
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"nodes": {
|
||||
"type": "string"
|
||||
},
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pushNode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"renew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -42726,6 +42956,27 @@ const docTemplate = `{
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"request.WebsiteSSLPush": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"nodes": {
|
||||
"type": "string"
|
||||
},
|
||||
"pushNode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"taskID"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.WebsiteSSLSearch": {
|
||||
"properties": {
|
||||
"acmeAccountID": {
|
||||
@@ -42745,6 +42996,7 @@ const docTemplate = `{
|
||||
"orderBy": {
|
||||
"enum": [
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"expire_date"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -42843,12 +43095,18 @@ const docTemplate = `{
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"nodes": {
|
||||
"type": "string"
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"privateKeyPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"pushNode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sslID": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -43976,6 +44234,26 @@ const docTemplate = `{
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServerConnectionTestRes": {
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"outputTransport": {
|
||||
"type": "string"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServerDTO": {
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
@@ -44005,6 +44283,9 @@ const docTemplate = `{
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gatewayImage": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostIP": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -44023,6 +44304,9 @@ const docTemplate = `{
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -44050,6 +44334,20 @@ const docTemplate = `{
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServerStatusDTO": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServersRes": {
|
||||
"properties": {
|
||||
"items": {
|
||||
|
||||
@@ -2879,6 +2879,44 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/connection/test": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.McpServerConnectionTest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.McpServerConnectionTestRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Test mcp server connection",
|
||||
"tags": [
|
||||
"McpServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/del": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -2914,6 +2952,44 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/detail": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.McpServerDetail"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.McpServerDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Load mcp server detail",
|
||||
"tags": [
|
||||
"McpServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/op": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -2949,6 +3025,47 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/status/sync": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.McpServerStatusSync"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/response.McpServerStatusDTO"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Sync mcp server status",
|
||||
"tags": [
|
||||
"McpServer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ai/mcp/server/update": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -27584,6 +27701,59 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/push": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteSSLPush"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
},
|
||||
{
|
||||
"Timestamp": []
|
||||
}
|
||||
],
|
||||
"summary": "Push ssl to nodes",
|
||||
"tags": [
|
||||
"Website SSL"
|
||||
],
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_ssls",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "primary_domain",
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "Push ssl to nodes [domain]",
|
||||
"formatZH": "推送证书到节点 [domain]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/resolve": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -31527,6 +31697,9 @@
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isImmediate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -39392,6 +39565,9 @@
|
||||
},
|
||||
"hostPort": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocol": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -40278,6 +40454,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerConnectionTest": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerCreate": {
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
@@ -40295,6 +40482,9 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gatewayImage": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostIP": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -40307,6 +40497,9 @@
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -40343,6 +40536,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerDetail": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerOperate": {
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -40379,6 +40583,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerStatusSync": {
|
||||
"properties": {
|
||||
"ids": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"request.McpServerUpdate": {
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
@@ -40396,6 +40611,9 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gatewayImage": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostIP": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -40411,6 +40629,9 @@
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -41687,6 +41908,9 @@
|
||||
},
|
||||
"request.Volume": {
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -41848,9 +42072,15 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"nodes": {
|
||||
"type": "string"
|
||||
},
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pushNode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"renew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -42722,6 +42952,27 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"request.WebsiteSSLPush": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"nodes": {
|
||||
"type": "string"
|
||||
},
|
||||
"pushNode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"taskID"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"request.WebsiteSSLSearch": {
|
||||
"properties": {
|
||||
"acmeAccountID": {
|
||||
@@ -42741,6 +42992,7 @@
|
||||
"orderBy": {
|
||||
"enum": [
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"expire_date"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -42839,12 +43091,18 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"nodes": {
|
||||
"type": "string"
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"privateKeyPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"pushNode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sslID": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -43972,6 +44230,26 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServerConnectionTestRes": {
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"outputTransport": {
|
||||
"type": "string"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServerDTO": {
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
@@ -44001,6 +44279,9 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gatewayImage": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostIP": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -44019,6 +44300,9 @@
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocolVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssePath": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -44046,6 +44330,20 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServerStatusDTO": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"response.McpServersRes": {
|
||||
"properties": {
|
||||
"items": {
|
||||
|
||||
@@ -1247,6 +1247,33 @@
|
||||
"formatZH": "更新 AI 网关分组 [name]",
|
||||
"formatEN": "update AI proxy group [name]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/model-groups/create": {
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "创建 AI 网关模型组 [name]",
|
||||
"formatEN": "create AI proxy model group [name]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/model-groups/delete": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "删除 AI 网关模型组 [id]",
|
||||
"formatEN": "delete AI proxy model group [id]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/model-groups/update": {
|
||||
"bodyKeys": [
|
||||
"name"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新 AI 网关模型组 [name]",
|
||||
"formatEN": "update AI proxy model group [name]"
|
||||
},
|
||||
"/core/enterprise/ai-proxy/service/operate": {
|
||||
"bodyKeys": [
|
||||
"operate"
|
||||
@@ -2166,6 +2193,15 @@
|
||||
"formatZH": "更新自定义应用配置 [status]",
|
||||
"formatEN": "update custom app config [status]"
|
||||
},
|
||||
"/core/xpack/settings/app/image-prefix": {
|
||||
"bodyKeys": [
|
||||
"imagePrefix"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新应用镜像前缀 [imagePrefix]",
|
||||
"formatEN": "update app image prefix [imagePrefix]"
|
||||
},
|
||||
"/core/xpack/settings/reset": {
|
||||
"bodyKeys": [],
|
||||
"paramKeys": [],
|
||||
@@ -2193,25 +2229,13 @@
|
||||
"/core/xpack/sync/app": {
|
||||
"bodyKeys": [
|
||||
"sourceNode",
|
||||
"targetNode",
|
||||
"appInstallID",
|
||||
"appBackupID"
|
||||
"targetNodes",
|
||||
"appInstallID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "同步应用备份 [appInstallID] 到节点 [targetNode]",
|
||||
"formatEN": "sync app backup [appInstallID] to node [targetNode]"
|
||||
},
|
||||
"/core/xpack/sync/app/install": {
|
||||
"bodyKeys": [
|
||||
"appKey",
|
||||
"version",
|
||||
"nodes"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "安装应用 [appKey] [version] 到节点 [nodes]",
|
||||
"formatEN": "install app [appKey] [version] to nodes [nodes]"
|
||||
"formatZH": "同步应用 [appInstallID] 到节点 [targetNodes]",
|
||||
"formatEN": "sync app [appInstallID] to nodes [targetNodes]"
|
||||
},
|
||||
"/core/xpack/sync/ssl": {
|
||||
"bodyKeys": [
|
||||
@@ -2352,6 +2376,15 @@
|
||||
"formatZH": "更新自定义应用配置 [status]",
|
||||
"formatEN": "update custom app config [status]"
|
||||
},
|
||||
"/custom/app/image-prefix": {
|
||||
"bodyKeys": [
|
||||
"imagePrefix"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "更新应用镜像前缀 [imagePrefix]",
|
||||
"formatEN": "update app image prefix [imagePrefix]"
|
||||
},
|
||||
"/custom/app/install": {
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
@@ -2379,6 +2412,15 @@
|
||||
"formatZH": "同步自定义应用商店",
|
||||
"formatEN": "sync custom app store"
|
||||
},
|
||||
"/custom/app/sync/backup": {
|
||||
"bodyKeys": [
|
||||
"appInstallID"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [],
|
||||
"formatZH": "创建应用同步备份 [appInstallID]",
|
||||
"formatEN": "create app sync backup [appInstallID]"
|
||||
},
|
||||
"/dashboard/app/launcher/show": {
|
||||
"bodyKeys": [
|
||||
"key",
|
||||
@@ -4431,6 +4473,24 @@
|
||||
"formatZH": "申请证书 [domain]",
|
||||
"formatEN": "apply ssl [domain]"
|
||||
},
|
||||
"/websites/ssl/push": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"paramKeys": [],
|
||||
"beforeFunctions": [
|
||||
{
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"db": "website_ssls",
|
||||
"output_column": "primary_domain",
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"formatZH": "推送证书到节点 [domain]",
|
||||
"formatEN": "Push ssl to nodes [domain]"
|
||||
},
|
||||
"/websites/ssl/update": {
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
@@ -5083,4 +5143,4 @@
|
||||
"formatZH": "从主节点同步设置",
|
||||
"formatEN": "sync settings from master"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/term v0.44.0
|
||||
golang.org/x/text v0.38.0
|
||||
|
||||
@@ -256,8 +256,8 @@ golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
134
frontend/package-lock.json
generated
134
frontend/package-lock.json
generated
@@ -20,11 +20,11 @@
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"anser": "^2.3.2",
|
||||
"anser": "^2.3.5",
|
||||
"axios": "^1.17.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dompurify": "^3.4.8",
|
||||
"dompurify": "^3.4.9",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "2.14.0",
|
||||
"js-base64": "^3.7.7",
|
||||
@@ -51,8 +51,8 @@
|
||||
"@tailwindcss/postcss": "^4.3.0",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
||||
"@typescript-eslint/parser": "^8.58.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.61.0",
|
||||
"@typescript-eslint/parser": "^8.61.0",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"@vue/compiler-sfc": "^3.5.35",
|
||||
@@ -3048,17 +3048,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz",
|
||||
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz",
|
||||
"integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.58.1",
|
||||
"@typescript-eslint/type-utils": "8.58.1",
|
||||
"@typescript-eslint/utils": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
||||
"@typescript-eslint/scope-manager": "8.61.0",
|
||||
"@typescript-eslint/type-utils": "8.61.0",
|
||||
"@typescript-eslint/utils": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -3071,7 +3071,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.58.1",
|
||||
"@typescript-eslint/parser": "^8.61.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
@@ -3087,17 +3087,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz",
|
||||
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz",
|
||||
"integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/typescript-estree": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
||||
"@typescript-eslint/scope-manager": "8.61.0",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3113,14 +3113,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz",
|
||||
"integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz",
|
||||
"integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.58.1",
|
||||
"@typescript-eslint/types": "^8.58.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.61.0",
|
||||
"@typescript-eslint/types": "^8.61.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3135,14 +3135,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz",
|
||||
"integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz",
|
||||
"integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1"
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3153,9 +3153,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz",
|
||||
"integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz",
|
||||
"integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3170,15 +3170,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz",
|
||||
"integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz",
|
||||
"integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/typescript-estree": "8.58.1",
|
||||
"@typescript-eslint/utils": "8.58.1",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0",
|
||||
"@typescript-eslint/utils": "8.61.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -3195,9 +3195,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz",
|
||||
"integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz",
|
||||
"integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3209,16 +3209,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz",
|
||||
"integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz",
|
||||
"integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.58.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/visitor-keys": "8.58.1",
|
||||
"@typescript-eslint/project-service": "8.61.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.61.0",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -3247,9 +3247,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3276,16 +3276,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz",
|
||||
"integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz",
|
||||
"integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.58.1",
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/typescript-estree": "8.58.1"
|
||||
"@typescript-eslint/scope-manager": "8.61.0",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3300,13 +3300,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.58.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz",
|
||||
"integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz",
|
||||
"integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.58.1",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3829,9 +3829,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anser": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/anser/-/anser-2.3.3.tgz",
|
||||
"integrity": "sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg==",
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/anser/-/anser-2.3.5.tgz",
|
||||
"integrity": "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
@@ -4998,9 +4998,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz",
|
||||
"integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==",
|
||||
"version": "3.4.9",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.9.tgz",
|
||||
"integrity": "sha512-4dPSRMRDqHvs0V4YDFCsaIZo4if5u0xM+llyxiM2fwuZFdKArUBAF3VtI2+n8NKg9P870WMdYk0UhqQNoWXbfQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"anser": "^2.3.2",
|
||||
"anser": "^2.3.5",
|
||||
"axios": "^1.17.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dompurify": "^3.4.8",
|
||||
"dompurify": "^3.4.9",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "2.14.0",
|
||||
"js-base64": "^3.7.7",
|
||||
@@ -62,8 +62,8 @@
|
||||
"@tailwindcss/postcss": "^4.3.0",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
||||
"@typescript-eslint/parser": "^8.58.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.61.0",
|
||||
"@typescript-eslint/parser": "^8.61.0",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"@vue/compiler-sfc": "^3.5.35",
|
||||
|
||||
@@ -256,6 +256,7 @@ export namespace AI {
|
||||
hostPort: number;
|
||||
containerPort: number;
|
||||
hostIP: string;
|
||||
protocol?: string;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
@@ -265,6 +266,7 @@ export namespace AI {
|
||||
export interface Volume {
|
||||
source: string;
|
||||
target: string;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface ExtraHosts {
|
||||
|
||||
@@ -71,6 +71,7 @@ export namespace Runtime {
|
||||
hostPort: number;
|
||||
containerPort: number;
|
||||
hostIP: string;
|
||||
protocol: string;
|
||||
}
|
||||
export interface Environment {
|
||||
key: string;
|
||||
@@ -79,6 +80,7 @@ export namespace Runtime {
|
||||
export interface Volume {
|
||||
source: string;
|
||||
target: string;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface ExtraHost {
|
||||
|
||||
@@ -4958,7 +4958,7 @@ const message = {
|
||||
theworld: 'TheWorld Browser',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Maxthon Browser',
|
||||
monitorStatusHelper: 'Monitoring is not enabled, please enable it in settings',
|
||||
monitorStatusHelper: 'Website monitoring is currently disabled. Enable it now',
|
||||
excludeIp: 'Exclude IP Addresses',
|
||||
excludeUa: 'Exclude User-Agent',
|
||||
remotePort: 'Remote Port',
|
||||
|
||||
@@ -5018,7 +5018,7 @@ const message = {
|
||||
theworld: 'TheWorld Browser',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Maxthon Browser',
|
||||
monitorStatusHelper: 'Monitorización no habilitada, actívala en Configuración',
|
||||
monitorStatusHelper: 'La monitorización del sitio web está deshabilitada. Actívala ahora',
|
||||
excludeIp: 'Excluir IPs',
|
||||
excludeUa: 'Excluir User-Agent',
|
||||
remotePort: 'Puerto remoto',
|
||||
|
||||
@@ -4996,7 +4996,7 @@ const message = {
|
||||
theworld: 'TheWorldブラウザ',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Maxthonブラウザ',
|
||||
monitorStatusHelper: 'モニタリングは有効ではありません。設定で有効にしてください',
|
||||
monitorStatusHelper: '現在、Webサイト監視は有効になっていません。今すぐ有効にしてください',
|
||||
excludeIp: 'IPアドレスを除外',
|
||||
excludeUa: 'ユーザーエージェントを除外',
|
||||
remotePort: 'リモートポート',
|
||||
|
||||
@@ -4899,7 +4899,7 @@ const message = {
|
||||
theworld: '더월드 브라우저',
|
||||
edge: '마이크로소프트 엣지',
|
||||
maxthon: '맥스톤 브라우저',
|
||||
monitorStatusHelper: '모니터링이 활성화되지 않았습니다. 설정에서 활성화하세요',
|
||||
monitorStatusHelper: '현재 웹사이트 모니터링이 비활성화되어 있습니다. 지금 활성화하세요',
|
||||
excludeIp: 'IP 주소 제외',
|
||||
excludeUa: '사용자 에이전트 제외',
|
||||
remotePort: '원격 포트',
|
||||
|
||||
@@ -5058,7 +5058,7 @@ const message = {
|
||||
theworld: 'Pelayar TheWorld',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Pelayar Maxthon',
|
||||
monitorStatusHelper: 'Pemantauan tidak diaktifkan, sila aktifkan dalam tetapan',
|
||||
monitorStatusHelper: 'Pemantauan laman web belum diaktifkan. Aktifkan sekarang',
|
||||
excludeIp: 'Kecualikan Alamat IP',
|
||||
excludeUa: 'Kecualikan User-Agent',
|
||||
remotePort: 'Port Jauh',
|
||||
|
||||
@@ -5202,7 +5202,7 @@ const message = {
|
||||
theworld: 'Navegador TheWorld',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Navegador Maxthon',
|
||||
monitorStatusHelper: 'O monitoramento não está habilitado, por favor habilite-o nas configurações',
|
||||
monitorStatusHelper: 'O monitoramento do site está desabilitado. Habilite agora',
|
||||
excludeIp: 'Excluir Endereços IP',
|
||||
excludeUa: 'Excluir User-Agent',
|
||||
remotePort: 'Porta Remota',
|
||||
|
||||
@@ -5049,7 +5049,7 @@ const message = {
|
||||
theworld: 'Браузер TheWorld',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Браузер Maxthon',
|
||||
monitorStatusHelper: 'Мониторинг не включён, пожалуйста, включите его в настройках',
|
||||
monitorStatusHelper: 'Мониторинг сайта сейчас отключён. Включите его сейчас',
|
||||
excludeIp: 'Исключить IP-адреса',
|
||||
excludeUa: 'Исключить User-Agent',
|
||||
remotePort: 'Удаленный порт',
|
||||
|
||||
@@ -5046,7 +5046,7 @@ const message = {
|
||||
theworld: 'TheWorld Tarayıcı',
|
||||
edge: 'Microsoft Edge',
|
||||
maxthon: 'Maxthon Tarayıcı',
|
||||
monitorStatusHelper: 'İzleme etkin değil, lütfen ayarlarda etkinleştirin',
|
||||
monitorStatusHelper: 'Web sitesi izleme şu anda devre dışı. Şimdi etkinleştirin',
|
||||
excludeIp: 'IP Adreslerini Hariç Tut',
|
||||
excludeUa: 'Kullanıcı-Aracısını Hariç Tut',
|
||||
remotePort: 'Uzak Port',
|
||||
|
||||
@@ -4645,7 +4645,7 @@ const message = {
|
||||
theworld: '世界之窗瀏覽器',
|
||||
edge: 'Edge',
|
||||
maxthon: '遨遊瀏覽器',
|
||||
monitorStatusHelper: '監控未開啟,請在設定中開啟',
|
||||
monitorStatusHelper: '目前網站監控未開啟,立即開啟',
|
||||
excludeIp: '排除 IP',
|
||||
excludeUa: '排除 User-Agent',
|
||||
remotePort: '遠端埠',
|
||||
|
||||
@@ -4083,7 +4083,7 @@ const message = {
|
||||
theworld: '世界之窗浏览器',
|
||||
edge: 'Edge',
|
||||
maxthon: '遨游浏览器',
|
||||
monitorStatusHelper: '监控未开启,请在设置中打开',
|
||||
monitorStatusHelper: '当前网站监控未开启,立即开启',
|
||||
excludeIp: '排除 IP',
|
||||
excludeUa: '排除 User-Agent',
|
||||
remotePort: '远程端口',
|
||||
|
||||
@@ -117,34 +117,7 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('runtime.environment')">
|
||||
<div class="mt-1.5">
|
||||
<el-row :gutter="20" v-for="(env, index) in mcpServer.environments" :key="index">
|
||||
<el-col :span="8">
|
||||
<el-form-item :prop="`environments.${index}.key`" :rules="rules.key">
|
||||
<el-input v-model="env.key" :placeholder="$t('aiTools.mcp.envKey')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :prop="`environments.${index}.value`" :rules="rules.value">
|
||||
<el-input v-model="env.value" :placeholder="$t('aiTools.mcp.envValue')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-button v-permission type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button v-permission @click="addEnv">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<Environment :environments="mcpServer.environments" :show-helper="false" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('container.mount')">
|
||||
<Volumes :volumes="mcpServer.volumes" />
|
||||
@@ -171,7 +144,8 @@ import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref, watch } from 'vue';
|
||||
import Volumes from '../volume/index.vue';
|
||||
import Environment from '@/views/website/runtime/components/environment/index.vue';
|
||||
import Volumes from '@/views/website/runtime/components/volume/index.vue';
|
||||
import Import from '../import/index.vue';
|
||||
|
||||
const open = ref(false);
|
||||
@@ -216,8 +190,6 @@ const rules = ref({
|
||||
containerName: [Rules.requiredInput],
|
||||
url: [Rules.requiredInput],
|
||||
ssePath: [Rules.requiredInput],
|
||||
key: [Rules.requiredInput],
|
||||
value: [Rules.requiredInput],
|
||||
outputTransport: [Rules.requiredSelect],
|
||||
streamableHttpPath: [Rules.requiredInput],
|
||||
type: [Rules.requiredSelect],
|
||||
@@ -288,17 +260,6 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
const addEnv = () => {
|
||||
mcpServer.value.environments.push({
|
||||
key: '',
|
||||
value: '',
|
||||
});
|
||||
};
|
||||
|
||||
const removeEnv = (index: number) => {
|
||||
mcpServer.value.environments.splice(index, 1);
|
||||
};
|
||||
|
||||
const normalizeGatewayConfig = () => {
|
||||
mcpServer.value.gatewayImage = mcpServer.value.gatewayImage || defaultGatewayImages[mcpServer.value.type];
|
||||
mcpServer.value.protocolVersion = mcpServer.value.protocolVersion || defaultProtocolVersion;
|
||||
|
||||
@@ -50,6 +50,7 @@ const addEnv = () => {
|
||||
props.volumes.push({
|
||||
source: '',
|
||||
target: '',
|
||||
mode: 'rw',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-text type="info">{{ $t('php.dateTimezoneHelper') }}</el-text>
|
||||
<el-text v-if="showHelper" type="info">{{ $t('php.dateTimezoneHelper') }}</el-text>
|
||||
<div class="mt-1.5">
|
||||
<el-row :gutter="20" v-for="(env, index) in environments" :key="index">
|
||||
<el-col :span="7">
|
||||
@@ -42,8 +42,14 @@ const props = defineProps({
|
||||
type: Array<Runtime.Environment>,
|
||||
required: true,
|
||||
},
|
||||
showHelper: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { showHelper } = props;
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
value: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane :label="$t('commons.table.port')">
|
||||
<PortConfig :exposedPorts="runtime.exposedPorts" />
|
||||
<PortConfig :exposedPorts="exposedPorts" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('runtime.environment')">
|
||||
<Environment :environments="runtime.environments" />
|
||||
<Environment :environments="environments" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('container.mount')">
|
||||
<Volumes :volumes="runtime.volumes" />
|
||||
<Volumes :volumes="volumes" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('runtime.extraHosts')">
|
||||
<ExtraHosts :extraHosts="runtime.extraHosts" />
|
||||
<ExtraHosts :extraHosts="extraHosts" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
@@ -21,13 +21,13 @@ import Environment from '@/views/website/runtime/components/environment/index.vu
|
||||
import Volumes from '@/views/website/runtime/components/volume/index.vue';
|
||||
import ExtraHosts from '@/views/website/runtime/components/extra-hosts/index.vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, type PropType } from 'vue';
|
||||
|
||||
interface RuntimeConfigFields {
|
||||
exposedPorts: any[];
|
||||
environments: any[];
|
||||
volumes: any[];
|
||||
extraHosts: any[];
|
||||
exposedPorts?: any[];
|
||||
environments?: any[];
|
||||
volumes?: any[];
|
||||
extraHosts?: any[];
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
@@ -42,4 +42,16 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const runtime = useVModel(props, 'modelValue', emit);
|
||||
|
||||
const ensureConfigArray = (key: keyof RuntimeConfigFields): any[] => {
|
||||
if (!runtime.value[key]) {
|
||||
runtime.value[key] = [];
|
||||
}
|
||||
return runtime.value[key] as any[];
|
||||
};
|
||||
|
||||
const exposedPorts = computed(() => ensureConfigArray('exposedPorts'));
|
||||
const environments = computed(() => ensureConfigArray('environments'));
|
||||
const volumes = computed(() => ensureConfigArray('volumes'));
|
||||
const extraHosts = computed(() => ensureConfigArray('extraHosts'));
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
<template>
|
||||
<div class="mt-1.5">
|
||||
<el-row :gutter="20" v-for="(port, index) in exposedPorts" :key="index">
|
||||
<el-col :span="7">
|
||||
<el-col :span="6">
|
||||
<el-form-item :prop="`exposedPorts.${index}.hostPort`" :rules="rules.port">
|
||||
<el-input v-model.number="port.hostPort" :placeholder="$t('runtime.externalPort')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="7">
|
||||
<el-col :span="6">
|
||||
<el-form-item :prop="`exposedPorts.${index}.containerPort`" :rules="rules.port">
|
||||
<el-input v-model.number="port.containerPort" :placeholder="$t('runtime.appPort')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="7">
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-select v-model="port.protocol" :placeholder="$t('commons.table.protocol')">
|
||||
<el-option label="tcp" value="tcp" />
|
||||
<el-option label="udp" value="udp" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-text>{{ $t('app.allowPort') }}</el-text>
|
||||
<el-switch
|
||||
class="ml-1"
|
||||
@@ -60,6 +68,7 @@ const addPort = () => {
|
||||
hostPort: undefined,
|
||||
containerPort: undefined,
|
||||
hostIP: '',
|
||||
protocol: 'tcp',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
<el-input v-model="volume.target" :placeholder="$t('container.containerDir')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-select v-model="volume.mode" :placeholder="$t('container.mode')">
|
||||
<el-option :label="$t('container.modeRW')" value="rw" />
|
||||
<el-option :label="$t('container.modeR')" value="ro" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-button v-permission type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
@@ -48,6 +56,7 @@ const addEnv = () => {
|
||||
props.volumes.push({
|
||||
source: '',
|
||||
target: '',
|
||||
mode: 'rw',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -122,19 +122,22 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
if (runtime.exposedPorts && runtime.exposedPorts.length > 0) {
|
||||
const containerPortMap = new Map();
|
||||
const hostPortMap = new Map();
|
||||
const containerPortMap = new Map<string, boolean>();
|
||||
const hostPortMap = new Map<string, boolean>();
|
||||
for (const port of runtime.exposedPorts) {
|
||||
if (containerPortMap[port.containerPort]) {
|
||||
const protocol = port.protocol || 'tcp';
|
||||
const containerPortKey = `${port.containerPort}/${protocol}`;
|
||||
const hostPortKey = `${port.hostPort}/${protocol}`;
|
||||
if (containerPortMap.has(containerPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
if (hostPortMap[port.hostPort]) {
|
||||
if (hostPortMap.has(hostPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
hostPortMap[port.hostPort] = true;
|
||||
containerPortMap[port.containerPort] = true;
|
||||
hostPortMap.set(hostPortKey, true);
|
||||
containerPortMap.set(containerPortKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,19 +147,22 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
if (runtime.exposedPorts && runtime.exposedPorts.length > 0) {
|
||||
const containerPortMap = new Map();
|
||||
const hostPortMap = new Map();
|
||||
const containerPortMap = new Map<string, boolean>();
|
||||
const hostPortMap = new Map<string, boolean>();
|
||||
for (const port of runtime.exposedPorts) {
|
||||
if (containerPortMap[port.containerPort]) {
|
||||
const protocol = port.protocol || 'tcp';
|
||||
const containerPortKey = `${port.containerPort}/${protocol}`;
|
||||
const hostPortKey = `${port.hostPort}/${protocol}`;
|
||||
if (containerPortMap.has(containerPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
if (hostPortMap[port.hostPort]) {
|
||||
if (hostPortMap.has(hostPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
hostPortMap[port.hostPort] = true;
|
||||
containerPortMap[port.containerPort] = true;
|
||||
hostPortMap.set(hostPortKey, true);
|
||||
containerPortMap.set(containerPortKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,19 +135,22 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
if (runtime.exposedPorts && runtime.exposedPorts.length > 0) {
|
||||
const containerPortMap = new Map();
|
||||
const hostPortMap = new Map();
|
||||
const containerPortMap = new Map<string, boolean>();
|
||||
const hostPortMap = new Map<string, boolean>();
|
||||
for (const port of runtime.exposedPorts) {
|
||||
if (containerPortMap[port.containerPort]) {
|
||||
const protocol = port.protocol || 'tcp';
|
||||
const containerPortKey = `${port.containerPort}/${protocol}`;
|
||||
const hostPortKey = `${port.hostPort}/${protocol}`;
|
||||
if (containerPortMap.has(containerPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
if (hostPortMap[port.hostPort]) {
|
||||
if (hostPortMap.has(hostPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
hostPortMap[port.hostPort] = true;
|
||||
containerPortMap[port.containerPort] = true;
|
||||
hostPortMap.set(hostPortKey, true);
|
||||
containerPortMap.set(containerPortKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,19 +167,22 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
if (runtime.exposedPorts && runtime.exposedPorts.length > 0) {
|
||||
const containerPortMap = new Map();
|
||||
const hostPortMap = new Map();
|
||||
const containerPortMap = new Map<string, boolean>();
|
||||
const hostPortMap = new Map<string, boolean>();
|
||||
for (const port of runtime.exposedPorts) {
|
||||
if (containerPortMap[port.containerPort]) {
|
||||
const protocol = port.protocol || 'tcp';
|
||||
const containerPortKey = `${port.containerPort}/${protocol}`;
|
||||
const hostPortKey = `${port.hostPort}/${protocol}`;
|
||||
if (containerPortMap.has(containerPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
if (hostPortMap[port.hostPort]) {
|
||||
if (hostPortMap.has(hostPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
hostPortMap[port.hostPort] = true;
|
||||
containerPortMap[port.containerPort] = true;
|
||||
hostPortMap.set(hostPortKey, true);
|
||||
containerPortMap.set(containerPortKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,19 +111,22 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
if (runtime.exposedPorts && runtime.exposedPorts.length > 0) {
|
||||
const containerPortMap = new Map();
|
||||
const hostPortMap = new Map();
|
||||
const containerPortMap = new Map<string, boolean>();
|
||||
const hostPortMap = new Map<string, boolean>();
|
||||
for (const port of runtime.exposedPorts) {
|
||||
if (containerPortMap[port.containerPort]) {
|
||||
const protocol = port.protocol || 'tcp';
|
||||
const containerPortKey = `${port.containerPort}/${protocol}`;
|
||||
const hostPortKey = `${port.hostPort}/${protocol}`;
|
||||
if (containerPortMap.has(containerPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
if (hostPortMap[port.hostPort]) {
|
||||
if (hostPortMap.has(hostPortKey)) {
|
||||
MsgError(i18n.global.t('runtime.portError'));
|
||||
return;
|
||||
}
|
||||
hostPortMap[port.hostPort] = true;
|
||||
containerPortMap[port.containerPort] = true;
|
||||
hostPortMap.set(hostPortKey, true);
|
||||
containerPortMap.set(containerPortKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user