- Introduced `runScheduledSyncTariffs` function to handle tariff synchronization independently from VPS and payment sync. - Updated `syncFromBillmanager` to accept options for skipping tariff and VPS payment syncs. - Added new database columns for `syncTariffsIntervalMinutes` and `customFields` in settings. - Enhanced settings page to configure tariff sync interval. - Implemented bulk update functionality for VPS status and deletion in the VPS management page. - Updated various components to support new features, including sync log display and improved payment handling.
231 lines
7.5 KiB
JavaScript
231 lines
7.5 KiB
JavaScript
import { Router } from 'express'
|
|
import { getDb } from '../db.js'
|
|
|
|
const router = Router()
|
|
|
|
export function rowToVps(row) {
|
|
if (!row) return null
|
|
let additionalIps = []
|
|
try {
|
|
additionalIps = row.additionalIps ? JSON.parse(row.additionalIps) : []
|
|
} catch {
|
|
additionalIps = []
|
|
}
|
|
let userOverrides = []
|
|
try {
|
|
userOverrides = row.userOverrides ? JSON.parse(row.userOverrides) : []
|
|
} catch {
|
|
userOverrides = []
|
|
}
|
|
return {
|
|
...row,
|
|
additionalIps,
|
|
userOverrides,
|
|
monitoringEnabled: Boolean(row.monitoringEnabled),
|
|
backupEnabled: Boolean(row.backupEnabled),
|
|
dailyRate: row.dailyRate != null ? row.dailyRate : '',
|
|
monthlyRate: row.monthlyRate != null ? row.monthlyRate : '',
|
|
}
|
|
}
|
|
|
|
router.get('/', (req, res) => {
|
|
try {
|
|
const db = getDb()
|
|
const rows = db.prepare('SELECT * FROM vps ORDER BY createdAt DESC').all()
|
|
res.json(rows.map(rowToVps))
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
})
|
|
|
|
router.post('/', (req, res) => {
|
|
try {
|
|
const db = getDb()
|
|
const r = req.body
|
|
const id = r.id || `vps-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
const additionalIps = Array.isArray(r.additionalIps) ? JSON.stringify(r.additionalIps) : '[]'
|
|
const dailyRate = r.dailyRate === '' || r.dailyRate == null ? null : Number(r.dailyRate)
|
|
const monthlyRate = r.monthlyRate === '' || r.monthlyRate == null ? null : Number(r.monthlyRate)
|
|
|
|
db.prepare(`
|
|
INSERT INTO vps (
|
|
id, ip, ipv6, additionalIps, dns, providerId, providerAccountId, country, city, datacenter,
|
|
os, vcpu, ramGb, diskGb, diskType, virtualization, bandwidthTb, sshPort, rootUser,
|
|
purpose, environment, project, monitoringEnabled, backupEnabled, status, tariffType,
|
|
currency, dailyRate, monthlyRate, createdAt, paidUntil, notes, userOverrides
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
id,
|
|
r.ip ?? '',
|
|
r.ipv6 ?? '',
|
|
additionalIps,
|
|
r.dns ?? '',
|
|
r.providerId ?? '',
|
|
r.providerAccountId ?? '',
|
|
r.country ?? '',
|
|
r.city ?? '',
|
|
r.datacenter ?? '',
|
|
r.os ?? '',
|
|
r.vcpu ?? 0,
|
|
r.ramGb ?? 0,
|
|
r.diskGb ?? 0,
|
|
r.diskType ?? '',
|
|
r.virtualization ?? '',
|
|
r.bandwidthTb ?? 0,
|
|
r.sshPort ?? 22,
|
|
r.rootUser ?? '',
|
|
r.purpose ?? '',
|
|
r.environment ?? '',
|
|
r.project ?? '',
|
|
r.monitoringEnabled ? 1 : 0,
|
|
r.backupEnabled ? 1 : 0,
|
|
r.status ?? 'active',
|
|
r.tariffType ?? '',
|
|
r.currency ?? '',
|
|
dailyRate,
|
|
monthlyRate,
|
|
r.createdAt ?? new Date().toISOString().slice(0, 10),
|
|
r.paidUntil ?? '',
|
|
r.notes ?? '',
|
|
r.userOverrides ? (Array.isArray(r.userOverrides) ? JSON.stringify(r.userOverrides) : r.userOverrides) : '[]',
|
|
)
|
|
const row = db.prepare('SELECT * FROM vps WHERE id = ?').get(id)
|
|
res.status(201).json(rowToVps(row))
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
})
|
|
|
|
const USER_OVERRIDABLE_FIELDS = ['country', 'city', 'datacenter', 'os', 'vcpu', 'ramGb', 'diskGb', 'diskType', 'virtualization', 'purpose', 'environment', 'project', 'notes', 'sshPort', 'rootUser', 'bandwidthTb', 'monitoringEnabled', 'backupEnabled']
|
|
|
|
router.put('/:id', (req, res) => {
|
|
try {
|
|
const db = getDb()
|
|
const { id } = req.params
|
|
const r = req.body
|
|
const existing = db.prepare('SELECT * FROM vps WHERE id = ?').get(id)
|
|
if (!existing) return res.status(404).json({ error: 'Not found' })
|
|
|
|
let userOverrides = []
|
|
try {
|
|
userOverrides = existing.userOverrides ? JSON.parse(existing.userOverrides) : []
|
|
} catch {
|
|
userOverrides = []
|
|
}
|
|
if (r.userOverrides === 'clear' || (Array.isArray(r.userOverrides) && r.userOverrides.length === 0)) {
|
|
userOverrides = []
|
|
} else {
|
|
for (const f of USER_OVERRIDABLE_FIELDS) {
|
|
const newVal = r[f]
|
|
const oldVal = existing[f]
|
|
const changed = String(newVal ?? '') !== String(oldVal ?? '')
|
|
if (changed && !userOverrides.includes(f)) {
|
|
userOverrides.push(f)
|
|
}
|
|
}
|
|
}
|
|
const userOverridesJson = JSON.stringify([...new Set(userOverrides)])
|
|
|
|
const additionalIps = Array.isArray(r.additionalIps) ? JSON.stringify(r.additionalIps) : '[]'
|
|
const dailyRate = r.dailyRate === '' || r.dailyRate == null ? null : Number(r.dailyRate)
|
|
const monthlyRate = r.monthlyRate === '' || r.monthlyRate == null ? null : Number(r.monthlyRate)
|
|
|
|
db.prepare(`
|
|
UPDATE vps SET
|
|
ip = ?, ipv6 = ?, additionalIps = ?, dns = ?, providerId = ?, providerAccountId = ?,
|
|
country = ?, city = ?, datacenter = ?, os = ?, vcpu = ?, ramGb = ?, diskGb = ?, diskType = ?,
|
|
virtualization = ?, bandwidthTb = ?, sshPort = ?, rootUser = ?, purpose = ?, environment = ?,
|
|
project = ?, monitoringEnabled = ?, backupEnabled = ?, status = ?, tariffType = ?,
|
|
currency = ?, dailyRate = ?, monthlyRate = ?, createdAt = ?, paidUntil = ?, notes = ?,
|
|
userOverrides = ?
|
|
WHERE id = ?
|
|
`).run(
|
|
r.ip ?? '',
|
|
r.ipv6 ?? '',
|
|
additionalIps,
|
|
r.dns ?? '',
|
|
r.providerId ?? '',
|
|
r.providerAccountId ?? '',
|
|
r.country ?? '',
|
|
r.city ?? '',
|
|
r.datacenter ?? '',
|
|
r.os ?? '',
|
|
r.vcpu ?? 0,
|
|
r.ramGb ?? 0,
|
|
r.diskGb ?? 0,
|
|
r.diskType ?? '',
|
|
r.virtualization ?? '',
|
|
r.bandwidthTb ?? 0,
|
|
r.sshPort ?? 22,
|
|
r.rootUser ?? '',
|
|
r.purpose ?? '',
|
|
r.environment ?? '',
|
|
r.project ?? '',
|
|
r.monitoringEnabled ? 1 : 0,
|
|
r.backupEnabled ? 1 : 0,
|
|
r.status ?? 'active',
|
|
r.tariffType ?? '',
|
|
r.currency ?? '',
|
|
dailyRate,
|
|
monthlyRate,
|
|
r.createdAt ?? '',
|
|
r.paidUntil ?? '',
|
|
r.notes ?? '',
|
|
userOverridesJson,
|
|
id,
|
|
)
|
|
const row = db.prepare('SELECT * FROM vps WHERE id = ?').get(id)
|
|
if (!row) return res.status(404).json({ error: 'Not found' })
|
|
res.json(rowToVps(row))
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
})
|
|
|
|
router.delete('/:id', (req, res) => {
|
|
try {
|
|
const db = getDb()
|
|
const { id } = req.params
|
|
const result = db.prepare('DELETE FROM vps WHERE id = ?').run(id)
|
|
if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
|
|
res.status(204).send()
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
})
|
|
|
|
router.patch('/bulk', (req, res) => {
|
|
try {
|
|
const db = getDb()
|
|
const { ids = [], action, value } = req.body
|
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
return res.status(400).json({ error: 'ids must be a non-empty array' })
|
|
}
|
|
if (action === 'status' && value) {
|
|
const validStatus = ['active', 'paused', 'archived']
|
|
if (!validStatus.includes(value)) {
|
|
return res.status(400).json({ error: 'value must be active, paused, or archived' })
|
|
}
|
|
const stmt = db.prepare('UPDATE vps SET status = ? WHERE id = ?')
|
|
for (const id of ids) {
|
|
stmt.run(value, id)
|
|
}
|
|
return res.json({ updated: ids.length, status: value })
|
|
}
|
|
if (action === 'delete') {
|
|
const stmt = db.prepare('DELETE FROM vps WHERE id = ?')
|
|
let deleted = 0
|
|
for (const id of ids) {
|
|
const result = stmt.run(id)
|
|
if (result.changes > 0) deleted++
|
|
}
|
|
return res.json({ deleted })
|
|
}
|
|
return res.status(400).json({ error: 'action must be status or delete' })
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message })
|
|
}
|
|
})
|
|
|
|
export default router
|