feat: Support customizing the number of upgrade backup copies (#10483)

Refs #7361
This commit is contained in:
ssongliu 2025-09-25 18:14:09 +08:00 committed by GitHub
parent 07d0b37692
commit 0a6163c89b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 168 additions and 19 deletions

View file

@ -5,9 +5,10 @@ import (
)
type SettingInfo struct {
UserName string `json:"userName"`
SystemVersion string `json:"systemVersion"`
DeveloperMode string `json:"developerMode"`
UserName string `json:"userName"`
SystemVersion string `json:"systemVersion"`
DeveloperMode string `json:"developerMode"`
UpgradeBackupCopies string `json:"upgradeBackupCopies"`
SessionTimeout string `json:"sessionTimeout"`
Port string `json:"port"`

View file

@ -145,6 +145,8 @@ func (u *SettingService) Update(key, value string) error {
if err := xpack.Sync(constant.SyncLanguage); err != nil {
global.LOG.Errorf("sync language to node failed, err: %v", err)
}
case "UpgradeBackupCopies":
dropBackupCopies()
}
return nil

View file

@ -7,6 +7,7 @@ import (
"net/http"
"os"
"path"
"sort"
"strconv"
"strings"
"time"
@ -190,6 +191,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
}
global.LOG.Info("upgrade successful!")
dropBackupCopies()
go writeLogs(req.Version)
_ = settingRepo.Update("SystemVersion", req.Version)
_ = global.AgentDB.Model(&model.Setting{}).Where("key = ?", "SystemVersion").Updates(map[string]interface{}{"value": req.Version}).Error
@ -473,3 +475,32 @@ func loadArch() (string, error) {
}
return "", fmt.Errorf("unsupported such arch: %s", std)
}
func dropBackupCopies() {
backupCopies, _ := settingRepo.GetValueByKey("UpgradeBackupCopies")
copies, _ := strconv.Atoi(backupCopies)
if copies == 0 {
return
}
backupDir := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/upgrade")
upgradeDir, err := os.ReadDir(backupDir)
if err != nil {
global.LOG.Errorf("read upgrade dir failed, err: %v", err)
return
}
var versions []string
for _, item := range upgradeDir {
if item.IsDir() && strings.HasPrefix(item.Name(), "v") {
versions = append(versions, item.Name())
}
}
if len(versions) <= copies {
return
}
sort.Slice(versions, func(i, j int) bool {
return common.ComparePanelVersion(versions[i], versions[j])
})
for i := copies - 1; i < len(versions); i++ {
_ = os.RemoveAll(backupDir + "/" + versions[i])
}
}

View file

@ -23,6 +23,7 @@ func Init() {
migrations.AddCronjobGroup,
migrations.AddDiskMenu,
migrations.AddSimpleNodeGroup,
migrations.AddUpgradeBackupCopies,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -535,3 +535,13 @@ var AddSimpleNodeGroup = &gormigrate.Migration{
return nil
},
}
var AddUpgradeBackupCopies = &gormigrate.Migration{
ID: "20250925-add-upgrade-backup-copies",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "UpgradeBackupCopies", Value: "0"}).Error; err != nil {
return err
}
return nil
},
}

View file

