Compare commits

...
11 Commits
Author SHA1 Message Date
王贺 90d11a3b74 remove ssl helper (#12102) 2026-03-06 15:31:28 +08:00
CityFun 883f3db725 feat: add openclaw default tool config (#12096) 2026-03-06 13:40:03 +08:00
ssongliu 75da72c265 fix: avoid pulling build-only compose images (#12095)
Refs  #12092
2026-03-06 02:29:39 +00:00
ssongliu 78eef15f5b feat(ai): hide coding plan providers in intl mode (#12090) 2026-03-05 10:47:38 +00:00
ssongliu 735cf3cc71 chore(agent): downgrade docker/cli to v28.5.1 (#12091) 2026-03-05 18:38:42 +08:00
Rowan Chen 99c5d9a8b2 feat: show SSL certificate issuer and expiry date in certificate selectors (#12071) 2026-03-05 15:44:12 +08:00
Rowan Chen 3a0d2039d0 fix: set sort_order for newly installed apps to avoid random positioning (#12069) 2026-03-05 15:43:18 +08:00
dependabot[bot] 98e2d6390a chore(deps): bump github.com/docker/cli in /agent (#12079)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 28.5.1+incompatible to 29.2.0+incompatible.
- [Commits](https://github.com/docker/cli/compare/v28.5.1...v29.2.0)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 29.2.0+incompatible
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 12:02:34 +08:00
ssongliu 975e7f9f58 revert: drop nginx safe string validation changes (#12076) 2026-03-05 11:55:23 +08:00
王贺 77c90a26f8 chore: change screenshot (#12075)
Updated UI display screenshot in README.
2026-03-05 11:07:33 +08:00
ssongliu 17cb12df8b fix: avoid sidebar menu flicker when loading and sorting (#12066) 2026-03-05 02:37:04 +00:00
32 changed files with 298 additions and 318 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ bash -c "$(curl -sSL https://resource.1panel.pro/v2/quick_start.sh)"
## Screenshot
![UI Display](https://resource.1panel.pro/img/1panel.png)
![UI Display](https://resource.1panel.pro/img/overview_en_v2.png)
## Star History
+29 -3
View File
@@ -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)) == "" {
+4
View File
@@ -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
}
+2 -12
View File
@@ -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)
+1 -7
View File
@@ -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)
+8 -28
View File
@@ -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"})
}
+16 -6
View File
@@ -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
}
-102
View File
@@ -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
}
+24
View File
@@ -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 {
+8 -4
View File
@@ -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>
+2 -4
View File
@@ -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.',
+2 -4
View File
@@ -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.',
+2 -2
View File
@@ -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:
'現在マスターノードのバックアップが設定されていません。データセキュリティを確保するため、障害時に新しいマスターノードに手動で切り替えられるよう、速やかにバックアップノードを設定してください。',
+2 -2
View File
@@ -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:
'현재 마스터 노드 백업이 구성되지 않았습니다. 데이터 보안을 위해 장애 시 새로운 마스터 노드로 수동 전환이 가능하도록 가능한 빨리 백업 노드를 설정하십시오.',
+2 -2
View File
@@ -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.',
+2 -2
View File
@@ -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:
+2 -2
View File
@@ -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:
+2 -4
View File
@@ -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.',
+2 -3
View File
@@ -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:
'目前未設定主節點備份,為保障資料安全,請盡快設定備份節點,便於主節點故障時可人工切換新主節點。',
+2 -3
View File
@@ -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>
+1 -1
View File
@@ -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>