feat(api, web): интеграция хостера 4VPS.SU — синк VPS, баланса и тарифов
Docker / build (push) Has been cancelled

Добавлен адаптер 4vps, обобщён pipeline синхронизации через ProviderAdapter и обновлён UI для Panel ID и API Key.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-06-29 01:13:50 +07:00
co-authored by Cursor
parent 05e7bf829e
commit 9be50aa03b
33 changed files with 1564 additions and 174 deletions
@@ -13,3 +13,32 @@ export function buildApiCredentials(login: string, password: string): string {
if (!l) return p
return p ? `${l}:${p}` : ''
}
export interface FourVpsCredentials {
panelId: number | null
apiKey: string
}
/** Разбор 4VPS-кредов формата `panelId:apiKey` (panelId опционален). */
export function parseFourVpsCredentials(credentials: string | null | undefined): FourVpsCredentials {
const cred = String(credentials ?? '').trim()
if (!cred) return { panelId: null, apiKey: '' }
const idx = cred.indexOf(':')
if (idx <= 0) return { panelId: null, apiKey: cred }
const panelPart = cred.slice(0, idx).trim()
const apiKey = cred.slice(idx + 1)
const panelId = panelPart ? Number.parseInt(panelPart, 10) : null
return {
panelId: panelId != null && Number.isFinite(panelId) ? panelId : null,
apiKey,
}
}
/** Собрать 4VPS-креды: panelId + API key. */
export function buildFourVpsCredentials(panelId: string, apiKey: string): string {
const pid = panelId.trim()
const key = apiKey
if (!pid && !key) return ''
if (!pid) return key
return key ? `${pid}:${key}` : pid
}
@@ -1,4 +1,5 @@
import { accountBalanceApi } from './account-balance.js'
import { isSyncApiType } from '../contracts/provider.js'
import { getPaidUntilDate, type PaidUntilContext } from './paid-until.js'
export const STALE_SYNC_HOURS = 48
@@ -166,12 +167,12 @@ export function computeInventoryHealth(input: InventoryHealthInput): InventoryIs
})
}
const bmAccounts = providerAccounts.filter((a) => {
const syncAccounts = providerAccounts.filter((a) => {
const p = providerById.get(a.providerId)
return p?.apiType === 'billmanager' && Boolean((p.apiBaseUrl || '').trim()) && a.apiCredentialsSet
return isSyncApiType(p?.apiType) && Boolean((p?.apiBaseUrl || '').trim()) && a.apiCredentialsSet
})
const staleMs = STALE_SYNC_HOURS * 60 * 60 * 1000
const staleAccounts = bmAccounts.filter((a) => {
const staleAccounts = syncAccounts.filter((a) => {
const t = lastOkSyncFinishedAt(a.id, syncLog)
if (t == null) return true
return now.getTime() - t > staleMs
@@ -226,12 +227,12 @@ export function getStaleSyncAccountIds(
now = new Date(),
): string[] {
const providerById = new Map(providers.map((p) => [p.id, p]))
const bmAccounts = providerAccounts.filter((a) => {
const syncAccounts = providerAccounts.filter((a) => {
const p = providerById.get(a.providerId)
return p?.apiType === 'billmanager' && Boolean((p.apiBaseUrl || '').trim()) && a.apiCredentialsSet
return isSyncApiType(p?.apiType) && Boolean((p?.apiBaseUrl || '').trim()) && a.apiCredentialsSet
})
const staleMs = STALE_SYNC_HOURS * 60 * 60 * 1000
return bmAccounts
return syncAccounts
.filter((a) => {
const t = lastOkSyncFinishedAt(a.id, syncLog)
if (t == null) return true