mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-18 05:19:19 +08:00
fix: Remove the jwt login mode (#8242)
This commit is contained in:
parent
b871c710b2
commit
11349642da
27 changed files with 153 additions and 268 deletions
|
|
@ -564,10 +564,12 @@ func loadTreeWithDir(isCheck bool, treeType, pathItem string, fileOp fileUtils.F
|
||||||
return lists
|
return lists
|
||||||
}
|
}
|
||||||
sort.Slice(files, func(i, j int) bool {
|
sort.Slice(files, func(i, j int) bool {
|
||||||
return files[i].Name() > files[j].Name()
|
infoI, _ := files[i].Info()
|
||||||
|
infoJ, _ := files[i].Info()
|
||||||
|
return infoI.ModTime().Before(infoJ.ModTime())
|
||||||
})
|
})
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if (treeType == "old_upgrade" || treeType == "upgrade") && !strings.HasPrefix(file.Name(), "upgrade_2023") {
|
if treeType == "old_upgrade" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if treeType == "task_log" && file.Name() == "ssl" {
|
if treeType == "task_log" && file.Name() == "ssl" {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||||
|
|
@ -25,7 +26,7 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.AuthMethod != "jwt" && !req.IgnoreCaptcha {
|
if !req.IgnoreCaptcha {
|
||||||
if errMsg := captcha.VerifyCode(req.CaptchaID, req.Captcha); errMsg != "" {
|
if errMsg := captcha.VerifyCode(req.CaptchaID, req.Captcha); errMsg != "" {
|
||||||
helper.BadAuth(c, errMsg, nil)
|
helper.BadAuth(c, errMsg, nil)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ type Login struct {
|
||||||
IgnoreCaptcha bool `json:"ignoreCaptcha"`
|
IgnoreCaptcha bool `json:"ignoreCaptcha"`
|
||||||
Captcha string `json:"captcha"`
|
Captcha string `json:"captcha"`
|
||||||
CaptchaID string `json:"captchaID"`
|
CaptchaID string `json:"captchaID"`
|
||||||
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
|
|
||||||
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR'"`
|
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +35,6 @@ type MFALogin struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code" validate:"required"`
|
||||||
AuthMethod string `json:"authMethod"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemSetting struct {
|
type SystemSetting struct {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/1Panel-dev/1Panel/core/constant"
|
"github.com/1Panel-dev/1Panel/core/constant"
|
||||||
"github.com/1Panel-dev/1Panel/core/global"
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/jwt"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/mfa"
|
"github.com/1Panel-dev/1Panel/core/utils/mfa"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
@ -59,7 +58,7 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d
|
||||||
if mfa.Value == constant.StatusEnable {
|
if mfa.Value == constant.StatusEnable {
|
||||||
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, "", nil
|
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, "", nil
|
||||||
}
|
}
|
||||||
res, err := u.generateSession(c, info.Name, info.AuthMethod)
|
res, err := u.generateSession(c, info.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
@ -96,14 +95,14 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin
|
||||||
if !success {
|
if !success {
|
||||||
return nil, "ErrAuth", nil
|
return nil, "ErrAuth", nil
|
||||||
}
|
}
|
||||||
res, err := u.generateSession(c, info.Name, info.AuthMethod)
|
res, err := u.generateSession(c, info.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
return res, "", nil
|
return res, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (*dto.UserLoginInfo, error) {
|
func (u *AuthService) generateSession(c *gin.Context, name string) (*dto.UserLoginInfo, error) {
|
||||||
setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout"))
|
setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -117,18 +116,6 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if authMethod == constant.AuthMethodJWT {
|
|
||||||
j := jwt.NewJWT()
|
|
||||||
claims := j.CreateClaims(jwt.BaseClaims{
|
|
||||||
Name: name,
|
|
||||||
IsAgent: false,
|
|
||||||
})
|
|
||||||
token, err := j.CreateToken(claims)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &dto.UserLoginInfo{Name: name, Token: token}, nil
|
|
||||||
}
|
|
||||||
sessionUser, err := global.SESSION.Get(c)
|
sessionUser, err := global.SESSION.Get(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := global.SESSION.Set(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime)
|
err := global.SESSION.Set(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime)
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,11 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||||
_ = settingRepo.Update("SystemVersion", req.Version)
|
_ = settingRepo.Update("SystemVersion", req.Version)
|
||||||
global.CONF.Base.Version = req.Version
|
global.CONF.Base.Version = req.Version
|
||||||
_ = settingRepo.Update("SystemStatus", "Free")
|
_ = settingRepo.Update("SystemStatus", "Free")
|
||||||
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1pane-agent.service && systemctl restart 1panel-core.service", 1*time.Minute)
|
|
||||||
|
go func() {
|
||||||
|
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1pane-agent.service", 1*time.Minute)
|
||||||
|
}()
|
||||||
|
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1panel-core.service", 1*time.Minute)
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,5 @@ const (
|
||||||
AuthMethodSession = "session"
|
AuthMethodSession = "session"
|
||||||
SessionName = "psession"
|
SessionName = "psession"
|
||||||
|
|
||||||
AuthMethodJWT = "jwt"
|
|
||||||
JWTHeaderName = "PanelAuthorization"
|
|
||||||
JWTBufferTime = 3600
|
|
||||||
JWTIssuer = "1Panel"
|
|
||||||
|
|
||||||
PasswordExpiredName = "expired"
|
PasswordExpiredName = "expired"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ require (
|
||||||
github.com/go-playground/validator/v10 v10.22.0
|
github.com/go-playground/validator/v10 v10.22.0
|
||||||
github.com/go-resty/resty/v2 v2.15.3
|
github.com/go-resty/resty/v2 v2.15.3
|
||||||
github.com/goh-chunlin/go-onedrive v1.1.1
|
github.com/goh-chunlin/go-onedrive v1.1.1
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/securecookie v1.1.2
|
github.com/gorilla/securecookie v1.1.2
|
||||||
github.com/gorilla/sessions v1.4.0
|
github.com/gorilla/sessions v1.4.0
|
||||||
|
|
@ -30,7 +29,6 @@ require (
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/spf13/afero v1.11.0
|
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
github.com/studio-b12/gowebdav v0.9.0
|
github.com/studio-b12/gowebdav v0.9.0
|
||||||
|
|
@ -105,6 +103,7 @@ require (
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,6 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12UuFc+PW8m4=
|
github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12UuFc+PW8m4=
|
||||||
github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs=
|
github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
|
|
||||||
|
|
@ -430,115 +430,134 @@ exit 0`
|
||||||
func loadInstallClamAV() string {
|
func loadInstallClamAV() string {
|
||||||
return `#!/bin/bash
|
return `#!/bin/bash
|
||||||
|
|
||||||
# 检查是否具有 sudo 权限
|
# ClamAV 安装配置脚本
|
||||||
if [ "$EUID" -ne 0 ]; then
|
# 支持系统:Ubuntu/Debian/CentOS/RHEL/Rocky/AlmaLinux
|
||||||
echo "请使用 sudo 或以 root 用户运行此脚本"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检测操作系统类型
|
# 识别系统类型
|
||||||
if [ -f /etc/os-release ]; then
|
if [ -f /etc/os-release ]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
OS=$ID
|
OS=$ID
|
||||||
OS_LIKE=$(echo $ID_LIKE | awk '{print $1}') # 获取类似的发行版信息
|
OS_VER=$VERSION_ID
|
||||||
|
elif type lsb_release >/dev/null 2>&1; then
|
||||||
|
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
|
||||||
|
OS_VER=$(lsb_release -sr)
|
||||||
else
|
else
|
||||||
echo "无法检测操作系统类型"
|
echo "无法识别操作系统"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 安装 ClamAV
|
# 安装ClamAV
|
||||||
if [ "$OS" == "ubuntu" ] || [ "$OS" == "debian" ]; then
|
install_clamav() {
|
||||||
echo "检测到 Debian/Ubuntu 系统,正在安装 ClamAV..."
|
case $OS in
|
||||||
|
ubuntu|debian)
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y clamav clamav-daemon clamav-freshclam
|
apt-get install -y clamav clamav-daemon
|
||||||
elif [ "$OS" == "centos" ] || [ "$OS" == "rhel" ] || [ "$OS_LIKE" == "rhel" ]; then
|
;;
|
||||||
echo "检测到 Red Hat/CentOS 系统,正在安装 ClamAV..."
|
centos|rhel|rocky|almalinux)
|
||||||
|
if [[ $OS_VER == 7* ]]; then
|
||||||
yum install -y epel-release
|
yum install -y epel-release
|
||||||
yum install -y clamav clamd clamav-update
|
yum install -y clamav clamd clamav-update
|
||||||
else
|
else
|
||||||
echo "不支持的操作系统: $OS"
|
dnf install -y clamav clamd clamav-update
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "不支持的OS: $OS"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# 配置 clamd
|
# 配置 clamd
|
||||||
CLAMD_CONF="/etc/clamd.d/scan.conf"
|
configure_clamd() {
|
||||||
if [ -f "$CLAMD_CONF" ]; then
|
CLAMD_CONF=""
|
||||||
echo "配置 clamd..."
|
if [ -f "/etc/clamd.d/scan.conf" ]; then
|
||||||
|
CLAMD_CONF="/etc/clamd.d/scan.conf"
|
||||||
|
elif [ -f "/etc/clamav/clamd.conf" ]; then
|
||||||
|
CLAMD_CONF="/etc/clamav/clamd.conf"
|
||||||
|
else
|
||||||
|
echo "未找到 freshclam 配置文件,请手动配置"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "配置 clamd $CLAMD_CONF..."
|
||||||
# 备份原始配置文件
|
# 备份原始配置文件
|
||||||
cp "$CLAMD_CONF" "$CLAMD_CONF.bak"
|
cp "$CLAMD_CONF" "$CLAMD_CONF.bak"
|
||||||
|
|
||||||
# 修改配置文件
|
# 修改配置文件
|
||||||
sed -i 's|^#LogFile .*|LogFile /var/log/clamd.scan|' "$CLAMD_CONF"
|
sed -i 's|^LogFileMaxSize .*|LogFileMaxSize 2M|' "$CLAMD_CONF"
|
||||||
sed -i 's|^#LogFileMaxSize .*|LogFileMaxSize 2M|' "$CLAMD_CONF"
|
sed -i 's|^PidFile .*|PidFile /run/clamd.scan/clamd.pid|' "$CLAMD_CONF"
|
||||||
sed -i 's|^#PidFile .*|PidFile /run/clamd.scan/clamd.pid|' "$CLAMD_CONF"
|
sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$CLAMD_CONF"
|
||||||
sed -i 's|^#DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$CLAMD_CONF"
|
sed -i 's|^LocalSocket .*|LocalSocket /run/clamd.scan/clamd.sock|' "$CLAMD_CONF"
|
||||||
sed -i 's|^#LocalSocket .*|LocalSocket /run/clamd.scan/clamd.sock|' "$CLAMD_CONF"
|
}
|
||||||
else
|
|
||||||
echo "未找到 clamd 配置文件,请手动配置"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 配置 freshclam
|
# 配置 freshclam
|
||||||
FRESHCLAM_CONF="/etc/freshclam.conf"
|
configure_freshclam() {
|
||||||
if [ -f "$FRESHCLAM_CONF" ]; then
|
FRESHCLAM_CONF=""
|
||||||
echo "配置 freshclam..."
|
if [ -f "/etc/freshclam.conf" ]; then
|
||||||
|
FRESHCLAM_CONF="/etc/freshclam.conf"
|
||||||
|
elif [ -f "/etc/clamav/freshclam.conf" ]; then
|
||||||
|
FRESHCLAM_CONF="/etc/clamav/freshclam.conf"
|
||||||
|
else
|
||||||
|
echo "未找到 freshclam 配置文件,请手动配置"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "freshclam.con $FRESHCLAM_CONF..."
|
||||||
# 备份原始配置文件
|
# 备份原始配置文件
|
||||||
cp "$FRESHCLAM_CONF" "$FRESHCLAM_CONF.bak"
|
cp "$FRESHCLAM_CONF" "$FRESHCLAM_CONF.bak"
|
||||||
|
|
||||||
# 修改配置文件
|
# 修改配置文件
|
||||||
sed -i 's|^#DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$FRESHCLAM_CONF"
|
sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$FRESHCLAM_CONF"
|
||||||
sed -i 's|^#UpdateLogFile .*|UpdateLogFile /var/log/freshclam.log|' "$FRESHCLAM_CONF"
|
sed -i 's|^PidFile .*|PidFile /var/run/freshclam.pid|' "$FRESHCLAM_CONF"
|
||||||
sed -i 's|^#PidFile .*|PidFile /var/run/freshclam.pid|' "$FRESHCLAM_CONF"
|
sed -i 's/DatabaseMirror db.local.clamav.net/DatabaseMirror database.clamav.net/' "$FRESHCLAM_CONF"
|
||||||
sed -i 's|^#DatabaseMirror .*|DatabaseMirror database.clamav.net|' "$FRESHCLAM_CONF"
|
sed -i 's|^Checks .*|Checks 12|' "$FRESHCLAM_CONF"
|
||||||
sed -i 's|^#Checks .*|Checks 12|' "$FRESHCLAM_CONF"
|
}
|
||||||
else
|
|
||||||
echo "未找到 freshclam 配置文件,请手动配置"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建必要的目录和文件
|
# 服务管理
|
||||||
echo "创建必要的目录和文件..."
|
setup_service() {
|
||||||
mkdir -p /run/clamd.scan
|
case $OS in
|
||||||
chown clamav:clamav /run/clamd.scan
|
ubuntu|debian)
|
||||||
mkdir -p /var/log/clamav
|
systemctl stop clamav-freshclam
|
||||||
chown clamav:clamav /var/log/clamav
|
|
||||||
touch /var/log/clamd.scan /var/log/freshclam.log
|
|
||||||
chown clamav:clamav /var/log/clamd.scan /var/log/freshclam.log
|
|
||||||
|
|
||||||
# 设置开机自启动并启动服务
|
|
||||||
if command -v systemctl &> /dev/null; then
|
|
||||||
echo "设置 ClamAV 开机自启动..."
|
|
||||||
systemctl enable clamav-daemon
|
|
||||||
systemctl enable clamav-freshclam
|
|
||||||
|
|
||||||
echo "启动 ClamAV 服务..."
|
|
||||||
systemctl start clamav-daemon
|
systemctl start clamav-daemon
|
||||||
|
systemctl enable clamav-daemon
|
||||||
systemctl start clamav-freshclam
|
systemctl start clamav-freshclam
|
||||||
|
systemctl enable clamav-freshclam
|
||||||
if systemctl is-active --quiet clamav-daemon && systemctl is-active --quiet clamav-freshclam; then
|
;;
|
||||||
echo "ClamAV 已成功安装并启动"
|
centos|rhel|rocky|almalinux)
|
||||||
|
if [[ $OS_VER == 7* ]]; then
|
||||||
|
systemctl stop freshclam
|
||||||
|
systemctl start clamd@scan
|
||||||
|
systemctl enable clamd@scan
|
||||||
|
systemctl start freshclam
|
||||||
|
systemctl enable freshclam
|
||||||
else
|
else
|
||||||
echo "ClamAV 启动失败,请检查日志"
|
systemctl stop clamav-freshclam
|
||||||
exit 1
|
systemctl start clamd@scan
|
||||||
|
systemctl enable clamd@scan
|
||||||
|
systemctl start clamav-freshclam
|
||||||
|
systemctl enable clamav-freshclam
|
||||||
fi
|
fi
|
||||||
else
|
;;
|
||||||
echo "systemctl 不可用,请手动启动 ClamAV"
|
esac
|
||||||
exit 1
|
}
|
||||||
fi
|
|
||||||
|
|
||||||
# 更新病毒数据库
|
# 主执行流程
|
||||||
echo "更新 ClamAV 病毒数据库..."
|
echo "正在安装 ClamAV..."
|
||||||
freshclam
|
install_clamav
|
||||||
|
|
||||||
# 检查 ClamAV 是否正常运行
|
echo -e "\n\n配置 clamd..."
|
||||||
if clamscan --version &> /dev/null; then
|
configure_clamd
|
||||||
echo "ClamAV 安装完成并正常运行!"
|
|
||||||
else
|
|
||||||
echo "ClamAV 安装或配置出现问题,请检查日志"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0`
|
echo -e "\n\n配置 freshclam..."
|
||||||
|
configure_freshclam
|
||||||
|
|
||||||
|
echo -e "\n\n设置服务..."
|
||||||
|
setup_service
|
||||||
|
|
||||||
|
echo -e "\n\n安装完成!"
|
||||||
|
|
||||||
|
echo 0`
|
||||||
}
|
}
|
||||||
func loadUninstallClamAV() string {
|
func loadUninstallClamAV() string {
|
||||||
return `#!/bin/bash
|
return `#!/bin/bash
|
||||||
|
|
|
||||||
|
|
@ -207,30 +207,29 @@ func Routers() *gin.Engine {
|
||||||
|
|
||||||
swaggerRouter := Router.Group("1panel")
|
swaggerRouter := Router.Group("1panel")
|
||||||
docs.SwaggerInfo.BasePath = "/api/v2"
|
docs.SwaggerInfo.BasePath = "/api/v2"
|
||||||
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
swaggerRouter.Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||||
PublicGroup := Router.Group("")
|
PublicGroup := Router.Group("")
|
||||||
{
|
{
|
||||||
PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
setWebStatic(PublicGroup)
|
setWebStatic(PublicGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
Router.Use(middleware.OperationLog())
|
|
||||||
Router.Use(middleware.PasswordExpired())
|
|
||||||
if global.CONF.Base.IsDemo {
|
if global.CONF.Base.IsDemo {
|
||||||
Router.Use(middleware.DemoHandle())
|
Router.Use(middleware.DemoHandle())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Router.Use(middleware.OperationLog())
|
||||||
|
Router.Use(middleware.GlobalLoading())
|
||||||
|
Router.Use(middleware.PasswordExpired())
|
||||||
|
Router.Use(middleware.WhiteAllow())
|
||||||
|
Router.Use(middleware.BindDomain())
|
||||||
|
|
||||||
PrivateGroup := Router.Group("/api/v2/core")
|
PrivateGroup := Router.Group("/api/v2/core")
|
||||||
PrivateGroup.Use(middleware.WhiteAllow())
|
|
||||||
PrivateGroup.Use(middleware.BindDomain())
|
|
||||||
PrivateGroup.Use(middleware.SetPasswordPublicKey())
|
PrivateGroup.Use(middleware.SetPasswordPublicKey())
|
||||||
for _, router := range rou.RouterGroupApp {
|
for _, router := range rou.RouterGroupApp {
|
||||||
router.InitRouter(PrivateGroup)
|
router.InitRouter(PrivateGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
Router.Use(middleware.JwtAuth())
|
|
||||||
Router.Use(middleware.SessionAuth())
|
Router.Use(middleware.SessionAuth())
|
||||||
Router.Use(middleware.GlobalLoading())
|
|
||||||
Router.Use(Proxy())
|
Router.Use(Proxy())
|
||||||
Router.NoRoute(func(c *gin.Context) {
|
Router.NoRoute(func(c *gin.Context) {
|
||||||
if !checkBindDomain(c) {
|
if !checkBindDomain(c) {
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/constant"
|
|
||||||
jwtUtils "github.com/1Panel-dev/1Panel/core/utils/jwt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func JwtAuth() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
token := c.Request.Header.Get(constant.JWTHeaderName)
|
|
||||||
if token == "" {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
j := jwtUtils.NewJWT()
|
|
||||||
claims, err := j.ParseToken(token)
|
|
||||||
if err != nil {
|
|
||||||
helper.BadAuth(c, "ErrInternalServer", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if claims.BaseClaims.IsAgent {
|
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/agent/") {
|
|
||||||
c.Set("claims", claims)
|
|
||||||
c.Set("authMethod", constant.AuthMethodJWT)
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
helper.BadAuth(c, "ErrInternalServer", fmt.Errorf("err token from request"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/agent/") {
|
|
||||||
helper.BadAuth(c, "ErrInternalServer", fmt.Errorf("err token from request"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Set("claims", claims)
|
|
||||||
c.Set("authMethod", constant.AuthMethodJWT)
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -22,10 +22,6 @@ func SessionAuth() gin.HandlerFunc {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if method, exist := c.Get("authMethod"); exist && method == constant.AuthMethodJWT {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
panelToken := c.GetHeader("1Panel-Token")
|
panelToken := c.GetHeader("1Panel-Token")
|
||||||
panelTimestamp := c.GetHeader("1Panel-Timestamp")
|
panelTimestamp := c.GetHeader("1Panel-Timestamp")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ type AppLauncherRouter struct{}
|
||||||
|
|
||||||
func (s *AppLauncherRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *AppLauncherRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
launcherRouter := Router.Group("launcher").
|
launcherRouter := Router.Group("launcher").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ type CommandRouter struct{}
|
||||||
|
|
||||||
func (s *CommandRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *CommandRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
commandRouter := Router.Group("commands").
|
commandRouter := Router.Group("commands").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ type BackupRouter struct{}
|
||||||
|
|
||||||
func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
backupRouter := Router.Group("backups").
|
backupRouter := Router.Group("backups").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ type GroupRouter struct {
|
||||||
|
|
||||||
func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) {
|
func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
groupRouter := Router.Group("groups").
|
groupRouter := Router.Group("groups").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ type HostRouter struct{}
|
||||||
|
|
||||||
func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
hostRouter := Router.Group("hosts").
|
hostRouter := Router.Group("hosts").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ type LogRouter struct{}
|
||||||
|
|
||||||
func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
operationRouter := Router.Group("logs").
|
operationRouter := Router.Group("logs").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ type ScriptRouter struct{}
|
||||||
|
|
||||||
func (s *ScriptRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *ScriptRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
scriptRouter := Router.Group("script").
|
scriptRouter := Router.Group("script").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,8 @@ type SettingRouter struct{}
|
||||||
|
|
||||||
func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||||
router := Router.Group("settings").
|
router := Router.Group("settings").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth())
|
Use(middleware.SessionAuth())
|
||||||
settingRouter := Router.Group("settings").
|
settingRouter := Router.Group("settings").
|
||||||
Use(middleware.JwtAuth()).
|
|
||||||
Use(middleware.SessionAuth()).
|
Use(middleware.SessionAuth()).
|
||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package jwt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/constant"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JWT struct {
|
|
||||||
SigningKey []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomClaims struct {
|
|
||||||
BaseClaims
|
|
||||||
BufferTime int64
|
|
||||||
jwt.RegisteredClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseClaims struct {
|
|
||||||
ID uint
|
|
||||||
Name string
|
|
||||||
IsAgent bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJWT() *JWT {
|
|
||||||
settingRepo := repo.NewISettingRepo()
|
|
||||||
jwtSign, _ := settingRepo.Get(repo.WithByKey("JWTSigningKey"))
|
|
||||||
return &JWT{
|
|
||||||
[]byte(jwtSign.Value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) CreateClaims(baseClaims BaseClaims) CustomClaims {
|
|
||||||
claims := CustomClaims{
|
|
||||||
BaseClaims: baseClaims,
|
|
||||||
BufferTime: constant.JWTBufferTime,
|
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(constant.JWTBufferTime))),
|
|
||||||
Issuer: constant.JWTIssuer,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return claims
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) CreateToken(request CustomClaims) (string, error) {
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &request)
|
|
||||||
return token.SignedString(j.SigningKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) ParseToken(tokenStr string) (*CustomClaims, error) {
|
|
||||||
token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
||||||
return j.SigningKey, nil
|
|
||||||
})
|
|
||||||
if err != nil || token == nil {
|
|
||||||
return nil, buserr.WithDetail("ErrTokenParse", "", err)
|
|
||||||
}
|
|
||||||
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
|
|
||||||
return claims, nil
|
|
||||||
}
|
|
||||||
return nil, buserr.New("ErrTokenParse")
|
|
||||||
}
|
|
||||||
|
|
@ -42,7 +42,7 @@ export const scan = () => {
|
||||||
return http.post<Toolbox.CleanData>(`/toolbox/scan`, {});
|
return http.post<Toolbox.CleanData>(`/toolbox/scan`, {});
|
||||||
};
|
};
|
||||||
export const clean = (param: any) => {
|
export const clean = (param: any) => {
|
||||||
return http.post(`/toolbox/clean`, param);
|
return http.post(`/toolbox/clean`, param, TimeoutEnum.T_5M);
|
||||||
};
|
};
|
||||||
|
|
||||||
// fail2ban
|
// fail2ban
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,7 @@ const initLog = async () => {
|
||||||
if (editorRef.value && scrollerElement.value == undefined) {
|
if (editorRef.value && scrollerElement.value == undefined) {
|
||||||
const parentElement = editorRef.value.$el as HTMLElement;
|
const parentElement = editorRef.value.$el as HTMLElement;
|
||||||
scrollerElement.value = parentElement.querySelector('.hljs') as HTMLElement;
|
scrollerElement.value = parentElement.querySelector('.hljs') as HTMLElement;
|
||||||
scrollerElement.value.style['min-height'] = '100px';
|
scrollerElement.value.style['height'] = 'calc(100vh - ' + props.heightDiff + 'px)';
|
||||||
scrollerElement.value.style['max-height'] = 'calc(100vh - ' + props.heightDiff + 'px)';
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ const onUpgrade = async () => {
|
||||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
type: 'info',
|
type: 'info',
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
globalStore.isLoading = true;
|
|
||||||
await upgrade(upgradeVersion.value);
|
await upgrade(upgradeVersion.value);
|
||||||
|
globalStore.isLoading = true;
|
||||||
globalStore.isOnRestart = true;
|
globalStore.isOnRestart = true;
|
||||||
drawerVisible.value = false;
|
drawerVisible.value = false;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div tabindex="0">
|
<div>
|
||||||
<el-tabs
|
<el-tabs
|
||||||
type="card"
|
type="card"
|
||||||
class="terminal-tabs"
|
class="terminal-tabs"
|
||||||
|
|
@ -168,7 +168,6 @@ const toggleFullscreen = () => {
|
||||||
if (screenfull.isEnabled) {
|
if (screenfull.isEnabled) {
|
||||||
screenfull.toggle();
|
screenfull.toggle();
|
||||||
}
|
}
|
||||||
globalStore.isFullScreen = !screenfull.isFullscreen;
|
|
||||||
};
|
};
|
||||||
const loadTooltip = () => {
|
const loadTooltip = () => {
|
||||||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||||
|
|
@ -403,16 +402,25 @@ function syncTerminal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changeFullScreen = () => {
|
||||||
|
globalStore.isFullScreen = screenfull.isFullscreen;
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
acceptParams,
|
acceptParams,
|
||||||
cleanTimer,
|
cleanTimer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener('fullscreenchange', changeFullScreen);
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (router.currentRoute.value.query.path) {
|
if (router.currentRoute.value.query.path) {
|
||||||
const path = String(router.currentRoute.value.query.path);
|
const path = String(router.currentRoute.value.query.path);
|
||||||
initCmd.value = `cd "${path}" \n`;
|
initCmd.value = `cd "${path}" \n`;
|
||||||
}
|
}
|
||||||
|
document.addEventListener('fullscreenchange', changeFullScreen);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@
|
||||||
<el-option :value="500" :label="500" />
|
<el-option :value="500" :label="500" />
|
||||||
<el-option :value="1000" :label="1000" />
|
<el-option :value="1000" :label="1000" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<HighlightLog :modelValue="logContent" />
|
<HighlightLog :modelValue="logContent" :heightDiff="533" />
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
|
||||||
|
|
@ -643,6 +643,10 @@ function load18n(label: string) {
|
||||||
case 'System':
|
case 'System':
|
||||||
case 'Website':
|
case 'Website':
|
||||||
return i18n.global.t('menu.' + label.toLowerCase());
|
return i18n.global.t('menu.' + label.toLowerCase());
|
||||||
|
case 'Compose':
|
||||||
|
return i18n.global.t('container.compose');
|
||||||
|
case 'CustomAppstore':
|
||||||
|
return i18n.global.t('xpack.customApp.name');
|
||||||
case 'RuntimeExtension':
|
case 'RuntimeExtension':
|
||||||
return i18n.global.t('website.runtime');
|
return i18n.global.t('website.runtime');
|
||||||
case 'Image':
|
case 'Image':
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue