feat: Support remote synchronization of script library scripts (#8371)

This commit is contained in:
ssongliu 2025-04-11 15:08:02 +08:00 committed by GitHub
parent 04f1722cce
commit ce4a770e23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 314 additions and 802 deletions

View file

@ -386,11 +386,11 @@ func RestartService(core, agent, reload bool) {
}
switch {
case core && agent:
command += "systemctl restart 1panel-core.service && systemctl restart 1pane-agent.service"
command += "systemctl restart 1panel-core.service && systemctl restart 1panel-agent.service"
case core:
command += "systemctl restart 1panel-core.service"
case agent:
command += "systemctl restart 1pane-agent.service"
command += "systemctl restart 1panel-agent.service"
default:
return
}

View file

@ -53,7 +53,7 @@ func (b *BaseApi) SearchScript(c *gin.Context) {
return
}
total, list, err := scriptService.Search(req)
total, list, err := scriptService.Search(c, req)
if err != nil {
helper.InternalServer(c, err)
return
@ -87,6 +87,21 @@ func (b *BaseApi) DeleteScript(c *gin.Context) {
helper.SuccessWithOutData(c)
}
// @Tags ScriptLibrary
// @Summary Sync script from remote
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /script/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步脚本库脚本","formatEN":"sync scripts"}
func (b *BaseApi) SyncScript(c *gin.Context) {
if err := scriptService.Sync(); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags ScriptLibrary
// @Summary Update script
// @Accept json

View file

@ -3,7 +3,6 @@ package model
type ScriptLibrary struct {
BaseModel
Name string `json:"name" gorm:"not null;"`
Lable string `json:"lable"`
Script string `json:"script" gorm:"not null;"`
Groups string `json:"groups"`
IsSystem bool `json:"isSystem"`

View file

@ -9,10 +9,11 @@ import (
type IScriptRepo interface {
Get(opts ...global.DBOption) (model.ScriptLibrary, error)
GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error)
Create(snap *model.ScriptLibrary) error
Create(script *model.ScriptLibrary) error
Update(id uint, vars map[string]interface{}) error
Page(limit, offset int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error)
Delete(opts ...global.DBOption) error
SyncAll(scripts []model.ScriptLibrary) error
WithByInfo(info string) global.DBOption
}
@ -34,13 +35,13 @@ func (u *ScriptRepo) Get(opts ...global.DBOption) (model.ScriptLibrary, error) {
}
func (u *ScriptRepo) GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) {
var snaps []model.ScriptLibrary
var scripts []model.ScriptLibrary
db := global.DB.Model(&model.ScriptLibrary{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&snaps).Error
return snaps, err
err := db.Find(&scripts).Error
return scripts, err
}
func (u *ScriptRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) {
@ -71,6 +72,20 @@ func (u *ScriptRepo) Delete(opts ...global.DBOption) error {
return db.Delete(&model.ScriptLibrary{}).Error
}
func (u *ScriptRepo) SyncAll(scripts []model.ScriptLibrary) error {
tx := global.DB.Begin()
if err := tx.Where("is_system = ?", 1).Delete(&model.ScriptLibrary{}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Save(&scripts).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
func (u *ScriptRepo) WithByInfo(info string) global.DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("name LIKE ? OR description LIKE ?", "%"+info+"%", "%"+info+"%")

View file

@ -1,31 +1,45 @@
package service
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/app/task"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/i18n"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/files"
"github.com/1Panel-dev/1Panel/core/utils/req_helper"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v2"
)
type ScriptService struct{}
type IScriptService interface {
Search(req dto.SearchPageWithGroup) (int64, interface{}, error)
Search(ctx *gin.Context, req dto.SearchPageWithGroup) (int64, interface{}, error)
Create(req dto.ScriptOperate) error
Update(req dto.ScriptOperate) error
Delete(ids dto.OperateByIDs) error
Sync() error
}
func NewIScriptService() IScriptService {
return &ScriptService{}
}
func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{}, error) {
func (u *ScriptService) Search(ctx *gin.Context, req dto.SearchPageWithGroup) (int64, interface{}, error) {
options := []global.DBOption{repo.WithOrderBy("created_at desc")}
if len(req.Info) != 0 {
options = append(options, scriptRepo.WithByInfo(req.Info))
@ -45,7 +59,20 @@ func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{},
if err := copier.Copy(&item, &itemData); err != nil {
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
}
matchGourp := false
if item.IsSystem {
lang := strings.ToLower(common.GetLang(ctx))
var nameMap = make(map[string]string)
_ = json.Unmarshal([]byte(item.Name), &nameMap)
var descriptionMap = make(map[string]string)
_ = json.Unmarshal([]byte(item.Description), &descriptionMap)
if val, ok := nameMap[lang]; ok {
item.Name = val
}
if val, ok := descriptionMap[lang]; ok {
item.Description = val
}
}
matchGroup := false
groupIDs := strings.Split(itemData.Groups, ",")
for _, idItem := range groupIDs {
id, _ := strconv.Atoi(idItem)
@ -53,7 +80,7 @@ func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{},
continue
}
if uint(id) == req.GroupID {
matchGourp = true
matchGroup = true
}
item.GroupList = append(item.GroupList, uint(id))
item.GroupBelong = append(item.GroupBelong, groupMap[uint(id)])
@ -62,7 +89,7 @@ func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{},
data = append(data, item)
continue
}
if matchGourp {
if matchGroup {
data = append(data, item)
}
}
@ -125,3 +152,96 @@ func (u *ScriptService) Update(req dto.ScriptOperate) error {
func LoadScriptInfo(id uint) (model.ScriptLibrary, error) {
return scriptRepo.Get(repo.WithByID(id))
}
func (u *ScriptService) Sync() error {
syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("LocalApp"), task.TaskSync, task.TaskScopeScript, "", 0)
if err != nil {
global.LOG.Errorf("create sync task failed %v", err)
return err
}
syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("LocalApp"), task.TaskSync, task.TaskScopeScript), func(t *task.Task) (err error) {
versionUrl := fmt.Sprintf("%s/scripts/version.txt", global.CONF.RemoteURL.ResourceURL)
_, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return fmt.Errorf("load scripts version from remote failed, err: %v", err)
}
var scriptSetting model.Setting
_ = global.DB.Where("key = ?", "ScriptVersion").First(&scriptSetting).Error
if scriptSetting.Value == string(versionRes) {
syncTask.Log("The local and remote versions are detected to be consistent. Skip...")
return nil
}
syncTask.Log("start to download data.yaml")
dataUrl := fmt.Sprintf("%s/scripts/data.yaml", global.CONF.RemoteURL.ResourceURL)
_, dataRes, err := req_helper.HandleRequest(dataUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return fmt.Errorf("load scripts data.yaml from remote failed, err: %v", err)
}
syncTask.Log("download successful!")
var scripts Scripts
if err = yaml.Unmarshal(dataRes, &scripts); err != nil {
return fmt.Errorf("the format of data.yaml is err: %v", err)
}
syncTask.Log("start to download scripts.tar.gz")
tmpDir := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/script")
if _, err := os.Stat(tmpDir); err != nil {
_ = os.MkdirAll(tmpDir, 0755)
}
scriptsUrl := fmt.Sprintf("%s/scripts/scripts.tar.gz", global.CONF.RemoteURL.ResourceURL)
if err := files.DownloadFile(scriptsUrl, tmpDir+"/scripts.tar.gz"); err != nil {
return fmt.Errorf("download scripts.tar.gz failed, err: %v", err)
}
syncTask.Log("download successful! now start to decompress...")
if err := files.HandleUnTar(tmpDir+"/scripts.tar.gz", tmpDir, ""); err != nil {
return fmt.Errorf("handle decompress scripts.tar.gz failed, err: %v", err)
}
var scriptsForDB []model.ScriptLibrary
for _, item := range scripts.Scripts.Sh {
itemName, _ := json.Marshal(item.Name)
itemDescription, _ := json.Marshal(item.Description)
shell, _ := os.ReadFile(fmt.Sprintf("%s/scripts/sh/%s.sh", tmpDir, item.Key))
scriptItem := model.ScriptLibrary{
Name: string(itemName),
IsSystem: true,
Script: string(shell),
Description: string(itemDescription),
}
scriptsForDB = append(scriptsForDB, scriptItem)
}
syncTask.Log("analytic completion! now start to refresh database...")
if err := scriptRepo.SyncAll(scriptsForDB); err != nil {
return fmt.Errorf("sync script with db failed, err: %v", err)
}
_ = os.RemoveAll(tmpDir)
if err := global.DB.Model(&model.Setting{}).Where("key = ?", "ScriptVersion").Updates(map[string]interface{}{"value": string(versionRes)}).Error; err != nil {
return fmt.Errorf("update script version in db failed, err: %v", err)
}
return nil
}, nil)
if err := syncTask.Execute(); err != nil {
return fmt.Errorf("sync scripts from remote failed, err: %v", err)
}
return nil
}
type Scripts struct {
Scripts ScriptDetail `json:"scripts"`
}
type ScriptDetail struct {
Sh []ScriptHelper `json:"sh"`
}
type ScriptHelper struct {
Key string `json:"key"`
Sort uint `json:"sort"`
Groups string `json:"groups"`
Name map[string]string `json:"name"`
Description map[string]string `json:"description"`
}

View file

@ -174,7 +174,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
_ = settingRepo.Update("SystemStatus", "Free")
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload", 30*time.Second)
_, _ = cmd.ExecWithTimeOut("systemctl restart 1pane-agent.service", 1*time.Second)
_, _ = cmd.ExecWithTimeOut("systemctl restart 1panel-agent.service", 1*time.Second)
_, _ = cmd.ExecWithTimeOut("systemctl restart 1panel-core.service", 1*time.Second)
}()
return nil

View file

@ -47,10 +47,12 @@ type SubTask struct {
const (
TaskUpgrade = "TaskUpgrade"
TaskAddNode = "TaskAddNode"
TaskSync = "TaskSync"
)
const (
TaskScopeSystem = "System"
TaskScopeScript = "Script"
)
const (

View file

@ -36,7 +36,8 @@ type ApiInterface struct {
}
type RemoteURL struct {
RepoUrl string `mapstructure:"repo_url"`
RepoUrl string `mapstructure:"repo_url"`
ResourceURL string `mapstructure:"resource_url"`
}
type LogConfig struct {

View file

@ -45,6 +45,7 @@ require (
golang.org/x/sys v0.26.0
golang.org/x/term v0.25.0
golang.org/x/text v0.19.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.11
)
@ -120,7 +121,6 @@ require (
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/sqlite v1.4.4 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} succeeded"
SubTaskFailed: "{{ .name }} failed: {{ .err }}"
TaskInstall: "Install"
TaskUpgrade: "Upgrade"
TaskSync: 'Synchronize'
SuccessStatus: "{{ .name }} succeeded"
FailedStatus: "{{ .name }} failed {{ .err }}"
Start: "Start"
SubTask: "Subtask"
#script
ScriptLibrary: "Script Library"
#upgrade node
NodeUpgrade: "Upgrade node {{ .name }}"
UpgradeCheck: "Check for node updates"

View file

@ -83,11 +83,15 @@ SubTaskSuccess: "{{ .name }} 成功"
SubTaskFailed: "{{ .name }} 失敗: {{ .err }}"
TaskInstall: "インストール"
TaskUpgrade: "アップグレード"
TaskSync: '同期'
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失敗 {{ .err }}"
Start: "開始"
SubTask: "サブタスク"
#script
ScriptLibrary: "スクリプトライブラリ"
#upgrade node
NodeUpgrade: "{{ .name }} ノードのアップグレード"
UpgradeCheck: "ノードの更新を確認"

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} 성공"
SubTaskFailed: "{{ .name }} 실패: {{ .err }}"
TaskInstall: "설치"
TaskUpgrade: "업그레이드"
TaskSync: '동기화'
SuccessStatus: "{{ .name }} 성공"
FailedStatus: "{{ .name }} 실패 {{ .err }}"
Start: "시작"
SubTask: "서브 작업"
#script
ScriptLibrary: "스크립트 라이브러리"
#upgrade node
NodeUpgrade: "{{ .name }} 노드 업그레이드"
UpgradeCheck: "노드 업데이트 확인"

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} berjaya"
SubTaskFailed: "{{ .name }} gagal: {{ .err }}"
TaskInstall: "Pasang"
TaskUpgrade: "Kemas kini"
TaskSync: 'Selaraskan'
SuccessStatus: "{{ .name }} berjaya"
FailedStatus: "{{ .name }} gagal {{ .err }}"
Start: "Mula"
SubTask: "Tugas Sub"
#script
ScriptLibrary: "Pustaka Skrip"
#upgrade node
NodeUpgrade: "Naik taraf node {{ .name }}"
UpgradeCheck: "Periksa kemas kini nod"

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} bem-sucedido"
SubTaskFailed: "{{ .name }} falhou: {{ .err }}"
TaskInstall: "Instalar"
TaskUpgrade: "Atualizar"
TaskSync: 'Sincronizar'
SuccessStatus: "{{ .name }} bem-sucedido"
FailedStatus: "{{ .name }} falhou {{ .err }}"
Start: "Iniciar"
SubTask: "Subtarefa"
#script
ScriptLibrary: "Biblioteca de Scripts"
#upgrade node
NodeUpgrade: "Atualizar nó {{ .name }}"
UpgradeCheck: "Verificar atualizações do nó"

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} успешно"
SubTaskFailed: "{{ .name }} не удалось: {{ .err }}"
TaskInstall: "Установить"
TaskUpgrade: "Обновить"
TaskSync: 'Синхронизация'
SuccessStatus: "{{ .name }} успешно"
FailedStatus: "{{ .name }} не удалось {{ .err }}"
Start: "Начать"
SubTask: "Подзадача"
#script
ScriptLibrary: "Библиотека скриптов"
#upgrade node
NodeUpgrade: "Обновление узла {{ .name }}"
UpgradeCheck: "Проверить обновления узла"

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} 成功"
SubTaskFailed: "{{ .name }} 失敗: {{ .err }}"
TaskInstall: "安裝"
TaskUpgrade: "升級"
TaskSync: '同步'
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失敗 {{ .err }}"
Start: "開始"
SubTask: "子任務"
#script
ScriptLibrary: "腳本庫"
#upgrade node
NodeUpgrade: "升級節點 {{ .name }}"
UpgradeCheck: "檢查節點更新"

View file

@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} 成功"
SubTaskFailed: "{{ .name }} 失败: {{ .err }}"
TaskInstall: "安装"
TaskUpgrade: "升级"
TaskSync: "同步"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失败 {{ .err }}"
Start: "开始"
SubTask: "子任务"
#script
ScriptLibrary: "脚本库"
#upgrade node
NodeUpgrade: "升级节点 {{ .name }}"
UpgradeCheck: "检查节点更新"

