Compare commits

...
22 Commits
Author SHA1 Message Date
zhengkunwang 1c826525e3 style: 优化中国地图显示 (#4681) 2024-04-29 14:52:56 +08:00
zhengkunwang 3f0fd48cf3 fix: 修复 WAF 地图英文模式下的显示错误 (#4704) 2024-04-29 14:27:46 +08:00
zhengkunwang 211dbfdead feat: 伪静态增加提示 (#4678) 2024-04-29 14:27:39 +08:00
zhengkunwang aafec142cc fix: 解决低版本 OpenResty 创建网站报错的问题 (#4661) 2024-04-29 14:27:25 +08:00
ssongliu 384ff57df7 style: 优化网站列表描述信息显示 (#4726) 2024-04-29 12:21:53 +08:00
zhengkunwang 01ddd203ea fix: 解决反向代理匹配规则为空之后无法删除的问题 (#4777)
Refs https://github.com/1Panel-dev/1Panel/issues/4771
2024-04-29 12:18:11 +08:00
zhengkunwang 29aed616ac fix: 解决第二次上传同名文件不显示的问题 (#4737) 2024-04-29 12:18:01 +08:00
ssongliu 83f0df9d84 style: 优化概览页 CPU 显示 (#4721) 2024-04-29 12:16:15 +08:00
ssongliu 7aa16c8142 fix: Docker 配置自动过滤空行 (#4707)
Refs #4701
2024-04-29 12:16:10 +08:00
ssongliu 3bd505954f fix: 解决计划任务本地保存份数错误的问题 (#4692)
Refs #4671
2024-04-29 12:15:53 +08:00
John Bro 275c0522a3 fix: 快速命令管理页面中增加关键词筛选 (#4679)
Refs #3525
2024-04-29 12:15:25 +08:00
John Bro 85dec6815b fix: 创建网站可以使用相同域名问题修复 (#4669)
Refs #4624
2024-04-29 12:15:19 +08:00
ssongliu 5e4e9f762e fix: 解决概览页折线图选择失败的问题 (#4656)
Refs #4647
2024-04-29 12:15:11 +08:00
John Bro 7ae3722997 fix: SSH 连接列表显示优化 (#4650)
Refs #4546
2024-04-29 12:15:00 +08:00
ssongliu 884a169a6f fix: 增加特殊字符命令过滤 (#4599) 2024-04-19 08:58:08 +00:00
ssongliu e79a5f9ae2 fix: 修改安全入口长度校验 (#4594) 2024-04-19 07:24:08 +00:00
ssongliu 25608e7378 fix: 修改许可证状态获取接口 (#4593)
Refs #4589
2024-04-19 07:22:08 +00:00
zhengkunwang dad8bac715 fix: 解决伪静态开启报错的问题 (#4591) 2024-04-19 07:20:08 +00:00
ssongliu a4ee16cf1f fix: 解决容器日志文件被删除但是进程未结束的问题 (#4588)
Refs #4582
2024-04-19 07:18:10 +00:00
dependabot[bot] 7f7465b383 chore(deps): bump github.com/docker/docker (#4586)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.0+incompatible to 26.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.0...v26.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 15:17:29 +08:00
zhengkunwang b7c3dc0d2a fix: 解决创建 PHP 网站首页无法访问的 BUG (#4577) 2024-04-18 14:40:07 +00:00
ssongliu bb02991b05 fix: 解决监控数据迁移失败的问题 (#4569)
Refs #4568
2024-04-18 14:38:06 +00:00
44 changed files with 279 additions and 1777 deletions
+4 -4
View File
@@ -61,7 +61,7 @@ func (b *BaseApi) LoadDaemonJson(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/daemonjson/update [post]
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json 配置 [key]=>[value]","formatEN":"Updated the docker daemon.json configuration [key]=>[value]"}
// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新配置 [key]","formatEN":"Updated configuration [key]"}
func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
var req dto.SettingUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -84,7 +84,7 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/logoption/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json 日志配置","formatEN":"Updated the docker daemon.json log option"}
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新日志配置","formatEN":"Updated the log option"}
func (b *BaseApi) UpdateLogOption(c *gin.Context) {
var req dto.LogOption
if err := helper.CheckBind(&req, c); err != nil {
@@ -107,7 +107,7 @@ func (b *BaseApi) UpdateLogOption(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/ipv6option/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json ipv6 配置","formatEN":"Updated the docker daemon.json ipv6 option"}
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 ipv6 配置","formatEN":"Updated the ipv6 option"}
func (b *BaseApi) UpdateIpv6Option(c *gin.Context) {
var req dto.Ipv6Option
if err := helper.CheckBind(&req, c); err != nil {
@@ -130,7 +130,7 @@ func (b *BaseApi) UpdateIpv6Option(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/daemonjson/update/byfile [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json 配置","formatEN":"Updated the docker daemon.json configuration"}
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新配置文件","formatEN":"Updated configuration file"}
func (b *BaseApi) UpdateDaemonJsonByFile(c *gin.Context) {
var req dto.DaemonJsonUpdateByFile
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+1
View File
@@ -6,6 +6,7 @@ type SearchCommandWithPage struct {
Order string `json:"order"`
GroupID uint `json:"groupID"`
Info string `json:"info"`
Name string `json:"name"`
}
type CommandOperate struct {
+1 -1
View File
@@ -178,7 +178,7 @@ type WebsiteProxyConfig struct {
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
+10
View File
@@ -16,6 +16,7 @@ type ICommandRepo interface {
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Command, error)
WithLikeName(name string) DBOption
}
func NewICommandRepo() ICommandRepo {
@@ -79,3 +80,12 @@ func (u *CommandRepo) Delete(opts ...DBOption) error {
}
return db.Delete(&model.Command{}).Error
}
func (a CommandRepo) WithLikeName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 {
return g
}
return g.Where("name like ? or command like ?", "%"+name+"%", "%"+name+"%")
}
}
+1 -1
View File
@@ -65,7 +65,7 @@ func (u *CommandService) SearchForTree() ([]dto.CommandTree, error) {
}
func (u *CommandService) SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) {
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
total, commands, err := commandRepo.Page(search.Page, search.PageSize, commandRepo.WithLikeName(search.Name), commonRepo.WithLikeName(search.Info), commonRepo.WithByGroupID(search.GroupID), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
if err != nil {
return 0, nil, err
}
+1 -1
View File
@@ -701,7 +701,7 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
return
}
global.LOG.Errorf("read bytes from log failed, err: %v", err)
continue
return
}
if !utf8.Valid(buffer[:n]) {
continue
-16
View File
@@ -2,7 +2,6 @@ package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
@@ -276,21 +275,6 @@ func loadClientMap(backupAccounts string) (map[string]cronjobUploadHelper, error
if account.BackupPath != "/" {
pathItem = strings.TrimPrefix(account.BackupPath, "/")
}
if target == constant.Local {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
return nil, err
}
if _, ok := varMap["dir"]; !ok {
return nil, errors.New("load local backup dir failed")
}
baseDir, ok := varMap["dir"].(string)
if ok {
pathItem = baseDir
} else {
return nil, fmt.Errorf("error type dir: %T", varMap["dir"])
}
}
clients[target] = cronjobUploadHelper{
client: client,
backupPath: pathItem,
+1 -1
View File
@@ -133,7 +133,7 @@ func (f *FileService) Create(op request.FileCreate) error {
}
}
if op.IsDir {
return fo.CreateDir(op.Path, fs.FileMode(mode))
return fo.CreateDirWithMode(op.Path, fs.FileMode(mode))
}
if op.IsLink {
if !fo.Stat(op.LinkPath) {
+2 -2
View File
@@ -1954,7 +1954,7 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
Parameters: []string{"off"},
})
validDir := &components.Directive{
Name: "valid_referrers",
Name: "valid_referers",
Parameters: []string{},
}
if req.NoneRef {
@@ -2023,7 +2023,7 @@ func (w WebsiteService) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, erro
}
lDirectives := location.GetBlock().GetDirectives()
for _, lDir := range lDirectives {
if lDir.GetName() == "valid_referrers" {
if lDir.GetName() == "valid_referers" {
res.Enable = true
params := lDir.GetParameters()
for _, param := range params {
+7
View File
@@ -288,6 +288,10 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
if err != nil {
return err
}
if !common.CompareVersion(nginxInstall.Version, "1.21.4.3-2-0") {
return nil
}
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
fileOp := files.NewFileOp()
if !fileOp.Stat(wafDataPath) {
@@ -419,6 +423,9 @@ func delWafConfig(website model.Website, force bool) error {
if err != nil {
return err
}
if !common.CompareVersion(nginxInstall.Version, "1.21.4.3-2-0") {
return nil
}
wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
fileOp := files.NewFileOp()
if !fileOp.Stat(wafDataPath) {
+12 -6
View File
@@ -74,8 +74,10 @@ var NewMonitorDB = &gormigrate.Migration{
} else {
itemData = bases[200*i:]
}
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
return err
if len(itemData) != 0 {
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
return err
}
}
}
}
@@ -87,8 +89,10 @@ var NewMonitorDB = &gormigrate.Migration{
} else {
itemData = ios[200*i:]
}
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
return err
if len(itemData) != 0 {
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
return err
}
}
}
}
@@ -100,8 +104,10 @@ var NewMonitorDB = &gormigrate.Migration{
} else {
itemData = networks[200*i:]
}
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
return err
if len(itemData) != 0 {
if err := global.MonitorDB.Create(&itemData).Error; err != nil {
return err
}
}
}
}
+1 -1
View File
@@ -54,7 +54,7 @@ func (c localClient) Upload(src, target string) (bool, error) {
}
}
if err := common.CopyFile(src, target); err != nil {
if err := common.CopyFile(src, targetFilePath); err != nil {
return false, fmt.Errorf("cp file failed, err: %v", err)
}
return true, nil
+1 -1
View File
@@ -178,7 +178,7 @@ func CheckIllegal(args ...string) bool {
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, "\n") || strings.Contains(arg, "\r") || strings.Contains(arg, ">") {
strings.Contains(arg, "\n") || strings.Contains(arg, "\r") || strings.Contains(arg, ">") || strings.Contains(arg, "<") {
return true
}
}
+23 -10
View File
@@ -54,17 +54,14 @@ func (f FileOp) GetContent(dst string) ([]byte, error) {
}
func (f FileOp) CreateDir(dst string, mode fs.FileMode) error {
return f.Fs.MkdirAll(dst, mode)
}
func (f FileOp) CreateDirWithMode(dst string, mode fs.FileMode) error {
if err := f.Fs.MkdirAll(dst, mode); err != nil {
return err
}
modStr := fmt.Sprintf("%o", mode)
modeInt, err := strconv.ParseInt(modStr, 10, 64)
if err != nil {
modeInt = 0755
}
return f.ChmodR(dst, modeInt, true)
return f.ChmodRWithMode(dst, mode, true)
}
func (f FileOp) CreateFile(dst string) error {
@@ -193,6 +190,23 @@ func (f FileOp) ChmodR(dst string, mode int64, sub bool) error {
return nil
}
func (f FileOp) ChmodRWithMode(dst string, mode fs.FileMode, sub bool) error {
cmdStr := fmt.Sprintf(`chmod %v "%s"`, fmt.Sprintf("%o", mode.Perm()), dst)
if sub {
cmdStr = fmt.Sprintf(`chmod -R %v "%s"`, fmt.Sprintf("%o", mode.Perm()), dst)
}
if cmd.HasNoPasswordSudo() {
cmdStr = fmt.Sprintf("sudo %s", cmdStr)
}
if msg, err := cmd.ExecWithTimeOut(cmdStr, 10*time.Second); err != nil {
if msg != "" {
return errors.New(msg)
}
return err
}
return nil
}
func (f FileOp) Rename(oldName string, newName string) error {
return f.Fs.Rename(oldName, newName)
}
@@ -300,6 +314,7 @@ func (f FileOp) DownloadFile(url, dst string) error {
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(dst)
if err != nil {
@@ -310,8 +325,6 @@ func (f FileOp) DownloadFile(url, dst string) error {
if _, err = io.Copy(out, resp.Body); err != nil {
return fmt.Errorf("save download file [%s] error, err %s", dst, err.Error())
}
out.Close()
resp.Body.Close()
return nil
}
+3 -5
View File
@@ -3,7 +3,6 @@ package files
import (
"bufio"
"fmt"
"github.com/spf13/afero"
"io"
"net/http"
"os"
@@ -12,6 +11,8 @@ import (
"strconv"
"strings"
"sync"
"github.com/spf13/afero"
)
func IsSymlink(mode os.FileMode) bool {
@@ -144,8 +145,5 @@ func GetParentMode(path string) (os.FileMode, error) {
}
func IsInvalidChar(name string) bool {
if strings.Contains(name, "&") {
return true
}
return false
return strings.Contains(name, "&")
}
+8 -8
View File
@@ -1444,8 +1444,8 @@ const docTemplate = `{
"key",
"value"
],
"formatEN": "Updated the docker daemon.json configuration [key]=\u003e[value]",
"formatZH": "更新 docker daemon.json 配置 [key]=\u003e[value]",
"formatEN": "Updated configuration [key]",
"formatZH": "更新配置 [key]",
"paramKeys": []
}
}
@@ -1484,8 +1484,8 @@ const docTemplate = `{
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json configuration",
"formatZH": "更新 docker daemon.json 配置",
"formatEN": "Updated configuration file",
"formatZH": "更新配置文件",
"paramKeys": []
}
}
@@ -2094,8 +2094,8 @@ const docTemplate = `{
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json ipv6 option",
"formatZH": "更新 docker daemon.json ipv6 配置",
"formatEN": "Updated the ipv6 option",
"formatZH": "更新 ipv6 配置",
"paramKeys": []
}
}
@@ -2233,8 +2233,8 @@ const docTemplate = `{
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 日志配置",
"formatEN": "Updated the log option",
"formatZH": "更新日志配置",
"paramKeys": []
}
}
+8 -8
View File
@@ -1437,8 +1437,8 @@
"key",
"value"
],
"formatEN": "Updated the docker daemon.json configuration [key]=\u003e[value]",
"formatZH": "更新 docker daemon.json 配置 [key]=\u003e[value]",
"formatEN": "Updated configuration [key]",
"formatZH": "更新配置 [key]",
"paramKeys": []
}
}
@@ -1477,8 +1477,8 @@
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json configuration",
"formatZH": "更新 docker daemon.json 配置",
"formatEN": "Updated configuration file",
"formatZH": "更新配置文件",
"paramKeys": []
}
}
@@ -2087,8 +2087,8 @@
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json ipv6 option",
"formatZH": "更新 docker daemon.json ipv6 配置",
"formatEN": "Updated the ipv6 option",
"formatZH": "更新 ipv6 配置",
"paramKeys": []
}
}
@@ -2226,8 +2226,8 @@
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 日志配置",
"formatEN": "Updated the log option",
"formatZH": "更新日志配置",
"paramKeys": []
}
}
+8 -8
View File
@@ -5938,8 +5938,8 @@ paths:
bodyKeys:
- key
- value
formatEN: Updated the docker daemon.json configuration [key]=>[value]
formatZH: 更新 docker daemon.json 配置 [key]=>[value]
formatEN: Updated configuration [key]
formatZH: 更新配置 [key]
paramKeys: []
/containers/daemonjson/update/byfile:
post:
@@ -5964,8 +5964,8 @@ paths:
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: Updated the docker daemon.json configuration
formatZH: 更新 docker daemon.json 配置
formatEN: Updated configuration file
formatZH: 更新配置文件
paramKeys: []
/containers/docker/operate:
post:
@@ -6353,8 +6353,8 @@ paths:
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: Updated the docker daemon.json ipv6 option
formatZH: 更新 docker daemon.json ipv6 配置
formatEN: Updated the ipv6 option
formatZH: 更新 ipv6 配置
paramKeys: []
/containers/limit:
get:
@@ -6438,8 +6438,8 @@ paths:
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: Updated the docker daemon.json log option
formatZH: 更新 docker daemon.json 日志配置
formatEN: Updated the log option
formatZH: 更新日志配置
paramKeys: []
/containers/network:
get:
+1
View File
@@ -24,6 +24,7 @@ export interface SearchWithPage {
pageSize: number;
orderBy?: string;
order?: string;
name?: string;
}
export interface CommonModel {
id: number;
+5
View File
@@ -139,6 +139,7 @@ export namespace Setting {
upload: string;
}
export interface UpgradeInfo {
testVersion: string;
newVersion: string;
latestVersion: string;
releaseNote: string;
@@ -151,4 +152,8 @@ export namespace Setting {
trial: boolean;
status: string;
}
export interface LicenseStatus {
productPro: string;
status: string;
}
}
+3
View File
@@ -13,6 +13,9 @@ export const UploadFileData = (params: FormData) => {
export const getLicense = () => {
return http.get<Setting.License>(`/licenses/get`);
};
export const getLicenseStatus = () => {
return http.get<Setting.LicenseStatus>(`/licenses/get/status`);
};
export const syncLicense = () => {
return http.post(`/licenses/sync`);
File diff suppressed because it is too large Load Diff
@@ -53,7 +53,7 @@
<script lang="ts" setup>
import i18n from '@/lang';
import { dateFormatForName, downloadWithContent } from '@/utils/util';
import { computed, reactive, ref, shallowRef, watch } from 'vue';
import { computed, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
@@ -128,6 +128,7 @@ const searchLogs = async () => {
MsgError(i18n.global.t('container.linesHelper'));
return;
}
terminalSocket.value?.send('close conn');
terminalSocket.value?.close();
logInfo.value = '';
const href = window.location.href;
@@ -180,6 +181,10 @@ const acceptParams = (props: DialogProps): void => {
}
};
onBeforeUnmount(() => {
handleClose();
});
defineExpose({
acceptParams,
});
@@ -102,6 +102,7 @@ const searchLogs = async () => {
MsgError(i18n.global.t('container.linesHelper'));
return;
}
terminalSocket.value?.send('close conn');
terminalSocket.value?.close();
logInfo.value = '';
const href = window.location.href;
@@ -83,7 +83,8 @@ const seriesStyle = [
function initChart() {
let itemChart = echarts?.getInstanceByDom(document.getElementById(props.id) as HTMLElement);
// 如果不存在,就进行初始化
const optionItem = itemChart?.getOption();
const itemSelect = optionItem?.legend;
if (itemChart == null) {
itemChart = echarts.init(document.getElementById(props.id) as HTMLElement);
}
@@ -109,10 +110,9 @@ function initChart() {
yAxis.push({
splitLine: {
show: true,
//分隔辅助线
lineStyle: {
type: 'dashed', //线的类型 虚线0
opacity: theme === 'dark' ? 0.1 : 1, //透明度
type: 'dashed',
opacity: theme === 'dark' ? 0.1 : 1,
},
},
...item,
@@ -120,7 +120,6 @@ function initChart() {
});
}
// 把配置和数据放这里
const option = {
title: [
{
@@ -168,7 +167,7 @@ function initChart() {
},
},
grid: props.option.grid || { left: '7%', right: '7%', bottom: '20%' },
legend: {
legend: itemSelect || {
right: 10,
itemWidth: 8,
textStyle: {
+6 -1
View File
@@ -58,6 +58,8 @@ const message = {
fullscreen: 'Fullscreen',
quitFullscreen: 'Quit Fullscreen',
update: 'Edit',
showAll: 'Show All',
hideSome: 'Hide Some',
},
search: {
timeStart: 'Time start',
@@ -1350,7 +1352,7 @@ const message = {
showEntrance: 'Enable Home Page Notification Not Enabled',
entranceHelper: 'Enabling secure entry will only allow logging in to the panel through specified secure entry.',
entranceError:
'Please enter a secure login entry point of 6-10 characters, only numbers or letters are supported.',
'Please enter a secure login entry point of 5-116 characters, only numbers or letters are supported.',
entranceInputHelper: 'When the security entry is set to blank, the security entry is cancelled.',
randomGenerate: 'Random',
expirationTime: 'Expiration Time',
@@ -1934,6 +1936,9 @@ const message = {
acmeAccountType: 'Account Type',
keyType: 'Key algorithm',
tencentCloud: 'Tencent Cloud',
containWarn: 'The domain name contains the main domain, please re-enter',
rewriteHelper2:
'Applications such as WordPress installed from the app store have pseudo-static configuration by default. Repeated configuration may result in errors',
},
php: {
short_open_tag: 'Short tag support',
+5 -1
View File
@@ -58,6 +58,8 @@ const message = {
fullscreen: '全屏',
quitFullscreen: '退出全屏',
update: '編輯',
showAll: '顯示所有',
hideSome: '隱藏部分',
},
search: {
timeStart: '開始時間',
@@ -1311,7 +1313,7 @@ const message = {
entrance: '安全入口',
showEntrance: '啟用概覽頁未開啟提醒',
entranceHelper: '開啟安全入口後只能通過指定安全入口登錄面板',
entranceError: '請輸入 6-10 位安全登錄入口,僅支持輸入數字或字母',
entranceError: '請輸入 5-116 位安全登錄入口,僅支持輸入數字或字母',
entranceInputHelper: '安全入口設置為空時,則取消安全入口',
randomGenerate: '隨機生成',
expirationTime: '密碼過期時間',
@@ -1810,6 +1812,8 @@ const message = {
acmeAccountType: '賬號類型',
keyType: '密鑰演算法',
tencentCloud: '騰訊雲',
containWarn: '其他域名中包含主域名,请重新輸入',
rewriteHelper2: '從應用程式商店安裝的 WordPress 等應用,預設已經配置好偽靜態,重複配置可能會報錯',
},
php: {
short_open_tag: '短標簽支持',
+5 -1
View File
@@ -58,6 +58,8 @@ const message = {
fullscreen: '全屏',
quitFullscreen: '退出全屏',
update: '编辑',
showAll: '显示所有',
hideSome: '隐藏部分',
},
search: {
timeStart: '开始时间',
@@ -1312,7 +1314,7 @@ const message = {
entrance: '安全入口',
showEntrance: '启用概览页未开启提醒',
entranceHelper: '开启安全入口后只能通过指定安全入口登录面板',
entranceError: '请输入 6-10 位安全登录入口,仅支持输入数字或字母',
entranceError: '请输入 5-116 位安全登录入口,仅支持输入数字或字母',
entranceInputHelper: '安全入口设置为空时,则取消安全入口',
randomGenerate: '随机生成',
expirationTime: '密码过期时间',
@@ -1810,6 +1812,8 @@ const message = {
acmeAccountType: '账号类型',
keyType: '密钥算法',
tencentCloud: '腾讯云',
containWarn: '其他域名中包含主域名,请重新输入',
rewriteHelper2: '从应用商店安装的 WordPress 等应用,默认已经配置好伪静态,重复配置可能会报错',
},
php: {
short_open_tag: '短标签支持',
+2 -2
View File
@@ -22,7 +22,7 @@ import { GlobalStore, MenuStore, TabsStore } from '@/store';
import { DeviceType } from '@/enums/app';
import { useI18n } from 'vue-i18n';
import { useTheme } from '@/hooks/use-theme';
import { getLicense, getSettingInfo, getSystemAvailable } from '@/api/modules/setting';
import { getLicenseStatus, getSettingInfo, getSystemAvailable } from '@/api/modules/setting';
import { useRoute, useRouter } from 'vue-router';
import { initFavicon, resetXSetting } from '@/utils/xpack';
useResize();
@@ -109,7 +109,7 @@ const loadDataFromXDB = async () => {
};
const loadProductProFromDB = async () => {
const res = await getLicense();
const res = await getLicenseStatus();
if (!res.data) {
globalStore.isProductPro = false;
return;
+11
View File
@@ -518,3 +518,14 @@ export function getAction(action: string) {
export function getLanguage() {
return localStorage.getItem('lang') || 'zh';
}
export function emptyLineFilter(str: string, spilt: string) {
let list = str.split(spilt);
let results = [];
for (let i = 0; i < list.length; i++) {
if (list[i].trim() !== '') {
results.push(list[i]);
}
}
return results.join(spilt);
}
@@ -46,6 +46,7 @@ import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { updateDaemonJson } from '@/api/modules/container';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { FormInstance } from 'element-plus';
import { emptyLineFilter } from '@/utils/util';
const emit = defineEmits<{ (e: 'search'): void }>();
@@ -101,7 +102,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
const onSubmit = async () => {
loading.value = true;
await updateDaemonJson('Mirrors', form.mirrors.replaceAll('\n', ','))
await updateDaemonJson('Mirrors', emptyLineFilter(form.mirrors, '\n').replaceAll('\n', ','))
.then(() => {
loading.value = false;
emit('search');
@@ -46,6 +46,7 @@ import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { updateDaemonJson } from '@/api/modules/container';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { FormInstance } from 'element-plus';
import { emptyLineFilter } from '@/utils/util';
const emit = defineEmits<{ (e: 'search'): void }>();
@@ -97,7 +98,7 @@ const onSave = async () => {
const onSubmit = async () => {
loading.value = true;
await updateDaemonJson('Registries', form.registries.replaceAll('\n', ','))
await updateDaemonJson('Registries', emptyLineFilter(form.registries, '\n').replaceAll('\n', ','))
.then(() => {
loading.value = false;
handleClose();
+42 -15
View File
@@ -1,12 +1,7 @@
<template>
<el-row :gutter="10">
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center">
<el-popover
placement="bottom"
:width="currentInfo.cpuPercent.length < 36 ? 300 : (currentInfo.cpuPercent.length / 12) * 150"
trigger="hover"
v-if="chartsOption['cpu']"
>
<el-popover placement="bottom" :width="loadWidth()" trigger="hover" v-if="chartsOption['cpu']">
<div>
<el-tooltip
effect="dark"
@@ -14,7 +9,7 @@
v-if="baseInfo.cpuModelName.length > 40"
placement="top"
>
<el-tag>
<el-tag class="cpuModeTag">
{{ baseInfo.cpuModelName.substring(0, 40) + '...' }}
</el-tag>
</el-tooltip>
@@ -22,14 +17,24 @@
{{ baseInfo.cpuModelName }}
</el-tag>
</div>
<el-tag class="tagClass">
{{ $t('home.core') }} *{{ baseInfo.cpuCores }} {{ $t('home.logicCore') }} *{{
baseInfo.cpuLogicalCores
}}
</el-tag>
<el-tag class="cpuDetailTag">{{ $t('home.core') }} *{{ baseInfo.cpuCores }}</el-tag>
<el-tag class="cpuDetailTag">{{ $t('home.logicCore') }} *{{ baseInfo.cpuLogicalCores }}</el-tag>
<br />
<div v-for="(item, index) of currentInfo.cpuPercent" :key="index">
<el-tag class="tagCPUClass">CPU-{{ index }}: {{ formatNumber(item) }}%</el-tag>
<el-tag v-if="cpuShowAll || (!cpuShowAll && index < 32)" class="tagCPUClass">
CPU-{{ index }}: {{ formatNumber(item) }}%
</el-tag>
</div>
<div v-if="currentInfo.cpuPercent.length > 32" class="mt-1 float-right">
<el-button v-if="!cpuShowAll" @click="cpuShowAll = true" link type="primary" size="small">
{{ $t('commons.button.showAll') }}
<el-icon><DArrowRight /></el-icon>
</el-button>
<el-button v-if="cpuShowAll" @click="cpuShowAll = false" link type="primary" size="small">
{{ $t('commons.button.hideSome') }}
<el-icon><DArrowLeft /></el-icon>
</el-button>
</div>
<template #reference>
<v-charts
@@ -302,6 +307,8 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
shotTime: new Date(),
});
const cpuShowAll = ref();
const chartsOption = ref({ cpu: null, memory: null, load: null });
const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo, isInit: boolean): void => {
@@ -359,6 +366,14 @@ const goGPU = () => {
router.push({ name: 'GPU' });
};
const loadWidth = () => {
if (!cpuShowAll.value || currentInfo.value.cpuPercent.length < 32) {
return 310;
}
let line = Math.floor(currentInfo.value.cpuPercent.length / 16);
return line * 141 + 28;
};
function formatNumber(val: number) {
return Number(val.toFixed(2));
}
@@ -369,6 +384,18 @@ defineExpose({
</script>
<style scoped lang="scss">
.cpuModeTag {
justify-content: flex-start !important;
text-align: left !important;
width: 280px;
}
.cpuDetailTag {
justify-content: flex-start !important;
text-align: left !important;
width: 140px;
margin-top: 3px;
margin-left: 1px;
}
.tagClass {
margin-top: 3px;
}
@@ -378,8 +405,8 @@ defineExpose({
text-align: left !important;
float: left;
margin-top: 3px;
margin-left: 4px;
width: 100px;
margin-left: 1px;
width: 140px;
}
.buttonClass {
@@ -111,27 +111,24 @@ let uploadHelper = ref('');
const em = defineEmits(['close']);
const handleClose = () => {
open.value = false;
uploadRef.value!.clearFiles();
clearFiles();
em('close', false);
};
const state = reactive({
uploadEle: null,
});
const uploaderFiles = ref<UploadFiles>([]);
const isUploadFolder = ref(false);
const hoverIndex = ref(null);
const uploadType = ref('file');
const tmpFiles = ref<UploadFiles>([]);
const breakFlag = ref(false);
const upload = (command: string) => {
uploadType.value = command;
if (command == 'dir') {
state.uploadEle.webkitdirectory = true;
} else {
state.uploadEle.webkitdirectory = false;
}
isUploadFolder.value = true;
uploadRef.value.$el.querySelector('input').value = '';
uploadRef.value.$el.querySelector('input').click();
};
@@ -253,10 +250,11 @@ const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
const clearFiles = () => {
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
};
const handleExceed: UploadProps['onExceed'] = () => {
uploadRef.value!.clearFiles();
clearFiles();
MsgWarning(i18n.global.t('file.uploadOverLimit'));
};
@@ -339,8 +337,7 @@ const submit = async () => {
loading.value = false;
uploadHelper.value = '';
if (success == files.length) {
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
clearFiles();
MsgSuccess(i18n.global.t('file.uploadSuccess'));
}
}
@@ -16,13 +16,20 @@
</el-button>
</template>
<template #search>
<el-select v-model="group" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('terminal.group') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<div v-for="item in groupList" :key="item.name">
<el-option :value="item.id" :label="item.name" />
</div>
</el-select>
<el-row :gutter="5">
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
<el-select v-model="group" @change="search()" clearable class="p-w-200">
<template #prefix>{{ $t('terminal.group') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<div v-for="item in groupList" :key="item.name">
<el-option :value="item.id" :label="item.name" />
</div>
</el-select>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<TableSearch @search="search()" v-model:searchName="commandReq.name" />
</el-col>
</el-row>
</template>
<template #main>
<ComplexTable
@@ -160,6 +167,10 @@ let commandInfo = reactive<Command.CommandOperate>({
command: '',
});
const commandReq = reactive({
name: '',
});
const cmdVisible = ref<boolean>(false);
const loadGroups = async () => {
@@ -274,6 +285,7 @@ const search = async (column?: any) => {
info: info.value,
orderBy: paginationConfig.orderBy,
order: paginationConfig.order,
name: commandReq.name,
};
loading.value = true;
await getCommandPage(params)
@@ -105,7 +105,7 @@
<span v-if="node.label.length <= 25">
<a @click="onClickConn(node, data)">{{ node.label }}</a>
</span>
<el-tooltip v-else :content="node.label" placement="top-start">
<el-tooltip v-else :content="node.label" placement="right">
<span>
<a @click="onClickConn(node, data)">
{{ node.label.substring(0, 22) }}...
@@ -157,7 +157,7 @@ import { GlobalStore, MenuStore, TabsStore } from '@/store';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { useI18n } from 'vue-i18n';
import { getLicense } from '@/api/modules/setting';
import { getLicenseStatus } from '@/api/modules/setting';
import { initFavicon } from '@/utils/xpack';
const globalStore = GlobalStore();
@@ -327,7 +327,7 @@ const loadLanguage = async () => {
};
const loadProductProFromDB = async () => {
const res = await getLicense();
const res = await getLicenseStatus();
if (!res.data) {
globalStore.isProductPro = false;
return;
@@ -72,7 +72,7 @@ const rules = reactive({
function checkSecurityEntrance(rule: any, value: any, callback: any) {
if (form.securityEntrance !== '') {
const reg = /^[A-Za-z0-9]{6,10}$/;
const reg = /^[A-Za-z0-9]{5,116}$/;
if (!reg.test(form.securityEntrance)) {
return callback(new Error(i18n.global.t('setting.entranceError')));
}
@@ -126,7 +126,6 @@ const proxyForm = ref<FormInstance>();
const rules = ref({
name: [Rules.requiredInput, Rules.appName],
match: [Rules.requiredInput],
modifier: [Rules.requiredInput],
cacheTime: [Rules.requiredInput, checkNumberRange(1, 65535)],
proxyPass: [Rules.requiredInput],
proxyHost: [Rules.requiredInput],
@@ -1,7 +1,7 @@
<template>
<div>
<el-form-item :label="$t('website.rewriteMode')">
<el-select v-model="req.name" filterable @change="getRewriteConfig(req.name)">
<el-select v-model="req.name" filterable @change="getRewriteConfig(req.name)" class="p-w-200">
<el-option :label="$t('website.current')" :value="'current'"></el-option>
<el-option
v-for="(rewrite, index) in Rewrites"
@@ -11,6 +11,7 @@
></el-option>
</el-select>
</el-form-item>
<el-text type="warning">{{ $t('website.rewriteHelper2') }}</el-text>
<Codemirror
ref="codeRef"
v-loading="loading"
@@ -26,7 +27,7 @@
:extensions="extensions"
v-model="content"
/>
<div style="margin-top: 10px">
<div class="mt-2">
<el-form-item>
<el-alert :title="$t('website.rewriteHelper')" type="info" :closable="false" />
</el-form-item>
@@ -349,7 +349,7 @@ import { ElForm, FormInstance } from 'element-plus';
import { reactive, ref } from 'vue';
import Params from '@/views/app-store/detail/params/index.vue';
import Check from '../check/index.vue';
import { MsgSuccess } from '@/utils/message';
import { MsgError, MsgSuccess } from '@/utils/message';
import { GetGroupList } from '@/api/modules/group';
import { Group } from '@/api/interface/group';
import { SearchRuntimes } from '@/api/modules/runtime';
@@ -582,6 +582,16 @@ const changeAppType = (type: string) => {
}
};
function isSubsetOfStrArray(primaryDomain: string, otherDomains: string): boolean {
const arr: string[] = otherDomains.split('\n');
for (const item of arr) {
if (primaryDomain === item) {
return false;
}
}
return true;
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
@@ -589,6 +599,12 @@ const submit = async (formEl: FormInstance | undefined) => {
return;
}
loading.value = true;
const flag = isSubsetOfStrArray(website.value.primaryDomain, website.value.otherDomains);
if (!flag) {
MsgError(i18n.global.t('website.containWarn'));
loading.value = false;
return false;
}
PreCheck({})
.then((res) => {
if (res.data) {
+6 -6
View File
@@ -111,11 +111,12 @@
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('website.remark')" prop="remark" min-width="120px">
<template #default="{ row }">
<MsgInfo :info="row.remark" />
</template>
</el-table-column>
<el-table-column
:label="$t('website.remark')"
prop="remark"
show-overflow-tooltip
min-width="120px"
></el-table-column>
<el-table-column
:label="$t('commons.table.protocol')"
prop="protocol"
@@ -194,7 +195,6 @@ import { dateFormatSimple } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
import { useI18n } from 'vue-i18n';
import { VideoPlay, VideoPause } from '@element-plus/icons-vue';
import MsgInfo from '@/components/msg-info/index.vue';
import { GetGroupList } from '@/api/modules/group';
import { Group } from '@/api/interface/group';
import { GlobalStore } from '@/store';
+1 -1
View File
@@ -10,7 +10,7 @@ require (
github.com/dgraph-io/badger/v4 v4.2.0
github.com/docker/cli v26.0.0+incompatible
github.com/docker/compose/v2 v2.26.1
github.com/docker/docker v26.0.0+incompatible
github.com/docker/docker v26.0.2+incompatible
github.com/docker/go-connections v0.5.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/gzip v0.0.6
+2 -2
View File
@@ -219,8 +219,8 @@ github.com/docker/compose/v2 v2.26.1/go.mod h1:5iVCMlr18ab0NlMxIPdtTgThTkzb34Z/z
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU=
github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE=
github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=