1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 14:08:06 +08:00

feat: merge from dev (#7216)

This commit is contained in:
zhengkunwang 2024-11-30 19:42:10 +08:00 committed by GitHub
parent 943cf2f9f9
commit 9b0916f0ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 340 additions and 77 deletions

View File

@ -11,6 +11,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
@ -338,7 +339,11 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
mode := info.Mode() mode := info.Mode()
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
stat, ok := info.Sys().(*syscall.Stat_t)
uid, gid := -1, -1
if ok {
uid, gid = int(stat.Uid), int(stat.Gid)
}
success := 0 success := 0
failures := make(buserr.MultiErr) failures := make(buserr.MultiErr)
for _, file := range uploadFiles { for _, file := range uploadFiles {
@ -351,6 +356,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
global.LOG.Error(e) global.LOG.Error(e)
continue continue
} }
_ = os.Chown(dstDir, uid, gid)
} }
tmpFilename := dstFilename + ".tmp" tmpFilename := dstFilename + ".tmp"
if err := c.SaveUploadedFile(file, tmpFilename); err != nil { if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
@ -378,6 +384,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
} else { } else {
_ = os.Chmod(dstFilename, mode) _ = os.Chmod(dstFilename, mode)
} }
if uid != -1 && gid != -1 {
_ = os.Chown(dstFilename, uid, gid)
}
success++ success++
} }
if success == 0 { if success == 0 {

View File

@ -10,6 +10,7 @@ type CommonBackup struct {
DetailName string `json:"detailName"` DetailName string `json:"detailName"`
Secret string `json:"secret"` Secret string `json:"secret"`
TaskID string `json:"taskID"` TaskID string `json:"taskID"`
FileName string `json:"fileName"`
} }
type CommonRecover struct { type CommonRecover struct {
DownloadAccountID uint `json:"downloadAccountID" validate:"required"` DownloadAccountID uint `json:"downloadAccountID" validate:"required"`

View File

@ -36,10 +36,11 @@ type FileWgetRes struct {
} }
type FileLineContent struct { type FileLineContent struct {
Content string `json:"content"` Content string `json:"content"`
End bool `json:"end"` End bool `json:"end"`
Path string `json:"path"` Path string `json:"path"`
Total int `json:"total"` Total int `json:"total"`
Lines []string `json:"lines"`
} }
type FileExist struct { type FileExist struct {

View File

@ -16,8 +16,10 @@ type IBackupRepo interface {
CreateRecord(record *model.BackupRecord) error CreateRecord(record *model.BackupRecord) error
DeleteRecord(ctx context.Context, opts ...DBOption) error DeleteRecord(ctx context.Context, opts ...DBOption) error
UpdateRecord(record *model.BackupRecord) error UpdateRecord(record *model.BackupRecord) error
WithByDetailName(detailName string) DBOption
WithByFileName(fileName string) DBOption WithByFileName(fileName string) DBOption
WithByCronID(cronjobID uint) DBOption WithByCronID(cronjobID uint) DBOption
WithFileNameStartWith(filePrefix string) DBOption
} }
func NewIBackupRepo() IBackupRepo { func NewIBackupRepo() IBackupRepo {
@ -55,6 +57,21 @@ func (u *BackupRepo) WithByFileName(fileName string) DBOption {
} }
} }
func (u *BackupRepo) WithByDetailName(detailName string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(detailName) == 0 {
return g
}
return g.Where("detail_name = ?", detailName)
}
}
func (u *BackupRepo) WithFileNameStartWith(filePrefix string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("file_name LIKE ?", filePrefix+"%")
}
}
func (u *BackupRepo) CreateRecord(record *model.BackupRecord) error { func (u *BackupRepo) CreateRecord(record *model.BackupRecord) error {
return global.DB.Create(record).Error return global.DB.Create(record).Error
} }

View File

