Files
miner-api/app.js
2026-02-01 17:37:16 +07:00

366 lines
9.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// server.js
const express = require('express')
const cors = require('cors')
const { WhatsMiner, ResponseError } = require('@thermocline-labs/whats-miner')
const app = express()
const PORT = process.env.PORT || 3000
const MINER_IP = process.env.MINER_IP || '192.168.31.174'
const MINER_PORT = parseInt(process.env.MINER_PORT || '4028')
// Middleware
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// Логирование запросов
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`)
next()
})
// Инициализация клиента майнера
const client = new WhatsMiner(MINER_IP, MINER_PORT)
// Обработчик ошибок
const errorHandler = (err, res, customMessage = 'Internal server error') => {
console.error('Error:', err)
if (err instanceof ResponseError) {
return res.status(502).json({
success: false,
error: {
type: 'ResponseError',
message: err.message,
code: err.code
}
})
}
if (err.name === 'AbortError') {
return res.status(504).json({
success: false,
error: {
type: 'TimeoutError',
message: 'Request timeout'
}
})
}
return res.status(500).json({
success: false,
error: {
type: err.name || 'UnknownError',
message: customMessage,
details: process.env.NODE_ENV === 'development' ? err.message : undefined
}
})
}
// Эндпоинт: получение общей информации
app.get('/api/summary', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 5000) // 5 сек таймаут
try {
const summary = await client.summary(abortController.signal)
clearTimeout(timeout)
res.json({
success: true,
timestamp: new Date().toISOString(),
data: summary
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch summary')
}
})
// Эндпоинт: информация об устройствах (чипах)
app.get('/api/devs', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 5000)
try {
const devs = await client.edevs(abortController.signal)
clearTimeout(timeout)
res.json({
success: true,
timestamp: new Date().toISOString(),
data: devs
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch devices')
}
})
app.get('/api/devdetails', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 5000)
try {
const devs = await client.devDetails(abortController.signal)
clearTimeout(timeout)
res.json({
success: true,
timestamp: new Date().toISOString(),
data: devs
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch devices')
}
})
// Эндпоинт: информация о пулах
app.get('/api/pools', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 5000)
try {
const pools = await client.pools(abortController.signal)
clearTimeout(timeout)
res.json({
success: true,
timestamp: new Date().toISOString(),
data: pools
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch pools')
}
})
// Эндпоинт: температура и вентиляторы
app.get('/api/temperature', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 5000)
try {
const devs = await client.edevs(abortController.signal)
clearTimeout(timeout)
// Извлечение температурных данных
const temperatureData = {
chip1: {
temp_current: devs[0]['Temperature'] || null,
temp_awg: devs[0]['Chip Temp Avg'] || null,
temp_min: devs[0]['Chip Temp Min'] || null,
temp_max: devs[0]['Chip Temp Max'] || null
},
chip2: {
temp_current: devs[1]['Temperature'] || null,
temp_awg: devs[1]['Chip Temp Avg'] || null,
temp_min: devs[1]['Chip Temp Min'] || null,
temp_max: devs[1]['Chip Temp Max'] || null
}
}
res.json({
success: true,
timestamp: new Date().toISOString(),
data: temperatureData
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch temperature data')
}
})
// Эндпоинт: хешрейт
app.get('/api/hashrate', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 5000)
function format(num) {
const del = 1_000_000
return Math.round(num / del * 100) / 100
}
try {
const summary = await client.summary(abortController.signal)
clearTimeout(timeout)
const hashrateData = {
current: format(summary[0]['HS RT']),
average: format(summary[0]['MHS av']),
'1_sec': format(summary[0]['MHS 5s']),
'1_min': format(summary[0]['MHS 1m']),
'5_min': format(summary[0]['MHS 5m']),
'15_min': format(summary[0]['MHS 15m']),
unit: 'TH/s',
power_watt: summary[0]['Power'],
'watt/th': summary[0]['Power Rate']
}
res.json({
success: true,
timestamp: new Date().toISOString(),
data: hashrateData
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch hashrate')
}
})
// Эндпоинт: перезагрузка майнера
app.post('/api/reboot', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 10000)
try {
const result = await client.reboot(abortController.signal)
clearTimeout(timeout)
res.json({
success: true,
message: 'Miner reboot initiated',
data: result
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to reboot miner')
}
})
// Эндпоинт: проверка состояния
app.get('/api/health', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 3000)
try {
const summary = await client.summary(abortController.signal)
clearTimeout(timeout)
res.json({
success: true,
status: 'healthy',
miner: {
ip: MINER_IP,
port: MINER_PORT,
connected: true,
uptime: summary.Uptime || null,
hashrate: summary.GHS_5s || 0
},
timestamp: new Date().toISOString()
})
} catch (err) {
clearTimeout(timeout)
res.status(503).json({
success: false,
status: 'unhealthy',
miner: {
ip: MINER_IP,
port: MINER_PORT,
connected: false
},
error: err.message,
timestamp: new Date().toISOString()
})
}
})
// Эндпоинт: все данные (агрегированный)
app.get('/api/all', async (req, res) => {
const abortController = new AbortController()
const timeout = setTimeout(() => abortController.abort(), 10000)
try {
const [summary, stats, devs, pools] = await Promise.all([
client.summary(abortController.signal),
client.stats(abortController.signal),
client.devs(abortController.signal),
client.pools(abortController.signal)
])
clearTimeout(timeout)
const aggregatedData = {
summary,
stats,
devs,
pools,
quick_stats: {
hashrate: summary.GHS_5s || 0,
temperature: summary.BoardTemp || summary.temp || null,
uptime: summary.Uptime || null,
chips_active: devs?.DEVS?.reduce((sum, dev) => sum + (dev.ChipsActive || 0), 0) || null
}
}
res.json({
success: true,
timestamp: new Date().toISOString(),
data: aggregatedData
})
} catch (err) {
clearTimeout(timeout)
errorHandler(err, res, 'Failed to fetch all data')
}
})
// Главная страница (документация)
app.get('/', (req, res) => {
res.json({
api: 'WhatsMiner REST API',
version: '1.0.0',
miner: {
ip: MINER_IP,
port: MINER_PORT
},
endpoints: {
'GET /api/summary': 'Общая информация о майнере',
'GET /api/devs': 'Информация об устройствах (чипах)',
'GET /api/devdetails': 'Информация об устройствах',
'GET /api/pools': 'Информация о пулах',
'GET /api/temperature': 'Температура и вентиляторы',
'GET /api/hashrate': 'Текущий хешрейт',
'GET /api/health': 'Проверка состояния',
'GET /api/all': 'Все данные (агрегированные)',
'POST /api/reboot': 'Перезагрузка майнера'
}
})
})
// Обработчик 404
app.use((req, res) => {
res.status(404).json({
success: false,
error: {
type: 'NotFoundError',
message: 'Endpoint not found',
path: req.path
}
})
})
// Запуск сервера
app.listen(PORT, () => {
console.log(`🚀 WhatsMiner API запущен на порту ${PORT}`)
console.log(`📍 IP майнера: ${MINER_IP}:${MINER_PORT}`)
console.log(`📚 Документация: http://localhost:${PORT}`)
})
// Обработка завершения
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully')
process.exit(0)
})
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully')
process.exit(0)
})