253 lines
9.0 KiB
JavaScript
253 lines
9.0 KiB
JavaScript
/**
|
||
* BILLmanager API response parsers
|
||
*/
|
||
|
||
/**
|
||
* Parse BILLmanager JSON list response.
|
||
* bjson: { elem: [ {...}, {...} ] } или { data: { elem: [...] } }
|
||
* xml/json: { doc: { vds: { elem: [...] } } }
|
||
*/
|
||
export function extractList(data, key) {
|
||
if (Array.isArray(data.elem)) return data.elem
|
||
if (data.data?.elem) return Array.isArray(data.data.elem) ? data.data.elem : [data.data.elem]
|
||
const doc = data.doc || data
|
||
let list = doc[key]
|
||
if (!list) return []
|
||
if (Array.isArray(list)) return list
|
||
if (list.elem) {
|
||
const elems = Array.isArray(list.elem) ? list.elem : [list.elem]
|
||
return elems
|
||
}
|
||
return []
|
||
}
|
||
|
||
/**
|
||
* Extract flat object from BILLmanager elem (can be array of { $name, $t } or already flat object)
|
||
*/
|
||
export function elemToObject(elem) {
|
||
if (!elem) return {}
|
||
if (Array.isArray(elem)) {
|
||
const obj = {}
|
||
for (const e of elem) {
|
||
const name = e.$name || e.name
|
||
const val = e.$t ?? e.$ ?? e
|
||
if (name) obj[name] = typeof val === 'object' && val !== null ? (val.$t ?? val.$ ?? JSON.stringify(val)) : val
|
||
}
|
||
return obj
|
||
}
|
||
return typeof elem === 'object' ? elem : {}
|
||
}
|
||
|
||
/**
|
||
* Parse pricelist string: "KVM SSD Start (1 CPU/768 MB RAM/7 GB SSD)"
|
||
* @returns {{ vcpu: number, ramGb: number, diskGb: number, diskType: string, virtualization: string }}
|
||
*/
|
||
export function parsePricelist(pricelist) {
|
||
const s = String(pricelist || '')
|
||
let vcpu = 0
|
||
let ramGb = 0
|
||
let diskGb = 0
|
||
let diskType = 'NVMe'
|
||
let virtualization = 'KVM'
|
||
|
||
const cpuMatch = s.match(/(\d+)\s*(?:CPU|СPU)/i)
|
||
if (cpuMatch) vcpu = parseInt(cpuMatch[1], 10) || 0
|
||
|
||
const ramMbMatch = s.match(/(\d+)\s*MB\s*RAM/i)
|
||
if (ramMbMatch) ramGb = Math.max(1, Math.round((parseInt(ramMbMatch[1], 10) || 0) / 1024))
|
||
else {
|
||
const ramGbMatch = s.match(/(\d+)\s*GB\s*RAM/i)
|
||
if (ramGbMatch) ramGb = parseInt(ramGbMatch[1], 10) || 0
|
||
}
|
||
|
||
const diskMatch = s.match(/(\d+)\s*GB\s*(SSD|NVMe|HDD)/i)
|
||
if (diskMatch) {
|
||
diskGb = parseInt(diskMatch[1], 10) || 0
|
||
diskType = diskMatch[2] || 'NVMe'
|
||
}
|
||
|
||
if (/\bKVM\b/i.test(s)) virtualization = 'KVM'
|
||
else if (/\bOpenVZ\b/i.test(s)) virtualization = 'OpenVZ'
|
||
else if (/\bLXC\b/i.test(s)) virtualization = 'LXC'
|
||
|
||
return { vcpu, ramGb, diskGb, diskType, virtualization }
|
||
}
|
||
|
||
/**
|
||
* Парсит описание тарифа из vds.order.pricelist (desc)
|
||
* Поддерживает два формата: текстовый (Firstbyte) и HTML (Selectel и др.)
|
||
* @param {string} desc - HTML или текстовое описание тарифа
|
||
* @returns {{ name: string, vcpu: number, ramGb: number, diskGb: number, diskType: string, virtualization: string, channel: string, location: string, cpuModel: string }}
|
||
*/
|
||
export function parseTariffDesc(desc) {
|
||
const s = String(desc || '')
|
||
let name = ''
|
||
let vcpu = 0
|
||
let ramGb = 0
|
||
let diskGb = 0
|
||
let diskType = 'SSD'
|
||
let virtualization = 'KVM'
|
||
let channel = ''
|
||
let location = ''
|
||
let cpuModel = ''
|
||
|
||
const firstLine = s.split(/\r?\n|<br\s*\/?>/i)[0]?.trim() || ''
|
||
name = firstLine.replace(/<[^>]+>/g, '').trim()
|
||
|
||
// Формат 1: "Процессор: 1 ядро", "Память: 1 GB", "Диск: 20 GB SSD", "Канал: 200Mb/s"
|
||
const cpuMatch = s.match(/Процессор:\s*(\d+)\s*(?:ядро|ядра|ядер)/i)
|
||
if (cpuMatch) vcpu = parseInt(cpuMatch[1], 10) || 0
|
||
|
||
const ramMbMatch = s.match(/Память:\s*(\d+)\s*MB/i)
|
||
if (ramMbMatch) ramGb = Math.max(0.5, Math.round((parseInt(ramMbMatch[1], 10) || 0) / 1024 * 10) / 10)
|
||
else {
|
||
const ramGbMatch = s.match(/Память:\s*(\d+)\s*GB/i)
|
||
if (ramGbMatch) ramGb = parseInt(ramGbMatch[1], 10) || 0
|
||
}
|
||
|
||
const diskMatch = s.match(/Диск:\s*(\d+)\s*GB\s*(SSD|SAS|HDD|NVMe)/i)
|
||
if (diskMatch) {
|
||
diskGb = parseInt(diskMatch[1], 10) || 0
|
||
diskType = diskMatch[2] || 'SSD'
|
||
}
|
||
|
||
const channelMatch = s.match(/Канал:\s*(\d+Mb\/s)/i)
|
||
if (channelMatch) channel = channelMatch[1]
|
||
|
||
if (/\bKVM\b/i.test(s)) virtualization = 'KVM'
|
||
else if (/\bOpenVZ\b/i.test(s)) virtualization = 'OpenVZ'
|
||
else if (/\bLXC\b/i.test(s)) virtualization = 'LXC'
|
||
|
||
// Формат 2 (HTML): "Публичная сеть: 250 Мбит/с", "Локация: Москва, Россия", "Процессор: Ryzen 7 5800X", "NVMe накопитель"
|
||
if (!channel) {
|
||
const netMatch = s.match(/(?:Публичная сеть|Канал)[:\s]*\*?\*?(\d+)\s*Мбит/i)
|
||
if (netMatch) channel = `${netMatch[1]} Мбит/с`
|
||
}
|
||
if (!location) {
|
||
const locMatch = s.match(/Локация[:\s]*([^;]+)/i)
|
||
if (locMatch) location = locMatch[1].replace(/<[^>]+>/g, '').trim()
|
||
}
|
||
if (!cpuModel) {
|
||
const procMatch = s.match(/Процессор[:\s]*([^;]+?)(?:\s+до\s|$)/i)
|
||
if (procMatch) cpuModel = procMatch[1].replace(/<[^>]+>/g, '').trim()
|
||
}
|
||
if (!diskType || diskType === 'SSD') {
|
||
if (/\bNVMe\b/i.test(s)) diskType = 'NVMe'
|
||
else if (/\bSAS\b/i.test(s)) diskType = 'SAS'
|
||
else if (/\bHDD\b/i.test(s)) diskType = 'HDD'
|
||
else if (/\bSSD\b/i.test(s)) diskType = 'SSD'
|
||
}
|
||
|
||
return { name, vcpu, ramGb, diskGb, diskType, virtualization, channel, location, cpuModel }
|
||
}
|
||
|
||
/**
|
||
* Парсит название датацентра для извлечения страны и локации
|
||
* @param {string} dcName - название ДЦ, напр. "1 Датацентр Россия, Москва", "[DE] Франкфурт", "Франция"
|
||
* @returns {{ country: string, location: string }}
|
||
*/
|
||
export function parseDatacenterName(dcName) {
|
||
const s = String(dcName || '').trim()
|
||
if (!s) return { country: '', location: '' }
|
||
|
||
const COUNTRY_CODE_MAP = {
|
||
DE: 'Германия',
|
||
FI: 'Финляндия',
|
||
RU: 'Россия',
|
||
FR: 'Франция',
|
||
GB: 'Великобритания',
|
||
NL: 'Нидерланды',
|
||
US: 'США',
|
||
SE: 'Швеция',
|
||
NO: 'Норвегия',
|
||
BE: 'Бельгия',
|
||
CH: 'Швейцария',
|
||
CZ: 'Чехия',
|
||
CA: 'Канада',
|
||
LV: 'Латвия',
|
||
LT: 'Литва',
|
||
EE: 'Эстония',
|
||
PL: 'Польша',
|
||
IT: 'Италия',
|
||
DK: 'Дания',
|
||
AU: 'Австралия',
|
||
ES: 'Испания',
|
||
SG: 'Сингапур',
|
||
}
|
||
|
||
// "[DE] Франкфурт | AMD EPYC" -> country: DE/Германия, location: Франкфурт
|
||
const codeMatch = s.match(/\[([A-Z]{2})\]\s*([^|]+)/)
|
||
if (codeMatch) {
|
||
const code = codeMatch[1]
|
||
const loc = codeMatch[2].trim()
|
||
return { country: COUNTRY_CODE_MAP[code] || code, location: loc }
|
||
}
|
||
|
||
// "N Датацентр Страна, Город" или "Датацентр Страна, Город"
|
||
const dcMatch = s.match(/(?:\d+\s+)?Датацентр\s+([^,]+),\s*(.+)/i)
|
||
if (dcMatch) {
|
||
return { country: dcMatch[1].trim(), location: dcMatch[2].trim() }
|
||
}
|
||
|
||
// "Страна, Город" (без слова Датацентр)
|
||
const commaMatch = s.match(/^([^,]+),\s*(.+)$/)
|
||
if (commaMatch) {
|
||
return { country: commaMatch[1].trim(), location: commaMatch[2].trim() }
|
||
}
|
||
|
||
// Только страна: "Франция", "Россия", "Германия", "Чехия" и т.д.
|
||
const countryOnly = [
|
||
'Россия', 'Чехия', 'Нидерланды', 'Франция', 'Великобритания', 'Германия',
|
||
'Финляндия', 'Швеция', 'Норвегия', 'Бельгия', 'Швейцария', 'Канада',
|
||
'Латвия', 'Литва', 'Эстония', 'Польша', 'Италия', 'Дания', 'Австралия',
|
||
'Испания', 'Сингапур', 'США', 'Азия', 'Европа',
|
||
]
|
||
for (const c of countryOnly) {
|
||
if (s === c || s.startsWith(c + ',') || s.startsWith(c + ' ')) {
|
||
const rest = s.slice(c.length).replace(/^[,\s]+/, '')
|
||
return { country: c, location: rest }
|
||
}
|
||
}
|
||
|
||
// "ММТС-9", "Adman", "Европа DC1", "Москва DC3" — по ключевым словам
|
||
if (/ММТС|Adman|Москва/i.test(s)) {
|
||
return { country: 'Россия', location: s }
|
||
}
|
||
if (/Европа/i.test(s)) {
|
||
return { country: 'Европа', location: s.replace(/Европа\s*/i, '').trim() || s }
|
||
}
|
||
|
||
// "США | Ryzen 9 9950X" — страна до |
|
||
const pipeMatch = s.match(/^([^|]+)\s*\|/)
|
||
if (pipeMatch) {
|
||
const part = pipeMatch[1].trim()
|
||
for (const c of countryOnly) {
|
||
if (part.includes(c)) return { country: c, location: '' }
|
||
}
|
||
return { country: part, location: '' }
|
||
}
|
||
|
||
return { country: s, location: '' }
|
||
}
|
||
|
||
/**
|
||
* Извлечь tariflist из ответа vds.order (поддержка разных форматов BILLmanager)
|
||
* @param {object} data - сырой ответ API
|
||
* @returns {Array}
|
||
*/
|
||
export function extractTariflist(data) {
|
||
if (!data) return []
|
||
const listNode = data.list ?? data.doc?.list ?? data.doc
|
||
if (!listNode) return []
|
||
|
||
// tariflist / tarifflist / pricelist — разные варианты названия
|
||
let list = listNode.tariflist ?? listNode.tarifflist ?? listNode.pricelist
|
||
if (Array.isArray(list)) return list
|
||
|
||
// elem-формат (как в vds, payment)
|
||
const elems = listNode.elem
|
||
if (elems) return Array.isArray(elems) ? elems : [elems]
|
||
|
||
return []
|
||
}
|