mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-10 16:00:31 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90d11a3b74 | ||
|
|
883f3db725 | ||
|
|
75da72c265 | ||
|
|
78eef15f5b | ||
|
|
735cf3cc71 | ||
|
|
99c5d9a8b2 | ||
|
|
3a0d2039d0 | ||
|
|
98e2d6390a | ||
|
|
975e7f9f58 | ||
|
|
77c90a26f8 | ||
|
|
17cb12df8b |
@@ -53,7 +53,7 @@ bash -c "$(curl -sSL https://resource.1panel.pro/v2/quick_start.sh)"
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
@@ -62,9 +62,11 @@ func NewIAgentService() IAgentService {
|
||||
}
|
||||
|
||||
const (
|
||||
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome"
|
||||
defaultBrowserProfile = "openclaw"
|
||||
defaultUserTimezone = "Asia/Shanghai"
|
||||
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome"
|
||||
defaultBrowserProfile = "openclaw"
|
||||
defaultUserTimezone = "Asia/Shanghai"
|
||||
defaultToolsProfile = "full"
|
||||
defaultToolsSessionVisibility = "all"
|
||||
)
|
||||
|
||||
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
@@ -1435,9 +1437,19 @@ type openclawConfig struct {
|
||||
Gateway gatewayConfig `json:"gateway"`
|
||||
Agents agentsConfig `json:"agents"`
|
||||
Browser browserConfig `json:"browser"`
|
||||
Tools toolsConfig `json:"tools"`
|
||||
Models *modelsConfig `json:"models,omitempty"`
|
||||
}
|
||||
|
||||
type toolsConfig struct {
|
||||
Profile string `json:"profile,omitempty"`
|
||||
Sessions toolSessionsConfig `json:"sessions,omitempty"`
|
||||
}
|
||||
|
||||
type toolSessionsConfig struct {
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
}
|
||||
|
||||
type gatewayConfig struct {
|
||||
Mode string `json:"mode"`
|
||||
Bind string `json:"bind"`
|
||||
@@ -1550,6 +1562,12 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens
|
||||
NoSandbox: true,
|
||||
DefaultProfile: defaultBrowserProfile,
|
||||
},
|
||||
Tools: toolsConfig{
|
||||
Profile: defaultToolsProfile,
|
||||
Sessions: toolSessionsConfig{
|
||||
Visibility: defaultToolsSessionVisibility,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
@@ -1870,6 +1888,14 @@ func writeOpenclawConfig(confDir, provider, modelName, apiType string, maxTokens
|
||||
}
|
||||
conf["browser"] = browserMap
|
||||
}
|
||||
toolsMap := ensureChildMap(conf, "tools")
|
||||
if profile, ok := toolsMap["profile"]; !ok || strings.TrimSpace(fmt.Sprintf("%v", profile)) == "" {
|
||||
toolsMap["profile"] = defaultToolsProfile
|
||||
}
|
||||
sessionsMap := ensureChildMap(toolsMap, "sessions")
|
||||
if visibility, ok := sessionsMap["visibility"]; !ok || strings.TrimSpace(fmt.Sprintf("%v", visibility)) == "" {
|
||||
sessionsMap["visibility"] = defaultToolsSessionVisibility
|
||||
}
|
||||
agentsMap := ensureChildMap(conf, "agents")
|
||||
defaultsMap := ensureChildMap(agentsMap, "defaults")
|
||||
if tz, ok := defaultsMap["userTimezone"]; !ok || strings.TrimSpace(fmt.Sprintf("%v", tz)) == "" {
|
||||
|
||||
@@ -516,6 +516,10 @@ func (a AppService) Install(req request.AppInstallCreate, executeScript bool) (a
|
||||
}
|
||||
appInstall.Env = string(paramByte)
|
||||
|
||||
var maxSort int
|
||||
global.DB.Model(&model.AppInstall{}).Where("favorite = ?", false).Select("COALESCE(MAX(sort_order),0)").Scan(&maxSort)
|
||||
appInstall.SortOrder = maxSort + 1
|
||||
|
||||
if err = appInstallRepo.Create(context.Background(), appInstall); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -459,12 +459,7 @@ func addProxy(server *model.McpServer) {
|
||||
} else {
|
||||
proxyPath = server.StreamableHttpPath
|
||||
}
|
||||
safePass, err := nginx.NginxSafeString(fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath), nginx.ModeURL)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("mcp add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{safePass})
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
|
||||
location.ChangePath("^~", proxyPath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr("ErrUpdateBuWebsite", err))
|
||||
@@ -522,12 +517,7 @@ func addMCPProxy(websiteID uint) error {
|
||||
} else {
|
||||
proxyPath = server.StreamableHttpPath
|
||||
}
|
||||
safePass, err := nginx.NginxSafeString(fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath), nginx.ModeURL)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("mcp add proxy failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{safePass})
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
|
||||
location.ChangePath("^~", proxyPath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
|
||||
@@ -295,12 +294,7 @@ func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) err
|
||||
config.FilePath = confPath
|
||||
directives := config.Directives
|
||||
location, _ := directives[0].(*components.Location)
|
||||
safePass, err := nginx.NginxSafeString(fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name)), nginx.ModePath)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("invalid auth_basic_user_file path, err: %v", err)
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("auth_basic_user_file", []string{safePass})
|
||||
location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))})
|
||||
location.ChangePath("~*", fmt.Sprintf("^%s", req.Path))
|
||||
var passwdHash []byte
|
||||
passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
|
||||
@@ -27,15 +27,15 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
switch req.Operate {
|
||||
case "delete":
|
||||
return w.DeleteProxy(request.WebsiteProxyDel{
|
||||
ID: req.ID,
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
})
|
||||
case "disable":
|
||||
fallthrough
|
||||
case "enable":
|
||||
return w.UpdateProxyStatus(request.WebsiteProxyStatusUpdate{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Status: req.Operate,
|
||||
})
|
||||
}
|
||||
@@ -114,16 +114,8 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
err = errors.New("invalid proxy config, no location found")
|
||||
return
|
||||
}
|
||||
safePass, err := nginx.NginxSafeString(req.ProxyPass, nginx.ModeURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safeHeader, err := nginx.NginxSafeString(req.ProxyHost, nginx.ModeHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{safePass})
|
||||
location.UpdateDirective("proxy_set_header", []string{"Host", safeHeader})
|
||||
location.UpdateDirective("proxy_pass", []string{req.ProxyPass})
|
||||
location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
|
||||
location.ChangePath(req.Modifier, req.Match)
|
||||
// Server Cache Settings
|
||||
if req.Cache {
|
||||
@@ -162,26 +154,14 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
}
|
||||
// CORS Settings
|
||||
if req.Cors {
|
||||
safeAllowOrigins, err := nginx.NginxSafeString(req.AllowOrigins, nginx.ModeGeneric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", safeAllowOrigins, "always"})
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
|
||||
if req.AllowMethods != "" {
|
||||
safeAllowMethods, err := nginx.NginxSafeString(req.AllowMethods, nginx.ModeAllowMethods)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", safeAllowMethods, "always"})
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
|
||||
}
|
||||
if req.AllowHeaders != "" {
|
||||
safeAllowHeaders, err := nginx.NginxSafeString(req.AllowHeaders, nginx.ModeGeneric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", safeAllowHeaders, "always"})
|
||||
location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
|
||||
} else {
|
||||
location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -139,18 +140,27 @@ func getComposeImagesByCommand(filePath string) ([]string, error) {
|
||||
}
|
||||
base, extra := getComposeBaseCmd()
|
||||
args := append(extra, strings.Fields(loadFiles(filePath))...)
|
||||
args = append(args, "config", "--images")
|
||||
args = append(args, "config", "--format", "json", "--no-normalize")
|
||||
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(5*time.Minute)).
|
||||
RunWithStdout(base, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("run compose config --images failed, std: %s, err: %v", stdout, err)
|
||||
return nil, fmt.Errorf("run compose config --format json --no-normalize failed, std: %s, err: %v", stdout, err)
|
||||
}
|
||||
|
||||
var composeConfig struct {
|
||||
Services map[string]struct {
|
||||
Image string `json:"image"`
|
||||
} `json:"services"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(stdout), &composeConfig); err != nil {
|
||||
return nil, fmt.Errorf("parse compose config json failed, std: %s, err: %v", stdout, err)
|
||||
}
|
||||
|
||||
var images []string
|
||||
seen := make(map[string]struct{})
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
image := strings.TrimSpace(line)
|
||||
if len(image) == 0 {
|
||||
for _, service := range composeConfig.Services {
|
||||
image := strings.TrimSpace(service.Image)
|
||||
if image == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[image]; ok {
|
||||
@@ -160,7 +170,7 @@ func getComposeImagesByCommand(filePath string) ([]string, error) {
|
||||
images = append(images, image)
|
||||
}
|
||||
if len(images) == 0 {
|
||||
return nil, errors.New("no images found from compose config")
|
||||
return nil, errors.New("no images found from compose config json")
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
)
|
||||
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
ModeGeneric Mode = iota
|
||||
ModeHost
|
||||
ModeURL
|
||||
ModePath
|
||||
ModeAllowMethods
|
||||
)
|
||||
|
||||
func NginxSafeString(input string, mode Mode) (string, error) {
|
||||
if input == "" {
|
||||
return "", errors.New("empty value not allowed")
|
||||
}
|
||||
|
||||
for _, r := range input {
|
||||
if unicode.IsControl(r) {
|
||||
return "", errors.New("control characters not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ContainsAny(input, ";\n\r{}#`$") {
|
||||
return "", errors.New("illegal nginx syntax characters")
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case ModeHost:
|
||||
return validateHost(input)
|
||||
case ModeURL:
|
||||
return validateURL(input)
|
||||
case ModePath:
|
||||
return validatePath(input)
|
||||
case ModeAllowMethods:
|
||||
return validateAllowMethods(input)
|
||||
default:
|
||||
return input, nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateHost(host string) (string, error) {
|
||||
if !re.GetRegex(re.NginxHostPattern).MatchString(host) {
|
||||
return "", errors.New("invalid host format")
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func validateURL(raw string) (string, error) {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", errors.New("invalid url")
|
||||
}
|
||||
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return "", errors.New("unsupported scheme")
|
||||
}
|
||||
|
||||
if u.Host == "" {
|
||||
return "", errors.New("missing host")
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func validatePath(p string) (string, error) {
|
||||
if !re.GetRegex(re.NginxPathPattern).MatchString(p) {
|
||||
return "", errors.New("invalid path")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
var allowedMethods = map[string]bool{
|
||||
"GET": true,
|
||||
"POST": true,
|
||||
"PUT": true,
|
||||
"DELETE": true,
|
||||
"PATCH": true,
|
||||
"OPTIONS": true,
|
||||
"HEAD": true,
|
||||
}
|
||||
|
||||
func validateAllowMethods(input string) (string, error) {
|
||||
parts := strings.Split(input, ",")
|
||||
for i, method := range parts {
|
||||
method = strings.TrimSpace(strings.ToUpper(method))
|
||||
if !allowedMethods[method] {
|
||||
return "", errors.New("invalid HTTP method: " + method)
|
||||
}
|
||||
parts[i] = method
|
||||
}
|
||||
return strings.Join(parts, ", "), nil
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -82,6 +83,15 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
||||
for _, set := range setting {
|
||||
settingMap[set.Key] = set.Value
|
||||
}
|
||||
if hideMenu, ok := settingMap["HideMenu"]; ok && len(hideMenu) > 0 {
|
||||
var menus []dto.ShowMenu
|
||||
if err := json.Unmarshal([]byte(hideMenu), &menus); err == nil {
|
||||
sortShowMenus(menus)
|
||||
if sortedBytes, err := json.Marshal(menus); err == nil {
|
||||
settingMap["HideMenu"] = string(sortedBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
var info dto.SettingInfo
|
||||
arr, err := json.Marshal(settingMap)
|
||||
if err != nil {
|
||||
@@ -103,6 +113,20 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
||||
return &info, err
|
||||
}
|
||||
|
||||
func sortShowMenus(menus []dto.ShowMenu) {
|
||||
for i := range menus {
|
||||
if len(menus[i].Children) > 0 {
|
||||
sortShowMenus(menus[i].Children)
|
||||
}
|
||||
}
|
||||
sort.SliceStable(menus, func(i, j int) bool {
|
||||
if menus[i].Sort == menus[j].Sort {
|
||||
return menus[i].ID < menus[j].ID
|
||||
}
|
||||
return menus[i].Sort < menus[j].Sort
|
||||
})
|
||||
}
|
||||
|
||||
func (u *SettingService) Update(key, value string) error {
|
||||
oldVal, err := settingRepo.Get(repo.WithByKey(key))
|
||||
if err != nil {
|
||||
|
||||
@@ -287,7 +287,11 @@ type noteDetailHelper struct {
|
||||
|
||||
func (u *UpgradeService) LoadRelease() ([]dto.ReleasesNotes, error) {
|
||||
var notes []dto.ReleasesNotes
|
||||
resp, err := req_helper.HandleGet("https://1panel.cn/docs/v2/search/search_index.json")
|
||||
url := "https://docs.1panel.pro/v2/search/search_index.json"
|
||||
if global.CONF.Base.Edition != "intl" {
|
||||
url = "https://1panel.cn/docs/v2/search/search_index.json"
|
||||
}
|
||||
resp, err := req_helper.HandleGet(url)
|
||||
if err != nil {
|
||||
return notes, err
|
||||
}
|
||||
@@ -321,13 +325,13 @@ func analyzeDoc(version, content string) dto.ReleasesNotes {
|
||||
}
|
||||
item.CreatedAt = strings.ReplaceAll(strings.TrimSpace(parts[1]), "</p>", "")
|
||||
for i := 1; i < len(parts); i++ {
|
||||
if strings.Contains(parts[i], "问题修复") {
|
||||
if strings.Contains(parts[i], "问题修复") || strings.Contains(parts[i], "Bug Fixes") {
|
||||
item.FixCount = strings.Count(parts[i], "<li>")
|
||||
}
|
||||
if strings.Contains(parts[i], "新增功能") {
|
||||
if strings.Contains(parts[i], "新增功能") || strings.Contains(parts[i], "New Features") {
|
||||
item.NewCount = strings.Count(parts[i], "<li>")
|
||||
}
|
||||
if strings.Contains(parts[i], "功能优化") {
|
||||
if strings.Contains(parts[i], "功能优化") || strings.Contains(parts[i], "Improvements") {
|
||||
item.OptimizationCount = strings.Count(parts[i], "<li>")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ const toLxware = () => {
|
||||
};
|
||||
|
||||
const to1Panel = () => {
|
||||
window.open('https://1panel.cn', '_blank', 'noopener,noreferrer');
|
||||
let url = globalStore.isIntl ? 'https://1panel.pro' : 'https://1panel.cn';
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const toDoc = () => {
|
||||
@@ -117,7 +118,7 @@ const toForum = () => {
|
||||
let url = globalStore.isIntl
|
||||
? 'https://github.com/1Panel-dev/1Panel/discussions'
|
||||
: 'https://bbs.fit2cloud.com/c/1p/7';
|
||||
window.open(url, '_blank');
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const toGithub = () => {
|
||||
|
||||
@@ -2,21 +2,28 @@
|
||||
<DrawerPro v-model="drawerVisible" :header="$t('setting.release')" @close="handleClose" size="large">
|
||||
<div class="note" v-loading="loading">
|
||||
<el-form ref="formRef" :model="form" :rules="rules">
|
||||
<el-form-item :label="$t('setting.versionItem')" prop="version">
|
||||
<el-input class="p-w-200" disabled v-model="form.version">
|
||||
<template #append>
|
||||
<CopyButton class="w-16" :isIcon="false" :content="form.version" type="primary" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupCopies')" prop="backupCopies">
|
||||
<el-input class="p-w-200" type="number" v-model.number="form.backupCopies">
|
||||
<template #append>
|
||||
<el-button @click="onSave(formRef)" class="w-16">{{ $t('commons.button.save') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">{{ $t('setting.backupCopiesHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div class="release-settings">
|
||||
<el-form-item :label="$t('setting.versionItem')" prop="version" class="compact-form-item">
|
||||
<div class="setting-control">
|
||||
<el-input disabled v-model="form.version" />
|
||||
<CopyButton
|
||||
class="setting-action-btn"
|
||||
:isIcon="false"
|
||||
:content="form.version"
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupCopies')" prop="backupCopies" class="compact-form-item">
|
||||
<div class="setting-control">
|
||||
<el-input-number v-model="form.backupCopies" :min="0" controls-position="right" />
|
||||
<el-button @click="onSave(formRef)" class="setting-action-btn" type="primary">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<span class="input-help">{{ $t('setting.backupCopiesHelper') }}</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-collapse v-if="notes && notes.length !== 0" v-model="currentVersion" :accordion="true">
|
||||
<div v-for="(item, index) in notes" :key="index">
|
||||
@@ -183,4 +190,31 @@ defineExpose({
|
||||
margin-left: 5px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
.release-settings {
|
||||
margin-bottom: 16px;
|
||||
padding: 14px 14px 10px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.compact-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.compact-form-item :deep(.el-form-item__label) {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
.setting-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
max-width: 340px;
|
||||
width: 100%;
|
||||
}
|
||||
.setting-control :deep(.el-input),
|
||||
.setting-control :deep(.el-input-number) {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
.setting-action-btn {
|
||||
min-width: 64px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2042,8 +2042,6 @@ const message = {
|
||||
bindDomain: 'Bind domain',
|
||||
unBindDomain: 'Unbind domain',
|
||||
panelSSL: 'Panel SSL',
|
||||
panelSSLHelper:
|
||||
'After the automatic renewal of the panel SSL, you need to manually restart the 1Panel service for the changes to take effect.',
|
||||
unBindDomainHelper: 'The action of unbinding a domain name may cause system insecurity. Continue?',
|
||||
bindDomainHelper: 'After you bind the domain, only that domain can access 1Panel service.',
|
||||
bindDomainHelper1: 'Leave it blank to disable the domain name binding.',
|
||||
@@ -2196,7 +2194,7 @@ const message = {
|
||||
|
||||
about: 'About',
|
||||
versionItem: 'Current Version',
|
||||
backupCopies: 'Number of Copies to Keep',
|
||||
backupCopies: 'Backup Copies',
|
||||
backupCopiesHelper: 'Set the number of upgrade backup copies to keep for version rollback. 0 means keep all.',
|
||||
backupCopiesRule: 'Please keep at least 3 upgrade backup records',
|
||||
release: 'Release Notes',
|
||||
@@ -3858,7 +3856,7 @@ const message = {
|
||||
masterBackup: 'Master Node Backup',
|
||||
backupNode: 'Backup Node',
|
||||
backupFrequency: 'Backup Frequency (hours)',
|
||||
backupCopies: 'Backup Retention Copies',
|
||||
backupCopies: 'Backup Copies',
|
||||
noBackupNode: 'The backup node is currently empty. Select a backup node to save and try again!',
|
||||
masterBackupAlert:
|
||||
'Master node backup is not currently configured. To ensure data security, please set up a backup node as soon as possible to facilitate manual switching to a new master node in case of failure.',
|
||||
|
||||
@@ -2067,8 +2067,6 @@ const message = {
|
||||
bindDomain: 'Vincular dominio',
|
||||
unBindDomain: 'Desvincular dominio',
|
||||
panelSSL: 'SSL del panel',
|
||||
panelSSLHelper:
|
||||
'Tras la renovación automática del SSL del panel, deberás reiniciar manualmente el servicio 1Panel para que los cambios surtan efecto.',
|
||||
unBindDomainHelper:
|
||||
'La acción de desvincular un dominio puede provocar inseguridad en el sistema. ¿Deseas continuar?',
|
||||
bindDomainHelper: 'Tras vincular el dominio, solo ese dominio podrá acceder al servicio de 1Panel.',
|
||||
@@ -2223,7 +2221,7 @@ const message = {
|
||||
|
||||
about: 'Acerca de',
|
||||
versionItem: 'Versión Actual',
|
||||
backupCopies: 'Número de Copias a Conservar',
|
||||
backupCopies: 'Copias de Respaldo',
|
||||
backupCopiesHelper:
|
||||
'Establezca el número de copias de respaldo de actualización para conservar para la reversión de versión. 0 significa conservar todas.',
|
||||
backupCopiesRule: 'Conserve al menos 3 registros de respaldo de actualización',
|
||||
@@ -3843,7 +3841,7 @@ const message = {
|
||||
masterBackup: 'Respaldo del Nodo Principal',
|
||||
backupNode: 'Nodo de Respaldo',
|
||||
backupFrequency: 'Frecuencia de Respaldo (horas)',
|
||||
backupCopies: 'Copias de Retención de Respaldo',
|
||||
backupCopies: 'Copias de Respaldo',
|
||||
noBackupNode: 'Actualmente no hay nodo de respaldo configurado. Selecciona uno y vuelve a intentarlo.',
|
||||
masterBackupAlert:
|
||||
'No se ha configurado un respaldo del nodo principal. Para garantizar la seguridad de los datos, configura un nodo de respaldo lo antes posible y así facilitar el cambio manual en caso de fallo.',
|
||||
|
||||
@@ -2145,7 +2145,7 @@ const message = {
|
||||
|
||||
about: 'について',
|
||||
versionItem: '現在のバージョン',
|
||||
backupCopies: '保持するバックアップ数',
|
||||
backupCopies: 'バックアップ数',
|
||||
backupCopiesHelper:
|
||||
'バージョンロールバック用に保持するアップグレードバックアップの数を設定します。0はすべて保持を意味します。',
|
||||
backupCopiesRule: '少なくとも3つのアップグレードバックアップ記録を保持してください',
|
||||
@@ -3781,7 +3781,7 @@ const message = {
|
||||
masterBackup: 'マスターノードバックアップ',
|
||||
backupNode: 'バックアップノード',
|
||||
backupFrequency: 'バックアップ頻度(時間)',
|
||||
backupCopies: 'バックアップ保持数',
|
||||
backupCopies: 'バックアップ数',
|
||||
noBackupNode: '現在バックアップノードが空です。保存するバックアップノードを選択して再試行してください!',
|
||||
masterBackupAlert:
|
||||
'現在マスターノードのバックアップが設定されていません。データセキュリティを確保するため、障害時に新しいマスターノードに手動で切り替えられるよう、速やかにバックアップノードを設定してください。',
|
||||
|
||||
@@ -2108,7 +2108,7 @@ const message = {
|
||||
|
||||
about: '정보',
|
||||
versionItem: '현재 버전',
|
||||
backupCopies: '보관할 백업 복사본 수',
|
||||
backupCopies: '백업 수',
|
||||
backupCopiesHelper:
|
||||
'버전 롤백을 위해 보관할 업그레이드 백업 복사본 수를 설정합니다. 0은 모두 보관을 의미합니다.',
|
||||
backupCopiesRule: '최소 3개의 업그레이드 백업 기록을 보관하세요',
|
||||
@@ -3708,7 +3708,7 @@ const message = {
|
||||
masterBackup: '마스터 노드 백업',
|
||||
backupNode: '백업 노드',
|
||||
backupFrequency: '백업 주기(시간)',
|
||||
backupCopies: '백업 기록 보관 수',
|
||||
backupCopies: '백업 수',
|
||||
noBackupNode: '현재 백업 노드가 비어 있습니다. 저장할 백업 노드를 선택한 후 다시 시도하십시오!',
|
||||
masterBackupAlert:
|
||||
'현재 마스터 노드 백업이 구성되지 않았습니다. 데이터 보안을 위해 장애 시 새로운 마스터 노드로 수동 전환이 가능하도록 가능한 빨리 백업 노드를 설정하십시오.',
|
||||
|
||||
@@ -2203,7 +2203,7 @@ const message = {
|
||||
|
||||
about: 'Mengenai',
|
||||
versionItem: 'Versi Semasa',
|
||||
backupCopies: 'Bilangan Salinan untuk Disimpan',
|
||||
backupCopies: 'Salinan Sandaran',
|
||||
backupCopiesHelper:
|
||||
'Tetapkan bilangan salinan sandaran naik taraf untuk disimpan untuk pemulihan versi. 0 bermakna simpan semua.',
|
||||
backupCopiesRule: 'Sila simpan sekurang-kurangnya 3 rekod sandaran naik taraf',
|
||||
@@ -3848,7 +3848,7 @@ const message = {
|
||||
masterBackup: 'Sandaran Nod Master',
|
||||
backupNode: 'Nod Sandaran',
|
||||
backupFrequency: 'Kekerapan Sandaran (jam)',
|
||||
backupCopies: 'Bilangan salinan sandaran yang disimpan',
|
||||
backupCopies: 'Salinan Sandaran',
|
||||
noBackupNode: 'Nod sandaran kosong. Sila pilih nod sandaran untuk disimpan dan cuba lagi!',
|
||||
masterBackupAlert:
|
||||
'Sandaran nod master belum dikonfigurasikan. Untuk memastikan keselamatan data, sila sediakan nod sandaran secepat mungkin untuk memudahkan pertukaran manual ke nod master baru sekiranya berlaku kegagalan.',
|
||||
|
||||
@@ -2192,7 +2192,7 @@ const message = {
|
||||
|
||||
about: 'Sobre',
|
||||
versionItem: 'Versão Atual',
|
||||
backupCopies: 'Número de Cópias a Manter',
|
||||
backupCopies: 'Cópias de Backup',
|
||||
backupCopiesHelper:
|
||||
'Defina o número de cópias de backup de atualização para manter para reversão de versão. 0 significa manter todas.',
|
||||
backupCopiesRule: 'Mantenha pelo menos 3 registros de backup de atualização',
|
||||
@@ -3867,7 +3867,7 @@ const message = {
|
||||
masterBackup: 'Backup do Nó Mestre',
|
||||
backupNode: 'Nó de Backup',
|
||||
backupFrequency: 'Frequência de Backup (horas)',
|
||||
backupCopies: 'Número de cópias de backup a reter',
|
||||
backupCopies: 'Cópias de Backup',
|
||||
noBackupNode:
|
||||
'O nó de backup está vazio atualmente. Selecione um nó de backup para salvar e tente novamente!',
|
||||
masterBackupAlert:
|
||||
|
||||
@@ -2189,7 +2189,7 @@ const message = {
|
||||
|
||||
about: 'О программе',
|
||||
versionItem: 'Текущая Версия',
|
||||
backupCopies: 'Количество Копий для Сохранения',
|
||||
backupCopies: 'Копии Бэкапа',
|
||||
backupCopiesHelper:
|
||||
'Установите количество копий резервных копий обновления для сохранения для отката версии. 0 означает сохранить все.',
|
||||
backupCopiesRule: 'Пожалуйста, сохраните как минимум 3 записи резервных копий обновления',
|
||||
@@ -3852,7 +3852,7 @@ const message = {
|
||||
masterBackup: 'Резервная копия главного узла',
|
||||
backupNode: 'Резервный узел',
|
||||
backupFrequency: 'Частота резервного копирования (часы)',
|
||||
backupCopies: 'Количество сохраняемых резервных копий',
|
||||
backupCopies: 'Копии Бэкапа',
|
||||
noBackupNode:
|
||||
'Резервный узел в настоящее время пуст. Выберите резервный узел для сохранения и повторите попытку!',
|
||||
masterBackupAlert:
|
||||
|
||||
@@ -2074,8 +2074,6 @@ const message = {
|
||||
bindDomain: 'Alan adı bağla',
|
||||
unBindDomain: 'Alan adı bağlamasını kaldır',
|
||||
panelSSL: 'Panel SSL',
|
||||
panelSSLHelper:
|
||||
'Panel SSL’nin otomatik yenilenmesinden sonra, değişikliklerin etkili olması için 1Panel servisini manuel olarak yeniden başlatmanız gerekir.',
|
||||
unBindDomainHelper:
|
||||
'Alan adı bağlamasını kaldırma işlemi sistem güvenliğini etkileyebilir. Devam etmek istiyor musunuz?',
|
||||
bindDomainHelper: 'Alan adı bağlandıktan sonra, yalnızca o alan adı 1Panel servisine erişebilir.',
|
||||
@@ -2228,7 +2226,7 @@ const message = {
|
||||
|
||||
about: 'Hakkında',
|
||||
versionItem: 'Mevcut Sürüm',
|
||||
backupCopies: 'Saklanacak Kopya Sayısı',
|
||||
backupCopies: 'Yedek Kopya',
|
||||
backupCopiesHelper:
|
||||
'Sürüm geri alma için saklanacak yükseltme yedek kopya sayısını ayarlayın. 0, tümünü sakla anlamına gelir.',
|
||||
backupCopiesRule: 'Lütfen en az 3 yükseltme yedek kaydı saklayın',
|
||||
@@ -3911,7 +3909,7 @@ const message = {
|
||||
masterBackup: 'Ana Düğüm Yedekleme',
|
||||
backupNode: 'Yedek Düğüm',
|
||||
backupFrequency: 'Yedekleme Sıklığı (saat)',
|
||||
backupCopies: 'Saklanacak yedek kopya sayısı',
|
||||
backupCopies: 'Yedek Kopya',
|
||||
noBackupNode: 'Yedek düğüm şu anda boş. Lütfen kaydetmek için bir yedek düğüm seçin ve tekrar deneyin!',
|
||||
masterBackupAlert:
|
||||
'Ana düğüm yedeklemesi şu anda yapılandırılmamış. Veri güvenliği için, lütfen arıza durumunda yeni bir ana düğüme manuel geçiş yapabilmek amacıyla en kısa sürede bir yedek düğüm ayarlayın.',
|
||||
|
||||
@@ -2006,7 +2006,6 @@ const message = {
|
||||
bindDomain: '網域綁定',
|
||||
unBindDomain: '域名解綁',
|
||||
panelSSL: '面板 SSL',
|
||||
panelSSLHelper: '面板 SSL 自動續期後需要手動重新啟動 1Panel 服務才可生效',
|
||||
unBindDomainHelper: '解除網域綁定可能造成系統不安全,是否繼續?',
|
||||
bindDomainHelper: '設定網域綁定後,僅能透過設定中域名存取 1Panel 服務',
|
||||
bindDomainHelper1: '綁定域名為空時,則取消網域綁定',
|
||||
@@ -2062,7 +2061,7 @@ const message = {
|
||||
|
||||
about: '關於',
|
||||
versionItem: '當前版本',
|
||||
backupCopies: '保留份數',
|
||||
backupCopies: '備份份數',
|
||||
backupCopiesHelper: '設定用於版本回滾的升級備份保留份數。0 表示保留所有。',
|
||||
backupCopiesRule: '請至少儲存 3 份升級備份記錄',
|
||||
release: '版本更新日誌',
|
||||
@@ -3580,7 +3579,7 @@ const message = {
|
||||
masterBackup: '主節點備份',
|
||||
backupNode: '備份節點',
|
||||
backupFrequency: '備份頻率(小時)',
|
||||
backupCopies: '備份記錄保留份數',
|
||||
backupCopies: '備份份數',
|
||||
noBackupNode: '目前備份節點為空,請選擇備份節點儲存後重試',
|
||||
masterBackupAlert:
|
||||
'目前未設定主節點備份,為保障資料安全,請盡快設定備份節點,便於主節點故障時可人工切換新主節點。',
|
||||
|
||||
@@ -1949,7 +1949,6 @@ const message = {
|
||||
bindDomain: '域名绑定',
|
||||
unBindDomain: '域名解绑',
|
||||
panelSSL: '面板 SSL',
|
||||
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
|
||||
unBindDomainHelper: '解除域名绑定可能造成系统不安全,是否继续?',
|
||||
bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务',
|
||||
bindDomainHelper1: '绑定域名为空时,则取消域名绑定',
|
||||
@@ -2032,7 +2031,7 @@ const message = {
|
||||
|
||||
about: '关于',
|
||||
versionItem: '当前版本',
|
||||
backupCopies: '保留份数',
|
||||
backupCopies: '备份份数',
|
||||
backupCopiesHelper: '设置用于版本回滚的升级备份保留份数,为 0 则保留所有。',
|
||||
backupCopiesRule: '请至少保存 3 份升级备份记录',
|
||||
release: '版本更新日志',
|
||||
@@ -3534,7 +3533,7 @@ const message = {
|
||||
masterBackup: '主节点备份',
|
||||
backupNode: '备份节点',
|
||||
backupFrequency: '备份频率(小时)',
|
||||
backupCopies: '备份记录保留份数',
|
||||
backupCopies: '备份份数',
|
||||
noBackupNode: '当前备份节点为空,请选择备份节点保存后重试!',
|
||||
masterBackupAlert:
|
||||
'当前未配置主节点备份,为保障数据安全,请尽快设置备份节点,便于主节点故障时可人工切换新主节点。',
|
||||
|
||||
@@ -39,7 +39,6 @@ import { GlobalStore, MenuStore } from '@/store';
|
||||
import { isString } from '@vueuse/core';
|
||||
import { getSettingInfo } from '@/api/modules/setting';
|
||||
import PrimaryMenu from '@/assets/images/menu-bg.svg?component';
|
||||
import { sortMenu } from '@/utils/util';
|
||||
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
@@ -72,13 +71,13 @@ const handleMenuClick = (path) => {
|
||||
emit('menuClick', path);
|
||||
};
|
||||
|
||||
function getCheckedLabels(menu: any, showMap: any) {
|
||||
function getCheckedLabels(menu: any, showSet: Set<string>) {
|
||||
for (const item of menu) {
|
||||
if (item.isShow) {
|
||||
showMap[item.label] = true;
|
||||
showSet.add(item.label);
|
||||
}
|
||||
if (item.children) {
|
||||
getCheckedLabels(item.children, showMap);
|
||||
getCheckedLabels(item.children, showSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,73 +87,50 @@ const openTask = () => {
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const res = await getSettingInfo();
|
||||
version.value = res.data.systemVersion;
|
||||
let hideMenu = JSON.parse(res.data.hideMenu);
|
||||
sortMenu(hideMenu);
|
||||
const showMap = new Map();
|
||||
getCheckedLabels(hideMenu, showMap);
|
||||
const rootMap = new Map();
|
||||
hideMenu.forEach((m, index) => {
|
||||
rootMap.set(m.label, index);
|
||||
});
|
||||
let rstMenuList: RouteRecordRaw[] = [];
|
||||
let resMenuList: RouteRecordRaw[] = [];
|
||||
resMenuList = adjustAndCleanMenu(hideMenu, menuStore.menuList);
|
||||
for (const menu of resMenuList) {
|
||||
let menuItem = JSON.parse(JSON.stringify(menu));
|
||||
if (!showMap[menuItem.name]) {
|
||||
continue;
|
||||
} else if (menuItem.name === 'Xpack-Menu') {
|
||||
menuItem.meta.hideInSidebar = false;
|
||||
}
|
||||
const childMenu = hideMenu.find((item) => item.label == menu.name);
|
||||
const childMap = buildIndexMap(childMenu?.children || []);
|
||||
const itemChildren =
|
||||
(menuItem.children ?? [])
|
||||
.filter(
|
||||
try {
|
||||
const res = await getSettingInfo();
|
||||
version.value = res.data.systemVersion;
|
||||
let hideMenu = JSON.parse(res.data.hideMenu);
|
||||
const showSet = new Set<string>();
|
||||
getCheckedLabels(hideMenu, showSet);
|
||||
const rstMenuList: RouteRecordRaw[] = [];
|
||||
const resMenuList = adjustAndCleanMenu(hideMenu, menuList);
|
||||
for (const menu of resMenuList) {
|
||||
let menuItem = JSON.parse(JSON.stringify(menu));
|
||||
if (!showSet.has(menuItem.name as string)) {
|
||||
continue;
|
||||
} else if (menuItem.name === 'Xpack-Menu') {
|
||||
menuItem.meta.hideInSidebar = false;
|
||||
}
|
||||
const itemChildren =
|
||||
(menuItem.children ?? []).filter(
|
||||
(item) =>
|
||||
item.name && showMap[item.name as string] && !(item.name === 'Upage' && globalStore.isIntl),
|
||||
)
|
||||
.sort(sortByMap(childMap)) || [];
|
||||
item.name && showSet.has(item.name as string) && !(item.name === 'Upage' && globalStore.isIntl),
|
||||
) || [];
|
||||
|
||||
if (itemChildren.length === 1) {
|
||||
menuItem.meta.icon = itemChildren[0].meta.icon;
|
||||
menuItem.meta.title = itemChildren[0].meta.title;
|
||||
if (itemChildren.length === 1) {
|
||||
menuItem.meta.icon = itemChildren[0].meta.icon;
|
||||
menuItem.meta.title = itemChildren[0].meta.title;
|
||||
}
|
||||
menuItem.children = itemChildren;
|
||||
rstMenuList.push(menuItem);
|
||||
}
|
||||
if (!isSameMenuList(menuStore.menuList as RouteRecordRaw[], rstMenuList)) {
|
||||
menuStore.setMenuList(rstMenuList);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!menuStore.menuList || menuStore.menuList.length === 0) {
|
||||
menuStore.setMenuList(menuList);
|
||||
}
|
||||
menuItem.children = itemChildren;
|
||||
rstMenuList.push(menuItem);
|
||||
}
|
||||
rstMenuList.sort((a, b) => {
|
||||
const labelA = a.name;
|
||||
const labelB = b.name;
|
||||
const indexA = rootMap.get(labelA) ?? Infinity;
|
||||
const indexB = rootMap.get(labelB) ?? Infinity;
|
||||
return indexA - indexB;
|
||||
});
|
||||
menuStore.menuList = rstMenuList;
|
||||
};
|
||||
|
||||
function buildIndexMap(list: any[]): Map<string, number> {
|
||||
const map = new Map<string, number>();
|
||||
list.forEach((m, i) => map.set(m.label, i));
|
||||
return map;
|
||||
}
|
||||
|
||||
function sortByMap(map: Map<string, number>) {
|
||||
return (a: { name: string }, b: { name: string }) => {
|
||||
const indexA = map.get(a.name) ?? Infinity;
|
||||
const indexB = map.get(b.name) ?? Infinity;
|
||||
return indexA - indexB;
|
||||
};
|
||||
function isSameMenuList(source: RouteRecordRaw[], target: RouteRecordRaw[]) {
|
||||
return JSON.stringify(source) === JSON.stringify(target);
|
||||
}
|
||||
|
||||
function adjustAndCleanMenu(menuItem, list) {
|
||||
const menuList = JSON.parse(JSON.stringify(list));
|
||||
const orderMap = new Map();
|
||||
menuItem.forEach((item, index) => {
|
||||
orderMap.set(item.label, index);
|
||||
});
|
||||
const itemMap = new Map();
|
||||
for (const parent of menuList) {
|
||||
itemMap.set(parent.name, parent);
|
||||
@@ -175,15 +151,7 @@ function adjustAndCleanMenu(menuItem, list) {
|
||||
if (!matched) continue;
|
||||
|
||||
if (Array.isArray(ref.children) && ref.children.length > 0) {
|
||||
const childMap = buildIndexMap(ref.children || []);
|
||||
matched.children = buildTree(ref.children);
|
||||
matched.children.sort((a, b) => {
|
||||
const labelA = a.name;
|
||||
const labelB = b.name;
|
||||
const indexA = childMap.get(labelA) ?? Infinity;
|
||||
const indexB = childMap.get(labelB) ?? Infinity;
|
||||
return indexA - indexB;
|
||||
});
|
||||
} else {
|
||||
delete matched.children;
|
||||
}
|
||||
@@ -195,11 +163,6 @@ function adjustAndCleanMenu(menuItem, list) {
|
||||
}
|
||||
|
||||
const newMenu = buildTree(menuItem);
|
||||
newMenu.sort((a, b) => {
|
||||
const indexA = orderMap.get(a.name) ?? Infinity;
|
||||
const indexB = orderMap.get(b.name) ?? Infinity;
|
||||
return indexA - indexB;
|
||||
});
|
||||
for (const menu of newMenu) {
|
||||
if (menu.children?.length === 1) {
|
||||
menu.meta.icon = menu.children[0].meta.icon;
|
||||
@@ -211,7 +174,9 @@ function adjustAndCleanMenu(menuItem, list) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
menuStore.setMenuList(menuList);
|
||||
if (!menuStore.menuList || menuStore.menuList.length === 0) {
|
||||
menuStore.setMenuList(menuList);
|
||||
}
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -60,7 +60,7 @@ const GlobalStore = defineStore({
|
||||
state.themeConfig.theme === 'dark' ||
|
||||
(state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches),
|
||||
isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro,
|
||||
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro/v1' : 'https://1panel.cn/docs/v2'),
|
||||
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro/v2' : 'https://1panel.cn/docs/v2'),
|
||||
isMaster: (state) => state.currentNode === 'local',
|
||||
},
|
||||
actions: {
|
||||
|
||||
@@ -108,6 +108,7 @@ import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
import { App } from '@/api/interface/app';
|
||||
import AdvancedSetting from '@/components/advanced-setting/index.vue';
|
||||
import AccountAddDialog from '@/views/ai/agents/model/add/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const emit = defineEmits(['search', 'task']);
|
||||
|
||||
@@ -121,6 +122,7 @@ const providerAccountCount = ref<Record<string, number>>({});
|
||||
const manualModel = ref(false);
|
||||
const appInfo = ref<App.AppDTO>();
|
||||
const accountAddRef = ref();
|
||||
const { isIntl } = useGlobalStore();
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
@@ -215,11 +217,13 @@ const loadProviders = async () => {
|
||||
}
|
||||
const res = await getAgentProviders();
|
||||
const data = res.data || [];
|
||||
providerOptions.value = data.map((item) => ({
|
||||
const blockedProviders = new Set(['ark-coding-plan', 'bailian-coding-plan']);
|
||||
const filteredData = isIntl.value ? data.filter((item) => !blockedProviders.has(item.provider)) : data;
|
||||
providerOptions.value = filteredData.map((item) => ({
|
||||
value: item.provider,
|
||||
label: getAgentProviderDisplayName(item.provider, item.displayName),
|
||||
}));
|
||||
providerModels.value = data.reduce((acc, item) => {
|
||||
providerModels.value = filteredData.reduce((acc, item) => {
|
||||
acc[item.provider] = item.models || [];
|
||||
return acc;
|
||||
}, {} as Record<string, AI.ProviderModelInfo[]>);
|
||||
|
||||
@@ -35,6 +35,7 @@ import { AI } from '@/api/interface/ai';
|
||||
import { getAgentProviders, pageAgentAccounts, updateAgentModelConfig } from '@/api/modules/ai';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const emit = defineEmits(['updated']);
|
||||
const { t } = useI18n();
|
||||
@@ -42,6 +43,8 @@ const { t } = useI18n();
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const { isIntl } = useGlobalStore();
|
||||
const blockedProviders = new Set(['ark-coding-plan', 'bailian-coding-plan']);
|
||||
|
||||
const agentId = ref(0);
|
||||
const providerModels = ref<Record<string, AI.ProviderModelInfo[]>>({});
|
||||
@@ -81,7 +84,8 @@ const loadProviders = async () => {
|
||||
}
|
||||
const res = await getAgentProviders();
|
||||
const data = res.data || [];
|
||||
providerModels.value = data.reduce((acc, item) => {
|
||||
const filteredData = isIntl.value ? data.filter((item) => !blockedProviders.has(item.provider)) : data;
|
||||
providerModels.value = filteredData.reduce((acc, item) => {
|
||||
acc[item.provider] = item.models || [];
|
||||
return acc;
|
||||
}, {} as Record<string, AI.ProviderModelInfo[]>);
|
||||
@@ -94,7 +98,8 @@ const loadAccounts = async () => {
|
||||
provider: '',
|
||||
name: '',
|
||||
});
|
||||
accountOptions.value = res.data.items || [];
|
||||
const items = res.data.items || [];
|
||||
accountOptions.value = isIntl.value ? items.filter((item) => !blockedProviders.has(item.provider)) : items;
|
||||
};
|
||||
|
||||
const setModelsByProvider = (provider: string) => {
|
||||
|
||||
@@ -76,6 +76,7 @@ import { Rules } from '@/global/form-rules';
|
||||
import { createAgentAccount, getAgentProviders, updateAgentAccount } from '@/api/modules/ai';
|
||||
import i18n from '@/lang';
|
||||
import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
@@ -85,6 +86,7 @@ const providerOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const providerBaseURL = ref<Record<string, string>>({});
|
||||
const loading = ref(false);
|
||||
const editableBaseURLProviders = ['ollama', 'custom', 'zai'];
|
||||
const { isIntl } = useGlobalStore();
|
||||
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
@@ -225,11 +227,13 @@ const openDrawer = async (params?: OpenParams) => {
|
||||
const loadProviders = async () => {
|
||||
const res = await getAgentProviders();
|
||||
const data = res.data || [];
|
||||
providerOptions.value = data.map((item) => ({
|
||||
const blockedProviders = new Set(['ark-coding-plan', 'bailian-coding-plan']);
|
||||
const filteredData = isIntl.value ? data.filter((item) => !blockedProviders.has(item.provider)) : data;
|
||||
providerOptions.value = filteredData.map((item) => ({
|
||||
value: item.provider,
|
||||
label: getAgentProviderDisplayName(item.provider, item.displayName),
|
||||
}));
|
||||
providerBaseURL.value = data.reduce((acc, item) => {
|
||||
providerBaseURL.value = filteredData.reduce((acc, item) => {
|
||||
acc[item.provider] = item.baseUrl || '';
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
@@ -63,12 +63,15 @@
|
||||
:placeholder="$t('website.selectSSL')"
|
||||
@change="changeSSl(req.sslID)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(ssl, index) in ssls"
|
||||
:key="index"
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
></el-option>
|
||||
<el-option v-for="(ssl, index) in ssls" :key="index" :label="ssl.primaryDomain" :value="ssl.id">
|
||||
<span>{{ ssl.primaryDomain }}</span>
|
||||
<el-tag class="tagClass" v-if="ssl.expireDate">
|
||||
{{ dateFormatSimple(ssl.expireDate) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass" v-if="ssl.organization">
|
||||
{{ ssl.organization }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -90,15 +93,17 @@ import { listSSL, searchAcmeAccount } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
import { getAccountName, dateFormatSimple } from '@/utils/util';
|
||||
import { bindMcpDomain, getMcpDomain, updateMcpDomain } from '@/api/modules/ai';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
type SSLItem = Website.SSL & { organization?: string };
|
||||
|
||||
const open = ref(false);
|
||||
const operate = ref('create');
|
||||
const loading = ref(false);
|
||||
const ssls = ref([]);
|
||||
const ssls = ref<SSLItem[]>([]);
|
||||
const websiteSSL = ref<Website.SSL>();
|
||||
const acmeAccounts = ref();
|
||||
const formRef = ref();
|
||||
@@ -225,4 +230,10 @@ defineExpose({
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.tagClass {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -62,12 +62,15 @@
|
||||
:placeholder="$t('website.selectSSL')"
|
||||
@change="changeSSl(req.sslID)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(ssl, index) in ssls"
|
||||
:key="index"
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
></el-option>
|
||||
<el-option v-for="(ssl, index) in ssls" :key="index" :label="ssl.primaryDomain" :value="ssl.id">
|
||||
<span>{{ ssl.primaryDomain }}</span>
|
||||
<el-tag class="tagClass" v-if="ssl.expireDate">
|
||||
{{ dateFormatSimple(ssl.expireDate) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass" v-if="ssl.organization">
|
||||
{{ ssl.organization }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-alert :closable="false">
|
||||
@@ -95,15 +98,17 @@ import { listSSL, searchAcmeAccount } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
import { getAccountName, dateFormatSimple } from '@/utils/util';
|
||||
import { bindDomain, getBindDomain, updateBindDomain } from '@/api/modules/ai';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
type SSLItem = Website.SSL & { organization?: string };
|
||||
|
||||
const open = ref(false);
|
||||
const operate = ref('create');
|
||||
const loading = ref(false);
|
||||
const ssls = ref([]);
|
||||
const ssls = ref<SSLItem[]>([]);
|
||||
const websiteSSL = ref<Website.SSL>();
|
||||
const acmeAccounts = ref();
|
||||
const formRef = ref();
|
||||
@@ -239,4 +244,10 @@ defineExpose({
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.tagClass {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
<template>
|
||||
<DrawerPro v-model="drawerVisible" :header="$t('setting.panelSSL')" @close="handleClose" size="large">
|
||||
<el-alert class="common-prompt" :closable="false" type="error">
|
||||
<template #default>
|
||||
<span>
|
||||
<span>{{ $t('setting.panelSSLHelper') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" v-loading="loading">
|
||||
<el-form-item :label="$t('setting.mode')" prop="ssl">
|
||||
<el-radio-group v-model="form.ssl">
|
||||
|
||||
@@ -67,7 +67,15 @@
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
:disabled="ssl.pem === ''"
|
||||
></el-option>
|
||||
>
|
||||
<span>{{ ssl.primaryDomain }}</span>
|
||||
<el-tag class="tagClass" v-if="ssl.expireDate">
|
||||
{{ dateFormatSimple(ssl.expireDate) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass" v-if="ssl.organization">
|
||||
{{ ssl.organization }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -139,11 +147,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, onMounted, ref } from 'vue';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
import { getAccountName, dateFormatSimple } from '@/utils/util';
|
||||
import WebsiteSSL from '@/views/website/website/components/website-ssl/index.vue';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { listSSL, searchAcmeAccount } from '@/api/modules/website';
|
||||
|
||||
type SSLItem = Website.SSL & { organization?: string };
|
||||
|
||||
interface AcmeAccount {
|
||||
id: number;
|
||||
email: string;
|
||||
@@ -187,7 +197,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const acmeAccounts = ref<AcmeAccount[]>([]);
|
||||
const ssls = ref<Website.SSL[]>([]);
|
||||
const ssls = ref<SSLItem[]>([]);
|
||||
const keyFileRef = ref();
|
||||
const certFileRef = ref();
|
||||
|
||||
@@ -298,4 +308,10 @@ onMounted(() => {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.tagClass {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -255,7 +255,15 @@
|
||||
:label="ssl.primaryDomain"
|
||||
:value="ssl.id"
|
||||
:disabled="ssl.pem == ''"
|
||||
></el-option>
|
||||
>
|
||||
<span>{{ ssl.primaryDomain }}</span>
|
||||
<el-tag class="tagClass" v-if="ssl.expireDate">
|
||||
{{ dateFormatSimple(ssl.expireDate) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass" v-if="ssl.organization">
|
||||
{{ ssl.organization }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="' '" v-if="websiteSSL && websiteSSL.id > 0">
|
||||
@@ -443,7 +451,7 @@ import { reactive, ref, watch } from 'vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { getRandomStr, getRuntimeLabel } from '@/utils/util';
|
||||
import { getRandomStr, getRuntimeLabel, dateFormatSimple } from '@/utils/util';
|
||||
import { getAppService } from '@/api/modules/app';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { getAccountName } from '@/utils/util';
|
||||
@@ -1098,4 +1106,10 @@ defineExpose({
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
.tagClass {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user