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:
parent
943cf2f9f9
commit
9b0916f0ed
@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
@ -338,7 +339,11 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
mode := info.Mode()
|
||||
|
||||
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
|
||||
failures := make(buserr.MultiErr)
|
||||
for _, file := range uploadFiles {
|
||||
@ -351,6 +356,7 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
global.LOG.Error(e)
|
||||
continue
|
||||
}
|
||||
_ = os.Chown(dstDir, uid, gid)
|
||||
}
|
||||
tmpFilename := dstFilename + ".tmp"
|
||||
if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
|
||||
@ -378,6 +384,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
} else {
|
||||
_ = os.Chmod(dstFilename, mode)
|
||||
}
|
||||
if uid != -1 && gid != -1 {
|
||||
_ = os.Chown(dstFilename, uid, gid)
|
||||
}
|
||||
success++
|
||||
}
|
||||
if success == 0 {
|
||||
|
@ -10,6 +10,7 @@ type CommonBackup struct {
|
||||
DetailName string `json:"detailName"`
|
||||
Secret string `json:"secret"`
|
||||
TaskID string `json:"taskID"`
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
type CommonRecover struct {
|
||||
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
|
||||
|
@ -40,6 +40,7 @@ type FileLineContent struct {
|
||||
End bool `json:"end"`
|
||||
Path string `json:"path"`
|
||||
Total int `json:"total"`
|
||||
Lines []string `json:"lines"`
|
||||
}
|
||||
|
||||
type FileExist struct {
|
||||
|
@ -16,8 +16,10 @@ type IBackupRepo interface {
|
||||
CreateRecord(record *model.BackupRecord) error
|
||||
DeleteRecord(ctx context.Context, opts ...DBOption) error
|
||||
UpdateRecord(record *model.BackupRecord) error
|
||||
WithByDetailName(detailName string) DBOption
|
||||
WithByFileName(fileName string) DBOption
|
||||
WithByCronID(cronjobID uint) DBOption
|
||||
WithFileNameStartWith(filePrefix string) DBOption
|
||||
}
|
||||
|
||||
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 {
|
||||
return global.DB.Create(record).Error
|
||||
}
|
||||
|
@ -161,6 +161,9 @@ func (a AppService) GetAppTags() ([]response.TagDTO, error) {
|
||||
|
||||
func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||
var appDTO response.AppDTO
|
||||
if key == "postgres" {
|
||||
key = "postgresql"
|
||||
}
|
||||
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -571,6 +571,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
if app.Key == constant.AppMysql {
|
||||
majorVersion := getMajorVersion(install.Version)
|
||||
if !strings.HasPrefix(detail.Version, majorVersion) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
versions = append(versions, dto.AppVersion{
|
||||
Version: detail.Version,
|
||||
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)
|
||||
} else {
|
||||
params = append(params, response.AppParam{
|
||||
|
@ -199,9 +199,12 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
|
||||
}
|
||||
case constant.AppRedis:
|
||||
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
authParam := dto.RedisAuthParam{
|
||||
RootPassword: password.(string),
|
||||
RootPassword: "",
|
||||
}
|
||||
if password != "" {
|
||||
authParam.RootPassword = password.(string)
|
||||
database.Password = password.(string)
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
@ -209,8 +212,6 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
database.Password = password.(string)
|
||||
}
|
||||
}
|
||||
return databaseRepo.Create(ctx, database)
|
||||
}
|
||||
@ -558,11 +559,23 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
)
|
||||
backUpApp := func(t *task.Task) error {
|
||||
if req.Backup {
|
||||
backupRecord, err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name})
|
||||
if err != nil {
|
||||
return buserr.WithNameAndErr("ErrAppBackup", install.Name, err)
|
||||
backupService := NewIBackupService()
|
||||
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 nil
|
||||
}
|
||||
@ -1390,7 +1403,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if sync && !doNotNeedSync(installed) {
|
||||
@ -1691,3 +1704,28 @@ func isHostModel(dockerCompose string) bool {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ type IBackupService interface {
|
||||
DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
||||
BatchDeleteRecord(ids []uint) error
|
||||
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
|
||||
|
||||
ListFiles(req dto.OperateByID) []string
|
||||
|
||||
@ -194,6 +195,20 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
|
||||
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 {
|
||||
var datas []string
|
||||
account, client, err := NewBackupClientWithID(req.ID)
|
||||
|
@ -38,7 +38,10 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
||||
itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName)
|
||||
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) {
|
||||
if err = handleAppBackup(&install, nil, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||
|
@ -1087,6 +1087,29 @@ func calculateNetwork(network map[string]container.NetworkStats) (float64, float
|
||||
}
|
||||
|
||||
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{})
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -467,11 +467,19 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
if err != nil {
|
||||
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{
|
||||
Content: strings.Join(lines, "\n"),
|
||||
End: isEndOfFile,
|
||||
Path: logFilePath,
|
||||
Total: total,
|
||||
Lines: lines,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
@ -378,6 +378,9 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
switch runtime.Type {
|
||||
case constant.RuntimePHP:
|
||||
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)
|
||||
} else {
|
||||
website.ProxyType = create.ProxyType
|
||||
@ -1085,6 +1088,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
|
||||
websiteSSL.Provider = constant.Manual
|
||||
websiteSSL.PrivateKey = privateKey
|
||||
websiteSSL.Pem = certificate
|
||||
websiteSSL.Status = constant.SSLReady
|
||||
|
||||
res.SSL = websiteSSL
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ location ^~ /test {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
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_http_version 1.1;
|
||||
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
proxy_ssl_server_name off;
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ ErrDomainFormat: "{{ .name }} domain format error"
|
||||
ErrDefaultAlias: "default is a reserved code name, please use another code name"
|
||||
ErrParentWebsite: "You need to delete the subsite(s) {{ .name }} first"
|
||||
ErrBuildDirNotFound: "Build directory does not exist"
|
||||
ErrImageNotExist: "Running environment {{.name}} image does not exist, please re-edit the running environment"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
|
||||
|
@ -104,6 +104,7 @@ ErrDomainFormat: "{{ .name }} 域名格式不正確"
|
||||
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
|
||||
ErrParentWebsite: "需要先刪除子網站 {{ .name }}"
|
||||
ErrBuildDirNotFound: "編譯目錄不存在"
|
||||
ErrImageNotExist: "執行環境 {{.name}} 鏡像不存在,請重新編輯執行環境"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
|
||||
|
@ -103,6 +103,7 @@ ErrDomainFormat: "{{ .name }} 域名格式不正确"
|
||||
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
|
||||
ErrParentWebsite: "需要先删除子网站 {{ .name }}"
|
||||
ErrBuildDirNotFound: "构建目录不存在"
|
||||
ErrImageNotExist: "运行环境 {{.name}} 镜像不存在,请重新编辑运行环境"
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@ -296,8 +297,12 @@ func (c *AcmeClient) GetDNSResolve(domains []string) (map[string]Resolve, error)
|
||||
continue
|
||||
}
|
||||
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
fqdn := challengeInfo.FQDN
|
||||
if strings.HasPrefix(domain, "*.") && strings.Contains(fqdn, "*.") {
|
||||
fqdn = strings.Replace(fqdn, "*.", "", 1)
|
||||
}
|
||||
resolves[domain] = Resolve{
|
||||
Key: challengeInfo.FQDN,
|
||||
Key: fqdn,
|
||||
Value: challengeInfo.Value,
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-loading="isLoading">
|
||||
<div v-if="defaultButton">
|
||||
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
|
||||
{{ $t('commons.button.watch') }}
|
||||
@ -84,10 +84,11 @@ let timer: NodeJS.Timer | null = null;
|
||||
const tailLog = ref(false);
|
||||
const content = ref('');
|
||||
const end = ref(false);
|
||||
const lastContent = ref('');
|
||||
const scrollerElement = ref<HTMLElement | null>(null);
|
||||
const minPage = ref(1);
|
||||
const maxPage = ref(1);
|
||||
const logs = ref([]);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const readReq = reactive({
|
||||
id: 0,
|
||||
@ -124,7 +125,12 @@ const stopSignals = [
|
||||
'image push successful!',
|
||||
];
|
||||
|
||||
const lastLogs = ref([]);
|
||||
|
||||
const getContent = (pre: boolean) => {
|
||||
if (isLoading.value) {
|
||||
return;
|
||||
}
|
||||
emit('update:isReading', true);
|
||||
readReq.id = props.config.id;
|
||||
readReq.type = props.config.type;
|
||||
@ -132,38 +138,52 @@ const getContent = (pre: boolean) => {
|
||||
if (readReq.page < 1) {
|
||||
readReq.page = 1;
|
||||
}
|
||||
isLoading.value = true;
|
||||
ReadByLine(readReq).then((res) => {
|
||||
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;
|
||||
if (res.data.content != '') {
|
||||
if (stopSignals.some((signal) => res.data.content.endsWith(signal))) {
|
||||
if (res.data.lines && res.data.lines.length > 0) {
|
||||
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();
|
||||
}
|
||||
if (end.value) {
|
||||
if (lastContent.value == '') {
|
||||
content.value = res.data.content;
|
||||
if ((logs.value.length = 0)) {
|
||||
logs.value = newLogs;
|
||||
} else {
|
||||
content.value = pre
|
||||
? res.data.content + '\n' + lastContent.value
|
||||
: lastContent.value + '\n' + res.data.content;
|
||||
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
|
||||
}
|
||||
} else {
|
||||
if (content.value == '') {
|
||||
content.value = res.data.content;
|
||||
if ((logs.value.length = 0)) {
|
||||
logs.value = newLogs;
|
||||
} else {
|
||||
content.value = pre
|
||||
? res.data.content + '\n' + content.value
|
||||
: content.value + '\n' + res.data.content;
|
||||
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
|
||||
}
|
||||
}
|
||||
}
|
||||
end.value = res.data.end;
|
||||
content.value = logs.value.join('\n');
|
||||
emit('update:hasContent', content.value !== '');
|
||||
nextTick(() => {
|
||||
if (pre) {
|
||||
@ -181,16 +201,52 @@ const getContent = (pre: boolean) => {
|
||||
maxPage.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) => {
|
||||
if (fromOutSide) {
|
||||
tailLog.value = !tailLog.value;
|
||||
}
|
||||
if (tailLog.value) {
|
||||
timer = setInterval(() => {
|
||||
getContent(false);
|
||||
search();
|
||||
}, 1000 * 3);
|
||||
} else {
|
||||
onCloseLog();
|
||||
@ -208,6 +264,7 @@ const onCloseLog = async () => {
|
||||
tailLog.value = false;
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
function isScrolledToBottom(element: HTMLElement): boolean {
|
||||
@ -228,7 +285,7 @@ const init = () => {
|
||||
changeTail(false);
|
||||
}
|
||||
readReq.latest = true;
|
||||
getContent(false);
|
||||
search();
|
||||
|
||||
nextTick(() => {});
|
||||
};
|
||||
@ -242,9 +299,14 @@ const initCodemirror = () => {
|
||||
if (editorRef.value) {
|
||||
scrollerElement.value = editorRef.value.$el as HTMLElement;
|
||||
scrollerElement.value.addEventListener('scroll', function () {
|
||||
if (tailLog.value) {
|
||||
return;
|
||||
}
|
||||
if (isScrolledToBottom(scrollerElement.value)) {
|
||||
if (maxPage.value > 1) {
|
||||
readReq.page = maxPage.value;
|
||||
getContent(false);
|
||||
}
|
||||
search();
|
||||
}
|
||||
if (isScrolledToTop(scrollerElement.value)) {
|
||||
readReq.page = minPage.value - 1;
|
||||
@ -252,7 +314,7 @@ const initCodemirror = () => {
|
||||
return;
|
||||
}
|
||||
minPage.value = readReq.page;
|
||||
getContent(true);
|
||||
throttledGetContent(true);
|
||||
}
|
||||
});
|
||||
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
|
||||
|
@ -86,6 +86,7 @@ onMounted(() => {
|
||||
background-color: var(--panel-button-active) !important;
|
||||
box-shadow: none !important;
|
||||
border: 2px solid transparent !important;
|
||||
color: var(--el-text-color-regular) !important;
|
||||
}
|
||||
|
||||
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||
|
@ -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',
|
||||
diffHelper:
|
||||
'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',
|
||||
deleteImageHelper: 'Delete the image related to the application. The task will not stop if deletion fails',
|
||||
requireMemory: 'Memory',
|
||||
|
@ -1804,6 +1804,8 @@ const message = {
|
||||
useCustom: '自訂 docker-compose.yml',
|
||||
useCustomHelper: '使用自訂 docker-compose.yml 文件,可能會導致應用程式升級失敗,如無必要,請勿勾選',
|
||||
diffHelper: '左側為舊版本,右側為新版,編輯之後點擊使用自訂版本保存',
|
||||
pullImage: '拉取鏡像',
|
||||
pullImageHelper: '在應用啟動之前執行 docker pull 來拉取鏡像',
|
||||
deleteImage: '刪除鏡像',
|
||||
deleteImageHelper: '刪除應用相關鏡像,刪除失敗任務不會終止',
|
||||
requireMemory: '最低內存',
|
||||
|
@ -1802,6 +1802,8 @@ const message = {
|
||||
useCustom: '自定义 docker-compose.yml',
|
||||
useCustomHelper: '使用自定义 docker-compose.yml 文件,可能会导致应用升级失败,如无必要,请勿勾选',
|
||||
diffHelper: '左侧为旧版本,右侧为新版,编辑之后点击使用自定义版本保存',
|
||||
pullImage: '拉取镜像',
|
||||
pullImageHelper: '在应用启动之前执行 docker pull 来拉取镜像',
|
||||
deleteImage: '删除镜像',
|
||||
deleteImageHelper: '删除应用相关镜像,删除失败任务不会终止',
|
||||
requireMemory: '内存需求',
|
||||
|
@ -348,15 +348,16 @@ html {
|
||||
|
||||
.el-input-group__append {
|
||||
border-left: 0;
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
background-color: #ffffff !important;
|
||||
border-top-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,
|
||||
-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 {
|
||||
|
@ -80,10 +80,6 @@ html.dark {
|
||||
border: 1px solid var(--panel-border-color);
|
||||
}
|
||||
|
||||
// * wangEditor
|
||||
--w-e-textarea-bg-color: #1b1b1b;
|
||||
--w-e-textarea-color: #eeeeee;
|
||||
|
||||
.md-editor-dark {
|
||||
--md-bk-color: #111417;
|
||||
}
|
||||
@ -251,6 +247,8 @@ html.dark {
|
||||
color: rgb(174, 166, 153);
|
||||
}
|
||||
.el-dialog {
|
||||
border: 1px solid var(--panel-border-color);
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: var(--panel-border);
|
||||
color: #999999;
|
||||
@ -258,7 +256,6 @@ html.dark {
|
||||
color: var(--el-menu-text-color);
|
||||
}
|
||||
}
|
||||
border: 1px solid var(--panel-border-color);
|
||||
}
|
||||
.el-tabs__item {
|
||||
color: #999999;
|
||||
@ -330,15 +327,16 @@ html.dark {
|
||||
|
||||
.el-input-group__append {
|
||||
border-left: 0;
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
background-color: #212426 !important;
|
||||
border-top-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,
|
||||
-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 {
|
||||
|
@ -174,12 +174,13 @@ html {
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
border-radius: 5px;
|
||||
|
||||
.el-dialog__header {
|
||||
.el-dialog__title {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.row-box {
|
||||
|
@ -95,8 +95,8 @@
|
||||
<span class="input-help">{{ $t('app.gpuConfigHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="pullImage">
|
||||
<el-checkbox v-model="req.pullImage" :label="$t('container.forcePull')" size="large" />
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
<el-checkbox v-model="req.pullImage" :label="$t('app.pullImage')" size="large" />
|
||||
<span class="input-help">{{ $t('app.pullImageHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="editCompose">
|
||||
<el-checkbox v-model="req.editCompose" :label="$t('app.editCompose')" size="large" />
|
||||
|
@ -31,7 +31,11 @@
|
||||
<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">
|
||||
<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
|
||||
v-if="p.type == 'number'"
|
||||
type="number"
|
||||
@ -53,6 +57,9 @@
|
||||
</el-select>
|
||||
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
||||
</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>
|
||||
<el-form-item prop="advanced">
|
||||
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
|
||||
|
@ -50,8 +50,8 @@
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
|
||||
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" />
|
||||
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
|
||||
<el-checkbox v-model="operateReq.pullImage" :label="$t('app.pullImage')" size="large" />
|
||||
<span class="input-help">{{ $t('app.pullImageHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
@ -1,12 +1,25 @@
|
||||
<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-item :label="$t('file.fileName')">{{ data.name }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('commons.table.type')">{{ data.type }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('file.path')">{{ data.path }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('file.size')">
|
||||
<el-descriptions-item label-class-name="detail-label" :label="$t('file.fileName')">
|
||||
{{ data.name }}
|
||||
</el-descriptions-item>
|
||||
<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">
|
||||
<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">
|
||||
{{ $t('file.calculate') }}
|
||||
</span>
|
||||
@ -15,14 +28,20 @@
|
||||
</span>
|
||||
<span v-else>{{ computeSize(data.size) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('file.role')">{{ data.mode }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('commons.table.user')">{{ data.user }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('file.group')">{{ data.group }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('commons.table.updatedAt')">
|
||||
<el-descriptions-item label-class-name="detail-label" :label="$t('file.role')">
|
||||
{{ data.mode }}
|
||||
</el-descriptions-item>
|
||||
<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) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</DrawerPro>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -30,6 +49,7 @@ import { ComputeDirSize, GetFileContent } from '@/api/modules/files';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { ref } from 'vue';
|
||||
import { dateFormatSimple } from '@/utils/util';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
|
||||
interface InfoProps {
|
||||
path: string;
|
||||
@ -72,3 +92,14 @@ defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</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>
|
||||
|
@ -420,18 +420,18 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.login-captcha {
|
||||
margin-top: 10px;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
margin-top: 10px;
|
||||
.el-input {
|
||||
width: 50%;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 45%;
|
||||
height: 44px;
|
||||
|
@ -86,6 +86,7 @@ const acceptParams = async (website: Website.WebsiteDTO) => {
|
||||
deleteBackup: false,
|
||||
forceDelete: false,
|
||||
};
|
||||
|
||||
subSites.value = '';
|
||||
if (website.childSites && website.childSites.length > 0) {
|
||||
subSites.value = website.childSites.join(',');
|
||||
|
Loading…
x
Reference in New Issue
Block a user