Compare commits

..
Author SHA1 Message Date
wanghe 3cd0c89c4c Update CONTRIBUTING.md 2024-03-20 22:36:07 +08:00
ssongliu c27fcf18fc feat: 升级依赖 fit2cloud-ui-plus (#4250) 2024-03-20 10:50:07 +00:00
zhengkunwang b1f20d125e fix: 解决 i18n 导致页面错误的问题 (#4247) 2024-03-20 08:22:07 +00:00
zhengkunwang cd43bff44d fix: 解决文件选择框关闭再打开依然选中的问题 (#4243) 2024-03-20 03:32:07 +00:00
ssongliu 8cf8375911 feat: 镜像拉取日志 Unicode 转义 (#4239) 2024-03-19 14:40:06 +00:00
zhengkunwang ebc3195df4 feat: ACL 规则增加匹配方式 (#4237) 2024-03-19 12:58:06 +00:00
zhengkunwang ce69f6a142 fix: 解决应用安装快速跳转失败的问题 (#4236) 2024-03-19 09:12:07 +00:00
zhengkunwang f6777d51b5 fix: 解决应用安装快速跳转失败的问题 (#4235) 2024-03-19 07:36:06 +00:00
zhengkunwang d916f2ba6f fix: 解决网站配置文件更新之后参数丢失的问题 (#4231)
Refs https://github.com/1Panel-dev/1Panel/issues/4171
2024-03-19 05:50:06 +00:00
zhengkunwang 0e159f944a feat: 修改应用已安装和可升级页面的标签选择 (#4229) 2024-03-18 14:34:10 +00:00
Anye 329bb94d3c 增加PHP模板 (#4227)
* PHP模板增加“苹果CMS-V10”

* PHP增加“SeaCMS”扩展模板
2024-03-18 22:32:06 +08:00
zhengkunwang 7f2f7fa195 feat(waf): 优化配置更新接口 (#4226) 2024-03-18 13:16:10 +00:00
ssongliu 67c79bcde4 fix: 还原 Makefile 内容 (#4225) 2024-03-18 09:50:08 +00:00
zhengkunwang d7d7c7fd73 feat(waf): 增加内部接口请求安全性 (#4224) 2024-03-18 09:36:08 +00:00
ssongliu 94028d97cf style: 修改重启面板及服务器按钮位置 (#4221) 2024-03-18 09:34:08 +00:00
zhengkunwang cbfedb5d5e feat(waf): 优化请求次数记录方式 (#4210) 2024-03-16 14:44:06 +00:00
ssongliu 7784ee9575 fix: 解决快照恢复时校验 daemon.json 路径失败的问题 (#4205)
Refs #4206
2024-03-16 02:12:06 +00:00
ssongliu 29d5d82fa5 feat: 增加 xpack Makefile (#4204) 2024-03-15 09:26:08 +00:00
zhengkunwang 46f01032b8 fix: 解决回收站文件放置错误问题 (#4203)
Refs https://github.com/1Panel-dev/1Panel/issues/4199
2024-03-15 08:40:07 +00:00
zhengkunwang 341f45b4ef feat: 修改网站导入证书的描述 (#4202)
Refs https://github.com/1Panel-dev/1Panel/issues/4184
2024-03-15 08:08:07 +00:00
ssongliu a0907592b2 fix: 解决未登录情况下的页面跳转问题 (#4195) 2024-03-15 08:00:08 +00:00
zhengkunwang 825ebd19fb feat: 守护进程增加加载状态 (#4201)
Refs https://github.com/1Panel-dev/1Panel/issues/4129
2024-03-15 07:40:08 +00:00
zhengkunwang 8f24432a01 feat: 网站搜索增加网站关联域名匹配 (#4198)
Refs https://github.com/1Panel-dev/1Panel/pull/4198
2024-03-15 07:38:08 +00:00
zhengkunwang 6ce35b723b fix: 解决 Node 运行环境状态异常问题 (#4197) 2024-03-15 07:36:07 +00:00
ssongliu a33eabb4f0 fix: 移除计划任务报告中的 ANSI 转义 (#4193)
Refs #4097
2024-03-15 03:56:07 +00:00
ssongliu 34a6587ebc fix: 升级依赖 jackc/pgx/v5 (#4190) 2024-03-15 02:30:07 +00:00
ssongliu 17020ffe9f fix: 解决计划任务列表备份账号显示问题 (#4187)
Refs #4096
2024-03-14 10:24:07 +00:00
ssongliu 0f9b0d5d82 feat: 同步数据库时标记已删除数据库 (#4185)
Refs #4165
2024-03-14 10:22:07 +00:00
zhengkunwang be36103475 feat: 增加应用商店显示的过滤数量 (#4183) 2024-03-14 08:02:06 +00:00
ssongliu a3b5869c2b fix: 移除未使用依赖 2024-03-14 15:19:39 +08:00
maninhill 0807da41a5 Update README.md 2024-03-14 15:19:04 +08:00
maninhill ea6870e3c9 Update README.md 2024-03-14 15:07:13 +08:00
ssongliu 70b7daf23a fix: 升级依赖 google.golang.org/protobuf (#4176) 2024-03-14 03:24:06 +00:00
建国 1d01fa1e14 fix: 解决计划任务报告刷新异常的问题 (#4177)
Refs #4175
2024-03-14 03:14:06 +00:00
Akirami b643fa2ca3 fix: 修复应用商店详情页面部分图标比例错误 (#4168)
* fix: 修复应用商店详情页面部分图标比例错误

* fix: 替换为内联样式
2024-03-13 22:28:03 +08:00
zhengkunwang 77f4f64f0d feat(waf): 优化规则 (#4166) 2024-03-12 12:34:07 +00:00
ssongliu 3b697c7520 feat: 快照支持设置排除目录 (#4154)
Refs #4076
2024-03-12 03:04:08 +00:00
刘瑞斌 8ca6b9b6fa chore: add pnpm-lock.yaml to .gitignore (#4162) 2024-03-12 10:40:37 +08:00
zhengkunwang 0b66be7a3a fix: 解决安装应用过程中出现的状态异常问题 (#4156) 2024-03-11 12:34:08 +00:00
ssongliu f9f68a975c feat: 增加界面设置功能 (#4128) 2024-03-11 09:12:08 +00:00
L1nyz-tel ba63907a65 fix: 解决命令注入waf被绕过的问题 (#4131) 2024-03-08 22:43:56 +08:00
zhengkunwang 2687c91e65 feat: 升级 github.com/go-jose/go-jose/v3 版本到 v3.0.3 (#4126) 2024-03-08 08:50:06 +00:00
zhengkunwang eb82c168c5 feat: 升级 github.com/go-jose/go-jose/v3 版本到 v3.0.3 (#4121) 2024-03-08 08:14:07 +00:00
ssongliu 21b1242a94 fix: 解决部分 OneDrive 上传大文件失败的问题 (#4117)
Refs #4125
2024-03-08 08:12:06 +00:00
zhengkunwang a6784772b9 feat: 增加一个外部访问地址校验规则 (#4109)
Refs https://github.com/1Panel-dev/1Panel/issues/4091
2024-03-07 10:19:06 +00:00
igophper cf4fcaa07f fix: 解决编辑ssh端口同步到终端本地服务器问题 (#4043) (#4093) 2024-03-07 11:48:00 +08:00
zhengkunwang 62b1bb3191 feat: 升级 element-plus 到 2.6.0 (#4092) 2024-03-07 03:07:06 +00:00
ssongliu 8ce066a9d4 fix: 修改升级过程中的架构判断 (#4102) 2024-03-07 03:05:05 +00:00
wanghe-fit2cloud 7d8da406cf feat: 增加论坛链接 2024-03-07 10:52:00 +08:00
mobeicanyue b3d6b2b0de Issue 跳转 GitHub discussions (#4090) 2024-03-07 10:47:53 +08:00
zhengkunwang 21f24f2e10 feat(waf): 增加全局设置 (#4088) 2024-03-06 10:16:08 +00:00
163 changed files with 3947 additions and 22348 deletions
+5 -5
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 问题与讨论 / Questions & Discussions
url: https://github.com/1Panel-dev/1Panel/discussions
about: 提出针对本项目安装部署、使用及其他方面的相关问题 / Raise questions about the installation, deployment, use and other aspects of the project.
- name: 对 1Panel 项目有其他问题
url: https://1panel.cn
about: 如果想要进一步了解 1Panel 项目,欢迎发送邮件到 support@fit2cloud.com 进行提问。
- name: 反馈安全问题 (Report Security Bug)
url: mailto://support@fit2cloud.com
about: 请通过邮箱 support@fit2cloud.com 反馈安全问题 (Please report security vulnerabilities to support@fit2cloud.com)
url: https://bbs.fit2cloud.com/c/1p/7
about: 如果你对 1Panel 有其他想要提问的,我们欢迎到我们的官方社区进行提问。
-12
View File
@@ -1,12 +0,0 @@
name: 问题咨询
description: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[QUESTION]"
labels: question
assignees: wanghe-fit2cloud
body:
- type: textarea
id: description
attributes:
label: "请描述您的问题"
validations:
required: true
+2 -2
View File
@@ -5,7 +5,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
This [development guideline](https://kubeoperator.io/docs/dev/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
This [development guideline](https://1panel.cn/docs/dev_manual/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
@@ -22,4 +22,4 @@ When reporting issues, always include:
* Snapshots or log files if needed
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
replace those parts with "REDACTED" or other strings like "****".
replace those parts with "REDACTED" or other strings like "****".
+1 -2
View File
@@ -50,8 +50,7 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
如果您在使用过程中发现任何安全问题,请通过以下方式直接联系我们:
- 邮箱:support@fit2cloud.com
- 电话:400-052-0755
- 邮箱:wanghe@fit2cloud.com
## Star History
+16
View File
@@ -203,3 +203,19 @@ func (b *BaseApi) OperateNodeModules(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Runtime
// @Summary Sync runtime status
// @Description 同步运行环境状态
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/sync [post]
func (b *BaseApi) SyncStatus(c *gin.Context) {
err := runtimeService.SyncRuntimeStatus()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+1 -1
View File
@@ -41,7 +41,7 @@ type MysqlDBInfo struct {
Username string `json:"username"`
Password string `json:"password"`
Permission string `json:"permission"`
BackupCount int `json:"backupCount"`
IsDelete bool `json:"isDelete"`
Description string `json:"description"`
}
+1 -1
View File
@@ -20,7 +20,7 @@ type PostgresqlDBInfo struct {
Username string `json:"username"`
Password string `json:"password"`
SuperUser bool `json:"superUser"`
BackupCount int `json:"backupCount"`
IsDelete bool `json:"isDelete"`
Description string `json:"description"`
}
+2
View File
@@ -52,6 +52,8 @@ type SettingInfo struct {
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
FileRecycleBin string `json:"fileRecycleBin"`
SnapshotIgnore string `json:"snapshotIgnore"`
}
type SettingUpdate struct {
+1
View File
@@ -9,5 +9,6 @@ type DatabaseMysql struct {
Username string `json:"username" gorm:"type:varchar(256);not null"`
Password string `json:"password" gorm:"type:varchar(256);not null"`
Permission string `json:"permission" gorm:"type:varchar(256);not null"`
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+2 -1
View File
@@ -8,6 +8,7 @@ type DatabasePostgresql struct {
Format string `json:"format" gorm:"type:varchar(64);not null"`
Username string `json:"username" gorm:"type:varchar(256);not null"`
Password string `json:"password" gorm:"type:varchar(256);not null"`
SuperUser bool `json:"superUser" gorm:"type:varchar(64)" `
SuperUser bool `json:"superUser" gorm:"type:varchar(64)"`
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+9 -2
View File
@@ -18,6 +18,7 @@ type IWebsiteRepo interface {
WithDefaultServer() DBOption
WithDomainLike(domain string) DBOption
WithRuntimeID(runtimeID uint) DBOption
WithIDs(ids []uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
List(opts ...DBOption) ([]model.Website, error)
GetFirst(opts ...DBOption) (model.Website, error)
@@ -36,9 +37,15 @@ func NewIWebsiteRepo() IWebsiteRepo {
type WebsiteRepo struct {
}
func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption {
func (w *WebsiteRepo) WithAppInstallId(appInstallID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallId)
return db.Where("app_install_id = ?", appInstallID)
}
}
func (w *WebsiteRepo) WithIDs(ids []uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id in (?)", ids)
}
}
+6
View File
@@ -14,6 +14,7 @@ type IWebsiteDomainRepo interface {
WithWebsiteId(websiteId uint) DBOption
WithPort(port int) DBOption
WithDomain(domain string) DBOption
WithDomainLike(domain string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error)
GetFirst(opts ...DBOption) (model.WebsiteDomain, error)
GetBy(opts ...DBOption) ([]model.WebsiteDomain, error)
@@ -44,6 +45,11 @@ func (w WebsiteDomainRepo) WithDomain(domain string) DBOption {
return db.Where("domain = ?", domain)
}
}
func (w WebsiteDomainRepo) WithDomainLike(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("domain like ?", "%"+domain+"%")
}
}
func (w WebsiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) {
var domains []model.WebsiteDomain
db := getDb(opts...).Model(&model.WebsiteDomain{})
+4 -2
View File
@@ -749,8 +749,10 @@ func syncAppInstallStatus(appInstall *model.AppInstall) error {
switch {
case count == 0:
appInstall.Status = constant.Error
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
if appInstall.Status != constant.Error {
appInstall.Status = constant.SyncErr
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
}
case exitedCount == total:
appInstall.Status = constant.Stopped
case runningCount == total:
+7 -2
View File
@@ -190,11 +190,13 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
if err != nil {
return err
}
deleteList := databases
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.MysqlName, data.MysqlName) {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) {
hasOld = true
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
}
@@ -208,6 +210,9 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
}
}
}
for _, delItem := range deleteList {
_ = mysqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
}
return nil
}
+7 -2
View File
@@ -216,11 +216,13 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
if err != nil {
return err
}
deleteList := databases
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.PostgresqlName, data.PostgresqlName) {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) {
hasOld = true
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
}
@@ -234,6 +236,9 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
}
}
}
for _, delItem := range deleteList {
_ = postgresqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
}
return nil
}
+12 -14
View File
@@ -153,22 +153,20 @@ func (r RecycleBinService) Clear() error {
}
func getClashDir(realPath string) (string, error) {
trimmedPath := strings.Trim(realPath, "/")
parts := strings.Split(trimmedPath, "/")
dir := ""
if len(parts) > 0 {
dir = parts[0]
partitions, err := disk.Partitions(false)
if err != nil {
return "", err
partitions, err := disk.Partitions(false)
if err != nil {
return "", err
}
for _, p := range partitions {
if p.Mountpoint == "/" {
continue
}
for _, p := range partitions {
if p.Mountpoint == dir {
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
return "", err
}
return dir, nil
if strings.HasPrefix(realPath, p.Mountpoint) {
clashDir := path.Join(p.Mountpoint, ".1panel_clash")
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
return "", err
}
return clashDir, nil
}
}
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir)
+14
View File
@@ -42,6 +42,7 @@ type IRuntimeService interface {
GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error)
OperateNodeModules(req request.NodeModuleOperateReq) error
SyncForRestart() error
SyncRuntimeStatus() error
}
func NewRuntimeService() IRuntimeService {
@@ -586,3 +587,16 @@ func (r *RuntimeService) SyncForRestart() error {
}
return nil
}
func (r *RuntimeService) SyncRuntimeStatus() error {
runtimes, err := runtimeRepo.List()
if err != nil {
return err
}
for _, runtime := range runtimes {
if runtime.Type == constant.RuntimeNode {
_ = SyncRuntimeContainerStatus(&runtime)
}
}
return nil
}
+12 -25
View File
@@ -181,33 +181,20 @@ func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
}
container := containers[0]
interval := 10 * time.Second
retries := 60
for i := 0; i < retries; i++ {
resp, err := cli.InspectContainer(container.ID)
if err != nil {
time.Sleep(interval)
continue
switch container.State {
case "exited":
runtime.Status = constant.RuntimeError
case "running":
runtime.Status = constant.RuntimeRunning
case "paused":
runtime.Status = constant.RuntimeStopped
default:
if runtime.Status != constant.RuntimeBuildIng {
runtime.Status = constant.RuntimeStopped
}
if resp.State.Health != nil {
status := strings.ToLower(resp.State.Health.Status)
switch status {
case "starting":
runtime.Status = constant.RuntimeStarting
_ = runtimeRepo.Save(runtime)
case "healthy":
runtime.Status = constant.RuntimeRunning
_ = runtimeRepo.Save(runtime)
return nil
case "unhealthy":
runtime.Status = constant.RuntimeUnhealthy
_ = runtimeRepo.Save(runtime)
return nil
}
}
time.Sleep(interval)
}
return nil
return runtimeRepo.Save(runtime)
}
func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
+19 -10
View File
@@ -138,7 +138,14 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
if strings.Contains(localDir, dataDir) {
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
}
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
rules := strings.Split(ignoreVal.Value, ",")
for _, ignore := range rules {
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
continue
}
exclusionRules += ("." + strings.ReplaceAll(ignore, dataDir, "") + ";")
}
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
_ = settingRepo.Update("SystemIP", "")
@@ -154,10 +161,6 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
}
func snapCompress(snap snapHelper, rootDir string) {
defer func() {
global.LOG.Debugf("remove snapshot file %s", rootDir)
_ = os.RemoveAll(rootDir)
}()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusRunning})
tmpDir := path.Join(global.CONF.System.TmpDir, "system")
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
@@ -178,15 +181,13 @@ func snapCompress(snap snapHelper, rootDir string) {
snap.Status.Compress = constant.StatusDone
snap.Status.Size = size
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
global.LOG.Debugf("remove snapshot file %s", rootDir)
_ = os.RemoveAll(rootDir)
}
func snapUpload(snap snapHelper, accounts string, file string) {
source := path.Join(global.CONF.System.TmpDir, "system", path.Base(file))
defer func() {
global.LOG.Debugf("remove snapshot file %s", source)
_ = os.Remove(source)
}()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusUploading})
accountMap, err := loadClientMap(accounts)
if err != nil {
@@ -207,6 +208,9 @@ func snapUpload(snap snapHelper, accounts string, file string) {
}
snap.Status.Upload = constant.StatusDone
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
global.LOG.Debugf("remove snapshot file %s", source)
_ = os.Remove(source)
}
func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
@@ -216,6 +220,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
}
}
exMap := make(map[string]struct{})
exStr := ""
excludes := strings.Split(exclusionRules, ";")
excludes = append(excludes, "*.sock")
@@ -223,8 +228,12 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
if len(exclude) == 0 {
continue
}
if _, ok := exMap[exclude]; ok {
continue
}
exStr += " --exclude "
exStr += exclude
exMap[exclude] = struct{}{}
}
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
+1 -1
View File
@@ -215,7 +215,7 @@ func recoverAppData(src string) error {
func recoverDaemonJson(src string, fileOp files.FileOp) error {
daemonJsonPath := "/etc/docker/daemon.json"
_, errSrc := os.Stat(src)
_, errSrc := os.Stat(path.Join(src, "docker/daemon.json"))
_, errPath := os.Stat(daemonJsonPath)
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
global.LOG.Debug("the daemon.json file does not exist, nothing happens.")
+5 -1
View File
@@ -177,7 +177,11 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
},
}
if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil {
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.OldValue, err)
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
}
if err = NewIHostService().Update(1, map[string]interface{}{"port": req.NewValue}); err != nil {
global.LOG.Errorf("reset host port %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
}
}
+19 -15
View File
@@ -7,7 +7,6 @@ import (
"net/http"
"os"
"path"
"runtime"
"strings"
"time"
@@ -250,19 +249,24 @@ func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
}
func loadArch() (string, error) {
switch runtime.GOARCH {
case "amd64", "ppc64le", "s390x", "arm64":
return runtime.GOARCH, nil
case "arm":
std, err := cmd.Exec("uname -m")
if err != nil {
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
}
if std == "armv7l\n" {
return "armv7", nil
}
return "", fmt.Errorf("unsupported such arch: arm-%s", std)
default:
return "", fmt.Errorf("unsupported such arch: %s", runtime.GOARCH)
std, err := cmd.Exec("uname -a")
if err != nil {
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
}
if strings.Contains(std, "x86_64") {
return "amd64", nil
}
if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") {
return "arm64", nil
}
if strings.Contains(std, "armv7l") {
return "armv7", nil
}
if strings.Contains(std, "ppc64le") {
return "ppc64le", nil
}
if strings.Contains(std, "s390x") {
return "s390x", nil
}
return "", fmt.Errorf("unsupported such arch: %s", std)
}
+10 -1
View File
@@ -119,7 +119,16 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
}
opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if req.Name != "" {
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name))
if len(domains) > 0 {
var websiteIds []uint
for _, domain := range domains {
websiteIds = append(websiteIds, domain.WebsiteID)
}
opts = append(opts, websiteRepo.WithIDs(websiteIds))
} else {
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
}
}
if req.WebsiteGroupID != 0 {
opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID))
+1 -1
View File
@@ -1,13 +1,13 @@
package service
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"gopkg.in/square/go-jose.v2/json"
)
type WebsiteDnsAccountService struct {
+1
View File
@@ -13,6 +13,7 @@ const (
Syncing = "Syncing"
SyncSuccess = "SyncSuccess"
Paused = "Paused"
SyncErr = "SyncErr"
ContainerPrefix = "1Panel-"
+6
View File
@@ -166,3 +166,9 @@ CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{
#toolbox
ErrNotExistUser: "The current user does not exist. Please modify and retry!"
ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!"
#waf
ErrScope: "Modification of this configuration is not supported"
ErrStateChange: "State modification failed"
ErrRuleExist: "Rule is Exist"
ErrRuleNotExist: "Rule is not Exist"
+6
View File
@@ -167,3 +167,9 @@ CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .p
#toolbox
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"
#waf
ErrScope: "不支援修改此配置"
ErrStateChange: "狀態修改失敗"
ErrRuleExist: "規則名稱已存在"
ErrRuleNotExist: "規則不存在"
+7 -1
View File
@@ -165,4 +165,10 @@ CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .p
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
#waf
ErrScope: "不支持修改此配置"
ErrStateChange: "状态修改失败"
ErrRuleExist: "规则名称已存在"
ErrRuleNotExist: "规则不存在"
+2 -4
View File
@@ -6,12 +6,10 @@ import (
"os"
"time"
"gorm.io/gorm/logger"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm/logger"
)
func Init() {
+3
View File
@@ -72,6 +72,9 @@ func Init() {
migrations.UpdateSnapshotRecords,
migrations.UpdateWebDavConf,
migrations.AddSnapshotIgnore,
migrations.AddDatabaseIsDelete,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -0,0 +1,27 @@
package migrations
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
var AddSnapshotIgnore = &gormigrate.Migration{
ID: "20240311-add-snapshot-ignore",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "SnapshotIgnore", Value: "*.sock"}).Error; err != nil {
return err
}
return nil
},
}
var AddDatabaseIsDelete = &gormigrate.Migration{
ID: "20240314-add-database-is-delete",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.DatabaseMysql{}, &model.DatabasePostgresql{}); err != nil {
return err
}
return nil
},
}
@@ -124,6 +124,12 @@ var AddTablePHPExtensions = &gormigrate.Migration{
if err := tx.Create(&model.PHPExtensions{Name: "默认", Extensions: "bcmath,gd,gettext,intl,pcntl,shmop,soap,sockets,sysvsem,xmlrpc,zip"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "苹果CMS-V10", Extensions: "mysqli,pdo_mysql,zip,gd,redis,memcache,memcached"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "SeaCMS", Extensions: "mysqli,pdo_mysql,gd,curl"}).Error; err != nil {
return err
}
return nil
},
}
+1
View File
@@ -20,6 +20,7 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter.POST("/del", baseApi.DeleteRuntime)
groupRouter.POST("/update", baseApi.UpdateRuntime)
groupRouter.GET("/:id", baseApi.GetRuntime)
groupRouter.POST("/sync", baseApi.SyncStatus)
groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript)
groupRouter.POST("/operate", baseApi.OperateRuntime)
+16 -6
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
@@ -14,6 +15,7 @@ import (
"path"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/utils/files"
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
@@ -321,7 +323,6 @@ func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
return false, fmt.Errorf("do request for list failed, err: %v", err)
}
fmt.Println(response)
return true, nil
}
@@ -362,7 +363,13 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
splitCount += 1
}
bfReader := bufio.NewReader(file)
var fileUploadResp *UploadSessionUploadResponse
httpClient := http.Client{
Timeout: time.Minute * 10,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
length, err := bfReader.Read(buffer)
if err != nil {
@@ -376,12 +383,15 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
if err != nil {
return false, err
}
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
res, err := httpClient.Do(sessionFileUploadReq)
if err != nil {
return false, err
}
}
if fileUploadResp.Id == "" {
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
data, _ := io.ReadAll(res.Body)
res.Body.Close()
return false, errors.New(string(data))
}
}
return true, nil
}
+2 -1
View File
@@ -177,7 +177,8 @@ func CheckIllegal(args ...string) bool {
for _, arg := range args {
if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") {
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
strings.Contains(arg, "\n") || strings.Contains(arg, "\r") {
return true
}
}
+15
View File
@@ -126,6 +126,21 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
return nil
}
func (f FileOp) SaveFileWithByte(dst string, content []byte, mode fs.FileMode) error {
if !f.Stat(path.Dir(dst)) {
_ = f.CreateDir(path.Dir(dst), mode.Perm())
}
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.Write(content)
write.Flush()
return nil
}
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
cmdStr := fmt.Sprintf(`chown %s:%s "%s"`, uid, gid, dst)
if sub {
+2 -2
View File
@@ -61,7 +61,7 @@ func NewParserFromLexer(lexer *lexer) *Parser {
parser.directiveWrappers = map[string]func(*components.Directive) components.IDirective{
"server": func(directive *components.Directive) components.IDirective {
return parser.parseUpstreamServer(directive)
return parser.wrapServer(directive)
},
}
@@ -113,7 +113,7 @@ parsingloop:
case p.curTokenIs(flag.LuaCode):
context.IsLuaBlock = true
context.LiteralCode = p.currentToken.Literal
case p.curTokenIs(flag.Keyword):
case p.curTokenIs(flag.Keyword) || p.curTokenIs(flag.QuotedString):
s, err := p.parseStatement()
if err != nil {
return nil, err
+3
View File
@@ -17818,6 +17818,9 @@ const docTemplate = `{
"sessionTimeout": {
"type": "string"
},
"snapshotIgnore": {
"type": "string"
},
"ssl": {
"type": "string"
},
+3
View File
@@ -17811,6 +17811,9 @@
"sessionTimeout": {
"type": "string"
},
"snapshotIgnore": {
"type": "string"
},
"ssl": {
"type": "string"
},
+2
View File
@@ -2538,6 +2538,8 @@ definitions:
type: string
sessionTimeout:
type: string
snapshotIgnore:
type: string
ssl:
type: string
sslType:
+2
View File
@@ -6,6 +6,8 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
pnpm-lock.yaml
package-lock.json
node_modules
dist
-11334
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -30,7 +30,7 @@
"echarts": "^5.3.0",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.3.4",
"fit2cloud-ui-plus": "^1.0.9",
"fit2cloud-ui-plus": "^1.1.4",
"js-base64": "^3.7.2",
"js-md5": "^0.7.3",
"md-editor-v3": "^2.7.2",
@@ -46,7 +46,7 @@
"vue": "^3.2.25",
"vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1",
"vue-i18n": "^9.1.9",
"vue-i18n": "^9.10.2",
"vue-router": "^4.0.12",
"vue3-seamless-scroll": "^1.2.0",
"xterm": "^4.19.0",
@@ -77,7 +77,7 @@
"sass": "^1.49.7",
"standard-version": "^9.5.0",
"stylelint": "^15.10.1",
"tailwindcss": "^3.3.2",
"tailwindcss": "^3.4.1",
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.16.4",
"unplugin-vue-components": "^0.25.0",
+2
View File
@@ -23,6 +23,7 @@ export namespace Database {
username: string;
password: string;
permission: string;
isDelete: string;
description: string;
}
export interface BaseInfo {
@@ -207,6 +208,7 @@ export namespace Database {
username: string;
password: string;
superUser: boolean;
isDelete: string;
description: string;
}
export interface ChangeInfo {
+1
View File
@@ -45,6 +45,7 @@ export namespace Setting {
emailVars: string;
weChatVars: string;
dingVars: string;
snapshotIgnore: string;
}
export interface SettingUpdate {
key: string;
+4
View File
@@ -58,3 +58,7 @@ export const UpdatePHPExtensions = (req: Runtime.PHPExtensionsUpdate) => {
export const DeletePHPExtensions = (req: Runtime.PHPExtensionsDelete) => {
return http.post<any>(`/runtimes/php/extensions/del`, req);
};
export const SyncRuntime = () => {
return http.post(`/runtimes/sync`, {});
};
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -188,5 +188,7 @@
"Yugoslavia": "南斯拉夫",
"Zaire": "扎伊尔",
"Zambia": "赞比亚",
"Zimbabwe": "津巴布韦"
"Zimbabwe": "津巴布韦",
"Hong Kong": "中国香港",
"Macau": "中国澳门"
}
@@ -0,0 +1,55 @@
<template>
<el-card class="config-card">
<div class="config-header">
<span>{{ header }}</span>
<slot name="header-r" />
</div>
<el-text type="info">{{ description }}</el-text>
<div class="config-content">
<slot name="content-r" />
</div>
<div class="config-form">
<slot name="content-form" />
</div>
</el-card>
</template>
<script setup lang="ts">
defineOptions({ name: 'ConfigCard' });
defineProps({
header: String,
description: String,
});
</script>
<style lang="scss" scoped>
.config-card {
cursor: pointer;
.config-header {
margin-bottom: 18px;
display: flex;
justify-content: space-between;
align-items: center;
span {
font-weight: normal;
font-size: 18px;
}
}
.config-content {
display: flex;
justify-content: flex-end;
}
.config-form {
margin-top: 20px;
}
}
.config-card {
border: var(--panel-border) !important;
&:hover {
cursor: pointer;
border: 1px solid var(--el-color-primary) !important;
}
}
</style>
+15 -2
View File
@@ -8,7 +8,7 @@
popper-class="file-list"
>
<template #reference>
<el-button :icon="Folder" :disabled="disabled" @click="popoverVisible = true"></el-button>
<el-button :icon="Folder" :disabled="disabled" @click="openPage()"></el-button>
</template>
<div>
<el-button class="close" link @click="closePage">
@@ -57,7 +57,7 @@
<template #default="{ row }">
<el-checkbox
v-model="rowName"
:true-label="row.name"
:true-value="row.name"
:disabled="disabledDir(row)"
@change="checkFile(row)"
/>
@@ -116,6 +116,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
isAll: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
@@ -141,7 +145,16 @@ const closePage = () => {
selectRow.value = {};
};
const openPage = () => {
popoverVisible.value = true;
selectRow.value = {};
rowName.value = '';
};
const disabledDir = (row: File.File) => {
if (props.isAll) {
return false;
}
if (!props.dir) {
return row.isDir;
}
@@ -162,6 +162,9 @@ const getContent = () => {
if (!end.value && res.data.end) {
lastContent.value = content.value;
}
res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
});
data.value = res.data;
if (res.data.content != '') {
if (stopSignals.some((signal) => res.data.content.endsWith(signal))) {
@@ -4,6 +4,7 @@
<el-radio-button
class="router_card_button"
:label="button.label"
:value="button.label"
v-for="(button, index) in buttonArray"
size="large"
:key="index"
+1 -1
View File
@@ -36,7 +36,7 @@ const getType = (status: string) => {
case 'removing':
return 'warning';
default:
return '';
return 'primary';
}
};
@@ -44,10 +44,10 @@
<h2 class="inline-block">{{ $t('app.version') }}</h2>
</div>
<el-radio-group class="inline-block tag" v-model="upgradeVersion" @change="changeOption">
<el-radio v-if="upgradeInfo.newVersion" :label="upgradeInfo.newVersion">
<el-radio v-if="upgradeInfo.newVersion" :value="upgradeInfo.newVersion">
{{ upgradeInfo.newVersion }} {{ $t('setting.newVersion') }}
</el-radio>
<el-radio :label="upgradeInfo.latestVersion">
<el-radio :value="upgradeInfo.latestVersion">
{{ upgradeInfo.latestVersion }} {{ $t('setting.latestVersion') }}
</el-radio>
</el-radio-group>
+19
View File
@@ -487,6 +487,19 @@ const checkPHPExtensions = (rule, value, callback) => {
}
};
const checkHttpOrHttps = (rule, value, callback) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.paramHttp')));
} else {
const regHttpHttps = /^(http|https):\/\//;
if (!regHttpHttps.test(value)) {
callback(new Error(i18n.global.t('commons.rule.paramHttp')));
} else {
callback();
}
}
};
interface CommonRule {
requiredInput: FormItemRule;
requiredSelect: FormItemRule;
@@ -527,6 +540,7 @@ interface CommonRule {
paramPort: FormItemRule;
paramExtUrl: FormItemRule;
paramSimple: FormItemRule;
paramHttp: FormItemRule;
}
export const Rules: CommonRule = {
@@ -730,4 +744,9 @@ export const Rules: CommonRule = {
validator: checkPHPExtensions,
trigger: 'blur',
},
paramHttp: {
required: true,
validator: checkHttpOrHttps,
trigger: 'blur',
},
};
+76
View File
@@ -181,3 +181,79 @@ export const DNSTypes = [
value: 'Godaddy',
},
];
export const Fields = [
{
label: 'URL',
value: 'URL',
},
{
label: 'IP',
value: 'IP',
},
{
label: 'Header',
value: 'Header',
},
{
label: 'Host',
value: 'Host',
},
];
export const Patterns = [
{
label: i18n.global.t('xpack.waf.contain'),
value: 'contain',
},
{
label: i18n.global.t('xpack.waf.equal'),
value: 'eq',
},
{
label: i18n.global.t('xpack.waf.regex'),
value: 'regex',
},
{
label: i18n.global.t('xpack.waf.notEqual'),
value: 'notEq',
},
];
export const HttpCodes = [
{
label: i18n.global.t('xpack.waf.badReq'),
value: 400,
},
{
label: i18n.global.t('xpack.waf.forbidden'),
value: 403,
},
{
label: i18n.global.t('xpack.waf.noRes'),
value: 444,
},
{
label: i18n.global.t('xpack.waf.serverErr'),
value: 500,
},
];
export const Actions = [
{
label: i18n.global.t('xpack.waf.actionAllow'),
value: 'allow',
},
{
label: i18n.global.t('xpack.waf.deny'),
value: 'deny',
},
{
label: i18n.global.t('xpack.waf.captcha'),
value: 'captcha',
},
{
label: i18n.global.t('xpack.waf.fiveSeconds'),
value: 'five_seconds',
},
];
+36
View File
@@ -0,0 +1,36 @@
import { GlobalStore } from '@/store';
import { searchXSetting } from '@/xpack/frontend/api/modules/setting';
import { computed } from 'vue';
export const useLogo = async () => {
const globalStore = GlobalStore();
const themeConfig = computed(() => globalStore.themeConfig);
if (!themeConfig.value.logo) {
const res = await searchXSetting();
globalStore.setThemeConfig({
...themeConfig.value,
title: res.data.title,
logo: res.data.logo,
logoWithText: res.data.logoWithText,
favicon: res.data.favicon,
});
}
let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement | null;
if (link) {
if (globalStore.themeConfig.favicon) {
link.href = globalStore.themeConfig.favicon;
} else {
link.href = '/public/favicon.png';
}
} else {
const newLink = document.createElement('link');
newLink.rel = 'icon';
if (globalStore.themeConfig.favicon) {
newLink.href = globalStore.themeConfig.favicon;
} else {
newLink.href = '/public/favicon.png';
}
document.head.appendChild(newLink);
}
};
+190 -62
View File
@@ -51,6 +51,7 @@ const message = {
uninstall: 'Uninstall',
fullscreen: 'Fullscreen',
quitFullscreen: 'Quit Fullscreen',
update: 'Edit',
},
search: {
timeStart: 'Time start',
@@ -195,6 +196,7 @@ const message = {
filePermission: 'File Permission Error',
formatErr: 'Format error, please check and retry',
phpExtension: 'Only supports , _ lowercase English and numbers',
paramHttp: 'Must start with http:// or https://',
},
res: {
paramError: 'The request failed, please try again later!',
@@ -245,6 +247,7 @@ const message = {
ready: 'normal',
applying: 'Applying',
applyerror: 'Failure',
syncerr: 'Error',
},
units: {
second: 'Second',
@@ -361,6 +364,7 @@ const message = {
goUpgrade: 'Go for upgrade',
goInstall: 'Go for install',
source: 'Source',
isDelete: 'Deleted',
permission: 'Permission',
permissionForIP: 'IP',
permissionAll: 'All of them(%)',
@@ -385,7 +389,7 @@ const message = {
portHelper:
'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
loadFromRemote: 'Load from server',
loadFromRemote: 'Sync from Server',
userBind: 'Bind User',
pgBindHelper:
'This operation is used to create a new user and bind it to the target database. Currently, selecting users already existing in the database is not supported.',
@@ -1393,6 +1397,10 @@ const message = {
snapshot: 'Snapshot',
status: 'Snapshot status',
ignoreRule: 'Ignore Rule',
ignoreHelper:
'This rule will be used to compress and backup the 1Panel data directory during snapshots, please modify with caution.',
ignoreHelper1: 'One item per line, e.g.: \n*.log\n/opt/1panel/cache',
panelInfo: 'Write 1Panel basic information',
panelBin: 'Backup 1Panel system files',
daemonJson: 'Backup Docker configuration file',
@@ -1418,7 +1426,7 @@ const message = {
rollbackHelper:
'This recovery is about to be rolled back, which will replace all the files recovered this time. In the process, docker and 1panel services may need to be restarted. Do you want to continue?',
upgradeHelper: 'The upgrade requires a service restart. Do you want to continue?',
upgradeHelper: 'The upgrade requires restarting the 1Panel service. Do you want to continue?',
noUpgrade: 'It is currently the latest version',
versionHelper:
'Name rules: [major version].[functional version].[Bug fix version], as shown in the following example:',
@@ -1438,8 +1446,8 @@ const message = {
doc: 'Official document',
star: 'Star',
description: '1Panel, Modern, open source Linux server operation and maintenance management panel.',
forum: 'Forum',
doc2: 'Document',
forum: 'Forum Help',
doc2: 'User Manual',
currentVersion: 'Version',
},
clean: {
@@ -1816,7 +1824,7 @@ const message = {
imported: 'Manually Created',
importType: 'Import Type',
pasteSSL: 'Paste code',
localSSL: 'Select local file',
localSSL: 'Select server file',
privateKeyPath: 'Private key file',
certificatePath: 'Certificate file',
ipWhiteListHelper: 'The role of IP whitelist: all rules are invalid for IP whitelist',
@@ -2143,63 +2151,183 @@ const message = {
statusCode: 'Status code',
manage: 'Management',
},
xpack: {
name: 'Professional Edition',
waf: {
name: 'WAF',
blackWhite: 'black and white list',
globalSetting: 'Global Setting',
websiteSetting: 'Website Settings',
blockRecords: 'Block records',
world: 'world',
china: 'China',
intercept: 'interception',
request: 'request',
count4xx: '4xx quantity',
count5xx: '5xx quantity',
todayStatus: 'Today status',
reqMap: 'Request map (30 days)',
resource: 'source',
count: 'Quantity',
hight: 'high',
low: 'low',
reqCount: 'Number of requests',
interceptCount: 'Interception number',
requestTrends: 'Request Trends (7 days)',
interceptTrends: 'Intercept Trends (7 days)',
},
monitor: {
name: 'Website Monitoring',
},
tamper: {
tamperHelper1:
'One-click deployment type of website, it is recommended to enable the application directory anti-tampering function;',
tamperHelper2:
'If the website cannot be used normally or backup and restore fail after enabling the anti-tampering function, please disable the anti-tampering function first;',
tamperHelper3:
'Enabling tamper-proofing will restrict reading, writing, deleting, permission, and owner modification operations of protected files under non-excluded directories. Please choose carefully when setting [Exclude Directory] and [Protect].',
op: 'Operation',
create: 'Create',
file: 'File',
tamperPath: 'Protection Directory',
tamperPathEdit: 'Modify Path',
log: 'Intercept Logs',
totalProtect: 'Total Protection',
todayProtect: 'Today’s Protection',
addRule: 'Add Rule',
ignore: 'Exclude Directory',
ignoreHelper: 'One per line, e.g., \ntmp\n./tmp',
ignoreHelper1: 'Add folder names or specific paths to ignore',
ignoreHelper2: 'To ignore specific folders, use relative paths starting with ./',
protect: 'Protect',
protectHelper: 'One per line, e.g., \npng\n./test.css',
protectHelper1: 'Specify file names, suffixes, or specific files for protection',
protectHelper2: 'To protect specific files, use relative paths starting with ./',
enableHelper:
'The anti-tampering function of website {0} is about to be enabled to enhance website security. Do you want to continue?',
disableHelper:
'The anti-tampering function of website {0} is about to be disabled. Do you want to continue?',
},
},
xpack: {
name: 'Professional',
waf: {
name: 'WAF',
blackWhite: 'black and white list',
globalSetting: 'Global Setting',
websiteSetting: 'Website Settings',
blockRecords: 'Block records',
world: 'world',
china: 'China',
intercept: 'interception',
request: 'request',
count4xx: '4xx quantity',
count5xx: '5xx quantity',
todayStatus: 'Today Status',
reqMap: 'Attack map (30 days)',
resource: 'source',
count: 'Quantity',
hight: 'high',
low: 'low',
reqCount: 'Number of requests',
interceptCount: 'Interception number',
requestTrends: 'Request Trends (7 days)',
interceptTrends: 'Intercept Trends (7 days)',
whiteList: 'whitelist',
blackList: 'blacklist',
ipBlackListHelper: 'IPs in the blacklist cannot access the website',
ipWhiteListHelper: 'IPs in the whitelist are not restricted by any rules',
uaBlackListHelper: 'Requests carrying User-Agent in the blacklist will be intercepted',
uaWhiteListHelper: 'Requests carrying User-Agent in the whitelist are not restricted by any rules',
urlBlackListHelper: 'Requests for URLs in the blacklist will be intercepted',
urlWhiteListHelper: 'Requests for URLs in the whitelist are not restricted by any rules',
ccHelper:
'If any website has been requested more than {1} times in {0} seconds, this IP will be blocked for {2} seconds',
blockTime: 'Block time',
attackHelper:
'The cumulative interception exceeds {1} times within {0} seconds, block this IP for {2} seconds',
notFoundHelper:
'The cumulative request returned 404 more than {1} times within {0} seconds, block this IP for {2} seconds',
frequencyLimit: 'frequency limit',
regionLimit: 'region limit',
defaultRule: 'Default rule',
accessFrequencyLmit: 'Access frequency limit',
attackLimit: 'Attack frequency limit',
notFoundLimit: '404 frequency limit',
urlLimit: 'URL frequency limit',
urlLimitHelper: 'Set access frequency for a single URL',
sqliDefense: 'SQL injection defense',
sqliHelper: 'Identify SQL injection in requests and intercept',
xssHelper: 'Identify XSS in the request and intercept it',
xssDefense: 'XSS Defense',
uaDefense: 'Malicious User-Agent Rule',
uaHelper: 'Contains common malicious crawler rules',
argsDefense: 'Malicious parameter rules',
argsHelper: 'Prohibit malicious parameters in requests',
cookieDefense: 'Malicious Cookie Rule',
cookieHelper: 'Prohibit malicious cookies from being carried in requests',
headerDefense: 'Malicious Header Rule',
headerHelper: 'Prohibit requests from containing malicious headers',
httpRule: 'HTTP request method rules',
httpHelper: 'Restrict the request method type of the website',
geoRule: 'Regional access restrictions',
geoHelper: 'Restrict access to your website from certain regions',
ipLocation: 'IP home location',
action: 'action',
ruleType: 'attack type',
ipHelper: 'Please enter IP',
attackLog: 'Attack Log',
rule: 'Rule',
ipArr: 'IPV4 range',
ipStart: 'Start IP',
ipEnd: 'End IP',
ipv4: 'IPV4',
ipv6: 'IPV6',
urlDefense: 'URL rules',
urlHelper: 'Forbidden URL',
dirFilter: 'Directory filter',
sqlInject: 'SQL injection',
xss: 'XSS',
phpExec: 'PHP script execution',
oneWordTrojan: 'One word Trojan',
appFilter: 'Apply dangerous directory filtering',
webshell: 'Webshell',
args: 'Malicious parameters',
protocolFilter: 'Protocol filter',
javaFileter: 'Java Dangerous File Filtering',
scannerFilter: 'Scanner filter',
escapeFilter: 'escape filter',
customRule: 'custom rule',
httpMethod: 'HTTP method filter',
fileExt: 'File upload limit',
fileExtHelper: 'File extensions that are prohibited from uploading',
deny: 'forbidden',
allow: 'only allow',
field: 'match object',
pattern: 'matching condition',
ruleContent: 'match content',
contain: 'include',
equal: 'equal',
regex: 'regular expression',
notEqual: 'Not equal to',
customRuleHelper: 'Perform corresponding actions based on condition matching',
actionAllow: 'Allow',
blockIP: 'Block IP',
code: 'return status code',
noRes: 'Disconnect 444',
badReq: 'Parameter error 400',
forbidden: 'Access Forbidden 403',
serverErr: 'Server error 500',
resHtml: 'Response page',
allowHelper: 'Allowing access will skip subsequent WAF rules, please use with caution',
captcha: 'human-machine verification',
fiveSeconds: '5 seconds verification',
location: 'Region',
redisConfig: 'Redis configuration',
redisHelper: 'Enable Redis to persist temporarily blocked IPs',
wafHelper: 'All websites will lose protection after closing',
attackIP: 'Attack IP',
attackParam: 'Attack information',
execRule: 'Hit rule',
acl: 'ACL',
sql: 'SQL injection',
cc: 'CC attack',
isBlocking: 'Blocking',
isFree: 'Unblocked',
},
monitor: {
name: 'Website Monitor',
},
tamper: {
tamper: 'Tamper',
tamperHelper1:
'One-click deployment type of website, it is recommended to enable the application directory anti-tampering function;',
tamperHelper2:
'If the website cannot be used normally or backup and restore fail after enabling the anti-tampering function, please disable the anti-tampering function first;',
tamperHelper3:
'Enabling tamper-proofing will restrict reading, writing, deleting, permission, and owner modification operations of protected files under non-excluded directories. Please choose carefully when setting [Exclude Directory] and [Protect].',
op: 'Operation',
create: 'Create',
file: 'File',
tamperPath: 'Protection Directory',
tamperPathEdit: 'Modify Path',
log: 'Intercept Logs',
totalProtect: 'Total Protection',
todayProtect: 'Today’s Protection',
addRule: 'Add Rule',
ignore: 'Exclude Directory',
ignoreHelper: 'One per line, e.g., \ntmp\n./tmp',
ignoreHelper1: 'Add folder names or specific paths to ignore',
ignoreHelper2: 'To ignore specific folders, use relative paths starting with ./',
protect: 'Protect',
protectHelper: 'One per line, e.g., \npng\n./test.css',
protectHelper1: 'Specify file names, suffixes, or specific files for protection',
protectHelper2: 'To protect specific files, use relative paths starting with ./',
enableHelper:
'The anti-tampering function of website {0} is about to be enabled to enhance website security. Do you want to continue?',
disableHelper:
'The anti-tampering function of website {0} is about to be disabled. Do you want to continue?',
},
setting: {
setting: 'Interface Settings',
title: 'Panel Description',
titleHelper:
'Will be displayed on the user login page (e.g., Linux Server Operation and Maintenance Management Panel)',
logo: 'Logo',
logoHelper:
'Will be displayed on the top left of the management page when the menu is collapsed (recommended image size: 82px*82px)',
logoWithText: 'Logo (with text)',
logoWithTextHelper:
'Will be displayed on the top left of the management page when the menu is expanded (recommended image size: 185px*55px)',
favicon: 'Website Icon',
faviconHelper: 'Website icon (recommended image size: 16px*16px)',
reUpload: 'Re-upload',
supportType: 'Only jpg/png/jpeg files are allowed!',
setDefault: 'Restore Default',
reset: 'Reset',
},
},
};
+179 -58
View File
@@ -51,6 +51,7 @@ const message = {
uninstall: '卸載',
fullscreen: '全屏',
quitFullscreen: '退出全屏',
update: '編輯',
},
search: {
timeStart: '開始時間',
@@ -193,6 +194,7 @@ const message = {
filePermission: '權限錯誤',
formatErr: '格式錯誤,檢查後重試',
phpExtension: '僅支持 , _ 小寫英文和數字',
paramHttp: '必須以 http:// 或 https:// 開頭',
},
res: {
paramError: '請求失敗,請稍後重試!',
@@ -243,6 +245,7 @@ const message = {
ready: '正常',
applying: '申請中',
applyerror: '失敗',
syncerr: '失敗',
},
units: {
second: '秒',
@@ -357,6 +360,7 @@ const message = {
goUpgrade: '去應用商店升級',
goInstall: '去應用商店安裝',
source: '來源',
isDelete: '已刪除',
permission: '權限',
permissionForIP: '指定 IP',
permissionAll: '所有人(%)',
@@ -377,7 +381,7 @@ const message = {
confChange: '配置修改',
confNotFound: '未能找到該應用配置文件,請在應用商店升級該應用至最新版本後重試!',
loadFromRemote: '從服務器獲取',
loadFromRemote: '從伺服器同步',
userBind: '綁定使用者',
pgBindHelper: '此操作用於創建新使用者並將其綁定到目標資料庫,暫不支援選擇已存在於資料庫中的使用者。',
pgSuperUser: '超級使用者',
@@ -1234,6 +1238,9 @@ const message = {
snapshot: '快照',
status: '快照狀態',
ignoreRule: '排除規則',
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,請謹慎修改。',
ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache',
panelInfo: '寫入 1Panel 基礎信息',
panelBin: '備份 1Panel 系統文件',
daemonJson: '備份 Docker 配置文件',
@@ -1259,7 +1266,7 @@ const message = {
'即將回滾本次恢復,回滾將替換所有本次恢復的文件,過程中可能需要重啟 docker 以及 1panel 服務,是否繼續?',
upgrading: '正在升級中,請稍候...',
upgradeHelper: '升級操作需要重啟服務,是否繼續?',
upgradeHelper: '升級操作需要重啟 1Panel 服務,是否繼續?',
noUpgrade: '當前已經是最新版本',
versionHelper: '1Panel 版本號命名規則為: [大版本].[功能版本].[Bug 修復版本],例:',
versionHelper1: 'v1.0.1 是 v1.0.0 之後的 Bug 修復版本',
@@ -1340,8 +1347,8 @@ const message = {
doc: '官方文檔',
star: '點亮 Star',
description: '1Panel,現代化、開源的 Linux 服務器運維管理面板。',
forum: '論壇',
doc2: '文檔',
forum: '論壇求助',
doc2: '使用手冊',
currentVersion: '當前運行版本:',
},
clean: {
@@ -1699,7 +1706,7 @@ const message = {
imported: '手動創建',
importType: '導入方式',
pasteSSL: '粘貼代碼',
localSSL: '選擇本地文件',
localSSL: '選擇伺服器文件',
privateKeyPath: '私鑰文件',
certificatePath: '證書文件',
ipWhiteListHelper: 'IP白名單的作用:所有規則對IP白名單無效',
@@ -2001,59 +2008,173 @@ const message = {
statusCode: '狀態碼',
manage: '管理',
},
xpack: {
name: '專業版',
waf: {
name: 'WAF',
blackWhite: '黑白名單',
globalSetting: '全域設定',
websiteSetting: '網站設定',
blockRecords: '封鎖記錄',
world: '世界',
china: '中國',
intercept: '攔截',
request: '請求',
count4xx: '4xx 數量',
count5xx: '5xx 數量',
todayStatus: '今日狀態',
reqMap: '請求地圖(30日)',
resource: '來源',
count: '數量',
hight: '高',
low: '低',
reqCount: '請求數',
interceptCount: '攔截數',
requestTrends: '請求趨勢(7天)',
interceptTrends: '攔截趨勢(7天)',
},
monitor: {
name: '網站監控',
},
tamper: {
tamperHelper1: '一鍵部署類型的網站,建議啟用應用目錄防篡改功能;',
tamperHelper2: '如果在啟用防篡改功能後出現網站無法正常使用或備份、恢復失敗的情況,請先關閉防篡改功能;',
tamperHelper3:
'啟用防篡改,將限制非排除目錄下受保護文件的讀寫、刪除、權限和所有者修改操作,請在設定 [排除目錄] 和 [保護] 時謹慎選擇。',
op: '操作',
create: '創建',
file: '文件',
tamperPath: '防護目錄',
tamperPathEdit: '修改路徑',
log: '攔截日誌',
totalProtect: '總防護',
todayProtect: '今日防護',
addRule: '添加規則',
ignore: '排除目錄',
ignoreHelper: '一行一個,例: \ntmp\n./tmp',
ignoreHelper1: '添加要忽略的文件夾名或特定路徑',
ignoreHelper2: '要忽略特定文件夾,請使用以 ./ 開頭的相對路徑',
protect: '保護',
protectHelper: '一行一個,例: \npng\n./test.css',
protectHelper1: '可指定文件名、後綴名或特定文件進行保護',
protectHelper2: '要保護特定文件,請使用以 ./ 開頭的相對路徑',
enableHelper: '即將啟用 {0} 網站的防窜改功能,以提升網站安全性,是否繼續?',
disableHelper: '即將關閉 {0} 網站的防窜改功能,是否繼續?',
},
},
xpack: {
name: '專業版',
waf: {
name: 'WAF',
blackWhite: '黑白名單',
globalSetting: '全域設定',
websiteSetting: '網站設定',
blockRecords: '封鎖記錄',
world: '世界',
china: '中國',
intercept: '攔截',
request: '請求',
count4xx: '4xx 數量',
count5xx: '5xx 數量',
todayStatus: '今日狀態',
reqMap: '攔截地圖(30日)',
resource: '來源',
count: '數量',
hight: '高',
low: '低',
reqCount: '請求數',
interceptCount: '攔截數',
requestTrends: '請求趨勢(7天)',
interceptTrends: '攔截趨勢(7天)',
whiteList: '白名單',
blackList: '黑名單',
ipBlackListHelper: '黑名單中的 IP 無法存取網站',
ipWhiteListHelper: '白名單中的 IP 不受任何規則限制',
uaBlackListHelper: '攜帶黑名單中的 User-Agent 的請求將被攔截',
uaWhiteListHelper: '攜帶白名單中的 User-Agent 的請求不受任何規則限制',
urlBlackListHelper: '請求黑名單中的 URL 將被攔截',
urlWhiteListHelper: '請求白名單中的 URL 請求不受任何規則限制',
ccHelper: '{0} 秒內累積請求任意網站超過 {1} 次,封鎖此 IP {2} 秒',
blockTime: '封鎖時間',
attackHelper: '{0} 秒內累計攔截超過 {1} 次,封鎖此 IP {2} 秒',
notFoundHelper: '{0} 秒內累計請求回傳 404 超過 {1} 次,封鎖此 IP {2} 秒',
frequencyLimit: '頻率限制',
regionLimit: '地區限制',
defaultRule: '預設規則',
accessFrequencyLmit: '存取頻率限制',
attackLimit: '攻擊頻率限制',
notFoundLimit: '404 頻率限制',
urlLimit: 'URL 頻率限制',
urlLimitHelper: '為單一 URL 設定存取頻率',
sqliDefense: 'SQL 注入防禦',
sqliHelper: '辨識請求中的 SQL 注入並攔截',
xssHelper: '辨識請求中的 XSS 並攔截',
xssDefense: 'XSS 防禦',
uaDefense: '惡意 User-Agent 規則',
uaHelper: '包含常見的惡意爬蟲規則',
argsDefense: '惡意參數規則',
argsHelper: '在禁止請求中攜帶惡意參數',
cookieDefense: '惡意 Cookie 規則',
cookieHelper: '禁止請求中攜帶惡意 Cookie',
headerDefense: '惡意 Header 規則',
headerHelper: '禁止請求中攜帶惡意 Header',
httpRule: 'HTTP 請求方法規則',
httpHelper: '限制網站的請求方法類型',
geoRule: '地區存取限制',
geoHelper: '限制某些地區造訪你的網站',
ipLocation: 'IP 歸屬地',
action: '動作',
ruleType: '攻擊類型',
ipHelper: '請輸入 IP',
attackLog: '攻擊日誌',
rule: '規則',
ipArr: 'IPV4 範圍',
ipStart: '起始 IP',
ipEnd: '結束 IP',
ipv4: 'IPV4',
ipv6: 'IPV6',
urlDefense: 'URL 規則',
urlHelper: '禁止存取的 URL',
dirFilter: '目錄過濾',
sqlInject: 'SQL 注入',
xss: 'XSS',
phpExec: 'PHP 腳本執行',
oneWordTrojan: '一句話木馬',
appFilter: '套用危險目錄過濾',
webshell: 'Webshell',
args: '惡意參數',
protocolFilter: '協議過濾',
javaFileter: 'Java 危險檔案過濾',
scannerFilter: '掃描器過濾',
escapeFilter: '轉義過濾',
customRule: '自訂規則',
httpMethod: 'HTTP 方法過濾',
fileExt: '檔案上傳限制',
fileExtHelper: '禁止上傳的檔案副檔名',
deny: '禁止',
allow: '只允許',
field: '匹配對象',
pattern: '符合條件',
ruleContent: '符合內容',
contain: '包含',
equal: '等於',
regex: '正規表示式',
notEqual: '不等於',
customRuleHelper: '根據條件匹配執行對應動作',
actionAllow: '允許',
blockIP: '封鎖 IP',
code: '返回狀態碼',
noRes: '斷開連線 444',
badReq: '參數錯誤 400',
forbidden: '禁止訪問 403',
serverErr: '伺服器錯誤 500',
resHtml: '回應頁面',
allowHelper: '允許訪問會跳過後續的 WAF 規則,請謹慎使用',
captcha: '人機驗證',
fiveSeconds: '5 秒驗證',
location: '地區',
redisConfig: 'Redis 配置',
redisHelper: '開啟 Redis 可以將暫時拉黑的 IP 持久化',
wafHelper: '關閉之後所有網站將失去防護',
attackIP: '攻擊 IP',
attackParam: '攻擊訊息',
execRule: '命中規則',
acl: 'ACL',
sql: 'SQL 注入',
cc: 'CC 攻擊',
isBlocking: '封鎖中',
isFree: '已解封',
},
monitor: {
name: '網站監控',
},
tamper: {
tamper: '防篡改',
tamperHelper1: '一鍵部署類型的網站,建議啟用應用目錄防篡改功能;',
tamperHelper2: '如果在啟用防篡改功能後出現網站無法正常使用或備份、恢復失敗的情況,請先關閉防篡改功能;',
tamperHelper3:
'啟用防篡改,將限制非排除目錄下受保護文件的讀寫、刪除、權限和所有者修改操作,請在設定 [排除目錄] 和 [保護] 時謹慎選擇。',
op: '操作',
create: '創建',
file: '文件',
tamperPath: '防護目錄',
tamperPathEdit: '修改路徑',
log: '攔截日誌',
totalProtect: '總防護',
todayProtect: '今日防護',
addRule: '添加規則',
ignore: '排除目錄',
ignoreHelper: '一行一個,例: \ntmp\n./tmp',
ignoreHelper1: '添加要忽略的文件夾名或特定路徑',
ignoreHelper2: '要忽略特定文件夾,請使用以 ./ 開頭的相對路徑',
protect: '保護',
protectHelper: '一行一個,例: \npng\n./test.css',
protectHelper1: '可指定文件名、後綴名或特定文件進行保護',
protectHelper2: '要保護特定文件,請使用以 ./ 開頭的相對路徑',
enableHelper: '即將啟用 {0} 網站的防窜改功能,以提升網站安全性,是否繼續?',
disableHelper: '即將關閉 {0} 網站的防窜改功能,是否繼續?',
},
setting: {
setting: '界面設置',
title: '面板描述',
titleHelper: '將會顯示在使用者登錄頁面 (例: Linux 伺服器運維管理面板)',
logo: 'Logo',
logoHelper: '將會顯示在菜單收縮時管理頁面左上方 (建議圖片大小為: 82px*82px)',
logoWithText: 'Logo (帶文字)',
logoWithTextHelper: '將會顯示在菜單展開時管理頁面左上方 (建議圖片大小為: 185px*55px)',
favicon: '網站圖標',
faviconHelper: '網站圖標 (建議圖片大小為: 16px*16px)',
reUpload: '重新上傳',
supportType: '只能上傳 jpg/png/jpeg 檔案!',
setDefault: '恢復默認',
reset: '重置',
},
},
};
+127 -6
View File
@@ -51,6 +51,7 @@ const message = {
uninstall: '卸载',
fullscreen: '全屏',
quitFullscreen: '退出全屏',
update: '编辑',
},
search: {
timeStart: '开始时间',
@@ -193,6 +194,7 @@ const message = {
filePermission: '权限错误',
formatErr: '格式错误,检查后重试',
phpExtension: '仅支持 , _ 小写英文和数字',
paramHttp: '必须以 http:// 或 https:// 开头',
},
res: {
paramError: '请求失败,请稍后重试!',
@@ -243,6 +245,7 @@ const message = {
ready: '正常',
applying: '申请中',
applyerror: '失败',
syncerr: '失败',
},
units: {
second: '秒',
@@ -357,6 +360,7 @@ const message = {
goUpgrade: '去应用列表升级',
goInstall: '去应用商店安装',
source: '来源',
isDelete: '已删除',
permission: '权限',
permissionForIP: '指定 IP',
permissionAll: '所有人(%)',
@@ -377,7 +381,7 @@ const message = {
confChange: '配置修改',
confNotFound: '未能找到该应用配置文件,请在应用商店升级该应用至最新版本后重试!',
loadFromRemote: '从服务器获取',
loadFromRemote: '从服务器同步',
userBind: '绑定用户',
pgBindHelper: '该操作用于创建新用户并将其绑定到目标数据库,暂不支持选择已存在于数据库中的用户。',
pgSuperUser: '超级用户',
@@ -1234,6 +1238,9 @@ const message = {
path: '路径',
snapshot: '快照',
ignoreRule: '排除规则',
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,请谨慎修改。',
ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache',
status: '快照状态',
panelInfo: '写入 1Panel 基础信息',
panelBin: '备份 1Panel 系统文件',
@@ -1260,7 +1267,7 @@ const message = {
'即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 docker 以及 1panel 服务,是否继续?',
upgrading: '正在升级中,请稍候...',
upgradeHelper: '升级操作需要重启服务,是否继续?',
upgradeHelper: '升级操作需要重启 1Panel 服务,是否继续?',
noUpgrade: '当前已经是最新版本',
versionHelper: '1Panel 版本号命名规则为: [大版本].[功能版本].[Bug 修复版本],例:',
versionHelper1: 'v1.0.1 是 v1.0.0 之后的 Bug 修复版本',
@@ -1341,8 +1348,8 @@ const message = {
doc: '官方文档',
star: '点亮 Star',
description: '1Panel,现代化、开源的 Linux 服务器运维管理面板。',
forum: '论坛',
doc2: '文档',
forum: '论坛求助',
doc2: '使用手冊',
currentVersion: '当前运行版本:',
},
clean: {
@@ -1699,7 +1706,7 @@ const message = {
imported: '手动创建',
importType: '导入方式',
pasteSSL: '粘贴代码',
localSSL: '选择本地文件',
localSSL: '选择服务器文件',
privateKeyPath: '私钥文件',
certificatePath: '证书文件',
ipWhiteListHelper: 'IP 白名单的作用:所有规则对IP白名单无效',
@@ -2004,6 +2011,7 @@ const message = {
},
xpack: {
name: '专业版',
menu: '高级功能',
waf: {
name: 'WAF',
blackWhite: '黑白名单',
@@ -2017,7 +2025,7 @@ const message = {
count4xx: '4xx 数量',
count5xx: '5xx 数量',
todayStatus: '今日状态',
reqMap: '请求地图(30日)',
reqMap: '拦截地图(30日)',
resource: '来源',
count: '数量',
hight: '高',
@@ -2026,6 +2034,104 @@ const message = {
interceptCount: '拦截数',
requestTrends: '请求趋势(7日)',
interceptTrends: '拦截趋势(7日)',
whiteList: '白名单',
blackList: '黑名单',
ipBlackListHelper: '黑名单中的 IP 无法访问网站',
ipWhiteListHelper: '白名单中的 IP 不受任何规则限制',
uaBlackListHelper: '携带黑名单中的 User-Agent 的请求将被拦截',
uaWhiteListHelper: '携带白名单中的 User-Agent 的请求不受任何规则限制',
urlBlackListHelper: '请求黑名单中的 URL 将被拦截',
urlWhiteListHelper: '请求白名单中的 URL 请求不受任何规则限制',
ccHelper: '{0} 秒内累计请求任意网站超过 {1} 次,封锁此 IP {2} 秒',
blockTime: '封禁时间',
attackHelper: '{0} 秒内累计拦截超过 {1} 次,封锁此 IP {2} 秒',
notFoundHelper: '{0} 秒内累计请求返回 404 超过 {1} 次,封锁此 IP {2} 秒',
frequencyLimit: '频率限制',
regionLimit: '地区限制',
defaultRule: '默认规则',
accessFrequencyLmit: '访问频率限制',
attackLimit: '攻击频率限制',
notFoundLimit: '404 频率限制',
urlLimit: 'URL 频率限制',
urlLimitHelper: '为单个 URL 设置访问频率',
sqliDefense: 'SQL 注入防御',
sqliHelper: '识别请求中的 SQL 注入并拦截',
xssHelper: '识别请求中的 XSS 并拦截',
xssDefense: 'XSS 防御',
uaDefense: 'User-Agent 规则',
uaHelper: '包含常见的恶意爬虫规则',
argsDefense: '参数规则',
argsHelper: '禁止请求中携带恶意参数',
cookieDefense: 'Cookie 规则',
cookieHelper: '禁止请求中携带恶意 Cookie',
headerDefense: 'Header 规则',
headerHelper: '禁止请求中携带恶意 Header',
httpRule: 'HTTP 规则',
httpHelper: '限制网站的请求方法类型',
geoRule: '地区访问限制',
geoHelper: '限制某些地区访问你的网站',
ipLocation: 'IP 归属地',
action: '动作',
ruleType: '攻击类型',
ipHelper: '请输入 IP',
attackLog: '攻击日志',
rule: '规则',
ipArr: 'IPV4 范围',
ipStart: '起始 IP',
ipEnd: '结束 IP',
ipv4: 'IPV4',
ipv6: 'IPV6',
urlDefense: 'URL 规则',
urlHelper: '禁止访问的 URL',
dirFilter: '目录过滤',
sqlInject: 'SQL 注入',
xss: 'XSS',
phpExec: 'PHP 脚本执行',
oneWordTrojan: '一句话木马',
appFilter: '应用危险目录过滤',
webshell: 'Webshell',
args: '恶意参数',
protocolFilter: '协议过滤',
javaFileter: 'Java 危险文件过滤',
scannerFilter: '扫描器过滤',
escapeFilter: '转义过滤',
customRule: '自定义规则',
httpMethod: 'HTTP 方法过滤',
fileExt: '文件上传限制',
fileExtHelper: '禁止上传的文件扩展名',
deny: '禁止',
allow: '仅允许',
field: '匹配对象',
pattern: '匹配条件',
ruleContent: '匹配内容',
contain: '包含',
equal: '等于',
regex: '正则表达式',
notEqual: '不等于',
customRuleHelper: '根据条件匹配执行相应动作',
actionAllow: '允许',
blockIP: '封禁 IP',
code: '返回状态码',
noRes: '断开连接 444',
badReq: '参数错误 400',
forbidden: '禁止访问 403',
serverErr: '服务器错误 500',
resHtml: '响应页面',
allowHelper: '允许访问会跳过后续的 WAF 规则,请谨慎使用',
captcha: '人机验证',
fiveSeconds: '5 秒验证',
location: '地区',
redisConfig: 'Redis 配置',
redisHelper: '开启 Redis 可以将临时拉黑的 IP 持久化',
wafHelper: '关闭之后所有网站将失去防护',
attackIP: '攻击 IP',
attackParam: '攻击信息',
execRule: '命中规则',
acl: 'ACL',
sql: 'SQL 注入',
cc: 'CC 攻击',
isBlocking: '封禁中',
isFree: '已解封',
},
monitor: {
name: '网站监控',
@@ -2056,6 +2162,21 @@ const message = {
enableHelper: '即将启用 {0} 网站的防窜改功能,以提升网站安全性,是否继续?',
disableHelper: '即将关闭 {0} 网站的防窜改功能,是否继续?',
},
setting: {
setting: '界面设置',
title: '面板描述',
titleHelper: '将会显示在用户登录页面 (例: Linux 服务器运维管理面板)',
logo: 'Logo',
logoHelper: '将会显示在菜单收缩时管理页面左上方 (建议图片大小为: 82px*82px)',
logoWithText: 'Logo (带文字)',
logoWithTextHelper: '将会显示在菜单展开时管理页面左上方 (建议图片大小为: 185px*55px)',
favicon: '网站图标',
faviconHelper: '网站图标 (建议图片大小为: 16px*16px)',
reUpload: '重新上传',
supportType: '只能上传 jpg/png/jpeg 文件!',
setDefault: '恢复默认',
reset: '重置',
},
},
};
export default {
+1 -2
View File
@@ -10,10 +10,9 @@
<script setup lang="ts">
import cacheRouter from '@/routers/cache-router';
import { useRoute } from 'vue-router';
import { computed } from 'vue';
const key = computed(() => {
return useRoute().path + Math.random();
return Math.random();
});
</script>
+1 -4
View File
@@ -5,15 +5,12 @@ import { AxiosCanceler } from '@/api/helper/axios-cancel';
const axiosCanceler = new AxiosCanceler();
/**
* @description 路由拦截 beforeEach(路由配置无数种方法,个人觉得最简便)
* */
router.beforeEach((to, from, next) => {
NProgress.start();
axiosCanceler.removeAllPending();
const globalStore = GlobalStore();
if (to.name === 'home' && !globalStore.isLogin) {
if (to.name !== 'entrance' && !globalStore.isLogin) {
next({
name: 'entrance',
params: { code: globalStore.entrance },
+5
View File
@@ -20,6 +20,11 @@ export const GlobalStore = defineStore({
primary: '#005EEB',
theme: 'auto',
footer: true,
title: '',
logo: '',
logoWithText: '',
favicon: '',
},
isFullScreen: false,
isOnRestart: false,
+5
View File
@@ -5,6 +5,11 @@ export interface ThemeConfigProp {
primary: string;
theme: string; // dark | bright | auto
footer: boolean;
title: string;
logo: string;
logoWithText: string;
favicon: string;
}
export interface GlobalState {
+25 -1
View File
@@ -412,5 +412,29 @@ html {
}
.p-mb-5 {
margin-bottom: 5px !important;
margin-bottom: 5px !important;
}
.p-w-200 {
width: 200px !important;
}
.p-w-100 {
width: 100px !important;
}
.p-w-150 {
width: 150px !important;
}
.p-w-400 {
width: 400px !important;
}
.p-ml-20 {
margin-left: 20px !important;
}
.p-mt-20 {
margin-top: 20px !important;
}
-3
View File
@@ -160,9 +160,6 @@ html {
.el-dialog {
.el-dialog__header {
padding: 15px 20px;
margin: 0;
border-bottom: 1px solid #f0f0f0;
.el-dialog__title {
font-size: 17px;
}
+18
View File
@@ -500,3 +500,21 @@ export async function copyText(content: string) {
MsgError(i18n.global.t('commons.msg.copyFailed'));
}
}
export function getRuleType(ruleType: string) {
if (ruleType == '') {
return '';
}
return i18n.global.t(`xpack.waf.${ruleType}`);
}
export function getAction(action: string) {
if (action == '') {
return '';
}
return i18n.global.t(`xpack.waf.${action}`);
}
export function getLanguage() {
return localStorage.getItem('lang') || 'zh';
}
+7 -7
View File
@@ -12,7 +12,7 @@
>
{{ $t('app.all') }}
</el-button>
<div v-for="item in tags.slice(0, 6)" :key="item.key" class="inline">
<div v-for="item in tags.slice(0, 7)" :key="item.key" class="inline">
<el-button
class="tag-button"
:class="activeTag === item.key ? '' : 'no-active'"
@@ -38,7 +38,7 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in tags.slice(6)"
v-for="item in tags.slice(7)"
@click="changeTag(item.key)"
:key="item.key"
>
@@ -57,7 +57,7 @@
<template #rightButton>
<div class="flex justify-end">
<div class="mr-10">
<el-checkbox v-model="req.resource" true-label="all" false-label="remote" @change="search(req)">
<el-checkbox v-model="req.resource" true-value="all" false-value="remote" @change="search(req)">
{{ $t('app.showLocal') }}
</el-checkbox>
</div>
@@ -106,7 +106,7 @@
<div class="app-content">
<div class="app-header">
<span class="app-title">{{ app.name }}</span>
<el-text type="success" style="margin-left: 10px" v-if="app.installed">
<el-text type="success" class="!ml-2" v-if="app.installed">
{{ $t('app.allReadyInstalled') }}
</el-text>
<el-button
@@ -174,8 +174,8 @@ import Detail from '../detail/index.vue';
import Install from '../detail/install/index.vue';
import router from '@/routers';
import { MsgSuccess } from '@/utils/message';
import { useI18n } from 'vue-i18n';
import { GlobalStore } from '@/store';
import { getLanguage } from '@/utils/util';
const globalStore = GlobalStore();
@@ -183,7 +183,7 @@ const mobile = computed(() => {
return globalStore.isMobile();
});
const language = useI18n().locale.value;
const language = getLanguage();
const paginationConfig = reactive({
cacheSizeKey: 'app-page-size',
@@ -274,7 +274,7 @@ const changeTag = (key: string) => {
req.tags = [key];
}
const index = tags.value.findIndex((tag) => tag.key === key);
if (index > 5) {
if (index > 6) {
moreTag.value = key;
} else {
moreTag.value = '';
@@ -6,7 +6,7 @@
<div class="brief" v-loading="loadingApp">
<div class="detail flex">
<div class="w-12 h-12 rounded p-1 shadow-md icon">
<img :src="app.icon" alt="App Icon" class="w-full h-full rounded" />
<img :src="app.icon" alt="App Icon" class="w-full h-full rounded" style="object-fit: contain" />
</div>
<div class="ml-4">
<div class="name mb-2">
@@ -78,13 +78,13 @@
import { GetApp, GetAppDetail } from '@/api/modules/app';
import MdEditor from 'md-editor-v3';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Install from './install/index.vue';
import router from '@/routers';
import { GlobalStore } from '@/store';
import { getLanguage } from '@/utils/util';
const globalStore = GlobalStore();
const language = useI18n().locale.value;
const language = getLanguage();
const app = ref<any>({});
const appDetail = ref<any>({});
@@ -25,7 +25,7 @@
@change="updateParam"
></el-input>
<el-select
style="width: 100%"
class="p-w-200"
v-model="form[p.envKey]"
v-if="p.type == 'service'"
@change="changeService(form[p.envKey], p.services)"
@@ -37,12 +37,12 @@
:label="service.label"
></el-option>
</el-select>
<span v-if="p.type === 'service' && p.services.length === 0">
<span v-if="p.type === 'service' && p.services.length === 0" class="ml-1.5">
<el-link type="primary" :underline="false" @click="toPage(p.key)">
{{ $t('app.toInstall') }}
</el-link>
</span>
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple">
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple" class="p-w-200">
<el-option
v-for="service in p.values"
:key="service.label"
@@ -56,7 +56,7 @@
<el-select
v-model="form[p.envKey]"
@change="getServices(p.child.envKey, form[p.envKey], p)"
style="width: 100%"
class="p-w-200"
>
<el-option
v-for="service in p.values"
@@ -73,6 +73,7 @@
v-model="form[p.child.envKey]"
v-if="p.child.type == 'service'"
@change="changeService(form[p.child.envKey], p.services)"
class="p-w-200"
>
<el-option
v-for="service in p.services"
@@ -106,10 +107,7 @@ import { getRandomStr } from '@/utils/util';
import { GetAppService } from '@/api/modules/app';
import { Rules } from '@/global/form-rules';
import { App } from '@/api/interface/app';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { getDBName } from '@/utils/util';
const router = useRouter();
interface ParamObj extends App.FromField {
services: App.AppService[];
@@ -247,7 +245,7 @@ const changeService = (value: string, services: App.AppService[]) => {
};
const getLabel = (row: ParamObj): string => {
const language = useI18n().locale.value;
const language = localStorage.getItem('lang') || 'zh';
if (language == 'zh' || language == 'tw') {
return row.labelZh;
} else {
@@ -256,7 +254,7 @@ const getLabel = (row: ParamObj): string => {
};
const toPage = (key: string) => {
router.push({ name: 'AppAll', query: { install: key } });
window.location.href = '/apps/all?install=' + key;
};
onMounted(() => {
@@ -122,7 +122,6 @@ import { App } from '@/api/interface/app';
import { GetAppInstallParams, UpdateAppInstallParams } from '@/api/modules/app';
import { reactive, ref } from 'vue';
import Header from '@/components/drawer-header/index.vue';
import { useI18n } from 'vue-i18n';
import { FormInstance } from 'element-plus';
import { Rules, checkNumberRange } from '@/global/form-rules';
import { MsgSuccess } from '@/utils/message';
@@ -130,6 +129,7 @@ import i18n from '@/lang';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { getLanguage } from '@/utils/util';
const extensions = [javascript(), oneDark];
@@ -228,7 +228,7 @@ const get = async () => {
};
const getLabel = (row: EditForm): string => {
const language = useI18n().locale.value;
const language = getLanguage();
if (language == 'zh' || language == 'tw') {
return row.labelZh;
} else {
@@ -13,7 +13,7 @@
>
{{ $t('app.all') }}
</el-button>
<div v-for="item in tags.slice(0, 6)" :key="item.key" class="inline">
<div v-for="item in tags.slice(0, 7)" :key="item.key" class="inline">
<el-button
class="tag-button"
:class="activeTag === item.key ? '' : 'no-active'"
@@ -39,7 +39,7 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in tags.slice(6)"
v-for="item in tags.slice(7)"
@click="changeTag(item.key)"
:key="item.key"
>
@@ -317,11 +317,10 @@ import AppDetail from '../detail/index.vue';
import ComposeLogs from '@/components/compose-log/index.vue';
import { App } from '@/api/interface/app';
import Status from '@/components/status/index.vue';
import { getAge } from '@/utils/util';
import { getAge, getLanguage } from '@/utils/util';
import { useRouter } from 'vue-router';
import { MsgSuccess } from '@/utils/message';
import { toFolder } from '@/global/business';
import { useI18n } from 'vue-i18n';
const data = ref<any>();
const loading = ref(false);
@@ -361,7 +360,7 @@ const router = useRouter();
const activeName = ref(i18n.global.t('app.installed'));
const mode = ref('installed');
const moreTag = ref('');
const language = useI18n().locale.value;
const language = getLanguage();
const appDetail = ref();
const sync = () => {
@@ -390,7 +389,7 @@ const changeTag = (key: string) => {
searchReq.tags = [key];
}
const index = tags.value.findIndex((tag) => tag.key === key);
if (index > 5) {
if (index > 6) {
moreTag.value = key;
} else {
moreTag.value = '';
@@ -15,9 +15,9 @@
<el-form ref="formRef" @submit.prevent label-position="top" :model="form" :rules="rules">
<el-form-item :label="$t('container.from')">
<el-radio-group v-model="form.from" @change="changeFrom">
<el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio label="path">{{ $t('container.pathSelect') }}</el-radio>
<el-radio label="template">{{ $t('container.composeTemplate') }}</el-radio>
<el-radio value="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio value="path">{{ $t('container.pathSelect') }}</el-radio>
<el-radio value="template">{{ $t('container.composeTemplate') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.from === 'path'" prop="path">
@@ -61,7 +61,7 @@
</el-row>
</template>
<template #search>
<el-select v-model="searchState" @change="search()" clearable>
<el-select v-model="searchState" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value="all"></el-option>
<el-option :label="$t('commons.status.created')" value="created"></el-option>
@@ -110,7 +110,12 @@
<Status :key="row.state" :status="row.state"></Status>
</template>
</el-table-column>
<el-table-column :label="$t('container.source')" show-overflow-tooltip min-width="100">
<el-table-column
:label="$t('container.source')"
show-overflow-tooltip
prop="resource"
min-width="100"
>
<template #default="{ row }">
<div v-if="row.hasLoad">
<div class="source-font">CPU: {{ row.cpuPercent.toFixed(2) }}%</div>
@@ -195,7 +200,7 @@
</div>
</template>
</el-table-column>
<el-table-column :label="$t('container.related')" min-width="200">
<el-table-column :label="$t('container.related')" min-width="200" prop="appName">
<template #default="{ row }">
<div>
<el-tooltip
@@ -288,6 +293,7 @@
:buttons="buttons"
:label="$t('commons.table.operate')"
:fixed="mobile ? false : 'right'"
prop="operate"
/>
</ComplexTable>
</template>
@@ -152,7 +152,7 @@ const searchLogs = async () => {
`${protocol}://${host}/api/v1/containers/search/log?container=${logSearch.containerID}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data.replace(/\x1B\[[0-9;]*[mG]/g, '');
logInfo.value += event.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
@@ -76,8 +76,8 @@
</el-form-item>
<el-form-item :label="$t('commons.table.port')">
<el-radio-group v-model="dialogData.rowData!.publishAllPorts" class="ml-4">
<el-radio :label="false">{{ $t('container.exposePort') }}</el-radio>
<el-radio :label="true">{{ $t('container.exposeAll') }}</el-radio>
<el-radio :value="false">{{ $t('container.exposePort') }}</el-radio>
<el-radio :value="true">{{ $t('container.exposeAll') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="!dialogData.rowData!.publishAllPorts">
@@ -139,8 +139,8 @@
<div v-for="(row, index) in dialogData.rowData!.volumes" :key="index" style="width: 100%">
<el-card class="mt-1">
<el-radio-group v-model="row.isVolume">
<el-radio-button :label="true">{{ $t('container.volumeOption') }}</el-radio-button>
<el-radio-button :label="false">{{ $t('container.hostOption') }}</el-radio-button>
<el-radio-button :value="true">{{ $t('container.volumeOption') }}</el-radio-button>
<el-radio-button :value="false">{{ $t('container.hostOption') }}</el-radio-button>
</el-radio-group>
<el-button
class="float-right mt-3"
@@ -216,10 +216,10 @@
</el-form-item>
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
<el-radio-group v-model="dialogData.rowData!.restartPolicy">
<el-radio label="no">{{ $t('container.no') }}</el-radio>
<el-radio label="always">{{ $t('container.always') }}</el-radio>
<el-radio label="on-failure">{{ $t('container.onFailure') }}</el-radio>
<el-radio label="unless-stopped">{{ $t('container.unlessStopped') }}</el-radio>
<el-radio value="no">{{ $t('container.no') }}</el-radio>
<el-radio value="always">{{ $t('container.always') }}</el-radio>
<el-radio value="on-failure">{{ $t('container.onFailure') }}</el-radio>
<el-radio value="unless-stopped">{{ $t('container.unlessStopped') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('container.cpuShare')" prop="cpuShares">
@@ -17,8 +17,8 @@
</el-form-item>
<el-form-item label="Dockerfile" prop="from">
<el-radio-group v-model="form.from">
<el-radio label="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio label="path">{{ $t('container.pathSelect') }}</el-radio>
<el-radio value="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio value="path">{{ $t('container.pathSelect') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.from === 'edit'" :rules="Rules.requiredInput">
@@ -8,8 +8,8 @@
<el-form ref="deleteForm" v-loading="loading">
<el-form-item>
<el-radio-group v-model="withTagAll">
<el-radio :label="false">{{ $t('container.imagePruneSome') }}</el-radio>
<el-radio :label="true">{{ $t('container.imagePruneAll') }}</el-radio>
<el-radio :value="false">{{ $t('container.imagePruneSome') }}</el-radio>
<el-radio :value="true">{{ $t('container.imagePruneAll') }}</el-radio>
</el-radio-group>
</el-form-item>
<span v-if="withTagAll">
@@ -26,8 +26,8 @@
</el-form-item>
<el-form-item :label="$t('container.auth')" prop="auth">
<el-radio-group v-model="dialogData.rowData!.auth">
<el-radio :label="true">{{ $t('commons.true') }}</el-radio>
<el-radio :label="false">{{ $t('commons.false') }}</el-radio>
<el-radio :value="true">{{ $t('commons.true') }}</el-radio>
<el-radio :value="false">{{ $t('commons.false') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.auth" :label="$t('commons.login.username')" prop="username">
@@ -38,8 +38,8 @@
<LayoutContent style="margin-top: 20px" :title="$t('container.setting')" :divider="true">
<template #main>
<el-radio-group v-model="confShowType" @change="changeMode">
<el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
<el-radio-button label="all">{{ $t('database.allConf') }}</el-radio-button>
<el-radio-button value="base">{{ $t('database.baseConf') }}</el-radio-button>
<el-radio-button value="all">{{ $t('database.allConf') }}</el-radio-button>
</el-radio-group>
<el-row style="margin-top: 20px" v-if="confShowType === 'base'">
<el-col :span="1"><br /></el-col>
@@ -146,8 +146,8 @@
</el-form-item>
<el-form-item label="cgroup-driver" prop="cgroupDriver">
<el-radio-group v-model="form.cgroupDriver" @change="handleCgroup">
<el-radio label="cgroupfs">cgroupfs</el-radio>
<el-radio label="systemd">systemd</el-radio>
<el-radio value="cgroupfs">cgroupfs</el-radio>
<el-radio value="systemd">systemd</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('container.sockPath')" prop="dockerSockPath">
@@ -33,8 +33,8 @@
</el-form-item>
<el-form-item :label="$t('container.version')" prop="nfsVersion">
<el-radio-group v-model="form.nfsVersion">
<el-radio label="v3">NFS</el-radio>
<el-radio label="v4">NFS4</el-radio>
<el-radio value="v3">NFS</el-radio>
<el-radio value="v4">NFS4</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('container.mountpoint')" prop="nfsMount">
+27 -24
View File
@@ -105,32 +105,35 @@
</el-table-column>
<el-table-column :min-width="80" :label="$t('setting.backupAccount')" prop="defaultDownload">
<template #default="{ row }">
<div v-for="(item, index) of row.backupAccounts?.split(',')" :key="index">
<div v-if="row.accountExpand || (!row.accountExpand && index < 3)">
<span v-if="row.backupAccounts">
<span>
{{ $t('setting.' + item) }}
<span v-if="!hasBackup(row.type)">-</span>
<div v-else>
<div v-for="(item, index) of row.backupAccounts?.split(',')" :key="index">
<div v-if="row.accountExpand || (!row.accountExpand && index < 3)">
<span v-if="row.backupAccounts">
<span>
{{ $t('setting.' + item) }}
</span>
<el-icon
size="12"
v-if="item === row.defaultDownload"
class="relative top-px left-1"
>
<Star />
</el-icon>
</span>
<el-icon
size="12"
v-if="item === row.defaultDownload"
class="relative top-px left-1"
>
<Star />
</el-icon>
</span>
<span v-else>-</span>
<span v-else>-</span>
</div>
</div>
<div v-if="!row.accountExpand && row.backupAccounts?.split(',').length > 3">
<el-button type="primary" link @click="row.accountExpand = true">
{{ $t('commons.button.expand') }}...
</el-button>
</div>
<div v-if="row.accountExpand && row.backupAccounts?.split(',').length > 3">
<el-button type="primary" link @click="row.accountExpand = false">
{{ $t('commons.button.collapse') }}
</el-button>
</div>
</div>
<div v-if="!row.accountExpand && row.backupAccounts?.split(',').length > 3">
<el-button type="primary" link @click="row.accountExpand = true">
{{ $t('commons.button.expand') }}...
</el-button>
</div>
<div v-if="row.accountExpand && row.backupAccounts?.split(',').length > 3">
<el-button type="primary" link @click="row.accountExpand = false">
{{ $t('commons.button.collapse') }}
</el-button>
</div>
</template>
</el-table-column>
+3 -3
View File
@@ -208,9 +208,9 @@
<div v-if="dialogData.rowData!.type === 'database'">
<el-form-item :label="$t('cronjob.database')">
<el-radio-group v-model="dialogData.rowData!.dbType" @change="loadDatabases">
<el-radio label="mysql">MySQL</el-radio>
<el-radio label="mariadb">Mariadb</el-radio>
<el-radio label="postgresql">PostgreSQL</el-radio>
<el-radio value="mysql">MySQL</el-radio>
<el-radio value="mariadb">Mariadb</el-radio>
<el-radio value="postgresql">PostgreSQL</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('cronjob.database')" prop="dbName">
+8 -4
View File
@@ -1,6 +1,6 @@
<template>
<div v-if="recordShow" v-loading="loading">
<div class="app-status" style="margin-top: 20px">
<div class="app-status p-mt-20">
<el-card>
<div>
<el-popover
@@ -17,7 +17,7 @@
</el-tag>
</template>
</el-popover>
<el-tag v-if="dialogData.rowData.name.length < 15" style="float: left" effect="dark" type="success">
<el-tag v-if="dialogData.rowData.name.length < 15" class="float-left" effect="dark" type="success">
{{ $t('cronjob.' + dialogData.rowData.type) }} - {{ dialogData.rowData.name }}
</el-tag>
@@ -73,7 +73,7 @@
></el-date-picker>
</el-col>
<el-col :span="16">
<el-select @change="search()" v-model="searchInfo.status">
<el-select @change="search()" v-model="searchInfo.status" class="p-w-200">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value="" />
<el-option :label="$t('commons.status.success')" value="Success" />
@@ -407,7 +407,11 @@ const forDetail = async (row: Cronjob.Record) => {
const loadRecord = async (row: Cronjob.Record) => {
if (row.records) {
const res = await getRecordLog(row.id);
currentRecordDetail.value = res.data;
let log = res.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');
if (currentRecordDetail.value === log) {
return;
}
currentRecordDetail.value = log;
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
+36 -7
View File
@@ -23,7 +23,7 @@
</template>
<template #search v-if="currentDB">
<el-select v-model="currentDBName" @change="changeDatabase()">
<el-select v-model="currentDBName" @change="changeDatabase()" class="p-w-200">
<template #prefix>{{ $t('commons.table.type') }}</template>
<el-option-group :label="$t('database.local')">
<div v-for="(item, index) in dbOptionsLocal" :key="index">
@@ -113,8 +113,19 @@
</template>
<template #main v-if="currentDB">
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
<el-table-column :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column :label="$t('commons.login.username')" prop="username">
<el-table-column :label="$t('commons.table.name')" prop="name" sortable min-width="90">
<template #default="{ row }">
<Tooltip v-if="!row.isDelete" :islink="false" :text="row.name" />
<div v-else>
<span v-if="row.name.length < 15">{{ row.name }}</span>
<el-tooltip v-else :content="row.name">{{ row.name.substring(0, 10) }}...</el-tooltip>
<el-tag round type="info" class="ml-1" size="small">
{{ $t('database.isDelete') }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.login.username')" show-overflow-tooltip prop="username">
<template #default="{ row }">
<div class="flex items-center" v-if="row.username">
<span>
@@ -122,7 +133,13 @@
</span>
</div>
<div v-else>
<el-button style="margin-left: -3px" type="primary" link @click="onBind(row)">
<el-button
:disabled="row.isDelete"
style="margin-left: -3px"
type="primary"
link
@click="onBind(row)"
>
{{ $t('database.userBind') }}
</el-button>
</div>
@@ -159,7 +176,13 @@
</div>
</div>
<div v-if="row.password === '' && row.username">
<el-button style="margin-left: -3px" link type="primary" @click="onChangePassword(row)">
<el-button
:disabled="row.isDelete"
style="margin-left: -3px"
link
type="primary"
@click="onChangePassword(row)"
>
{{ $t('database.passwordHelper') }}
</el-button>
</div>
@@ -542,7 +565,7 @@ const buttons = [
{
label: i18n.global.t('database.changePassword'),
disabled: (row: Database.MysqlDBInfo) => {
return !row.username;
return !row.username || row.isDelete;
},
click: (row: Database.MysqlDBInfo) => {
onChangePassword(row);
@@ -551,7 +574,7 @@ const buttons = [
{
label: i18n.global.t('database.permission'),
disabled: (row: Database.MysqlDBInfo) => {
return !row.password;
return !row.password || row.isDelete;
},
click: (row: Database.MysqlDBInfo) => {
let param = {
@@ -576,6 +599,9 @@ const buttons = [
},
{
label: i18n.global.t('database.backupList'),
disabled: (row: Database.MysqlDBInfo) => {
return row.isDelete;
},
click: (row: Database.MysqlDBInfo) => {
let params = {
type: currentDB.value.type,
@@ -587,6 +613,9 @@ const buttons = [
},
{
label: i18n.global.t('database.loadBackup'),
disabled: (row: Database.MysqlDBInfo) => {
return row.isDelete;
},
click: (row: Database.MysqlDBInfo) => {
let params = {
type: currentDB.value.type,
@@ -21,18 +21,18 @@
</el-form-item>
<el-form-item :label="$t('commons.table.type')" prop="type">
<el-radio-group v-model="dialogData.rowData!.type" @change="changeType">
<el-radio-button label="mysql">MySQL</el-radio-button>
<el-radio-button label="mariadb">MariaDB</el-radio-button>
<el-radio-button value="mysql">MySQL</el-radio-button>
<el-radio-button value="mariadb">MariaDB</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('database.version')" prop="version">
<el-radio-group v-model="dialogData.rowData!.version" @change="isOK = false">
<div v-if="dialogData.rowData!.type === 'mysql'">
<el-radio label="8.x" />
<el-radio label="5.7" />
<el-radio label="5.6" />
<el-radio label="8.x" value="8.x" />
<el-radio label="5.7" value="5.7" />
<el-radio label="5.6" value="5.6" />
</div>
<el-radio v-else label="10.x" />
<el-radio v-else label="10.x" value="10.x" />
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('database.address')" prop="address">
@@ -21,7 +21,7 @@
</template>
<template #search v-if="currentDB">
<el-select v-model="currentDBName" @change="changeDatabase()">
<el-select v-model="currentDBName" @change="changeDatabase()" class="p-w-200">
<template #prefix>{{ $t('commons.table.type') }}</template>
<el-option-group :label="$t('database.local')">
<div v-for="(item, index) in dbOptionsLocal" :key="index">
@@ -90,7 +90,18 @@
</template>
<template #main v-if="currentDB">
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
<el-table-column :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column :label="$t('commons.table.name')" prop="name" sortable>
<template #default="{ row }">
<Tooltip v-if="!row.isDelete" :islink="false" :text="row.name" />
<div v-else>
<span v-if="row.name.length < 15">{{ row.name }}</span>
<el-tooltip v-else :content="row.name">{{ row.name.substring(0, 10) }}...</el-tooltip>
<el-tag round type="info" class="ml-1" size="small">
{{ $t('database.isDelete') }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.login.username')" prop="username">
<template #default="{ row }">
<div class="flex items-center" v-if="row.username">
@@ -99,7 +110,13 @@
</span>
</div>
<div v-else>
<el-button style="margin-left: -3px" type="primary" link @click="onBind(row)">
<el-button
:disabled="row.isDelete"
style="margin-left: -3px"
type="primary"
link
@click="onBind(row)"
>
{{ $t('database.userBind') }}
</el-button>
</div>
@@ -498,7 +515,7 @@ const buttons = [
{
label: i18n.global.t('database.changePassword'),
disabled: (row: Database.PostgresqlDBInfo) => {
return !row.username;
return !row.username || row.isDelete;
},
click: (row: Database.PostgresqlDBInfo) => {
onChangePassword(row);
@@ -507,7 +524,7 @@ const buttons = [
{
label: i18n.global.t('database.permission'),
disabled: (row: Database.PostgresqlDBInfo) => {
return !row.username;
return !row.username || row.isDelete;
},
click: (row: Database.PostgresqlDBInfo) => {
let param = {
@@ -521,6 +538,9 @@ const buttons = [
},
{
label: i18n.global.t('database.backupList'),
disabled: (row: Database.PostgresqlDBInfo) => {
return row.isDelete;
},
click: (row: Database.PostgresqlDBInfo) => {
let params = {
type: currentDB.value.type,
@@ -532,6 +552,9 @@ const buttons = [
},
{
label: i18n.global.t('database.loadBackup'),
disabled: (row: Database.PostgresqlDBInfo) => {
return row.isDelete;
},
click: (row: Database.PostgresqlDBInfo) => {
let params = {
type: currentDB.value.type,
@@ -21,9 +21,9 @@
</el-form-item>
<el-form-item :label="$t('database.version')" prop="version">
<el-radio-group v-model="dialogData.rowData!.version" @change="isOK = false">
<el-radio label="16.x" />
<el-radio label="15.x" />
<el-radio label="14.x" />
<el-radio label="16.x" value="16.x" />
<el-radio label="15.x" value="15.x" />
<el-radio label="14.x" value="14.x" />
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('database.address')" prop="address">
@@ -21,9 +21,9 @@
</el-form-item>
<el-form-item label="appendfsync" prop="appendfsync">
<el-radio-group style="width: 100%" v-model="form.appendfsync">
<el-radio label="always">always</el-radio>
<el-radio label="everysec">everysec</el-radio>
<el-radio label="no">no</el-radio>
<el-radio value="always">always</el-radio>
<el-radio value="everysec">everysec</el-radio>
<el-radio value="no">no</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
+2 -2
View File
@@ -43,11 +43,11 @@
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { SearchApp } from '@/api/modules/app';
import { getLanguage } from '@/utils/util';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
const router = useRouter();
const language = useI18n().locale.value;
const language = getLanguage();
let req = reactive({
name: '',
+6 -39
View File
@@ -7,19 +7,7 @@
path: '/',
},
]"
>
<template #route-button>
<div class="router-button">
<el-button link type="primary" @click="onRestart('1panel')">
{{ $t('home.restart_1panel') }}
</el-button>
<el-divider direction="vertical" />
<el-button link type="primary" @click="onRestart('system')">
{{ $t('home.restart_system') }}
</el-button>
</div>
</template>
</RouterButton>
/>
<el-alert
v-if="!isSafety && globalStore.showEntranceWarn"
style="margin-top: 20px"
@@ -91,14 +79,14 @@
v-model="chartOption"
@change="changeOption"
>
<el-radio-button label="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button label="io">{{ $t('home.io') }}</el-radio-button>
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
</el-radio-group>
<el-select
v-if="chartOption === 'network'"
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
style="float: right"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
@@ -112,7 +100,7 @@
v-if="chartOption === 'io'"
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
style="float: right"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
@@ -230,13 +218,10 @@
</CardWithHeader>
</el-col>
</el-row>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSave"></ConfirmDialog>
</div>
</template>
<script lang="ts" setup>
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { onMounted, onBeforeUnmount, ref, reactive } from 'vue';
import Status from '@/views/home/status/index.vue';
import App from '@/views/home/app/index.vue';
@@ -246,17 +231,14 @@ import i18n from '@/lang';
import { Dashboard } from '@/api/interface/dashboard';
import { dateFormatForSecond, computeSize } from '@/utils/util';
import { useRouter } from 'vue-router';
import { loadBaseInfo, loadCurrentInfo, systemRestart } from '@/api/modules/dashboard';
import { loadBaseInfo, loadCurrentInfo } from '@/api/modules/dashboard';
import { getIOOptions, getNetworkOptions } from '@/api/modules/monitor';
import { getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting';
import { GlobalStore } from '@/store';
import { MsgSuccess } from '@/utils/message';
const router = useRouter();
const globalStore = GlobalStore();
const statusRef = ref();
const restartType = ref();
const confirmDialogRef = ref();
const appRef = ref();
const isSafety = ref();
@@ -549,21 +531,6 @@ const loadSafeStatus = async () => {
isSafety.value = res.data.securityEntrance;
};
const onRestart = (type: string) => {
restartType.value = type;
let params = {
header: i18n.global.t('home.restart_' + type),
operationInfo: '',
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRef.value!.acceptParams(params);
};
const onSave = async () => {
globalStore.isOnRestart = true;
MsgSuccess(i18n.global.t('home.operationSuccess'));
await systemRestart(restartType.value);
};
const onFocus = () => {
isActive.value = true;
};
@@ -8,24 +8,24 @@
@opened="onOpen"
:top="'5vh'"
>
<el-form :inline="true" :model="config">
<el-form :inline="true" :model="config" class="mt-1.5">
<el-form-item :label="$t('file.theme')">
<el-select v-model="config.theme" @change="changeTheme()">
<el-select v-model="config.theme" @change="changeTheme()" class="p-w-200">
<el-option v-for="item in themes" :key="item.label" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item :label="$t('file.language')">
<el-select v-model="config.language" @change="changeLanguage()">
<el-select v-model="config.language" @change="changeLanguage()" class="p-w-200">
<el-option v-for="lang in Languages" :key="lang.label" :value="lang.label" :label="lang.label" />
</el-select>
</el-form-item>
<el-form-item :label="$t('file.eol')">
<el-select v-model="config.eol" @change="changeEOL()">
<el-select v-model="config.eol" @change="changeEOL()" class="p-w-200">
<el-option v-for="eol in eols" :key="eol.label" :value="eol.value" :label="eol.label" />
</el-select>
</el-form-item>
<el-form-item :label="$t('file.wordWrap')">
<el-select v-model="config.wordWrap" @change="changeWarp()">
<el-select v-model="config.wordWrap" @change="changeWarp()" class="p-w-200">
<el-option :label="$t('commons.button.enable')" value="on"></el-option>
<el-option :label="$t('commons.button.disable')" value="off"></el-option>
</el-select>
@@ -29,8 +29,8 @@
</el-form-item>
<el-form-item :label="$t('file.linkType')" v-if="addForm.isLink" prop="linkType">
<el-radio-group v-model="addForm.isSymlink">
<el-radio :label="true">{{ $t('file.softLink') }}</el-radio>
<el-radio :label="false">{{ $t('file.hardLink') }}</el-radio>
<el-radio :value="true">{{ $t('file.softLink') }}</el-radio>
<el-radio :value="false">{{ $t('file.hardLink') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="addForm.isLink" :label="$t('file.linkPath')" prop="linkPath">
@@ -23,8 +23,8 @@
<el-input v-model="addForm.name" :disabled="addForm.cover"></el-input>
</el-form-item>
<el-radio-group v-model="addForm.cover" @change="changeType">
<el-radio :label="true" size="large">{{ $t('file.replace') }}</el-radio>
<el-radio :label="false" size="large">{{ $t('file.rename') }}</el-radio>
<el-radio :value="true" size="large">{{ $t('file.replace') }}</el-radio>
<el-radio :value="false" size="large">{{ $t('file.rename') }}</el-radio>
</el-radio-group>
</div>
</el-form>
@@ -37,7 +37,7 @@
</template>
<template #search>
<div class="flx-align-center">
<el-select v-model="searchStrategy" @change="search()" clearable>
<el-select v-model="searchStrategy" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('firewall.strategy') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('firewall.allow')" value="accept"></el-option>
@@ -20,8 +20,8 @@
</el-form-item>
<el-form-item :label="$t('firewall.strategy')" prop="strategy">
<el-radio-group v-model="dialogData.rowData!.strategy">
<el-radio label="accept">{{ $t('firewall.allow') }}</el-radio>
<el-radio label="drop">{{ $t('firewall.deny') }}</el-radio>
<el-radio value="accept">{{ $t('firewall.allow') }}</el-radio>
<el-radio value="drop">{{ $t('firewall.deny') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
@@ -37,13 +37,19 @@
</template>
<template #search>
<div class="flx-align-center">
<el-select v-model="searchStatus" @change="search()" clearable>
<el-select v-model="searchStatus" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('firewall.unUsed')" value="free"></el-option>
<el-option :label="$t('firewall.used')" value="used"></el-option>
</el-select>
<el-select v-model="searchStrategy" style="margin-left: 10px" @change="search()" clearable>
<el-select
v-model="searchStrategy"
style="margin-left: 10px"
@change="search()"
clearable
class="p-w-200"
>
<template #prefix>{{ $t('firewall.strategy') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('firewall.accept')" value="accept"></el-option>
@@ -27,8 +27,8 @@
<el-form-item :label="$t('firewall.source')" prop="source">
<el-radio-group v-model="dialogData.rowData!.source">
<el-radio label="anyWhere">{{ $t('firewall.anyWhere') }}</el-radio>
<el-radio label="address">{{ $t('firewall.address') }}</el-radio>
<el-radio value="anyWhere">{{ $t('firewall.anyWhere') }}</el-radio>
<el-radio value="address">{{ $t('firewall.address') }}</el-radio>
</el-radio-group>
</el-form-item>
@@ -44,8 +44,8 @@
<el-form-item :label="$t('firewall.strategy')" prop="strategy">
<el-radio-group v-model="dialogData.rowData!.strategy">
<el-radio label="accept">{{ $t('firewall.accept') }}</el-radio>
<el-radio label="drop">{{ $t('firewall.drop') }}</el-radio>
<el-radio value="accept">{{ $t('firewall.accept') }}</el-radio>
<el-radio value="drop">{{ $t('firewall.drop') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
@@ -6,7 +6,7 @@
<el-row>
<el-col :span="24">
<div style="width: 100%">
<el-form-item style="float: right">
<el-form-item class="float-right">
<el-row :gutter="20">
<el-col :span="8">
<TableSearch
+1 -1
View File
@@ -6,7 +6,7 @@
<div class="mt-2"><el-alert type="info" :title="$t('ssh.sshAlert')" :closable="false" /></div>
</template>
<template #search>
<el-select v-model="searchStatus" @change="search()">
<el-select v-model="searchStatus" @change="search()" class="p-w-200">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value="All"></el-option>
<el-option :label="$t('commons.status.success')" value="Success"></el-option>

Some files were not shown because too many files have changed in this diff Show More