@ -161,6 +161,9 @@ func (a AppService) GetAppTags() ([]response.TagDTO, error) {
func (a AppService) GetApp(key string) (*response.AppDTO, error) { func (a AppService) GetApp(key string) (*response.AppDTO, error) {
var appDTO response.AppDTO var appDTO response.AppDTO
if key == "postgres" {
key = "postgresql"
}
app, err := appRepo.GetFirst(appRepo.WithKey(key)) app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -571,6 +571,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
if err != nil { if err != nil {
return versions, err return versions, err
} }
if app.Key == constant.AppMysql {
majorVersion := getMajorVersion(install.Version)
if !strings.HasPrefix(detail.Version, majorVersion) {
continue
}
}
versions = append(versions, dto.AppVersion{ versions = append(versions, dto.AppVersion{
Version: detail.Version, Version: detail.Version,
DetailId: detail.ID, DetailId: detail.ID,
@ -740,7 +746,24 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
} }
} }
} }
} else if form.Type == "apps" {
if m, ok := form.Child.(map[string]interface{}); ok {
result := make(map[string]string)
for key, value := range m {
if strVal, ok := value.(string); ok {
result[key] = strVal
}
}
if envKey, ok := result["envKey"]; ok {
serviceName := envs[envKey]
if serviceName != nil {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string)))
appParam.ShowValue = appInstall.Name
}
}
}
} }
params = append(params, appParam) params = append(params, appParam)
} else { } else {
params = append(params, response.AppParam{ params = append(params, response.AppParam{

View File

@ -199,17 +199,18 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
} }
case constant.AppRedis: case constant.AppRedis:
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok { if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
if password != "" { authParam := dto.RedisAuthParam{
authParam := dto.RedisAuthParam{ RootPassword: "",
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
} }
database.Password = password.(string) if password != "" {
authParam.RootPassword = password.(string)
database.Password = password.(string)
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
} }
} }
return databaseRepo.Create(ctx, database) return databaseRepo.Create(ctx, database)
@ -558,11 +559,23 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
) )
backUpApp := func(t *task.Task) error { backUpApp := func(t *task.Task) error {
if req.Backup { if req.Backup {
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}) backupService := NewIBackupService()
if err != nil { fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupRecord, err := backupService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
if err == nil {
backups, _ := backupService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
if len(backups) > 3 {
backupsToDelete := backups[:len(backups)-3]
var deleteIDs []uint
for _, backup := range backupsToDelete {
deleteIDs = append(deleteIDs, backup.ID)
}
_ = backupService.BatchDeleteRecord(deleteIDs)
}
backupFile = path.Join(global.CONF.System.Backup, backupRecord.FileDir, backupRecord.FileName)
} else {
return buserr.WithNameAndErr("ErrAppBackup", install.Name, err) return buserr.WithNameAndErr("ErrAppBackup", install.Name, err)
} }
backupFile = path.Join(global.CONF.System.Backup, backupRecord.FileDir, backupRecord.FileName)
} }
return nil return nil
} }
@ -1390,7 +1403,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
} }
for _, installed := range appInstallList { for _, installed := range appInstallList {
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) { if updated && ignoreUpdate(installed) {
continue continue
} }
if sync && !doNotNeedSync(installed) { if sync && !doNotNeedSync(installed) {
@ -1691,3 +1704,28 @@ func isHostModel(dockerCompose string) bool {
} }
return false return false
} }
func getMajorVersion(version string) string {
parts := strings.Split(version, ".")
if len(parts) >= 2 {
return parts[0] + "." + parts[1]
}
return version
}
func ignoreUpdate(installed model.AppInstall) bool {
if installed.App.Type == "php" || installed.Status == constant.Installing {
return true
}
if installed.App.Key == constant.AppMysql {
majorVersion := getMajorVersion(installed.Version)
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
for _, appDetail := range appDetails {
if strings.HasPrefix(appDetail.Version, majorVersion) && common.CompareVersion(appDetail.Version, installed.Version) {
return false
}
}
return true
}
return false
}

View File