View file

@ -1,6 +1,8 @@
package cron
import (
"fmt"
mathRand "math/rand"
"time"
"github.com/1Panel-dev/1Panel/core/global"
@ -14,7 +16,13 @@ func Init() {
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil {
global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error())
global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error())
}
scriptJob := job.NewScriptJob()
scriptJob.Run()
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())
}
global.Cron.Start()
}

View file

@ -0,0 +1,18 @@
package job
import (
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/global"
)
type script struct{}
func NewScriptJob() *script {
return &script{}
}
func (s *script) Run() {
if err := service.NewIScriptService().Sync(); err != nil {
global.LOG.Errorf("sync scripts from remote failed, err: %v", err)
}
}

View file

@ -1,781 +0,0 @@
package helper
import (
"fmt"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/global"
)
func LoadScript() {
groups := []model.Group{
{Name: "install", Type: "script", IsDefault: false},
{Name: "docker", Type: "script", IsDefault: false},
{Name: "firewall", Type: "script", IsDefault: false},
{Name: "supervisor", Type: "script", IsDefault: false},
{Name: "clamav", Type: "script", IsDefault: false},
{Name: "ftp", Type: "script", IsDefault: false},
{Name: "fail2ban", Type: "script", IsDefault: false}}
_ = global.DB.Where("`type` = ?", "script").Delete(&model.Group{}).Error
_ = global.DB.Create(&groups).Error
_ = global.DB.Where("is_system = ?", 1).Delete(model.ScriptLibrary{}).Error
list := []model.ScriptLibrary{
{Name: "Install Docker", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[1].ID, groups[0].ID), Script: "bash <(curl -sSL https://linuxmirrors.cn/docker.sh)"},
{Name: "Install Firewall", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[2].ID, groups[0].ID), Script: loadInstallFirewall()},
{Name: "Install Supervisor", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[3].ID, groups[0].ID), Script: loadInstallSupervisor()},
{Name: "Install ClamAV", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[4].ID, groups[0].ID), Script: loadInstallClamAV()},
{Name: "Install Pure-FTPd", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[5].ID, groups[0].ID), Script: loadInstallFTP()},
{Name: "Install Fail2ban", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[6].ID, groups[0].ID), Script: loadInstallFail2ban()},
}
_ = global.DB.Create(&list).Error
}
func loadInstallFirewall() string {
return `#!/bin/bash
# 防火墙 安装配置脚本
# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
FIREWALL=""
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
VERSION=$(lsb_release -sr)
elif [ -f /etc/redhat-release ]; then
OS="rhel"
VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release)
elif [ -f /etc/alpine-release ]; then
OS="alpine"
VERSION=$(cat /etc/alpine-release)
else
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
VERSION=$(uname -r)
fi
}
# 安装防火墙
install_firewall() {
if [ "$OS" == "ubuntu" ] || [ "$OS" == "debian" ]; then
echo -e "${GREEN}检测到操作系统: $OS $VERSION正在安装 ufw...${NC}"
FIREWALL="ufw"
apt-get update
apt-get install -y ufw
elif [ "$OS" == "centos" ] || [ "$OS" == "rhel" ] || [ "$OS_LIKE" == "rhel" ]; then
echo -e "${GREEN}检测到操作系统: $OS $VERSION正在安装 firewall...${NC}"
FIREWALL="firewalld"
yum update
yum install -y firewalld
else
echo -e "${RED}不支持的操作系统${NC}"
exit 1
fi
}
# 初始化并启动
start_with_init() {
read -p "请输入需要放行的端口(多个端口用空格分隔,如 80 443 22): " PORTS
# 验证端口输入
if [ -z "$PORTS" ]; then
echo -e "${RED}错误:未输入任何端口${NC}"
exit 1
fi
case $FIREWALL in
firewalld)
echo -e "${GREEN}配置firewalld...${NC}"
echo "初始化并启动firewalld..."
systemctl start firewalld
systemctl enable firewalld
for port in $PORTS; do
firewall-cmd --zone=public --permanent --add-port="$port/tcp"
done
firewall-cmd --reload
echo -e "${GREEN}已放行以下TCP端口: $PORTS ${NC}"
;;
ufw)
echo -e "${GREEN}初始化并启动ufw...${NC}"
ufw --force enable
for port in $PORTS; do
ufw allow "$port/tcp"
done
echo -e "${GREEN}已放行以下TCP端口: $PORTS ${NC}"
;;
esac
}
# 检查防火墙是否正常运行
check_install() {
if [ "$FIREWALL" = "firewalld" ]; then
if command -v firewall-cmd &> /dev/null; then
systemctl status firewalld || true
fi
else
if command -v ufw &> /dev/null; then
ufw status || true
fi
fi
echo -e "${GREEN}$FIREWALL 安装完成并启动${NC}"
}
# 主函数
main() {
detect_os
install_firewall
start_with_init
check_install
}
main "$@"`
}
func loadInstallFTP() string {
return `#!/bin/bash
# Pure-FTPd 安装配置脚本
# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
VERSION=$(lsb_release -sr)
elif [ -f /etc/redhat-release ]; then
OS="rhel"
VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release)
elif [ -f /etc/alpine-release ]; then
OS="alpine"
VERSION=$(cat /etc/alpine-release)
else
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
VERSION=$(uname -r)
fi
}
# 安装Pure-FTPd
install_pureftpd() {
echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}"
case "$OS" in
ubuntu|debian)
apt-get update
apt-get install -y pure-ftpd
;;
centos|rhel|fedora)
if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then
dnf install -y epel-release
dnf install -y pure-ftpd
else
yum install -y epel-release
yum install -y pure-ftpd
fi
;;
alpine)
apk add --no-cache pure-ftpd
;;
arch)
pacman -Sy --noconfirm pure-ftpd
;;
*)
echo -e "${RED}不支持的操作系统${NC}"
exit 1
;;
esac
if ! command -v pure-ftpd &> /dev/null; then
echo -e "${RED}Pure-FTPd安装失败${NC}"
exit 1
fi
}
# 配置Pure-FTPd
configure_pureftpd() {
echo -e "${GREEN}配置Pure-FTPd...${NC}"
PURE_FTPD_CONF="/etc/pure-ftpd/pure-ftpd.conf"
if [ -f "$PURE_FTPD_CONF" ]; then
cp "$PURE_FTPD_CONF" "$PURE_FTPD_CONF.bak"
sed -i 's/^NoAnonymous[[:space:]]\+no$/NoAnonymous yes/' "$PURE_FTPD_CONF"
sed -i 's/^PAMAuthentication[[:space:]]\+yes$/PAMAuthentication no/' "$PURE_FTPD_CONF"
sed -i 's/^# PassivePortRange[[:space:]]\+30000 50000$/PassivePortRange 39000 40000/' "$PURE_FTPD_CONF"
sed -i 's/^VerboseLog[[:space:]]\+no$/VerboseLog yes/' "$PURE_FTPD_CONF"
sed -i 's/^# PureDB[[:space:]]\+\/etc\/pure-ftpd\/pureftpd\.pdb[[:space:]]*$/PureDB \/etc\/pure-ftpd\/pureftpd.pdb/' "$PURE_FTPD_CONF"
else
touch /etc/pure-ftpd/pureftpd.pdb
chmod 644 /etc/pure-ftpd/pureftpd.pdb
echo '/etc/pure-ftpd/pureftpd.pdb' > /etc/pure-ftpd/conf/PureDB
echo yes > /etc/pure-ftpd/conf/VerboseLog
echo yes > /etc/pure-ftpd/conf/NoAnonymous
echo '39000 40000' > /etc/pure-ftpd/conf/PassivePortRange
echo 'no' > /etc/pure-ftpd/conf/PAMAuthentication
echo 'no' > /etc/pure-ftpd/conf/UnixAuthentication
echo 'clf:/var/log/pure-ftpd/transfer.log' > /etc/pure-ftpd/conf/AltLog
ln -s /etc/pure-ftpd/conf/PureDB /etc/pure-ftpd/auth/50puredb
fi
}
# 启动服务
start_service() {
echo -e "${GREEN}启动Pure-FTPd服务...${NC}"
case "$OS" in
ubuntu|debian)
systemctl enable pure-ftpd
systemctl restart pure-ftpd
;;
centos|rhel|fedora)
systemctl enable pure-ftpd
systemctl restart pure-ftpd
;;
alpine)
rc-update add pure-ftpd
rc-service pure-ftpd start
;;
arch)
systemctl enable pure-ftpd
systemctl restart pure-ftpd
;;
*)
echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}"
;;
esac
# 验证服务状态
if command -v systemctl &> /dev/null; then
systemctl status pure-ftpd || true
else
rc-service pure-ftpd status || true
fi
}
# 主函数
main() {
detect_os
install_pureftpd
configure_pureftpd
start_service
}
main "$@"`
}
func loadInstallClamAV() string {
return `#!/bin/bash
# ClamAV 安装启动脚本
# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
VERSION=$(lsb_release -sr)
elif [ -f /etc/redhat-release ]; then
OS="rhel"
VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release)
elif [ -f /etc/alpine-release ]; then
OS="alpine"
VERSION=$(cat /etc/alpine-release)
else
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
VERSION=$(uname -r)
fi
}
# 安装ClamAV
install_clamav() {
echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}"
case "$OS" in
ubuntu|debian)
apt-get update
apt-get install -y clamav clamav-daemon clamav-freshclam
;;
centos|rhel|fedora)
if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then
dnf install -y epel-release
dnf install -y clamav clamd clamav-update
else
yum install -y epel-release
yum install -y clamav clamd clamav-update
fi
;;
alpine)
apk add --no-cache clamav clamav-libunrar clamav-daemon clamav-freshclam
;;
arch)
pacman -Sy --noconfirm clamav
;;
*)
echo -e "${RED}不支持的操作系统${NC}"
exit 1
;;
esac
if ! command -v clamscan &> /dev/null; then
echo -e "${RED}ClamAV安装失败${NC}"
exit 1
fi
}
# clamd
configure_clamd() {
echo -e "${GREEN}配置clamd...${NC}"
# 备份原始配置
CLAMD_CONF=""
if [ -f "/etc/clamd.d/scan.conf" ]; then
CLAMD_CONF="/etc/clamd.d/scan.conf"
elif [ -f "/etc/clamav/clamd.conf" ]; then
CLAMD_CONF="/etc/clamav/clamd.conf"
else
echo "未找到 freshclam 配置文件,请手动配置"
exit 1
fi
cp "$CLAMD_CONF" "$CLAMD_CONF.bak"
# 禁用检查新版本以避免权限问题
sed -i 's|^LogFileMaxSize .*|LogFileMaxSize 2M|' "$CLAMD_CONF"
sed -i 's|^PidFile .*|PidFile /run/clamd.scan/clamd.pid|' "$CLAMD_CONF"
sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$CLAMD_CONF"
sed -i 's|^LocalSocket .*|LocalSocket /run/clamd.scan/clamd.sock|' "$CLAMD_CONF"
}
# 配置freshclam
configure_freshclam() {
echo -e "${GREEN}配置freshclam...${NC}"
# 备份原始配置
FRESHCLAM_CONF=""
if [ -f "/etc/freshclam.conf" ]; then
FRESHCLAM_CONF="/etc/freshclam.conf"
elif [ -f "/etc/clamav/freshclam.conf" ]; then
FRESHCLAM_CONF="/etc/clamav/freshclam.conf"
else
echo "未找到 freshclam 配置文件,请手动配置"
exit 1
fi
cp "$FRESHCLAM_CONF" "$FRESHCLAM_CONF.bak"
# 禁用检查新版本以避免权限问题
sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$FRESHCLAM_CONF"
sed -i 's|^PidFile .*|PidFile /var/run/freshclam.pid|' "$FRESHCLAM_CONF"
sed -i '/^DatabaseMirror/d' "$FRESHCLAM_CONF"
echo "DatabaseMirror database.clamav.net" | sudo tee -a "$FRESHCLAM_CONF"
sed -i 's|^Checks .*|Checks 12|' "$FRESHCLAM_CONF"
}
# 下载病毒数据库
download_database() {
systemctl stop clamav-freshclam
echo -e "${GREEN}开始下载病毒数据库...${NC}"
MAX_RETRIES=5
RETRY_DELAY=60
ATTEMPT=1
while [ $ATTEMPT -le $MAX_RETRIES ]; do
echo -e "${YELLOW}尝试 $ATTEMPT/$MAX_RETRIES: 运行freshclam...${NC}"
if freshclam --verbose; then
echo -e "${GREEN}成功下载病毒数据库${NC}"
return 0
fi
if [ $ATTEMPT -lt $MAX_RETRIES ]; then
echo -e "${YELLOW}下载失败,等待 $RETRY_DELAY 秒后重试...${NC}"
sleep $RETRY_DELAY
fi
ATTEMPT=$((ATTEMPT+1))
done
echo -e "${RED}错误: 无法在 $MAX_RETRIES 次尝试后下载病毒数据库${NC}" >&2
exit 1
}
# 启动ClamAV服务
start_services() {
echo -e "${GREEN}启动ClamAV服务...${NC}"
case "$OS" in
ubuntu|debian)
systemctl enable --now clamav-daemon
systemctl enable --now clamav-freshclam
;;
centos|rhel|fedora)
systemctl enable --now clamd@scan
systemctl enable --now clamav-freshclam
;;
alpine)
rc-update add clamd boot
rc-update add freshclam boot
rc-service clamd start
rc-service freshclam start
;;
arch)
systemctl enable --now clamav-daemon
systemctl enable --now clamav-freshclam
;;
*)
echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}"
;;
esac
# 验证服务状态
if command -v systemctl &> /dev/null; then
systemctl status clamav-daemon || true
systemctl status clamav-freshclam || true
fi
echo -e "${GREEN}ClamAV安装完成并启动${NC}"
}
# 主函数
main() {
detect_os
install_clamav
configure_clamd
configure_freshclam
download_database
start_services
}
main "$@"`
}
func loadInstallFail2ban() string {
return `#!/bin/bash
# Fail2ban 安装配置脚本
# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
VERSION=$(lsb_release -sr)
elif [ -f /etc/redhat-release ]; then
OS="rhel"
VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release)
elif [ -f /etc/alpine-release ]; then
OS="alpine"
VERSION=$(cat /etc/alpine-release)
else
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
VERSION=$(uname -r)
fi
}
# 安装Fail2ban
install_fail2ban() {
echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}"
case "$OS" in
ubuntu|debian)
apt-get update
apt-get install -y fail2ban
;;
centos|rhel|fedora)
if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then
dnf install -y epel-release
dnf install -y fail2ban
else
yum install -y epel-release
yum install -y fail2ban
fi
;;
alpine)
apk add --no-cache fail2ban
;;
arch)
pacman -Sy --noconfirm fail2ban
;;
*)
echo -e "${RED}不支持的操作系统${NC}"
exit 1
;;
esac
sleep 2
if command -v systemctl &> /dev/null; then
systemctl status fail2ban --no-pager || true
else
rc-service fail2ban status || true
fi
fail2ban-client status
}
# 配置Fail2ban
configure_fail2ban() {
echo -e "${GREEN}配置Fail2ban...${NC}"
FAIL2BAN_CONF="/etc/fail2ban/jail.local"
LOG_FILE=""
BAN_ACTION=""
if systemctl is-active --quiet firewalld 2>/dev/null; then
BAN_ACTION="firewallcmd-ipset"
elif systemctl is-active --quiet ufw 2>/dev/null || service ufw status 2>/dev/null | grep -q "active"; then
BAN_ACTION="ufw"
else
BAN_ACTION="iptables-allports"
fi
if [ -f /var/log/secure ]; then
LOG_FILE="/var/log/secure"
else
LOG_FILE="/var/log/auth.log"
fi
cat <<EOF > "$FAIL2BAN_CONF"
#DEFAULT-START
[DEFAULT]
bantime = 600
findtime = 300
maxretry = 5
banaction = $BAN_ACTION
action = %(action_mwl)s
#DEFAULT-END
[sshd]
ignoreip = 127.0.0.1/8
enabled = true
filter = sshd
port = 22
maxretry = 5
findtime = 300
bantime = 600
banaction = $BAN_ACTION
action = %(action_mwl)s
logpath = $LOG_FILE
EOF
}
# 启动服务
start_service() {
echo -e "${GREEN}启动Fail2ban服务...${NC}"
case "$OS" in
ubuntu|debian)
systemctl enable fail2ban
systemctl restart fail2ban
;;
centos|rhel|fedora)
systemctl enable fail2ban
systemctl restart fail2ban
;;
alpine)
rc-update add fail2ban
rc-service fail2ban start
;;
arch)
systemctl enable fail2ban
systemctl restart fail2ban
;;
*)
echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}"
;;
esac
# 验证服务状态
if command -v systemctl &> /dev/null; then
systemctl status fail2ban || true
else
rc-service fail2ban status || true
fi
}
# 主函数
main() {
detect_os
install_fail2ban
configure_fail2ban
start_service
}
main "$@"`
}
func loadInstallSupervisor() string {
return `#!/bin/bash
# Supervisor 安装管理脚本
# 功能自动安装 + 基础配置 + 进程管理模板
# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
elif type lsb_release >/dev/null 2>&1; then
OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]')
VERSION=$(lsb_release -sr)
elif [ -f /etc/redhat-release ]; then
OS="rhel"
VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release)
elif [ -f /etc/alpine-release ]; then
OS="alpine"
VERSION=$(cat /etc/alpine-release)
else
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
VERSION=$(uname -r)
fi
}
# 安装Supervisor
install_supervisor() {
echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}"
case "$OS" in
ubuntu|debian)
apt-get update
apt-get install -y supervisor
;;
centos|rhel|fedora)
if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then
dnf install -y supervisor
else
yum install -y supervisor
fi
;;
alpine)
apk add --no-cache supervisor
mkdir -p /etc/supervisor.d
;;
arch)
pacman -Sy --noconfirm supervisor
;;
*)
echo -e "${RED}不支持的操作系统尝试pip安装...${NC}"
if ! command -v pip &> /dev/null; then
python -m ensurepip --upgrade
fi
pip install supervisor
;;
esac
}
# 启动服务
start_service() {
echo -e "${GREEN}启动Supervisor服务...${NC}"
case "$OS" in
ubuntu|debian)
systemctl enable supervisor
systemctl restart supervisor
;;
centos|rhel|fedora)
systemctl enable supervisor
systemctl restart supervisor
;;
alpine)
rc-update add supervisor
rc-service supervisor start
;;
arch)
systemctl enable supervisor
systemctl restart supervisor
;;
*)
echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}"
;;
esac
# 验证服务状态
if ! command -v supervisord &> /dev/null; then
echo -e "${RED}Supervisor安装失败${NC}"
exit 1
fi
}
# 主函数
main() {
detect_os
install_supervisor
start_service
}
main "$@"`
}

