Initial commit: TaskPool React panel

- React frontend with route-level code splitting
- Backend rebranded from Baihu to TaskPool
- DB brand migration script and local compatibility
This commit is contained in:
2026-07-26 08:43:52 +08:00
commit e6956aa001
397 changed files with 73621 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
package models
import (
"database/sql/driver"
"encoding/json"
"errors"
"time"
"github.com/engigu/taskpool/internal/constant"
)
// AgentSchedulerConfig Agent 调度器配置
type AgentSchedulerConfig struct {
WorkerCount int `json:"worker_count"`
QueueSize int `json:"queue_size"`
RateInterval time.Duration `json:"rate_interval"`
Verbose bool `json:"verbose"`
StrictQueue bool `json:"strict_queue"`
}
// Value 序列化为数据库字符串
func (c AgentSchedulerConfig) Value() (driver.Value, error) {
bytes, err := json.Marshal(c)
if err != nil {
return nil, err
}
return string(bytes), nil
}
// Scan 反序列化数据库字符串为结构体
func (c *AgentSchedulerConfig) Scan(value interface{}) error {
if value == nil {
return nil
}
bytes, ok := value.([]byte)
if !ok {
str, ok := value.(string)
if !ok {
return errors.New("invalid type for AgentSchedulerConfig")
}
bytes = []byte(str)
}
return json.Unmarshal(bytes, c)
}
// Agent 远程执行代理
type Agent struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:100;not null"` // Agent 名称
Token string `json:"token" gorm:"size:64;index"` // 认证 Token(可重复使用)
MachineID string `json:"machine_id" gorm:"size:64;uniqueIndex"` // 机器识别码(唯一)
Description string `json:"description" gorm:"size:255"` // 描述
Status string `json:"status" gorm:"size:20;default:'pending';index"` // 状态: constant.AgentStatusOnline, constant.AgentStatusOffline
LastSeen *LocalTime `json:"last_seen"` // 最后心跳时间
IP string `json:"ip" gorm:"size:45"` // Agent IP 地址
Version string `json:"version" gorm:"size:50"` // Agent 版本
BuildTime string `json:"build_time" gorm:"size:30"` // Agent 构建时间
Hostname string `json:"hostname" gorm:"size:100"` // Agent 主机名
OS string `json:"os" gorm:"size:20"` // 操作系统
Arch string `json:"arch" gorm:"size:20"` // 架构
ForceUpdate bool `json:"force_update" gorm:"default:false"` // 强制更新标志
Enabled *bool `json:"enabled" gorm:"default:true"` // 是否启用
SchedulerConfig AgentSchedulerConfig `json:"scheduler_config" gorm:"type:text"` // 调度配置,以 JSON 字符串形式存储在 Text 类型字段中
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (Agent) TableName() string {
return constant.TablePrefix + "agents"
}
// AgentToken Agent 令牌
type AgentToken struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Token string `json:"token" gorm:"size:64;uniqueIndex;not null"` // 令牌
Remark string `json:"remark" gorm:"size:255"` // 备注
MaxUses int `json:"max_uses" gorm:"default:0"` // 最大使用次数,0 表示无限制
UsedCount int `json:"used_count" gorm:"default:0"` // 已使用次数
ExpiresAt *LocalTime `json:"expires_at"` // 过期时间,null 表示永不过期
Enabled *bool `json:"enabled" gorm:"default:true"` // 是否启用
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (AgentToken) TableName() string {
return constant.TablePrefix + "tokens"
}
// AgentTask Agent 任务配置(用于下发给 Agent)
type AgentTask struct {
ID string `json:"id"`
Name string `json:"name"`
Command string `json:"command"`
PreCommand string `json:"pre_command"`
PostCommand string `json:"post_command"`
Schedule string `json:"schedule"`
Timeout int `json:"timeout"`
WorkDir string `json:"work_dir"`
Envs string `json:"envs"`
Languages []map[string]string `json:"languages"`
RandomRange int `json:"random_range"`
Secrets []string `json:"secrets"`
Enabled bool `json:"enabled"`
}
func (t AgentTask) GetID() string {
return t.ID
}
func (t AgentTask) GetName() string {
return t.Name
}
func (t AgentTask) GetCommand() string {
return t.Command
}
func (t AgentTask) GetPreCommand() string {
return t.PreCommand
}
func (t AgentTask) GetPostCommand() string {
return t.PostCommand
}
func (t AgentTask) GetSchedule() string {
return t.Schedule
}
func (t AgentTask) GetRandomRange() int {
return t.RandomRange
}
func (t AgentTask) GetSecrets() []string {
return t.Secrets
}
// AgentTaskResult Agent 上报的任务执行结果
type AgentTaskResult struct {
TaskID string `json:"task_id"`
LogID string `json:"log_id"`
AgentID string `json:"agent_id"`
Command string `json:"command"`
Output string `json:"output"`
Error string `json:"error"` // 额外的系统错误信息
Status string `json:"status"` // success, failed
Duration int64 `json:"duration"` // 耗时(毫秒)
ExitCode int `json:"exit_code"`
StartTime int64 `json:"start_time"` // Unix 时间戳
EndTime int64 `json:"end_time"` // Unix 时间戳
}
// AgentRegisterRequest Agent 注册请求
type AgentRegisterRequest struct {
Name string `json:"name"`
Hostname string `json:"hostname"`
Version string `json:"version"`
BuildTime string `json:"build_time"`
Token string `json:"token"` // 注册令牌
MachineID string `json:"machine_id"` // 机器识别码
}
+24
View File
@@ -0,0 +1,24 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// AppLog 统一应用日志与通知记录
type AppLog struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Category string `json:"category" gorm:"size:50;index;not null"` // 大类:constant.LogCategorySystemNotice(系统通知), constant.LogCategoryPushLog(推送记录) 等
Title string `json:"title" gorm:"size:255"` // 消息标题
Content BigText `json:"content"` // 详细内容/Payload
Level string `json:"level" gorm:"size:20;index"` // 级别:constant.LogLevelInfo, constant.LogLevelWarning, constant.LogLevelError
Status string `json:"status" gorm:"size:20;index"` // 状态:系统通知为 constant.LogStatusRead/constant.LogStatusUnread,推送为 constant.LogStatusSuccess/constant.LogStatusFailed
RefID string `json:"ref_id" gorm:"size:50;index"` // 关联对象ID(选填,比如绑定的通知渠道ID、任务ID等)
ErrorMsg BigText `json:"error_msg"` // 执行错误信息详情
CreatedAt LocalTime `json:"created_at" gorm:"index"`
ReadAt *LocalTime `json:"read_at"` // 已读时间(仅对通知生效)
ChannelName string `json:"channel_name" gorm:"-"` // 推送记录的关联渠道名称(动态查询)
}
func (AppLog) TableName() string {
return constant.TablePrefix + "app_logs"
}
+24
View File
@@ -0,0 +1,24 @@
package models
import (
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
// BigText 自定义大数据文本类型,自动处理跨数据库类型差异
// MySQL: LONGTEXT (4GB)
// PostgreSQL: TEXT
// SQLite: TEXT
type BigText string
func (BigText) GormDBDataType(db *gorm.DB, field *schema.Field) string {
switch db.Dialector.Name() {
case "mysql":
return "LONGTEXT"
case "postgres":
return "TEXT"
case "sqlite":
return "TEXT"
}
return "TEXT"
}
+33
View File
@@ -0,0 +1,33 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// DataRelation 通用数据关联表
type DataRelation struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
DataID string `json:"data_id" gorm:"size:20;index;not null"`
RelateID string `json:"relate_id" gorm:"size:20;index;not null"`
Type string `json:"type" gorm:"size:50;index;not null"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (DataRelation) TableName() string {
return constant.TablePrefix + "data_relations"
}
// DataStorage 通用数据存储表
type DataStorage struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Type string `json:"type" gorm:"size:50;index;not null"`
Name string `json:"name" gorm:"size:255;index;not null"`
Data BigText `json:"data"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (DataStorage) TableName() string {
return constant.TablePrefix + "data_storages"
}
+24
View File
@@ -0,0 +1,24 @@
package models
import (
"time"
"github.com/engigu/taskpool/internal/constant"
)
// Dependency 依赖包模型
type Dependency struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:100;not null"`
Version string `json:"version" gorm:"size:50"`
Language string `json:"language" gorm:"size:100;index"` // 关联语言 (node, python...)
LangVersion string `json:"lang_version" gorm:"size:100;index"` // 关联语言版本
Remark string `json:"remark" gorm:"size:255"`
Log BigText `json:"log"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (Dependency) TableName() string {
return constant.TablePrefix + "deps"
}
+38
View File
@@ -0,0 +1,38 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// EnvironmentVariable represents an environment variable
type EnvironmentVariable struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:255;not null"`
Value BigText `json:"value"`
Remark string `json:"remark" gorm:"size:500"`
Type string `json:"type" gorm:"size:20;default:'normal'"`
Hidden *bool `json:"hidden" gorm:"default:true"`
Enabled *bool `json:"enabled" gorm:"default:true"`
UserID string `json:"user_id" gorm:"size:20;index"`
Tags string `json:"-" gorm:"-"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (EnvironmentVariable) TableName() string {
return constant.TablePrefix + "envs"
}
// Script represents a script file
type Script struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:255;not null"`
Content BigText `json:"content"`
UserID string `json:"user_id" gorm:"size:20;index"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (Script) TableName() string {
return constant.TablePrefix + "scripts"
}
+25
View File
@@ -0,0 +1,25 @@
package models
import "time"
// ExportData 全量或部分业务数据的导出/导入结构
type ExportData struct {
Version string `json:"version"`
ExportAt LocalTime `json:"export_at"`
Tasks []Task `json:"tasks"`
Envs []EnvironmentVariable `json:"envs"`
Tags []DataStorage `json:"tags"`
Bindings []NotifyBinding `json:"bindings"`
}
// NewExportData 创建一个导出数据对象
func NewExportData() *ExportData {
return &ExportData{
Version: "1.0",
ExportAt: LocalTime(time.Now()),
Tasks: make([]Task, 0),
Envs: make([]EnvironmentVariable, 0),
Tags: make([]DataStorage, 0),
Bindings: make([]NotifyBinding, 0),
}
}
+32
View File
@@ -0,0 +1,32 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// NodeMetrics 表示互联节点的性能指标,使用 JSON 存储
type NodeMetrics struct {
CPUPercent float64 `json:"cpu_percent"`
MemPercent float64 `json:"mem_percent"`
DiskPercent float64 `json:"disk_percent"`
TxBytes uint64 `json:"tx_bytes,omitempty"`
RxBytes uint64 `json:"rx_bytes,omitempty"`
}
// InterconnectNode represents a connected remote panel
type InterconnectNode struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:255;not null"`
URL string `json:"url" gorm:"size:255;not null"`
Token string `json:"token" gorm:"size:255"`
Remark string `json:"remark" gorm:"size:500"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
Status string `json:"status" gorm:"size:50"` // online / offline
Metrics NodeMetrics `json:"metrics" gorm:"serializer:json"`
LastHeartbeatAt *LocalTime `json:"last_heartbeat_at"`
}
func (InterconnectNode) TableName() string {
return constant.TablePrefix + "interconnect_nodes"
}
+20
View File
@@ -0,0 +1,20 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
type Language struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Plugin string `json:"plugin" gorm:"size:100;not null;index"`
Version string `json:"version" gorm:"size:100;not null;index"`
InstallPath string `json:"install_path" gorm:"size:255"`
Source string `json:"source" gorm:"size:255"`
InstalledAt *LocalTime `json:"installed_at"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (Language) TableName() string {
return constant.TablePrefix + "languages"
}
+27
View File
@@ -0,0 +1,27 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// NotifyBinding 事件绑定表
type NotifyBinding struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Type string `json:"type" gorm:"size:20;not null;index"` // system 或 task
Event string `json:"event" gorm:"size:50;not null;index"` // 事件类型
WayID string `json:"way_id" gorm:"size:20;not null;index"` // 通知渠道ID
DataID string `json:"data_id" gorm:"size:20;index"` // 关联ID,系统事件为空,任务事件为任务ID
Extra BigText `json:"extra"` // 额外配置(如是否开启日志推送等,对应 BindingExtra 结构)
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
// BindingExtra 存储在 Extra 字段中的 JSON 配置
type BindingExtra struct {
EnableLog bool `json:"enable_log"`
LogLimit int `json:"log_limit"` // 日志字数限制,默认 1000
}
func (NotifyBinding) TableName() string {
return constant.TablePrefix + "notify_bindings"
}
+20
View File
@@ -0,0 +1,20 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// NotifyWay 消息推送渠道
type NotifyWay struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:100;not null"`
Type string `json:"type" gorm:"size:50;not null;index"`
Config BigText `json:"config"`
Enabled *bool `json:"enabled" gorm:"default:true;index"`
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (NotifyWay) TableName() string {
return constant.TablePrefix + "notify_ways"
}
+18
View File
@@ -0,0 +1,18 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// SendStats 任务执行统计
type SendStats struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
TaskID string `json:"task_id" gorm:"size:20;uniqueIndex:idx_task_day_status"`
Day string `json:"day" gorm:"size:10;uniqueIndex:idx_task_day_status"` // 格式: 2006-01-02
Status string `json:"status" gorm:"size:20;uniqueIndex:idx_task_day_status"`
Num int `json:"num" gorm:"default:0"`
}
func (SendStats) TableName() string {
return constant.TablePrefix + "send_stats"
}
+17
View File
@@ -0,0 +1,17 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// Setting 系统设置
type Setting struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Section string `json:"section" gorm:"size:50;not null;index:idx_section_key"`
Key string `json:"key" gorm:"size:100;not null;index:idx_section_key"`
Value BigText `json:"value"`
}
func (Setting) TableName() string {
return constant.TablePrefix + "settings"
}
+196
View File
@@ -0,0 +1,196 @@
package models
import (
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/engigu/taskpool/internal/constant"
)
// TaskLanguages 自定义语言配置列表类型,处理 JSON 序列化
type TaskLanguages []map[string]string
func (t TaskLanguages) Value() (driver.Value, error) {
if t == nil {
return "[]", nil
}
b, err := json.Marshal(t)
return string(b), err
}
func (t *TaskLanguages) Scan(v interface{}) error {
if v == nil {
*t = nil
return nil
}
var data []byte
switch s := v.(type) {
case string:
data = []byte(s)
case []byte:
data = s
default:
return fmt.Errorf("invalid type for TaskLanguages: %T", v)
}
return json.Unmarshal(data, t)
}
// CleanConfig 清理配置结构
type CleanConfig struct {
Type string `json:"type"` // "day" 或 "count"
Keep int `json:"keep"` // 保留天数或条数
}
// RepoConfig 仓库同步配置
type RepoConfig struct {
SourceType string `json:"source_type"` // url 或 git
SourceURL string `json:"source_url"` // 源地址
TargetPath string `json:"target_path"` // 目标路径
Branch string `json:"branch"` // Git 分支
SparsePath string `json:"sparse_path"` // 稀疏检出路径(仅拉取指定目录或文件)
SingleFile bool `json:"single_file"` // 单文件模式(直接下载文件而非 sparse-checkout
Proxy string `json:"proxy"` // 代理类型: none, ghproxy, mirror, custom
ProxyURL string `json:"proxy_url"` // 自定义代理地址
AuthToken string `json:"auth_token"` // 认证 Token
WhitelistPaths string `json:"whitelist_paths"` // 同步时保留的路径及脚本筛选白名单关键词,逗号或竖线分割
Blacklist string `json:"blacklist"` // 脚本筛选黑名单关键词,竖线分割
Dependence string `json:"dependence"` // 脚本依赖文件关键词,竖线分割
Extensions string `json:"extensions"` // 脚本文件后缀关键词,竖线分割
AutoAddCron bool `json:"auto_add_cron"` // 自动解析脚本注释添加定时任务
CommentToTask string `json:"commenttotask"` // 兼容 QL 格式任务脚本注释解析
RepoSource string `json:"repo_source"` // 仓库来源,如果是选择了这个 ql 导入的仓库,= ql
RepoDirName string `json:"repo_dir_name"` // 自定义仓库目录名
}
// TaskConfig 任务配置 RepoConfig+TaskConfig=task.config
type TaskConfig struct {
Concurrency int `json:"$task_concurrency"` // 0: disable concurrency, 1: enable concurrency
AllEnvs bool `json:"$task_all_envs"` // 开启则注入全部环境变量
}
// Task 代表一个计划任务
type Task struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Name string `json:"name" gorm:"size:255;not null"`
Remark string `json:"remark" gorm:"size:255;default:''"`
PinType string `json:"pin_type" gorm:"size:20;default:none;index"` // 置顶类型: constant.PinTypeNone, constant.PinTypeTop
Command BigText `json:"command"` // 普通任务的命令
PreCommand BigText `json:"pre_command"` // 执行前的命令
PostCommand BigText `json:"post_command"` // 执行后的命令
Tags string `json:"tags" gorm:"-"` // 标签,逗号分隔
Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo
TriggerType string `json:"trigger_type" gorm:"size:25;default:'cron'"` // 触发类型: constant.TriggerTypeCron, constant.TriggerTypeTaskPoolStartup
Config BigText `json:"config"` // 配置 JSON(仓库同步配置等)
Schedule string `json:"schedule" gorm:"size:100"` // cron 表达式
Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟
WorkDir string `json:"work_dir" gorm:"size:255;default:''"` // 工作目录,为空则使用 scripts 目录
CleanConfig string `json:"clean_config" gorm:"size:255;default:''"` // 清理配置 JSON
Envs BigText `json:"envs" gorm:"-"` // 环境变量ID列表,逗号分隔
Languages TaskLanguages `json:"languages" gorm:"type:text"` // 针对本地任务的语言配置列表
AgentID *string `json:"agent_id" gorm:"size:20;index"` // Agent ID,为空表示本地执行
RetryCount int `json:"retry_count" gorm:"default:0"` // 失败重试次数
RetryInterval int `json:"retry_interval" gorm:"default:0"` // 失败重试间隔(秒)
RandomRange int `json:"random_range" gorm:"default:0"` // 随机延迟范围(秒)
Enabled *bool `json:"enabled" gorm:"default:true"`
RunningGo BigText `json:"running_go"` // 正在运行的 go routine id 数组 (JSON)
RuntimeEnvs []string `json:"-" gorm:"-"` // 运行时环境变量(非持久化)
RuntimeSecrets []string `json:"-" gorm:"-"` // 运行时安全机密(非持久化)
LastRun *LocalTime `json:"last_run"`
NextRun *LocalTime `json:"next_run"`
SourceID string `json:"source_id" gorm:"size:255;index"` // 脚本资源唯一标识(路径 sanitized)
RepoTaskID string `json:"repo_task_id" gorm:"size:20;index"` // 所属的仓库任务 ID
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (t *Task) IsRunning() bool {
if string(t.RunningGo) == "" || string(t.RunningGo) == "[]" {
return false
}
return true
}
func (Task) TableName() string {
return constant.TablePrefix + "tasks"
}
func (t *Task) GetID() string {
return t.ID
}
func (t *Task) GetName() string {
return t.Name
}
func (t *Task) GetCommand() string {
return string(t.Command)
}
func (t *Task) GetPreCommand() string {
return string(t.PreCommand)
}
func (t *Task) GetPostCommand() string {
return string(t.PostCommand)
}
func (t *Task) GetTimeout() int {
return t.Timeout
}
func (t *Task) GetWorkDir() string {
return t.WorkDir
}
func (t *Task) GetEnvs() string {
return string(t.Envs)
}
func (t *Task) GetLanguages() []map[string]string {
return []map[string]string(t.Languages)
}
func (t *Task) GetEnvVars() []string {
return t.RuntimeEnvs
}
func (t *Task) GetSecrets() []string {
return t.RuntimeSecrets
}
func (t *Task) GetUseMise() bool {
return t.AgentID == nil || *t.AgentID == ""
}
func (t *Task) UseMise() bool {
return t.GetUseMise()
}
// CronTask 计划任务接口
func (t *Task) GetSchedule() string {
return t.Schedule
}
func (t *Task) GetRandomRange() int {
return t.RandomRange
}
// TaskLog 代表任务执行的日志记录
type TaskLog struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
TaskID string `json:"task_id" gorm:"size:20;index"`
AgentID *string `json:"agent_id" gorm:"size:20;index"` // Agent ID,为空表示本地执行
Command BigText `json:"command"`
Output BigText `json:"-"` // gzip+base64 压缩后的日志
Error BigText `json:"error"` // 额外的系统错误信息
Status string `json:"status" gorm:"size:20;index"` // success, failed
Duration int64 `json:"duration"` // 执行耗时(毫秒)
ExitCode int `json:"exit_code"`
StartTime *LocalTime `json:"start_time"`
EndTime *LocalTime `json:"end_time"`
CreatedAt LocalTime `json:"created_at"`
}
func (TaskLog) TableName() string {
return constant.TablePrefix + "task_logs"
}
+74
View File
@@ -0,0 +1,74 @@
package models
import (
"database/sql/driver"
"fmt"
"time"
"github.com/engigu/taskpool/internal/systime"
)
const TimeFormat = "2006-01-02 15:04:05"
// LocalTime 自定义时间类型,JSON 序列化为 "年-月-日 时:分:秒" 格式
type LocalTime time.Time
func (t LocalTime) MarshalJSON() ([]byte, error) {
tt := time.Time(t)
if tt.IsZero() {
return []byte("null"), nil
}
// 统一输出为东八区时间
tt = systime.InCST(tt)
return []byte(fmt.Sprintf(`"%s"`, tt.Format(TimeFormat))), nil
}
func (t *LocalTime) UnmarshalJSON(data []byte) error {
if string(data) == "null" || string(data) == `""` {
return nil
}
// 去掉引号
s := string(data)
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
s = s[1 : len(s)-1]
}
tt, err := time.ParseInLocation(TimeFormat, s, time.Local)
if err != nil {
// 尝试解析 ISO 格式
tt, err = time.Parse(time.RFC3339, s)
if err != nil {
return err
}
}
*t = LocalTime(tt)
return nil
}
func (t LocalTime) Value() (driver.Value, error) {
return time.Time(t), nil
}
func (t *LocalTime) Scan(v interface{}) error {
if v == nil {
return nil
}
switch val := v.(type) {
case time.Time:
*t = LocalTime(val)
case string:
tt, err := time.ParseInLocation(TimeFormat, val, time.Local)
if err != nil {
return err
}
*t = LocalTime(tt)
}
return nil
}
func (t LocalTime) Time() time.Time {
return time.Time(t)
}
func Now() LocalTime {
return LocalTime(systime.Now())
}
+21
View File
@@ -0,0 +1,21 @@
package models
import (
"github.com/engigu/taskpool/internal/constant"
)
// User represents a system user
type User struct {
ID string `json:"id" gorm:"primaryKey;size:20"`
Username string `json:"username" gorm:"size:100;uniqueIndex;not null"`
Password string `json:"password" gorm:"size:255;not null"`
Email string `json:"email" gorm:"size:255"`
Role string `json:"role" gorm:"size:20;default:user"` // admin, user
TokenVersion int `json:"-" gorm:"default:1"` // 用于 JWT 失效校验
CreatedAt LocalTime `json:"created_at"`
UpdatedAt LocalTime `json:"updated_at"`
}
func (User) TableName() string {
return constant.TablePrefix + "users"
}
+144
View File
@@ -0,0 +1,144 @@
package vo
import (
"time"
"github.com/engigu/taskpool/internal/models"
"github.com/engigu/taskpool/internal/utils"
)
// AgentVO 代理视图对象
type AgentVO struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status string `json:"status"`
LastSeen *models.LocalTime `json:"last_seen"`
IP string `json:"ip"`
Version string `json:"version"`
BuildTime string `json:"build_time"`
Hostname string `json:"hostname"`
OS string `json:"os"`
Arch string `json:"arch"`
ForceUpdate bool `json:"force_update"`
Enabled bool `json:"enabled"`
SchedulerConfig *AgentSchedulerConfigVO `json:"scheduler_config"`
CreatedAt models.LocalTime `json:"created_at"`
UpdatedAt models.LocalTime `json:"updated_at"`
// 隐藏 Token 和 MachineID
}
// ToAgentVO 将 Agent 模型转换为 AgentVO
func ToAgentVO(agent *models.Agent) *AgentVO {
if agent == nil {
return nil
}
var schedulerConfigVO *AgentSchedulerConfigVO
if agent.SchedulerConfig.WorkerCount > 0 {
schedulerConfigVO = &AgentSchedulerConfigVO{
WorkerCount: agent.SchedulerConfig.WorkerCount,
QueueSize: agent.SchedulerConfig.QueueSize,
RateInterval: int(agent.SchedulerConfig.RateInterval / time.Millisecond),
Verbose: agent.SchedulerConfig.Verbose,
StrictQueue: agent.SchedulerConfig.StrictQueue,
}
}
return &AgentVO{
ID: agent.ID,
Name: agent.Name,
Description: agent.Description,
Status: agent.Status,
LastSeen: agent.LastSeen,
IP: agent.IP,
Version: agent.Version,
BuildTime: agent.BuildTime,
Hostname: agent.Hostname,
OS: agent.OS,
Arch: agent.Arch,
ForceUpdate: agent.ForceUpdate,
Enabled: utils.DerefBool(agent.Enabled, true),
SchedulerConfig: schedulerConfigVO,
CreatedAt: agent.CreatedAt,
UpdatedAt: agent.UpdatedAt,
}
}
// ToAgentVOList 将 Agent 模型列表转换为 AgentVO 列表
func ToAgentVOList(agents []*models.Agent) []*AgentVO {
if agents == nil {
return nil
}
vos := make([]*AgentVO, len(agents))
for i, a := range agents {
vos[i] = ToAgentVO(a)
}
return vos
}
// ToAgentVOListFromModels 将 Agent 模型列表转换为 AgentVO 列表
func ToAgentVOListFromModels(agents []models.Agent) []*AgentVO {
vos := make([]*AgentVO, len(agents))
for i := range agents {
vos[i] = ToAgentVO(&agents[i])
}
return vos
}
// AgentTokenVO 代理令牌视图对象
type AgentTokenVO struct {
ID string `json:"id"`
Token string `json:"token"`
Remark string `json:"remark"`
MaxUses int `json:"max_uses"`
UsedCount int `json:"used_count"`
ExpiresAt *models.LocalTime `json:"expires_at"`
Enabled bool `json:"enabled"`
CreatedAt models.LocalTime `json:"created_at"`
}
// ToAgentTokenVO 将 AgentToken 模型转换为 AgentTokenVO
func ToAgentTokenVO(token *models.AgentToken) *AgentTokenVO {
if token == nil {
return nil
}
return &AgentTokenVO{
ID: token.ID,
Token: token.Token,
Remark: token.Remark,
MaxUses: token.MaxUses,
UsedCount: token.UsedCount,
ExpiresAt: token.ExpiresAt,
Enabled: utils.DerefBool(token.Enabled, true),
CreatedAt: token.CreatedAt,
}
}
// ToAgentTokenVOList 将 AgentToken 模型列表转换为 AgentTokenVO 列表
func ToAgentTokenVOList(tokens []*models.AgentToken) []*AgentTokenVO {
if tokens == nil {
return nil
}
vos := make([]*AgentTokenVO, len(tokens))
for i, t := range tokens {
vos[i] = ToAgentTokenVO(t)
}
return vos
}
// ToAgentTokenVOListFromModels 将 AgentToken 模型列表转换为 AgentTokenVO 列表
func ToAgentTokenVOListFromModels(tokens []models.AgentToken) []*AgentTokenVO {
vos := make([]*AgentTokenVO, len(tokens))
for i := range tokens {
vos[i] = ToAgentTokenVO(&tokens[i])
}
return vos
}
// AgentSchedulerConfigVO 调度配置视图对象
type AgentSchedulerConfigVO struct {
WorkerCount int `json:"worker_count"`
QueueSize int `json:"queue_size"`
RateInterval int `json:"rate_interval"` // 毫秒
Verbose bool `json:"verbose"`
StrictQueue bool `json:"strict_queue"`
}
+47
View File
@@ -0,0 +1,47 @@
package vo
import (
"time"
"github.com/engigu/taskpool/internal/models"
)
// DependencyVO 依赖包视图对象
type DependencyVO struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Language string `json:"language"`
LangVersion string `json:"lang_version"`
Remark string `json:"remark"`
Log string `json:"log,omitempty"` // 仅在需要时返回
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ToDependencyVO 将 Dependency 模型转换为 DependencyVO
func ToDependencyVO(dep *models.Dependency) *DependencyVO {
if dep == nil {
return nil
}
return &DependencyVO{
ID: dep.ID,
Name: dep.Name,
Version: dep.Version,
Language: dep.Language,
LangVersion: dep.LangVersion,
Remark: dep.Remark,
Log: string(dep.Log),
CreatedAt: dep.CreatedAt,
UpdatedAt: dep.UpdatedAt,
}
}
// ToDependencyVOListFromModels 将 Dependency 模型列表转换为 DependencyVO 列表
func ToDependencyVOListFromModels(deps []models.Dependency) []*DependencyVO {
vos := make([]*DependencyVO, len(deps))
for i := range deps {
vos[i] = ToDependencyVO(&deps[i])
}
return vos
}
+37
View File
@@ -0,0 +1,37 @@
package vo
import (
"github.com/engigu/taskpool/internal/models"
)
// ScriptVO 脚本视图对象
type ScriptVO struct {
ID string `json:"id"`
Name string `json:"name"`
Content string `json:"content,omitempty"` // 仅在拉取详情时返回
CreatedAt models.LocalTime `json:"created_at"`
UpdatedAt models.LocalTime `json:"updated_at"`
}
// ToScriptVO 将 Script 模型转换为 ScriptVO
func ToScriptVO(script *models.Script) *ScriptVO {
if script == nil {
return nil
}
return &ScriptVO{
ID: script.ID,
Name: script.Name,
Content: string(script.Content),
CreatedAt: script.CreatedAt,
UpdatedAt: script.UpdatedAt,
}
}
// ToScriptVOListFromModels 将 Script 模型列表转换为 ScriptVO 列表
func ToScriptVOListFromModels(scripts []models.Script) []*ScriptVO {
vos := make([]*ScriptVO, len(scripts))
for i := range scripts {
vos[i] = ToScriptVO(&scripts[i])
}
return vos
}
+108
View File
@@ -0,0 +1,108 @@
package vo
import (
"github.com/engigu/taskpool/internal/constant"
"github.com/engigu/taskpool/internal/models"
"github.com/engigu/taskpool/internal/utils"
)
// UserVO 用户视图对象
type UserVO struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Role string `json:"role"`
CreatedAt models.LocalTime `json:"created_at"`
UpdatedAt models.LocalTime `json:"updated_at"`
}
// ToUserVO 将 User 模型转换为 UserVO
func ToUserVO(user *models.User) *UserVO {
if user == nil {
return nil
}
return &UserVO{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Role: user.Role,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
}
// EnvVO 环境变量视图对象
type EnvVO struct {
ID string `json:"id"`
Name string `json:"name"`
Value string `json:"value"`
Remark string `json:"remark"`
Type string `json:"type"`
Tags string `json:"tags"`
Hidden bool `json:"hidden"`
Enabled bool `json:"enabled"`
CreatedAt models.LocalTime `json:"created_at"`
UpdatedAt models.LocalTime `json:"updated_at"`
}
// ToEnvVO 将 Env 模型转换为 EnvVO
func ToEnvVO(env *models.EnvironmentVariable) *EnvVO {
if env == nil {
return nil
}
val := string(env.Value)
if env.Type == constant.EnvTypeSecret {
val = "********"
}
return &EnvVO{
ID: env.ID,
Name: env.Name,
Value: val,
Remark: env.Remark,
Type: env.Type,
Tags: env.Tags,
Hidden: utils.DerefBool(env.Hidden, true),
Enabled: utils.DerefBool(env.Enabled, true),
CreatedAt: env.CreatedAt,
UpdatedAt: env.UpdatedAt,
}
}
// ToEnvVOList 将 Env 模型列表转换为 EnvVO 列表
func ToEnvVOList(envs []*models.EnvironmentVariable) []*EnvVO {
if envs == nil {
return nil
}
vos := make([]*EnvVO, len(envs))
for i, e := range envs {
vos[i] = ToEnvVO(e)
}
return vos
}
// ToEnvVOListFromModels 将 Env 模型列表转换为 EnvVO 列表
func ToEnvVOListFromModels(envs []models.EnvironmentVariable) []*EnvVO {
vos := make([]*EnvVO, len(envs))
for i := range envs {
vos[i] = ToEnvVO(&envs[i])
}
return vos
}
// LoginLogVO 登录日志视图对象
type LoginLogVO struct {
ID string `json:"id"`
Username string `json:"username"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt models.LocalTime `json:"created_at"`
}
// TokenConfig Token 配置结构体
type TokenConfig struct {
Enabled bool `json:"enabled"`
Token string `json:"token"`
ExpireAt string `json:"expire_at"`
}
+263
View File
@@ -0,0 +1,263 @@
package vo
import (
"github.com/engigu/taskpool/internal/executor"
"github.com/engigu/taskpool/internal/models"
"github.com/engigu/taskpool/internal/utils"
)
// TaskCreateReq 任务创建请求
type TaskCreateReq struct {
Name string `json:"name" binding:"required" example:"测试任务"`
Remark string `json:"remark" example:"备注信息"`
Command string `json:"command" example:"echo 'Hello World'"`
PreCommand string `json:"pre_command" example:"echo 'pre'"`
PostCommand string `json:"post_command" example:"echo 'post'"`
Tags string `json:"tags" example:"test,dev"`
Type string `json:"type" example:"repo"` // 可以是 common, repo 等
Config string `json:"config" swaggertype:"string" example:"{\"source_url\":\"https://github.com/abc/repo\",\"branch\":\"main\"}"`
Schedule string `json:"schedule" example:"0 0 * * *"`
Timeout int `json:"timeout" example:"3600"`
WorkDir string `json:"work_dir" example:"/tmp"`
CleanConfig string `json:"clean_config" example:"true"`
Envs string `json:"envs" example:"{\"ENV_VAR\":\"value\"}"`
Languages models.TaskLanguages `json:"languages"`
AgentID *string `json:"agent_id" example:"agent-1"`
TriggerType string `json:"trigger_type" example:"cron"`
RetryCount int `json:"retry_count" example:"3"`
RetryInterval int `json:"retry_interval" example:"60"`
RandomRange int `json:"random_range" example:"10"`
PinType string `json:"pin_type" example:"time"`
}
// TaskUpdateReq 任务更新请求
type TaskUpdateReq struct {
Name string `json:"name" example:"测试任务"`
Remark string `json:"remark" example:"备注信息"`
Command string `json:"command" example:"echo 'Hello World'"`
PreCommand string `json:"pre_command" example:"echo 'pre'"`
PostCommand string `json:"post_command" example:"echo 'post'"`
Tags string `json:"tags" example:"test,dev"`
Type string `json:"type" example:"repo"`
Config string `json:"config" swaggertype:"string" example:"{\"source_url\":\"https://github.com/abc/repo\",\"branch\":\"main\"}"`
Schedule string `json:"schedule" example:"0 0 * * *"`
Timeout int `json:"timeout" example:"3600"`
WorkDir string `json:"work_dir" example:"/tmp"`
CleanConfig string `json:"clean_config" example:"true"`
Envs string `json:"envs" example:"{\"ENV_VAR\":\"value\"}"`
Enabled bool `json:"enabled" example:"true"`
Languages models.TaskLanguages `json:"languages"`
AgentID *string `json:"agent_id" example:"agent-1"`
TriggerType string `json:"trigger_type" example:"cron"`
RetryCount int `json:"retry_count" example:"3"`
RetryInterval int `json:"retry_interval" example:"60"`
RandomRange int `json:"random_range" example:"10"`
PinType string `json:"pin_type" example:"time"`
}
// TaskVO 任务视图对象
type TaskVO struct {
ID string `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
Command string `json:"command"`
PreCommand string `json:"pre_command"`
PostCommand string `json:"post_command"`
Tags string `json:"tags"`
Type string `json:"type"`
TriggerType string `json:"trigger_type"`
Config string `json:"config"`
Schedule string `json:"schedule"`
Timeout int `json:"timeout"`
WorkDir string `json:"work_dir"`
CleanConfig string `json:"clean_config"`
Envs string `json:"envs"`
Languages models.TaskLanguages `json:"languages"`
AgentID *string `json:"agent_id"`
RepoTaskID string `json:"repo_task_id"`
Enabled bool `json:"enabled"`
RetryCount int `json:"retry_count"`
RetryInterval int `json:"retry_interval"`
RandomRange int `json:"random_range"`
PinType string `json:"pin_type"`
LastRun *models.LocalTime `json:"last_run"`
NextRun *models.LocalTime `json:"next_run"`
CreatedAt models.LocalTime `json:"created_at"`
UpdatedAt models.LocalTime `json:"updated_at"`
RunningStatus string `json:"running_status"`
}
// ToTaskVO 将 Task 模型转换为 TaskVO
func ToTaskVO(task *models.Task) *TaskVO {
if task == nil {
return nil
}
return &TaskVO{
ID: task.ID,
Name: task.Name,
Remark: task.Remark,
Command: string(task.Command),
PreCommand: string(task.PreCommand),
PostCommand: string(task.PostCommand),
Tags: task.Tags,
Type: task.Type,
TriggerType: task.TriggerType,
Config: string(task.Config),
Schedule: task.Schedule,
Timeout: task.Timeout,
WorkDir: task.WorkDir,
CleanConfig: task.CleanConfig,
Envs: string(task.Envs),
Languages: task.Languages,
AgentID: task.AgentID,
RepoTaskID: task.RepoTaskID,
Enabled: utils.DerefBool(task.Enabled, true),
RetryCount: task.RetryCount,
RetryInterval: task.RetryInterval,
RandomRange: task.RandomRange,
PinType: task.PinType,
LastRun: task.LastRun,
NextRun: task.NextRun,
CreatedAt: task.CreatedAt,
UpdatedAt: task.UpdatedAt,
RunningStatus: func() string {
if task.IsRunning() {
return "running"
}
return "idle"
}(),
}
}
// ToTaskVOList 将 Task 模型列表转换为 TaskVO 列表
func ToTaskVOList(tasks []*models.Task) []*TaskVO {
if tasks == nil {
return nil
}
vos := make([]*TaskVO, len(tasks))
for i, t := range tasks {
vos[i] = ToTaskVO(t)
}
return vos
}
// ToTaskVOListFromModels 将 Task 模型列表转换为 TaskVO 列表
func ToTaskVOListFromModels(tasks []models.Task) []*TaskVO {
vos := make([]*TaskVO, len(tasks))
for i := range tasks {
vos[i] = ToTaskVO(&tasks[i])
}
return vos
}
// TaskLogVO 任务历史视图对象
type TaskLogVO struct {
ID string `json:"id"`
TaskID string `json:"task_id"`
TaskName string `json:"task_name"`
TaskType string `json:"task_type"`
AgentID *string `json:"agent_id"`
Command string `json:"command"`
Error string `json:"error"`
Status string `json:"status"`
Duration int64 `json:"duration"`
ExitCode int `json:"exit_code"`
StartTime *models.LocalTime `json:"start_time"`
EndTime *models.LocalTime `json:"end_time"`
CreatedAt models.LocalTime `json:"created_at"`
Output string `json:"output,omitempty"`
}
// ToTaskLogVO 将 TaskLog 模型转换为 TaskLogVO
// Note: This function assumes the Task field within models.TaskLog is preloaded
// or that taskName and taskType are provided from an external source.
func ToTaskLogVO(log *models.TaskLog) *TaskLogVO {
if log == nil {
return nil
}
return &TaskLogVO{
ID: log.ID,
TaskID: log.TaskID,
AgentID: log.AgentID,
Command: string(log.Command),
Error: string(log.Error),
Status: log.Status,
Duration: log.Duration,
ExitCode: log.ExitCode,
StartTime: log.StartTime,
EndTime: log.EndTime,
CreatedAt: log.CreatedAt,
Output: string(log.Output),
}
}
// ToTaskLogVOList 将 TaskLog 模型列表转换为 TaskLogVO 列表
func ToTaskLogVOList(logs []*models.TaskLog) []*TaskLogVO {
if logs == nil {
return nil
}
vos := make([]*TaskLogVO, len(logs))
for i, l := range logs {
vos[i] = ToTaskLogVO(l)
}
return vos
}
// ToTaskLogVOListFromModels 将 TaskLog 模型列表转换为 TaskLogVO 列表
func ToTaskLogVOListFromModels(logs []models.TaskLog) []*TaskLogVO {
vos := make([]*TaskLogVO, len(logs))
for i := range logs {
vos[i] = ToTaskLogVO(&logs[i])
}
return vos
}
// ExecutionResultVO 任务执行结果视图对象
type ExecutionResultVO struct {
TaskID string `json:"task_id"`
LogID string `json:"log_id,omitempty"`
Success bool `json:"success"`
Status string `json:"status"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Duration int64 `json:"duration,omitempty"`
ExitCode int `json:"exit_code,omitempty"`
StartTime string `json:"start_time,omitempty"`
EndTime string `json:"end_time,omitempty"`
}
// ToExecutionResultVO 将 ExecutionResult 转换为 ExecutionResultVO
func ToExecutionResultVO(res *executor.ExecutionResult) *ExecutionResultVO {
if res == nil {
return nil
}
vo := &ExecutionResultVO{
TaskID: res.TaskID,
LogID: res.LogID,
Success: res.Success,
Status: res.Status,
Output: res.Output,
Error: res.Error,
Duration: res.Duration,
ExitCode: res.ExitCode,
}
if !res.StartTime.IsZero() {
vo.StartTime = res.StartTime.Format("2006-01-02 15:04:05")
}
if !res.EndTime.IsZero() {
vo.EndTime = res.EndTime.Format("2006-01-02 15:04:05")
}
return vo
}
// ToExecutionResultVOList 将 ExecutionResult 列表转换为 ExecutionResultVO 列表
func ToExecutionResultVOList(results []executor.ExecutionResult) []*ExecutionResultVO {
if results == nil {
return nil
}
vos := make([]*ExecutionResultVO, len(results))
for i := range results {
vos[i] = ToExecutionResultVO(&results[i])
}
return vos
}
+8
View File
@@ -0,0 +1,8 @@
package vo
// WSMessage 通用 WebSocket 消息结构
type WSMessage struct {
Type string `json:"type"` // 事件类型: task_status, notice, system_stats
Timestamp int64 `json:"timestamp"` // 毫秒时间戳
Payload interface{} `json:"payload"` // 负载数据
}