172 lines
5.0 KiB
TypeScript
172 lines
5.0 KiB
TypeScript
import path from 'node:path'
|
|
|
|
import { PROJECT_ROOT } from '../../config/runtime.js'
|
|
import { readJsonFile, writeJsonFile } from '../../utils/json-file-store.js'
|
|
|
|
const SCHEDULED_JOBS_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'scheduled-jobs.json')
|
|
const CLOUDTENTACLES_HEALTH_JOB_ID = 'cloudtentacles-health'
|
|
|
|
type JsonObject = Record<string, any>
|
|
|
|
export function getScheduledJobsFilePath() {
|
|
return SCHEDULED_JOBS_FILE_PATH
|
|
}
|
|
|
|
export function getScheduledJobsConfig() {
|
|
return loadScheduledJobsConfigFromFile()
|
|
}
|
|
|
|
export function saveScheduledJobsConfig(rawValue: unknown) {
|
|
return writeJsonFile(SCHEDULED_JOBS_FILE_PATH, rawValue, normalizeScheduledJobsConfig)
|
|
}
|
|
|
|
export function getCloudtentaclesHealthJob(config: JsonObject = getScheduledJobsConfig()) {
|
|
return (Array.isArray(config.jobs) ? config.jobs : [])
|
|
.find((item) => String(item.id || '').trim() === CLOUDTENTACLES_HEALTH_JOB_ID)
|
|
|| createDefaultCloudtentaclesHealthJob()
|
|
}
|
|
|
|
function loadScheduledJobsConfigFromFile() {
|
|
return readJsonFile(
|
|
SCHEDULED_JOBS_FILE_PATH,
|
|
createDefaultScheduledJobsConfig,
|
|
normalizeScheduledJobsConfig,
|
|
)
|
|
}
|
|
|
|
export function normalizeScheduledJobsConfig(rawValue: unknown) {
|
|
const source = isPlainObject(rawValue) ? rawValue : {}
|
|
const rawJobs = Array.isArray(source.jobs) ? source.jobs : []
|
|
const jobs = rawJobs
|
|
.map((item) => normalizeScheduledJob(item))
|
|
.filter((item): item is ReturnType<typeof normalizeCloudtentaclesHealthJob> => Boolean(item))
|
|
const hasCloudtentaclesHealth = jobs.some((item) => item.id === CLOUDTENTACLES_HEALTH_JOB_ID)
|
|
|
|
if (!hasCloudtentaclesHealth) {
|
|
jobs.push(createDefaultCloudtentaclesHealthJob())
|
|
}
|
|
|
|
return {
|
|
enabled: typeof source.enabled === 'boolean' ? source.enabled : true,
|
|
jobs,
|
|
}
|
|
}
|
|
|
|
function normalizeScheduledJob(rawValue: unknown) {
|
|
if (!isPlainObject(rawValue)) {
|
|
return null
|
|
}
|
|
|
|
const type = String(rawValue.type || '').trim()
|
|
if (type !== 'cloudtentacles_health') {
|
|
return null
|
|
}
|
|
|
|
return normalizeCloudtentaclesHealthJob(rawValue)
|
|
}
|
|
|
|
function normalizeCloudtentaclesHealthJob(rawValue: JsonObject) {
|
|
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
|
|
const defaultAssetThreshold = normalizeRangeInteger(config.assetThreshold, 500, 0, 999999)
|
|
|
|
return {
|
|
id: CLOUDTENTACLES_HEALTH_JOB_ID,
|
|
type: 'cloudtentacles_health',
|
|
enabled: rawValue.enabled === true,
|
|
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 300, 60, 86400),
|
|
cooldownSeconds: normalizeRangeInteger(rawValue.cooldownSeconds, 1800, 60, 86400),
|
|
config: {
|
|
assetThreshold: defaultAssetThreshold,
|
|
accounts: normalizeCloudtentaclesHealthAccounts(config, defaultAssetThreshold),
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeCloudtentaclesHealthAccounts(config: JsonObject, defaultAssetThreshold: number) {
|
|
const rawAccounts = Array.isArray(config.accounts)
|
|
? config.accounts
|
|
: Array.isArray(config.sourceKeys)
|
|
? config.sourceKeys.map((sourceKey) => ({ sourceKey }))
|
|
: []
|
|
const accounts = rawAccounts
|
|
.map((item) => normalizeCloudtentaclesHealthAccount(item, defaultAssetThreshold))
|
|
.filter(Boolean) as ReturnType<typeof normalizeCloudtentaclesHealthAccount>[]
|
|
|
|
if (accounts.length === 0) {
|
|
accounts.push(normalizeCloudtentaclesHealthAccount({
|
|
sourceKey: config.sourceKey || 'default',
|
|
assetThreshold: defaultAssetThreshold,
|
|
}, defaultAssetThreshold))
|
|
}
|
|
|
|
const seen = new Set<string>()
|
|
return accounts.filter((item) => {
|
|
const sourceKey = String(item?.sourceKey || '').trim()
|
|
if (!sourceKey || seen.has(sourceKey)) {
|
|
return false
|
|
}
|
|
seen.add(sourceKey)
|
|
return true
|
|
})
|
|
}
|
|
|
|
function normalizeCloudtentaclesHealthAccount(rawValue: unknown, defaultAssetThreshold: number) {
|
|
const source = isPlainObject(rawValue) ? rawValue : {}
|
|
const sourceKey = String(source.sourceKey || source.key || '').trim() || 'default'
|
|
|
|
return {
|
|
sourceKey,
|
|
label: String(source.label || '').trim(),
|
|
enabled: source.enabled !== false,
|
|
assetThreshold: normalizeRangeInteger(
|
|
source.assetThreshold ?? source.threshold,
|
|
defaultAssetThreshold,
|
|
0,
|
|
999999,
|
|
),
|
|
}
|
|
}
|
|
|
|
function createDefaultScheduledJobsConfig() {
|
|
return {
|
|
enabled: true,
|
|
jobs: [
|
|
createDefaultCloudtentaclesHealthJob(),
|
|
],
|
|
}
|
|
}
|
|
|
|
function createDefaultCloudtentaclesHealthJob() {
|
|
return {
|
|
id: CLOUDTENTACLES_HEALTH_JOB_ID,
|
|
type: 'cloudtentacles_health',
|
|
enabled: false,
|
|
intervalSeconds: 300,
|
|
cooldownSeconds: 1800,
|
|
config: {
|
|
assetThreshold: 500,
|
|
accounts: [
|
|
{
|
|
sourceKey: 'default',
|
|
label: '默认账号',
|
|
enabled: true,
|
|
assetThreshold: 500,
|
|
},
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeRangeInteger(value: unknown, fallback: number, min: number, max: number) {
|
|
const parsed = Number(value)
|
|
if (!Number.isInteger(parsed)) {
|
|
return fallback
|
|
}
|
|
|
|
return Math.min(max, Math.max(min, parsed))
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is JsonObject {
|
|
return Object.prototype.toString.call(value) === '[object Object]'
|
|
}
|