View file

@ -24,6 +24,7 @@ func Init() {
migrations.AddSystemIP,
migrations.InitScriptLibrary,
migrations.AddOperationNode,
migrations.AddScriptVersion,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -328,7 +328,9 @@ var InitScriptLibrary = &gormigrate.Migration{
if err := tx.AutoMigrate(&model.ScriptLibrary{}); err != nil {
return err
}
helper.LoadScript()
if err := tx.Create(&model.Group{Name: "Default", Type: "script", IsDefault: true}).Error; err != nil {
return err
}
return nil
},
}
@ -342,3 +344,13 @@ var AddOperationNode = &gormigrate.Migration{
return nil
},
}
var AddScriptVersion = &gormigrate.Migration{
ID: "20250410-add-script-version",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "ScriptVersion", Value: ""}).Error; err != nil {
return err
}
return nil
},
}

View file

@ -18,6 +18,7 @@ func (s *ScriptRouter) InitRouter(Router *gin.RouterGroup) {
scriptRouter.POST("/search", baseApi.SearchScript)
scriptRouter.POST("/del", baseApi.DeleteScript)
scriptRouter.POST("/update", baseApi.UpdateScript)
scriptRouter.POST("/sync", baseApi.SyncScript)
scriptRouter.GET("/run", baseApi.RunScript)
}
}