@ -36,6 +36,7 @@ type IBackupService interface {
DownloadRecord(info dto.DownloadRecord) (string, error) DownloadRecord(info dto.DownloadRecord) (string, error)
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
BatchDeleteRecord(ids []uint) error BatchDeleteRecord(ids []uint) error
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
ListFiles(req dto.OperateByID) []string ListFiles(req dto.OperateByID) []string
@ -194,6 +195,20 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
return backupRepo.DeleteRecord(context.Background(), commonRepo.WithByIDs(ids)) return backupRepo.DeleteRecord(context.Background(), commonRepo.WithByIDs(ids))
} }
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
records, err := backupRepo.ListRecord(
commonRepo.WithOrderBy("created_at asc"),
commonRepo.WithByName(name),
commonRepo.WithByType("app"),
backupRepo.WithFileNameStartWith(fileName),
backupRepo.WithByDetailName(detailName),
)
if err != nil {
return nil, err
}
return records, err
}
func (u *BackupService) ListFiles(req dto.OperateByID) []string { func (u *BackupService) ListFiles(req dto.OperateByID) []string {
var datas []string var datas []string
account, client, err := NewBackupClientWithID(req.ID) account, client, err := NewBackupClientWithID(req.ID)

View File

@ -38,7 +38,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName) itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
backupDir := path.Join(global.CONF.System.Backup, itemDir) backupDir := path.Join(global.CONF.System.Backup, itemDir)
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5)) fileName := req.FileName
if req.FileName == "" {
fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
}
backupApp := func() (*model.BackupRecord, error) { backupApp := func() (*model.BackupRecord, error) {
if err = handleAppBackup(&install, nil, backupDir, fileName, "", req.Secret, req.TaskID); err != nil { if err = handleAppBackup(&install, nil, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {

View File

@ -1087,6 +1087,29 @@ func calculateNetwork(network map[string]container.NetworkStats) (float64, float
} }
func checkImageExist(client *client.Client, imageItem string) bool { func checkImageExist(client *client.Client, imageItem string) bool {
if client == nil {
var err error
client, err = docker.NewDockerClient()
if err != nil {
return false
}
}
images, err := client.ImageList(context.Background(), image.ListOptions{})
if err != nil {
return false
}
for _, img := range images {
for _, tag := range img.RepoTags {
if tag == imageItem || tag == imageItem+":latest" {
return true
}
}
}
return false
}
func checkImage(client *client.Client, imageItem string) bool {
images, err := client.ImageList(context.Background(), image.ListOptions{}) images, err := client.ImageList(context.Background(), image.ListOptions{})
if err != nil { if err != nil {
return false return false

View File

@ -467,11 +467,19 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
if err != nil { if err != nil {
return nil, err return nil, err
} }
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
if err != nil {
return nil, err
}
lines = append(preLines, lines...)
}
res := &response.FileLineContent{ res := &response.FileLineContent{
Content: strings.Join(lines, "\n"), Content: strings.Join(lines, "\n"),
End: isEndOfFile, End: isEndOfFile,
Path: logFilePath, Path: logFilePath,
Total: total, Total: total,
Lines: lines,
} }
return res, nil return res, nil
} }

View File

@ -378,6 +378,9 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
switch runtime.Type { switch runtime.Type {
case constant.RuntimePHP: case constant.RuntimePHP:
if runtime.Resource == constant.ResourceAppstore { if runtime.Resource == constant.ResourceAppstore {
if !checkImageExist(nil, runtime.Image) {
return buserr.WithName("ErrImageNotExist", runtime.Name)
}
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port) website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
} else { } else {
website.ProxyType = create.ProxyType website.ProxyType = create.ProxyType
@ -1085,6 +1088,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
websiteSSL.Provider = constant.Manual websiteSSL.Provider = constant.Manual
websiteSSL.PrivateKey = privateKey websiteSSL.PrivateKey = privateKey
websiteSSL.Pem = certificate websiteSSL.Pem = certificate
websiteSSL.Status = constant.SSLReady
res.SSL = websiteSSL res.SSL = websiteSSL
} }

View File

@ -5,9 +5,11 @@ location ^~ /test {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection $http_connection;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1; proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status; add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
proxy_ssl_server_name off;
} }

View File

@ -104,6 +104,7 @@ ErrDomainFormat: "{{ .name }} domain format error"
ErrDefaultAlias: "default is a reserved code name, please use another code name" ErrDefaultAlias: "default is a reserved code name, please use another code name"
ErrParentWebsite: "You need to delete the subsite(s) {{ .name }} first" ErrParentWebsite: "You need to delete the subsite(s) {{ .name }} first"
ErrBuildDirNotFound: "Build directory does not exist" ErrBuildDirNotFound: "Build directory does not exist"
ErrImageNotExist: "Running environment {{.name}} image does not exist, please re-edit the running environment"
#ssl #ssl
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed" ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"

View File

@ -104,6 +104,7 @@ ErrDomainFormat: "{{ .name }} 域名格式不正確"
ErrDefaultAlias: "default 為保留代號,請使用其他代號" ErrDefaultAlias: "default 為保留代號,請使用其他代號"
ErrParentWebsite: "需要先刪除子網站 {{ .name }}" ErrParentWebsite: "需要先刪除子網站 {{ .name }}"
ErrBuildDirNotFound: "編譯目錄不存在" ErrBuildDirNotFound: "編譯目錄不存在"
ErrImageNotExist: "執行環境 {{.name}} 鏡像不存在,請重新編輯執行環境"
#ssl #ssl
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除" ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"

View File

@ -103,6 +103,7 @@ ErrDomainFormat: "{{ .name }} 域名格式不正确"
ErrDefaultAlias: "default 为保留代号,请使用其他代号" ErrDefaultAlias: "default 为保留代号,请使用其他代号"
ErrParentWebsite: "需要先删除子网站 {{ .name }}" ErrParentWebsite: "需要先删除子网站 {{ .name }}"
ErrBuildDirNotFound: "构建目录不存在" ErrBuildDirNotFound: "构建目录不存在"
ErrImageNotExist: "运行环境 {{.name}} 镜像不存在,请重新编辑运行环境"
#ssl #ssl
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除" ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"

View File

@ -4,6 +4,7 @@ import (
"crypto" "crypto"
"encoding/json" "encoding/json"
"os" "os"
"strings"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
@ -296,8 +297,12 @@ func (c *AcmeClient) GetDNSResolve(domains []string) (map[string]Resolve, error)
continue continue
} }
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth) challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
fqdn := challengeInfo.FQDN
if strings.HasPrefix(domain, "*.") && strings.Contains(fqdn, "*.") {
fqdn = strings.Replace(fqdn, "*.", "", 1)
}
resolves[domain] = Resolve{ resolves[domain] = Resolve{
Key: challengeInfo.FQDN, Key: fqdn,
Value: challengeInfo.Value, Value: challengeInfo.Value,
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div v-loading="isLoading">
<div v-if="defaultButton"> <div v-if="defaultButton">
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)"> <el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
{{ $t('commons.button.watch') }} {{ $t('commons.button.watch') }}
@ -84,10 +84,11 @@ let timer: NodeJS.Timer | null = null;
const tailLog = ref(false); const tailLog = ref(false);
const content = ref(''); const content = ref('');
const end = ref(false); const end = ref(false);
const lastContent = ref('');
const scrollerElement = ref<HTMLElement | null>(null); const scrollerElement = ref<HTMLElement | null>(null);
const minPage = ref(1); const minPage = ref(1);
const maxPage = ref(1); const maxPage = ref(1);
const logs = ref([]);
const isLoading = ref(false);
const readReq = reactive({ const readReq = reactive({
id: 0, id: 0,
@ -124,7 +125,12 @@ const stopSignals = [
'image push successful!', 'image push successful!',
]; ];
const lastLogs = ref([]);
const getContent = (pre: boolean) => { const getContent = (pre: boolean) => {
if (isLoading.value) {
return;
}
emit('update:isReading', true); emit('update:isReading', true);
readReq.id = props.config.id; readReq.id = props.config.id;
readReq.type = props.config.type; readReq.type = props.config.type;
@ -132,38 +138,52 @@ const getContent = (pre: boolean) => {
if (readReq.page < 1) { if (readReq.page < 1) {
readReq.page = 1; readReq.page = 1;
} }
isLoading.value = true;
ReadByLine(readReq).then((res) => { ReadByLine(readReq).then((res) => {
if (!end.value && res.data.end) { if (!end.value && res.data.end) {
lastContent.value = content.value; lastLogs.value = [...logs.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; data.value = res.data;
if (res.data.content != '') { if (res.data.lines && res.data.lines.length > 0) {
if (stopSignals.some((signal) => res.data.content.endsWith(signal))) { res.data.lines = res.data.lines.map((line) =>
line.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
}),
);
const newLogs = res.data.lines;
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
readReq.page++;
}
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
return;
}
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog(); onCloseLog();
} }
if (end.value) { if (end.value) {
if (lastContent.value == '') { if ((logs.value.length = 0)) {
content.value = res.data.content; logs.value = newLogs;
} else { } else {
content.value = pre logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
? res.data.content + '\n' + lastContent.value
: lastContent.value + '\n' + res.data.content;
} }
} else { } else {
if (content.value == '') { if ((logs.value.length = 0)) {
content.value = res.data.content; logs.value = newLogs;
} else { } else {
content.value = pre logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
? res.data.content + '\n' + content.value
: content.value + '\n' + res.data.content;
} }
} }
} }
end.value = res.data.end; end.value = res.data.end;
content.value = logs.value.join('\n');
emit('update:hasContent', content.value !== ''); emit('update:hasContent', content.value !== '');
nextTick(() => { nextTick(() => {
if (pre) { if (pre) {
@ -181,16 +201,52 @@ const getContent = (pre: boolean) => {
maxPage.value = res.data.total; maxPage.value = res.data.total;
minPage.value = res.data.total; minPage.value = res.data.total;
} }
if (logs.value && logs.value.length > 3000) {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value--;
}
}
isLoading.value = false;
}); });
}; };
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
let inThrottle: boolean;
let lastFunc: ReturnType<typeof setTimeout>;
let lastRan: number;
return function (this: any, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
lastRan = Date.now();
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
const throttledGetContent = throttle(getContent, 3000);
const search = () => {
throttledGetContent(false);
};
const changeTail = (fromOutSide: boolean) => { const changeTail = (fromOutSide: boolean) => {
if (fromOutSide) { if (fromOutSide) {
tailLog.value = !tailLog.value; tailLog.value = !tailLog.value;
} }
if (tailLog.value) { if (tailLog.value) {
timer = setInterval(() => { timer = setInterval(() => {
getContent(false); search();
}, 1000 * 3); }, 1000 * 3);
} else { } else {
onCloseLog(); onCloseLog();
@ -208,6 +264,7 @@ const onCloseLog = async () => {
tailLog.value = false; tailLog.value = false;
clearInterval(Number(timer)); clearInterval(Number(timer));
timer = null; timer = null;
isLoading.value = false;
}; };
function isScrolledToBottom(element: HTMLElement): boolean { function isScrolledToBottom(element: HTMLElement): boolean {
@ -228,7 +285,7 @@ const init = () => {
changeTail(false); changeTail(false);
} }
readReq.latest = true; readReq.latest = true;
getContent(false); search();
nextTick(() => {}); nextTick(() => {});
}; };
@ -242,9 +299,14 @@ const initCodemirror = () => {
if (editorRef.value) { if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement; scrollerElement.value = editorRef.value.$el as HTMLElement;
scrollerElement.value.addEventListener('scroll', function () { scrollerElement.value.addEventListener('scroll', function () {
if (tailLog.value) {
return;
}
if (isScrolledToBottom(scrollerElement.value)) { if (isScrolledToBottom(scrollerElement.value)) {
readReq.page = maxPage.value; if (maxPage.value > 1) {
getContent(false); readReq.page = maxPage.value;
}
search();
} }
if (isScrolledToTop(scrollerElement.value)) { if (isScrolledToTop(scrollerElement.value)) {
readReq.page = minPage.value - 1; readReq.page = minPage.value - 1;
@ -252,7 +314,7 @@ const initCodemirror = () => {
return; return;
} }
minPage.value = readReq.page; minPage.value = readReq.page;
getContent(true); throttledGetContent(true);
} }
}); });
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement; let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;

View File

@ -86,6 +86,7 @@ onMounted(() => {
background-color: var(--panel-button-active) !important; background-color: var(--panel-button-active) !important;
box-shadow: none !important; box-shadow: none !important;
border: 2px solid transparent !important; border: 2px solid transparent !important;
color: var(--el-text-color-regular) !important;
} }
.el-radio-button__original-radio:checked + .el-radio-button__inner { .el-radio-button__original-radio:checked + .el-radio-button__inner {

View File

@ -1941,6 +1941,8 @@ const message = {
'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it', 'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it',
diffHelper: diffHelper:
'The left side is the old version, the right side is the new version. After editing, click to save the custom version', 'The left side is the old version, the right side is the new version. After editing, click to save the custom version',
pullImage: 'Pull Image',
pullImageHelper: 'Execute docker pull to pull the image before the application starts',
deleteImage: 'Delete Image', deleteImage: 'Delete Image',
deleteImageHelper: 'Delete the image related to the application. The task will not stop if deletion fails', deleteImageHelper: 'Delete the image related to the application. The task will not stop if deletion fails',
requireMemory: 'Memory', requireMemory: 'Memory',

View File

@ -1804,6 +1804,8 @@ const message = {
useCustom: '自訂 docker-compose.yml', useCustom: '自訂 docker-compose.yml',
useCustomHelper: '使用自訂 docker-compose.yml 文件可能會導致應用程式升級失敗如無必要請勿勾選', useCustomHelper: '使用自訂 docker-compose.yml 文件可能會導致應用程式升級失敗如無必要請勿勾選',
diffHelper: '左側為舊版本右側為新版編輯之後點擊使用自訂版本保存', diffHelper: '左側為舊版本右側為新版編輯之後點擊使用自訂版本保存',
pullImage: '拉取鏡像',
pullImageHelper: '在應用啟動之前執行 docker pull 來拉取鏡像',
deleteImage: '刪除鏡像', deleteImage: '刪除鏡像',
deleteImageHelper: '刪除應用相關鏡像刪除失敗任務不會終止', deleteImageHelper: '刪除應用相關鏡像刪除失敗任務不會終止',
requireMemory: '最低內存', requireMemory: '最低內存',

View File

@ -1802,6 +1802,8 @@ const message = {
useCustom: '自定义 docker-compose.yml', useCustom: '自定义 docker-compose.yml',
useCustomHelper: '使用自定义 docker-compose.yml 文件可能会导致应用升级失败如无必要请勿勾选', useCustomHelper: '使用自定义 docker-compose.yml 文件可能会导致应用升级失败如无必要请勿勾选',
diffHelper: '左侧为旧版本右侧为新版编辑之后点击使用自定义版本保存', diffHelper: '左侧为旧版本右侧为新版编辑之后点击使用自定义版本保存',
pullImage: '拉取镜像',
pullImageHelper: '在应用启动之前执行 docker pull 来拉取镜像',
deleteImage: '删除镜像', deleteImage: '删除镜像',
deleteImageHelper: '删除应用相关镜像删除失败任务不会终止', deleteImageHelper: '删除应用相关镜像删除失败任务不会终止',
requireMemory: '内存需求', requireMemory: '内存需求',

View File

@ -348,15 +348,16 @@ html {
.el-input-group__append { .el-input-group__append {
border-left: 0; border-left: 0;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
background-color: #ffffff !important; background-color: #ffffff !important;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset, box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
-1px 0 0 0 var(--el-input-border-color) inset; -1px 0 0 0 var(--el-input-border-color) inset;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
} }
.tag-button { .tag-button {

View File

@ -80,10 +80,6 @@ html.dark {
border: 1px solid var(--panel-border-color); border: 1px solid var(--panel-border-color);
} }
// * wangEditor
--w-e-textarea-bg-color: #1b1b1b;
--w-e-textarea-color: #eeeeee;
.md-editor-dark { .md-editor-dark {
--md-bk-color: #111417; --md-bk-color: #111417;
} }
@ -251,6 +247,8 @@ html.dark {
color: rgb(174, 166, 153); color: rgb(174, 166, 153);
} }
.el-dialog { .el-dialog {
border: 1px solid var(--panel-border-color);
.el-dialog__header { .el-dialog__header {
border-bottom: var(--panel-border); border-bottom: var(--panel-border);
color: #999999; color: #999999;
@ -258,7 +256,6 @@ html.dark {
color: var(--el-menu-text-color); color: var(--el-menu-text-color);
} }
} }
border: 1px solid var(--panel-border-color);
} }
.el-tabs__item { .el-tabs__item {
color: #999999; color: #999999;
@ -330,15 +327,16 @@ html.dark {
.el-input-group__append { .el-input-group__append {
border-left: 0; border-left: 0;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
background-color: #212426 !important; background-color: #212426 !important;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset, box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset,
-1px 0 0 0 var(--el-input-border-color) inset; -1px 0 0 0 var(--el-input-border-color) inset;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9) !important;
}
} }
.el-collapse-item__header { .el-collapse-item__header {

View File

@ -174,12 +174,13 @@ html {
} }
.el-dialog { .el-dialog {
border-radius: 5px;
.el-dialog__header { .el-dialog__header {
.el-dialog__title { .el-dialog__title {
font-size: 17px; font-size: 17px;
} }
} }
border-radius: 5px;
} }
.row-box { .row-box {

View File

@ -95,8 +95,8 @@
<span class="input-help">{{ $t('app.gpuConfigHelper') }}</span> <span class="input-help">{{ $t('app.gpuConfigHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item pro="pullImage"> <el-form-item pro="pullImage">
<el-checkbox v-model="req.pullImage" :label="$t('container.forcePull')" size="large" /> <el-checkbox v-model="req.pullImage" :label="$t('app.pullImage')" size="large" />
<span class="input-help">{{ $t('container.forcePullHelper') }}</span> <span class="input-help">{{ $t('app.pullImageHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item prop="editCompose"> <el-form-item prop="editCompose">
<el-checkbox v-model="req.editCompose" :label="$t('app.editCompose')" size="large" /> <el-checkbox v-model="req.editCompose" :label="$t('app.editCompose')" size="large" />

View File

@ -31,7 +31,11 @@
<el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" /> <el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" />
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules"> <el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
<div v-for="(p, index) in params" :key="index"> <div v-for="(p, index) in params" :key="index">
<el-form-item :prop="p.key" :label="getLabel(p)"> <el-form-item
:prop="p.key"
:label="getLabel(p)"
v-if="p.showValue == undefined || p.showValue == ''"
>
<el-input <el-input
v-if="p.type == 'number'" v-if="p.type == 'number'"
type="number" type="number"
@ -53,6 +57,9 @@
</el-select> </el-select>
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input> <el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
</el-form-item> </el-form-item>
<el-form-item :prop="p.key" :label="getLabel(p)" v-else>
<el-input v-model.trim="p.showValue" :disabled="!p.edit"></el-input>
</el-form-item>
</div> </div>
<el-form-item prop="advanced"> <el-form-item prop="advanced">
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" /> <el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />

View File

@ -50,8 +50,8 @@
</span> </span>
</el-form-item> </el-form-item>
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'"> <el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" /> <el-checkbox v-model="operateReq.pullImage" :label="$t('app.pullImage')" size="large" />
<span class="input-help">{{ $t('container.forcePullHelper') }}</span> <span class="input-help">{{ $t('app.pullImageHelper') }}</span>
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -1,12 +1,25 @@
<template> <template>
<DrawerPro v-model="open" :header="$t('file.info')" :back="handleClose" size="large"> <el-drawer v-model="open" width="30%" :close-on-click-modal="false" :close-on-press-escape="false">
<template #header>
<DrawerHeader :header="$t('file.info')" :back="handleClose" />
</template>
<el-descriptions :column="1" border> <el-descriptions :column="1" border>
<el-descriptions-item :label="$t('file.fileName')">{{ data.name }}</el-descriptions-item> <el-descriptions-item label-class-name="detail-label" :label="$t('file.fileName')">
<el-descriptions-item :label="$t('commons.table.type')">{{ data.type }}</el-descriptions-item> {{ data.name }}
<el-descriptions-item :label="$t('file.path')">{{ data.path }}</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('file.size')"> <el-descriptions-item
label-class-name="detail-label"
:label="$t('commons.table.type')"
v-if="data.type != ''"
>
{{ data.type }}
</el-descriptions-item>
<el-descriptions-item class-name="detail-content" label-class-name="detail-label" :label="$t('file.path')">
{{ data.path }}
</el-descriptions-item>
<el-descriptions-item label-class-name="detail-label" :label="$t('file.size')">
<span v-if="data.isDir"> <span v-if="data.isDir">
<el-button type="primary" link small @click="getDirSize(data)"> <el-button type="primary" link small @click="getDirSize(data)" :loading="loading">
<span v-if="data.dirSize == undefined"> <span v-if="data.dirSize == undefined">
{{ $t('file.calculate') }} {{ $t('file.calculate') }}
</span> </span>
@ -15,14 +28,20 @@
</span> </span>
<span v-else>{{ computeSize(data.size) }}</span> <span v-else>{{ computeSize(data.size) }}</span>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('file.role')">{{ data.mode }}</el-descriptions-item> <el-descriptions-item label-class-name="detail-label" :label="$t('file.role')">
<el-descriptions-item :label="$t('commons.table.user')">{{ data.user }}</el-descriptions-item> {{ data.mode }}
<el-descriptions-item :label="$t('file.group')">{{ data.group }}</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('commons.table.updatedAt')"> <el-descriptions-item label-class-name="detail-label" :label="$t('commons.table.user')">
{{ data.user }}
</el-descriptions-item>
<el-descriptions-item label-class-name="detail-label" :label="$t('file.group')">
{{ data.group }}
</el-descriptions-item>
<el-descriptions-item label-class-name="detail-label" :label="$t('commons.table.updatedAt')">
{{ dateFormatSimple(data.modTime) }} {{ dateFormatSimple(data.modTime) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</DrawerPro> </el-drawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -30,6 +49,7 @@ import { ComputeDirSize, GetFileContent } from '@/api/modules/files';
import { computeSize } from '@/utils/util'; import { computeSize } from '@/utils/util';
import { ref } from 'vue'; import { ref } from 'vue';
import { dateFormatSimple } from '@/utils/util'; import { dateFormatSimple } from '@/utils/util';
import DrawerHeader from '@/components/drawer-header/index.vue';
interface InfoProps { interface InfoProps {
path: string; path: string;
@ -72,3 +92,14 @@ defineExpose({
acceptParams, acceptParams,
}); });
</script> </script>
<style scoped>
:deep(.detail-label) {
min-width: 100px !important;
}
:deep(.detail-content) {
max-width: 295px;
word-break: break-all;
word-wrap: break-word;
}
</style>

View File

@ -420,18 +420,18 @@ onMounted(() => {
} }
.login-captcha { .login-captcha {
margin-top: 10px;
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
background: none !important; background: none !important;
box-shadow: none !important; box-shadow: none !important;
border-radius: 0 !important; border-radius: 0 !important;
border-bottom: 1px solid #dcdfe6; border-bottom: 1px solid #dcdfe6;
} }
margin-top: 10px;
.el-input { .el-input {
width: 50%; width: 50%;
height: 44px; height: 44px;
} }
img { img {
width: 45%; width: 45%;
height: 44px; height: 44px;

View File

@ -86,6 +86,7 @@ const acceptParams = async (website: Website.WebsiteDTO) => {
deleteBackup: false, deleteBackup: false,
forceDelete: false, forceDelete: false,
}; };
subSites.value = ''; subSites.value = '';
if (website.childSites && website.childSites.length > 0) { if (website.childSites && website.childSites.length > 0) {
subSites.value = website.childSites.join(','); subSites.value = website.childSites.join(',');