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"
"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 {

View File

@ -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"`

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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{

View File

@ -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
}

View File

@ -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)

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)
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 {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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;
}

View File

@ -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"

View File

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

View File

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

View File

@ -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,
}
}

View File

@ -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;

View File

@ -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 {

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',
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',

View File

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

View File

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

View File

@ -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 {

View File

@ -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 {

View File

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

View File

@ -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" />

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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(',');