init repo
This commit is contained in:
4
.env
Normal file
4
.env
Normal file
@@ -0,0 +1,4 @@
|
||||
PORT=3000
|
||||
MINER_IP=192.168.31.174
|
||||
MINER_PORT=4028
|
||||
NODE_ENV=production
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/node_modules
|
||||
352
app.js
Normal file
352
app.js
Normal file
@@ -0,0 +1,352 @@
|
||||
// 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/stats', async (req, res) => {
|
||||
const abortController = new AbortController()
|
||||
const timeout = setTimeout(() => abortController.abort(), 5000)
|
||||
|
||||
try {
|
||||
const stats = await client.stats(abortController.signal)
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: stats
|
||||
})
|
||||
} catch (err) {
|
||||
clearTimeout(timeout)
|
||||
errorHandler(err, res, 'Failed to fetch stats')
|
||||
}
|
||||
})
|
||||
|
||||
// Эндпоинт: информация об устройствах (чипах)
|
||||
app.get('/api/devs', async (req, res) => {
|
||||
const abortController = new AbortController()
|
||||
const timeout = setTimeout(() => abortController.abort(), 5000)
|
||||
|
||||
try {
|
||||
const devs = await client.devs(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 summary = await client.summary(abortController.signal)
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
// Извлечение температурных данных
|
||||
const temperatureData = {
|
||||
board_temp: summary.BoardTemp || summary.temp || null,
|
||||
chip_temp: summary.ChipTemp || summary.temp2 || null,
|
||||
fan_speed: summary.FanSpeed || summary.fan || null,
|
||||
fan_percent: summary.FanPercent || 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)
|
||||
|
||||
try {
|
||||
const summary = await client.summary(abortController.signal)
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
const hashrateData = {
|
||||
current: summary.GHS_5s || summary.hashrate || 0,
|
||||
average: summary.GHS_av || summary.hashrate_avg || 0,
|
||||
realtime: summary.GHS_rt || summary.hashrate_rt || 0,
|
||||
unit: 'GH/s'
|
||||
}
|
||||
|
||||
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/stats': 'Статистика майнера',
|
||||
'GET /api/devs': 'Информация об устройствах (чипах)',
|
||||
'GET /api/pools': 'Информация о пулах',
|
||||
'GET /api/temperature': 'Температура и вентиляторы',
|
||||
'GET /api/hashrate': 'Текущий хешрейт',
|
||||
'GET /api/health': 'Проверка состояния',
|
||||
'GET /api/all': 'Все данные (агрегированные)',
|
||||
'POST /api/reboot': 'Перезагрузка майнера'
|
||||
},
|
||||
documentation: 'https://github.com/thermocline-labs/whats-miner'
|
||||
})
|
||||
})
|
||||
|
||||
// Обработчик 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)
|
||||
})
|
||||
1240
package-lock.json
generated
Normal file
1240
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "whatsminer-api",
|
||||
"version": "1.0.0",
|
||||
"description": "REST API для мониторинга WhatsMiner",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"@thermocline-labs/whats-miner": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user