@ -7,6 +7,7 @@ export namespace Setting {
email: string;
systemIP: string;
systemVersion: string;
upgradeBackupCopies: string;
dockerSockPath: string;
developerMode: string;

View file

@ -90,7 +90,7 @@ const getVersionLog = () => {
if (isOffLine) {
return;
}
releasesRef.value.acceptParams({ version: version });
releasesRef.value.acceptParams();
};
const toLxware = () => {

View file

@ -1,18 +1,29 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('setting.release')" @close="handleClose" size="large">
<template #buttons>
<span>{{ version }}</span>
<CopyButton :content="version" type="primary" />
</template>
<div class="note" v-loading="loading">
<el-form ref="formRef" :model="form" :rules="rules">
<el-form-item :label="$t('setting.versionItem')" prop="version">
<el-input class="p-w-200" disabled v-model="form.version">
<template #append>
<CopyButton class="w-16" :isIcon="false" :content="form.version" type="primary" />
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('setting.backupCopies')" prop="backupCopies">
<el-input class="p-w-200" type="number" v-model.number="form.backupCopies">
<template #append>
<el-button @click="onSave(formRef)" class="w-16">{{ $t('commons.button.save') }}</el-button>
</template>
</el-input>
<span class="input-help">{{ $t('setting.backupCopiesHelper') }}</span>
</el-form-item>
</el-form>
<el-collapse v-if="notes && notes.length !== 0" v-model="currentVersion" :accordion="true">
<div v-for="(item, index) in notes" :key="index">
<el-collapse-item :name="index">
<template #title>
<div>
<span class="version">{{ item.version }}</span>
<span v-if="!mobile" class="date">{{ item.createdAt }}</span>
</div>
<span class="version">{{ item.version }}</span>
<span v-if="!mobile" class="date">{{ item.createdAt }}</span>
<svg-icon class="icon" iconName="p-featureshitu"></svg-icon>
<span class="icon-span">{{ item.newCount }}</span>
<svg-icon class="icon" iconName="p-youhuawendang"></svg-icon>
@ -46,12 +57,16 @@
</template>
<script setup lang="ts">
import { listReleases } from '@/api/modules/setting';
import { getSettingInfo, listReleases, updateSetting } from '@/api/modules/setting';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import { ref } from 'vue';
import { GlobalStore } from '@/store';
import { storeToRefs } from 'pinia';
import { FormInstance } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
import { Rules } from '@/global/form-rules';
const globalStore = GlobalStore();
const mobile = computed(() => {
@ -64,17 +79,55 @@ const drawerVisible = ref(false);
const currentVersion = ref(0);
const notes = ref([]);
const loading = ref();
const version = ref();
const formRef = ref();
interface DialogProps {
version: string;
}
const acceptParams = (params: DialogProps): void => {
version.value = params.version;
const form = reactive({
version: '',
backupCopies: 0,
});
const rules = reactive({
version: [Rules.requiredInput],
backupCopies: [{ validator: checkBackupCopies, trigger: 'blur', required: true }],
});
const acceptParams = (): void => {
search();
loadInfo();
drawerVisible.value = true;
};
const loadInfo = async () => {
const res = await getSettingInfo();
form.version = res.data.systemVersion;
form.backupCopies = Number(res.data.upgradeBackupCopies) || 0;
};
function checkBackupCopies(rule: any, value: any, callback: any) {
if (value === 0) {
return callback();
}
if (value < 3) {
return callback(new Error(i18n.global.t('setting.backupCopiesRule')));
}
callback();
}
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
await updateSetting({ key: 'UpgradeBackupCopies', value: form.backupCopies + '' })
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.finally(() => {
loading.value = false;
});
});
};
const handleClose = () => {
drawerVisible.value = false;
};
@ -124,6 +177,7 @@ defineExpose({
padding: 0px;
}
.icon {
display: inline-block;
font-size: 7px;
margin-left: 50px;
}

View file

@ -1990,6 +1990,10 @@ const message = {
'Detected that node {0} is already at the latest upgradable version. Please check the primary node version and try again!',
about: 'About',
versionItem: 'Current Version',
backupCopies: 'Number of Copies to Keep',
backupCopiesHelper: 'Set the number of upgrade backup copies to keep for version rollback. 0 means keep all.',
backupCopiesRule: 'Please keep at least 3 upgrade backup records',
release: 'Release Notes',
releaseHelper:
'Failed to fetch release notes for the current environment. You can manually check the official documentation.',

View file

@ -1994,7 +1994,13 @@ const message = {
'La versión del nodo no coincide con la del nodo principal. Actualiza en la gestión de nodos antes de reintentar.',
versionCompare:
'Se detectó que el nodo {0} ya está en la última versión actualizable. Verifica la versión del nodo principal e inténtalo de nuevo.',
about: 'Acerca de',
versionItem: 'Versión Actual',
backupCopies: 'Número de Copias a Conservar',
backupCopiesHelper:
'Establezca el número de copias de respaldo de actualización para conservar para la reversión de versión. 0 significa conservar todas.',
backupCopiesRule: 'Conserve al menos 3 registros de respaldo de actualización',
release: 'Notas de lanzamiento',
releaseHelper:
'No se pudieron obtener las notas de lanzamiento para el entorno actual. Puedes consultarlas manualmente en la documentación oficial.',

View file

@ -1907,6 +1907,11 @@ const message = {
'ノード {0} は既にアップグレード可能な最新バージョンですマスターノードのバージョンを確認後再試行してください',
about: 'について',
versionItem: '現在のバージョン',
backupCopies: '保持するバックアップ数',
backupCopiesHelper:
'バージョンロールバック用に保持するアップグレードバックアップの数を設定します0はすべて保持を意味します',
backupCopiesRule: '少なくとも3つのアップグレードバックアップ記録を保持してください',
release: 'バージョン更新履歴',
releaseHelper: '現在の環境の更新履歴の取得に異常が発生しました手動で公式ドキュメントを確認してください',
project: 'GitHub',

View file

@ -1876,6 +1876,11 @@ const message = {
'노드 {0}() 이미 업그레이드 가능한 최신 버전입니다. 마스터 노드 버전을 확인 다시 시도하세요!',
about: '정보',
versionItem: '현재 버전',
backupCopies: '보관할 백업 복사본 ',
backupCopiesHelper:
'버전 롤백을 위해 보관할 업그레이드 백업 복사본 수를 설정합니다. 0 모두 보관을 의미합니다.',
backupCopiesRule: '최소 3개의 업그레이드 백업 기록을 보관하세요',
release: '버전 업데이트 로그',
releaseHelper:
'현재 환경의 업데이트 로그를 가져오는 오류가 발생했습니다. 공식 문서에서 수동으로 확인하실 있습니다.',

View file

@ -1963,6 +1963,11 @@ const message = {
'Nod {0} telah berada pada versi terkini yang boleh dinaik taraf. Sila periksa versi nod utama dan cuba lagi!',
about: 'Mengenai',
versionItem: 'Versi Semasa',
backupCopies: 'Bilangan Salinan untuk Disimpan',
backupCopiesHelper:
'Tetapkan bilangan salinan sandaran naik taraf untuk disimpan untuk pemulihan versi. 0 bermakna simpan semua.',
backupCopiesRule: 'Sila simpan sekurang-kurangnya 3 rekod sandaran naik taraf',
release: 'Log Kemaskini Versi',
releaseHelper:
'Pengambilan log kemaskini untuk persekitaran semasa mengalami异常. Anda boleh menyemak dokumentasi rasmi secara manual.',

View file

@ -1953,6 +1953,11 @@ const message = {
'Detectado que o {0} está na última versão atualizável. Por favor, verifique a versão do principal e tente novamente!',
about: 'Sobre',
versionItem: 'Versão Atual',
backupCopies: 'Número de Cópias a Manter',
backupCopiesHelper:
'Defina o número de cópias de backup de atualização para manter para reversão de versão. 0 significa manter todas.',
backupCopiesRule: 'Mantenha pelo menos 3 registros de backup de atualização',
release: 'Registro de Atualizações de Versão',
releaseHelper:
'Falha ao obter o registro de atualizações para o ambiente atual. Você pode verificar a documentação oficial manualmente.',

View file

@ -1951,6 +1951,11 @@ const message = {
'Обнаружено, что узел {0} уже имеет последнюю обновляемую версию. Пожалуйста, проверьте версию основного узла и повторите попытку!',
about: 'О программе',
versionItem: 'Текущая Версия',
backupCopies: 'Количество Копий для Сохранения',
backupCopiesHelper:
'Установите количество копий резервных копий обновления для сохранения для отката версии. 0 означает сохранить все.',
backupCopiesRule: 'Пожалуйста, сохраните как минимум 3 записи резервных копий обновления',
release: 'Журнал обновлений версий',
releaseHelper:
'Не удалось получить журнал обновлений для текущей среды. Вы можете вручную проверить официальную документацию.',

View file

@ -2007,7 +2007,13 @@ const message = {
versionNotSame: 'Düğüm sürümü ana düğümle uyuşmuyor. Lütfen Düğüm Yönetiminde yükseltin ve tekrar deneyin.',
versionCompare:
'{0} düğümünün zaten en son yükseltilebilir sürümde olduğu tespit edildi. Lütfen birincil düğüm sürümünü kontrol edin ve tekrar deneyin!',
about: 'Hakkında',
versionItem: 'Mevcut Sürüm',
backupCopies: 'Saklanacak Kopya Sayısı',
backupCopiesHelper:
'Sürüm geri alma için saklanacak yükseltme yedek kopya sayısını ayarlayın. 0, tümünü sakla anlamına gelir.',
backupCopiesRule: 'Lütfen en az 3 yükseltme yedek kaydı saklayın',
release: 'Sürüm Güncelleme Günlüğü',
releaseHelper:
'Mevcut ortam için güncelleme günlükleri alınırken hata oluştu. Resmi belgeleri manuel olarak kontrol edebilirsiniz.',

View file

@ -1862,6 +1862,10 @@ const message = {
certificate: '證書',
about: '關於',
versionItem: '目前版本',
backupCopies: '保留份數',
backupCopiesHelper: '設定用於版本回滾的升級備份保留份數0 表示保留所有',
backupCopiesRule: '請至少保存 3 份升級備份記錄',
release: '版本更新日誌',
releaseHelper: '目前環境更新日誌取得異常可手動前往官方文件查看',
project: '項目地址',

View file

@ -1854,6 +1854,10 @@ const message = {
certificate: '证书',
about: '关于',
versionItem: '当前版本',
backupCopies: '保留份数',
backupCopiesHelper: '设置用于版本回滚的升级备份保留份数 0 则保留所有',
backupCopiesRule: '请至少保存 3 份升级备份记录',
release: '版本更新日志',
releaseHelper: '当前环境更新日志获取异常可手动前往官方文档查看',
project: '项目地址',