From 9dbcf7885cb1a09019b231b9a72ffc4893bd2413 Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 11 Dec 2025 16:04:17 +0000 Subject: [PATCH] feat: Enhance dashboard layout with responsive stats bar and chart integration --- docs/docs.go | 6 +- docs/swagger.json | 6 +- docs/swagger.yaml | 5 +- ui/src/app/dashboard.component.css | 113 +++++++++------------- ui/src/app/dashboard.component.html | 19 +++- ui/src/app/dashboard.component.ts | 73 +------------- ui/src/app/stats-bar.component.ts | 141 +++++++++++++++++++++------- ui/src/index.html | 30 ++++-- 8 files changed, 194 insertions(+), 199 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 4d363d1..d029d62 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -66,7 +66,7 @@ const docTemplate = `{ }, "/miners": { "get": { - "description": "Get a list of all running miners", + "description": "Get a list of all running miners, including their full stats.", "produces": [ "application/json" ], @@ -79,9 +79,7 @@ const docTemplate = `{ "description": "OK", "schema": { "type": "array", - "items": { - "$ref": "#/definitions/mining.XMRigMiner" - } + "items": {} } } } diff --git a/docs/swagger.json b/docs/swagger.json index 157348b..9c25a5c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -60,7 +60,7 @@ }, "/miners": { "get": { - "description": "Get a list of all running miners", + "description": "Get a list of all running miners, including their full stats.", "produces": [ "application/json" ], @@ -73,9 +73,7 @@ "description": "OK", "schema": { "type": "array", - "items": { - "$ref": "#/definitions/mining.XMRigMiner" - } + "items": {} } } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c734675..70593ba 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -318,15 +318,14 @@ paths: - system /miners: get: - description: Get a list of all running miners + description: Get a list of all running miners, including their full stats. produces: - application/json responses: "200": description: OK schema: - items: - $ref: '#/definitions/mining.XMRigMiner' + items: {} type: array summary: List all running miners tags: diff --git a/ui/src/app/dashboard.component.css b/ui/src/app/dashboard.component.css index 76a0616..eafdf67 100644 --- a/ui/src/app/dashboard.component.css +++ b/ui/src/app/dashboard.component.css @@ -1,74 +1,56 @@ -.admin-panel { - padding: 1rem; +/* Set up the container */ +.dashboard-view { + container-type: inline-size; + container-name: dashboard; + height: 100%; + display: flex; /* Ensure it fills height */ + flex-direction: column; } -.admin-title { - margin-top: 0; -} - -.miner-list, .path-list { - margin-bottom: 1.5rem; -} - -.miner-item, .path-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.5rem; - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 0.5rem; -} - -.path-list ul { - list-style-type: none; - padding: 0; -} - -.path-list li { - background-color: #f5f5f5; - padding: 0.5rem; - border-radius: 4px; - margin-bottom: 0.5rem; - font-family: monospace; -} - -.dashboard-summary { - padding: 1rem; -} - -.miner-summary-item { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; -} - -.miner-name { - font-weight: bold; -} - -.start-buttons { - display: flex; - gap: 0.5rem; -} - -.start-options { - padding: 1rem; - border: 1px solid #ccc; - border-radius: 4px; - margin-top: 0.5rem; +.dashboard-content { display: flex; flex-direction: column; - gap: 1rem; + height: 100%; + flex-grow: 1; /* Allow content to grow */ } -.dashboard-charts { - padding: 1rem; +/* Default (mobile/narrow) layout */ +.stats-list-container { + display: none; /* Hide the detailed list by default */ } -.miner-chart-item { - margin-bottom: 1.5rem; +.stats-bar-container { + display: block; /* Show the bar by default */ +} + +.chart-container { + flex-grow: 1; + min-height: 200px; /* Ensure chart has a minimum height */ +} + +/* Wide layout using container queries */ +@container dashboard (min-width: 768px) { + .dashboard-content { + display: grid; + grid-template-columns: 2fr 1fr; /* 2/3 for chart, 1/3 for stats */ + gap: 1.5rem; + align-items: stretch; /* Stretch items to fill height */ + } + + .stats-bar-container { + display: none; /* Hide the bar in wide view */ + } + + .stats-list-container { + display: block; /* Show the detailed list in wide view */ + grid-column: 2; + grid-row: 1; + } + + .chart-container { + grid-column: 1; + grid-row: 1; + } } .centered-container { @@ -78,10 +60,7 @@ justify-content: center; padding: 2rem; text-align: center; -} - -.button-spinner { - font-size: 1rem; + height: 100%; } .card-error { diff --git a/ui/src/app/dashboard.component.html b/ui/src/app/dashboard.component.html index 052948d..8e5b0dd 100644 --- a/ui/src/app/dashboard.component.html +++ b/ui/src/app/dashboard.component.html @@ -9,11 +9,22 @@ } - @if (state().runningMiners.length > 0) { -
- - +
+ +
+ +
+ + +
+ +
+ + +
+ +
} @else {
diff --git a/ui/src/app/dashboard.component.ts b/ui/src/app/dashboard.component.ts index bd5264e..3ad89f2 100644 --- a/ui/src/app/dashboard.component.ts +++ b/ui/src/app/dashboard.component.ts @@ -4,9 +4,6 @@ import { FormsModule } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { MinerService } from './miner.service'; import { ChartComponent } from './chart.component'; -import { ProfileListComponent } from './profile-list.component'; -import { ProfileCreateComponent } from './profile-create.component'; -import { StatsBarComponent } from './stats-bar.component'; // Import Web Awesome components import "@awesome.me/webawesome/dist/webawesome.js"; @@ -17,84 +14,18 @@ import '@awesome.me/webawesome/dist/components/icon/icon.js'; import '@awesome.me/webawesome/dist/components/spinner/spinner.js'; import '@awesome.me/webawesome/dist/components/input/input.js'; import '@awesome.me/webawesome/dist/components/select/select.js'; +import {StatsBarComponent} from './stats-bar.component'; @Component({ selector: 'snider-mining-dashboard', standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], - imports: [CommonModule, FormsModule, ChartComponent, ProfileListComponent, ProfileCreateComponent, StatsBarComponent], + imports: [CommonModule, FormsModule, ChartComponent, StatsBarComponent], // Add to imports templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.css'] }) export class MiningDashboardComponent { minerService = inject(MinerService); state = this.minerService.state; - - actionInProgress = signal(null); error = signal(null); - - showProfileManager = signal(false); - // Use a map to track the selected profile for each miner type - selectedProfileIds = signal>(new Map()); - - handleProfileSelection(minerType: string, event: Event) { - const selectedValue = (event.target as HTMLSelectElement).value; - this.selectedProfileIds.update(m => m.set(minerType, selectedValue)); - } - - private handleError(err: HttpErrorResponse, defaultMessage: string) { - console.error(err); - this.actionInProgress.set(null); - if (err.error && err.error.error) { - this.error.set(`${defaultMessage}: ${err.error.error}`); - } else if (typeof err.error === 'string' && err.error.length < 200) { - this.error.set(`${defaultMessage}: ${err.error}`); - } else { - this.error.set(`${defaultMessage}. Please check the console for details.`); - } - } - - startMiner(minerType: string): void { - const profileId = this.selectedProfileIds().get(minerType); - if (!profileId) { - this.error.set('Please select a profile to start.'); - return; - } - this.actionInProgress.set(`start-${profileId}`); - this.error.set(null); - this.minerService.startMiner(profileId).subscribe({ - next: () => { this.actionInProgress.set(null); }, - error: (err: HttpErrorResponse) => { - this.handleError(err, `Failed to start miner for profile ${profileId}`); - } - }); - } - - stopMiner(miner: any): void { - const runningInstance = this.getRunningMinerInstance(miner); - if (!runningInstance) { - this.error.set("Cannot stop a miner that is not running."); - return; - } - this.actionInProgress.set(`stop-${miner.type}`); - this.error.set(null); - this.minerService.stopMiner(runningInstance.name).subscribe({ - next: () => { this.actionInProgress.set(null); }, - error: (err: HttpErrorResponse) => { - this.handleError(err, `Failed to stop ${runningInstance.name}`); - } - }); - } - - getRunningMinerInstance(miner: any): any { - return this.state().runningMiners.find((m: any) => m.name.startsWith(miner.type)); - } - - isMinerRunning(miner: any): boolean { - return !!this.getRunningMinerInstance(miner); - } - - toggleProfileManager() { - this.showProfileManager.set(!this.showProfileManager()); - } } diff --git a/ui/src/app/stats-bar.component.ts b/ui/src/app/stats-bar.component.ts index 0b136bd..4abcafe 100644 --- a/ui/src/app/stats-bar.component.ts +++ b/ui/src/app/stats-bar.component.ts @@ -8,44 +8,112 @@ import { CommonModule } from '@angular/common'; schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` @if(stats) { -
-
- Hashrate: - {{ stats.hashrate?.total[0] | number:'1.0-2' }} H/s + @if (mode === 'list') { +
+
+ +
Algorithm
{{ stats.algo }}
+
Uptime
{{ stats.uptime }}s
+
Version
{{ stats.version }}
+ + +
Hashrate (10s)
{{ stats.hashrate?.total[0] | number:'1.0-2' }} H/s
+
Hashrate (60s)
{{ stats.hashrate?.total[1] | number:'1.0-2' }} H/s
+
Hashrate (15m)
{{ stats.hashrate?.total[2] | number:'1.0-2' }} H/s
+
Highest Hashrate
{{ stats.hashrate?.highest | number:'1.0-2' }} H/s
+ + +
Good Shares
{{ stats.results?.shares_good }}
+
Total Shares
{{ stats.results?.shares_total }}
+
Avg. Time
{{ stats.results?.avg_time }}s
+
Total Hashes
{{ stats.results?.hashes_total | number }}
+ + +
Pool
{{ stats.connection?.pool }}
+
Pool Uptime
{{ stats.connection?.uptime }}s
+
Pool Ping
{{ stats.connection?.ping }}ms
+
Current Difficulty
{{ stats.connection?.diff | number }}
+
Accepted Shares
{{ stats.connection?.accepted }}
+
Rejected Shares
{{ stats.connection?.rejected }}
+ + +
CPU Brand
{{ stats.cpu?.brand }}
+
CPU Cores/Threads
{{ stats.cpu?.cores }} / {{ stats.cpu?.threads }}
+
-
- Algorithm: - {{ stats.algo }} + } @else { +
+
+ Hashrate: + {{ stats.hashrate?.total[0] | number:'1.0-2' }} H/s +
+
+ Algorithm: + {{ stats.algo }} +
+
+ Difficulty: + {{ stats.connection?.diff | number }} +
+
+ Accepted: + {{ stats.results?.shares_good }} +
+
+ Rejected: + {{ stats.connection?.rejected }} +
+
+ Avg Time: + {{ stats.results?.avg_time | number }}s +
+
+ Uptime: + {{ stats.uptime | number }}s +
+
+ Pool Uptime: + {{ stats.connection?.uptime | number }}s +
-
- Difficulty: - {{ stats.connection?.diff | number }} -
-
- Accepted: - {{ stats.results?.shares_good }} -
-
- Rejected: - {{ stats.connection?.rejected }} -
-
- Avg Time: - {{ stats.results?.avg_time | number }}s -
-
- Uptime: - {{ stats.uptime | number }}s -
-
- Pool Uptime: - {{ stats.connection?.uptime | number }}s -
-
+ } } `, styles: [` - .stats-bar { + /* List Mode Styles */ + .stats-container.list-mode { + height: 100%; + max-height: 400px; + overflow-y: auto; + padding-right: 1rem; + background-color: #f9f9f9; + border: 1px solid #eee; + border-radius: 8px; + } + .list-mode .stats-dl { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem 1rem; + padding: 1rem; + } + .list-mode dt { + font-weight: bold; + color: #555; + grid-column: 1; + white-space: nowrap; + } + .list-mode dd { + margin: 0; + grid-column: 2; + text-align: right; + font-family: monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + /* Bar Mode Styles */ + .stats-bar.bar-mode { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; @@ -55,7 +123,7 @@ import { CommonModule } from '@angular/common'; border-radius: 8px; margin-bottom: 1rem; } - .stat-item { + .bar-mode .stat-item { display: flex; flex-direction: column; align-items: center; @@ -64,12 +132,12 @@ import { CommonModule } from '@angular/common'; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } - .label { + .bar-mode .label { font-size: 0.8rem; color: #666; margin-bottom: 0.25rem; } - .value { + .bar-mode .value { font-weight: bold; font-size: 1.1rem; } @@ -77,4 +145,5 @@ import { CommonModule } from '@angular/common'; }) export class StatsBarComponent { @Input() stats: any; + @Input() mode: 'bar' | 'list' = 'bar'; } diff --git a/ui/src/index.html b/ui/src/index.html index 8755be8..62922bf 100644 --- a/ui/src/index.html +++ b/ui/src/index.html @@ -1,5 +1,5 @@ - + Snider Mining @@ -8,10 +8,10 @@ - +
-
+

Mining Dashboard (Dev View)

@@ -22,10 +22,23 @@
-
-
- +
+ +
+

Wide Dashboard

+
+ +
+ + +
+

Standard Dashboard

+
+ +
+
+
@@ -35,9 +48,6 @@
-
- -
@@ -46,7 +56,7 @@
-