feat: Support customizing whether scripts are automatically synchronized (#10487)

Refs #10057
This commit is contained in:
ssongliu
2025-09-25 10:44:08 +00:00
committed by GitHub
parent b1dde54e38
commit 8af6868694
19 changed files with 174 additions and 7 deletions
+20
View File
@@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
@@ -32,6 +33,25 @@ func (b *BaseApi) GetSettingInfo(c *gin.Context) {
helper.SuccessWithData(c, setting)
}
// @Tags System Setting
// @Summary Load system setting by key
// @Success 200 {string} info
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/settings/by [post]
func (b *BaseApi) GetSettingByKey(c *gin.Context) {
var req dto.SettingKey
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
setting, err := repo.NewISettingRepo().GetValueByKey(req.Key)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, setting)
}
// @Tags System Setting
// @Summary Load system terminal setting info
// @Success 200 {object} dto.TerminalInfo
+4
View File
@@ -52,6 +52,10 @@ type SettingInfo struct {
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
}
type SettingKey struct {
Key string `json:"key" validate:"required,oneof=ScriptSync"`
}
type SettingUpdate struct {
Key string `json:"key" validate:"required"`
Value string `json:"value"`
+4 -1
View File
@@ -5,6 +5,8 @@ import (
mathRand "math/rand"
"time"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/init/cron/job"
"github.com/1Panel-dev/1Panel/core/utils/common"
@@ -19,7 +21,8 @@ func Init() {
global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error())
}
if !global.CONF.Base.IsOffLine {
scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync")
if !global.CONF.Base.IsOffLine && scriptSync == constant.StatusEnable {
scriptJob := job.NewScriptJob()
if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), scriptJob); err != nil {
global.LOG.Errorf("[core] can not add script sync corn job: %s", err.Error())
+1
View File
@@ -24,6 +24,7 @@ func Init() {
migrations.AddDiskMenu,
migrations.AddSimpleNodeGroup,
migrations.AddUpgradeBackupCopies,
migrations.AddScriptSync,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+10
View File
@@ -545,3 +545,13 @@ var AddUpgradeBackupCopies = &gormigrate.Migration{
return nil
},
}
var AddScriptSync = &gormigrate.Migration{
ID: "20250916-add-script-sync",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "ScriptSync", Value: constant.StatusEnable}).Error; err != nil {
return err
}
return nil
},
}
+7 -2
View File
@@ -2,12 +2,17 @@ package run
import (
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
)
func Init() {
if err := service.NewIScriptService().Sync(dto.OperateByTaskID{}); err != nil {
global.LOG.Errorf("sync scripts from remote failed, err: %v", err)
scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync")
if !global.CONF.Base.IsOffLine && scriptSync == constant.StatusEnable {
if err := service.NewIScriptService().Sync(dto.OperateByTaskID{}); err != nil {
global.LOG.Errorf("sync scripts from remote failed, err: %v", err)
}
}
}
+1
View File
@@ -20,6 +20,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
{
router.POST("/search", baseApi.GetSettingInfo)
router.POST("/expired/handle", baseApi.HandlePasswordExpired)
settingRouter.POST("/by", baseApi.GetSettingByKey)
settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo)
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
settingRouter.POST("/update", baseApi.UpdateSetting)
+3
View File
@@ -76,6 +76,9 @@ export const getAgentSettingByKey = (key: string) => {
export const getSettingInfo = () => {
return http.post<Setting.SettingInfo>(`/core/settings/search`);
};
export const getSettingBy = (key: string) => {
return http.post<string>(`/core/settings/by`, { key: key });
};
export const getTerminalInfo = () => {
return http.post<Setting.TerminalInfo>(`/core/settings/terminal/search`);
};
+6
View File
@@ -1131,6 +1131,12 @@ const message = {
alertTitle: 'Planned Task - {0} 「{1}」 Task Failure Alert',
library: {
script: 'Script',
syncNow: 'Sync Now',
turnOnSync: 'Enable Auto Sync',
turnOnSyncHelper:
'Enabling auto sync will perform automatic synchronization during early morning hours daily',
turnOffSync: 'Disable Auto Sync',
turnOffSyncHelper: 'Disabling auto sync may cause script synchronization delays, confirm?',
isInteractive: 'Interactive',
interactive: 'Interactive script',
interactiveHelper: 'Requires user input during execution and cannot be used in scheduled tasks.',
+7
View File
@@ -1131,6 +1131,13 @@ const message = {
alertTitle: 'Tarea programada - {0} 「{1}」 Alerta de fallo',
library: {
script: 'Script',
syncNow: 'Sincronizar Ahora',
turnOnSync: 'Activar Sincronización Automática',
turnOnSyncHelper:
'Activar la sincronización automática realizará sincronizaciones automáticas durante las primeras horas de la madrugada diariamente',
turnOffSync: 'Desactivar Sincronización Automática',
turnOffSyncHelper:
'Desactivar la sincronización automática puede causar retrasos en la sincronización de scripts, ¿confirmar?',
isInteractive: 'Interactivo',
interactive: 'Script interactivo',
interactiveHelper:
+5
View File
@@ -1094,6 +1094,11 @@ const message = {
alertTitle: '計画タスク - {0}「{1}」タスク障害アラート',
library: {
script: 'スクリプト',
syncNow: '今すぐ同期',
turnOnSync: '自動同期を有効化',
turnOnSyncHelper: '自動同期を有効にすると、毎日未明の時間帯に自動同期が実行されます',
turnOffSync: '自動同期を無効化',
turnOffSyncHelper: '自動同期を無効にするとスクリプトの同期が遅れる可能性がありますが、よろしいですか?',
isInteractive: '対話型',
interactive: '対話型スクリプト',
interactiveHelper: '実行中にユーザー入力が必要で、スケジュールタスクでは使用できません。',
+5
View File
@@ -1087,6 +1087,11 @@ const message = {
alertTitle: '예정된 작업 - {0} 「{1}」 작업 실패 경고',
library: {
script: '스크립트',
syncNow: '지금 동기화',
turnOnSync: '자동 동기화 켜기',
turnOnSyncHelper: '자동 동기화를 켜면 매일 새벽 시간에 자동 동기화가 수행됩니다',
turnOffSync: '자동 동기화 끄기',
turnOffSyncHelper: '자동 동기화를 끄면 스크립트 동기화가 지연될 수 있습니다. 확인하시겠습니까?',
isInteractive: '대화형',
interactive: '대화형 스크립트',
interactiveHelper: '실행 중 사용자 입력이 필요하며 예약 작업에서는 사용할 수 없습니다.',
+6
View File
@@ -1122,6 +1122,12 @@ const message = {
alertTitle: 'Tugas Terancang - {0} 「{1}」 Amaran Kegagalan Tugas',
library: {
script: 'Skrip',
syncNow: 'Segerakan Sekarang',
turnOnSync: 'Hidupkan Segerakan Auto',
turnOnSyncHelper:
'Menghidupkan segerakan auto akan melakukan penyegerakan automatik pada waktu awal pagi setiap hari',
turnOffSync: 'Matikan Segerakan Auto',
turnOffSyncHelper: 'Mematikan segerakan auto mungkin menyebabkan kelewatan penyegerakan skrip, sahkan?',
isInteractive: 'Interaktif',
interactive: 'Skrip interaktif',
interactiveHelper:
+7
View File
@@ -1114,6 +1114,13 @@ const message = {
alertTitle: 'Tarefa Planejada - {0} {1} Alerta de Falha na Tarefa',
library: {
script: 'Script',
syncNow: 'Sincronizar Agora',
turnOnSync: 'Ativar Sincronização Automática',
turnOnSyncHelper:
'Ativar a sincronização automática realizará sincronizações automáticas durante as primeiras horas da manhã diariamente',
turnOffSync: 'Desativar Sincronização Automática',
turnOffSyncHelper:
'Desativar a sincronização automática pode causar atrasos na sincronização de scripts, confirmar?',
isInteractive: 'Interativo',
interactive: 'Script interativo',
interactiveHelper:
+7
View File
@@ -1117,6 +1117,13 @@ const message = {
alertTitle: 'Плановая задача - {0} «{1}» Оповещение о сбое задачи',
library: {
script: 'Скрипт',
syncNow: 'Синхронизировать Сейчас',
turnOnSync: 'Включить Автосинхронизацию',
turnOnSyncHelper:
'Включение автосинхронизации будет выполнять автоматическую синхронизацию в ранние утренние часы ежедневно',
turnOffSync: 'Отключить Автосинхронизацию',
turnOffSyncHelper:
'Отключение автосинхронизации может вызвать задержки синхронизации скриптов, подтвердить?',
isInteractive: 'Интерактивный',
interactive: 'Интерактивный скрипт',
interactiveHelper:
+7
View File
@@ -1142,6 +1142,13 @@ const message = {
alertTitle: 'Planlanmış Görev - {0} {1} Görev Başarısızlık Uyarısı',
library: {
script: 'Script',
syncNow: 'Hemen Senkronize Et',
turnOnSync: 'Otomatik Senkronizasyonu ',
turnOnSyncHelper:
'Otomatik senkronizasyonu açmak, her gün sabahın erken saatlerinde otomatik senkronizasyon gerçekleştirecektir',
turnOffSync: 'Otomatik Senkronizasyonu Kapat',
turnOffSyncHelper:
'Otomatik senkronizasyonu kapatmak, betik senkronizasyon gecikmelerine neden olabilir, onaylıyor musunuz?',
isInteractive: 'Etkileşimli',
interactive: 'Etkileşimli script',
interactiveHelper: 'Yürütme sırasında kullanıcı girişi gerektirir ve zamanlanmış görevlerde kullanılamaz.',
+5
View File
@@ -1076,6 +1076,11 @@ const message = {
alertTitle: '計畫任務-{0}{1}任務失敗告警',
library: {
script: '腳本',
syncNow: '立即同步',
turnOnSync: '開啟自動同步',
turnOnSyncHelper: '開啟自動同步將在每天凌晨時段進行自動同步',
turnOffSync: '關閉自動同步',
turnOffSyncHelper: '關閉自動同步可能導致腳本同步不及時是否確認',
isInteractive: '互動式',
interactive: '互動式腳本',
interactiveHelper: '在腳本執行過程中需要使用者輸入參數或做出選擇且無法用於計劃任務中',
+5
View File
@@ -1076,6 +1076,11 @@ const message = {
alertTitle: '计划任务-{0} {1} 任务失败告警',
library: {
script: '脚本',
syncNow: '立即同步',
turnOnSync: '开启自动同步',
turnOnSyncHelper: '开启自动同步将在每天凌晨时段进行自动同步',
turnOffSync: '关闭自动同步',
turnOffSyncHelper: '关闭自动同步可能导致脚本同步不及时是否确认',
isInteractive: '交互式',
interactive: '交互式脚本',
interactiveHelper: '在脚本执行过程中需要用户输入参数或做出选择且无法用于计划任务中',
+64 -4
View File
@@ -5,9 +5,26 @@
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button type="primary" plain @click="onSync()">
{{ $t('commons.button.sync') }}
</el-button>
<el-dropdown @command="handleSyncOp" class="mr-2.5">
<el-button type="primary" plain>
{{ $t('commons.button.sync') }}
<el-icon><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="sync">
{{ $t('cronjob.library.syncNow') }}
</el-dropdown-item>
<el-dropdown-item v-if="scriptSync === 'Disable'" command="turnOnSync">
{{ $t('cronjob.library.turnOnSync') }}
</el-dropdown-item>
<el-dropdown-item v-if="scriptSync === 'Enable'" command="turnOffSync">
{{ $t('cronjob.library.turnOffSync') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button type="primary" plain @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }}
</el-button>
@@ -116,6 +133,7 @@ import { GlobalStore } from '@/store';
import { getGroupList } from '@/api/modules/group';
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
import { MsgSuccess } from '@/utils/message';
import { getSettingBy, updateSetting } from '@/api/modules/setting';
const globalStore = GlobalStore();
const mobile = computed(() => {
@@ -129,6 +147,7 @@ const opRef = ref();
const runRef = ref();
const taskLogRef = ref();
const scriptSync = ref();
const data = ref();
const paginationConfig = reactive({
@@ -194,7 +213,7 @@ const onDelete = async (row: Cronjob.ScriptInfo | null) => {
};
const onSync = async () => {
ElMessageBox.confirm(i18n.global.t('cronjob.library.syncHelper', i18n.global.t('commons.button.sync')), {
ElMessageBox.confirm(i18n.global.t('cronjob.library.syncHelper'), i18n.global.t('commons.button.syncNow'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
@@ -217,6 +236,46 @@ const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID, true, 'local');
};
const loadSyncStatus = async () => {
const res = await getSettingBy('ScriptSync');
scriptSync.value = res.data;
};
const handleSyncOp = async (command: string) => {
let val = 'Enable';
switch (command) {
case 'sync':
onSync();
return;
case 'turnOnSync':
val = 'Enable';
break;
case 'turnOffSync':
val = 'Disable';
break;
default:
return;
}
ElMessageBox.confirm(
i18n.global.t('cronjob.library.' + command + 'Helper'),
i18n.global.t('cronjob.library.' + command),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(async () => {
loading.value = true;
await updateSetting({ key: 'ScriptSync', value: val })
.then(() => {
loadSyncStatus();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.finally(() => {
loading.value = false;
});
});
};
const search = async () => {
let params = {
info: searchInfo.value,
@@ -297,6 +356,7 @@ const buttons = [
onMounted(() => {
search();
loadSyncStatus();
loadGroupOptions();
});
</script>