View file

@ -63,6 +63,9 @@ export const searchScript = (params: SearchWithPage) => {
export const addScript = (params: Cronjob.ScriptOperate) => {
return http.post(`/core/script`, params);
};
export const syncScript = () => {
return http.post(`/core/script/sync`, {}, TimeoutEnum.T_60S);
};
export const editScript = (params: Cronjob.ScriptOperate) => {
return http.post(`/core/script/update`, params);
};

View file

@ -36,6 +36,7 @@ const message = {
view: 'View',
watch: 'Watch',
handle: 'Handle',
clone: 'Clone',
expand: 'Expand',
collapse: 'Collapse',
log: 'Log',
@ -1047,6 +1048,7 @@ const message = {
'Set different groups based on script characteristics, which allows for faster script filtering operations.',
handleHelper: 'Execute script {1} on {0}, continue?',
noSuchApp: 'The {0} service was not detected. Please install it quickly using the script library first!',
syncHelper: 'About to sync system script library. This operation only affects system scripts. Continue?',
},
},
monitor: {

View file

@ -34,6 +34,7 @@ const message = {
view: 'ビュー',
watch: '時計',
handle: 'トリガー',
clone: 'クローン',
expand: '拡大する',
collapse: '崩壊',
log: 'ログ',
@ -1007,6 +1008,8 @@ const message = {
handleHelper: '{0} {1} スクリプトを実行します続行しますか',
noSuchApp:
'{0} サービスが検出されませんでしたスクリプトライブラリを使って素早くインストールしてください',
syncHelper:
'システムスクリプトライブラリを同期しますこの操作はシステムスクリプトのみ影響します続行しますか',
},
},
monitor: {

View file

@ -34,6 +34,7 @@ const message = {
view: '보기',
watch: '감시',
handle: '트리거',
clone: '복제',
expand: '확장',
collapse: '축소',
log: '로그',
@ -1000,6 +1001,8 @@ const message = {
'스크립트 특성에 따라 다양한 그룹을 설정하여 스크립트 필터링 작업을 빠르게 수행할 있습니다.',
handleHelper: '{0} 에서 {1} 스크립트를 실행합니다. 계속하시겠습니까?',
noSuchApp: '{0} 서비스가 감지되지 않았습니다. 스크립트 라이브러리를 사용하여 먼저 빠르게 설치하세요!',
syncHelper:
'시스템 스크립트 라이브러리를 동기화합니다. 작업은 시스템 스크립트에만 적용됩니다. 계속하시겠습니까?',
},
},
monitor: {

View file

@ -34,6 +34,7 @@ const message = {
view: 'Lihat',
watch: 'Pantau',
handle: 'Picu',
clone: 'Klon',
expand: 'Kembang',
collapse: 'Runtuh',
log: 'Log',
@ -1037,6 +1038,7 @@ const message = {
handleHelper: 'Akan melaksanakan skrip {1} pada {0}, teruskan?',
noSuchApp:
'Perkhidmatan {0} tidak dikesan. Sila pasang dengan cepat menggunakan pustaka skrip terlebih dahulu!',
syncHelper: 'Akan menyelaraskan pustaka skrip sistem. Operasi ini hanya melibatkan skrip sistem. Teruskan?',
},
},
monitor: {

View file

@ -34,6 +34,7 @@ const message = {
view: 'Visualizar',
watch: 'Monitorar',
handle: 'Disparar',
clone: 'Clonar',
expand: 'Expandir',
collapse: 'Recolher',
log: 'Logs',
@ -1026,6 +1027,8 @@ const message = {
handleHelper: 'Executar o script {1} em {0}, continuar?',
noSuchApp:
'O serviço {0} não foi detectado. Por favor, instale-o rapidamente usando a biblioteca de scripts primeiro!',
syncHelper:
'Preparando para sincronizar a biblioteca de scripts do sistema. Esta operação afeta apenas scripts do sistema. Continuar?',
},
},
monitor: {

View file

@ -35,6 +35,7 @@ const message = {
watch: 'Наблюдать',
handle: 'Запустить',
expand: 'Развернуть',
clone: 'Клонировать',
collapse: 'Свернуть',
log: 'Логи',
back: 'Назад',
@ -1032,6 +1033,8 @@ const message = {
handleHelper: 'Выполнить сценарий {1} на {0}, продолжить?',
noSuchApp:
'Служба {0} не обнаружена. Пожалуйста, сначала быстро установите её, используя библиотеку скриптов!',
syncHelper:
'Выполнить синхронизацию библиотеки системных скриптов? Это действие затрагивает только системные скрипты. Продолжить?',
},
},
monitor: {

View file

@ -36,6 +36,7 @@ const message = {
view: '詳情',
watch: '追蹤',
handle: '執行',
clone: '克隆',
expand: '展開',
collapse: '收起',
log: '日誌',
@ -994,6 +995,7 @@ const message = {
groupHelper: '根據腳本特徵設置不同的分組可以更快地對腳本進行篩選操作',
handleHelper: '將在 {0} 上執行 {1} 腳本是否繼續',
noSuchApp: '未檢測到 {0} 服務請先使用腳本庫快速安裝',
syncHelper: '即將同步系統腳本庫該操作僅針對系統腳本是否繼續',
},
},
monitor: {

View file

@ -36,6 +36,7 @@ const message = {
view: '详情',
watch: '追踪',
handle: '执行',
clone: '克隆',
expand: '展开',
collapse: '收起',
log: '日志',
@ -992,6 +993,7 @@ const message = {
groupHelper: '针对脚本特征设置不同的分组可以更加快速的对脚本进行筛选操作',
handleHelper: '将在 {0} 上执行 {1} 脚本是否继续',
noSuchApp: '未检测到 {0} 服务请先使用脚本库安装脚本快速安装',
syncHelper: '即将同步系统脚本库该操作仅针对系统脚本是否继续',
},
},
monitor: {

View file

@ -84,6 +84,19 @@ export function loadUpTime(uptime: number) {
return seconds + i18n.global.t('commons.units.second');
}
// 20250310
export function getCurrentDateFormatted() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}${month}${day}${hours}${minutes}${seconds}`;
}
export function dateFormat(row: any, col: any, dataStr: any) {
const date = new Date(dataStr);
const y = date.getFullYear();

View file

@ -5,6 +5,9 @@
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.add') }}
</el-button>
<el-button type="primary" plain @click="onSync()">
{{ $t('commons.button.sync') }}
</el-button>
<el-button type="primary" plain @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }}
</el-button>
@ -83,11 +86,11 @@
</template>
<script setup lang="ts">
import { dateFormat } from '@/utils/util';
import { dateFormat, deepCopy, getCurrentDateFormatted } from '@/utils/util';
import GroupDialog from '@/components/group/index.vue';
import OperateDialog from '@/views/cronjob/library/operate/index.vue';
import TerminalDialog from '@/views/cronjob/library/run/index.vue';
import { deleteScript, searchScript } from '@/api/modules/cronjob';
import { deleteScript, searchScript, syncScript } from '@/api/modules/cronjob';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import { Cronjob } from '@/api/interface/cronjob';
import i18n from '@/lang';
@ -170,6 +173,24 @@ const onDelete = async (row: Cronjob.ScriptInfo | null) => {
});
};
const onSync = async () => {
ElMessageBox.confirm(i18n.global.t('cronjob.library.syncHelper', i18n.global.t('commons.button.sync')), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
await syncScript()
.then(() => {
loading.value = false;
search();
})
.catch(() => {
loading.value = false;
});
});
};
const search = async () => {
let params = {
info: searchInfo.value,
@ -214,6 +235,18 @@ const buttons = [
});
},
},
{
label: i18n.global.t('commons.button.clone'),
disabled: (row: any) => {
return !row.isSystem;
},
click: (row: Cronjob.ScriptInfo) => {
let item = deepCopy(row) as Cronjob.ScriptInfo;
item.id = 0;
item.name += '-' + getCurrentDateFormatted();
onOpenDialog('create', item);
},
},
{
label: i18n.global.t('commons.button.edit'),
disabled: (row: any) => {

View file

@ -149,7 +149,7 @@
</el-input>
</el-form-item>
<el-form-item :label="$t('setting.apiInterface')" prop="apiInterface">
<el-form-item :label="$t('setting.apiInterface')" prop="apiInterface" v-if="isMaster">
<el-switch
@change="onChangeApiInterfaceStatus"
v-model="form.apiInterfaceStatus"