diff --git a/.playwright-mcp/console-page.png b/.playwright-mcp/console-page.png new file mode 100644 index 0000000..bfda903 Binary files /dev/null and b/.playwright-mcp/console-page.png differ diff --git a/.playwright-mcp/console-with-colors.png b/.playwright-mcp/console-with-colors.png new file mode 100644 index 0000000..9fda9a5 Binary files /dev/null and b/.playwright-mcp/console-with-colors.png differ diff --git a/.playwright-mcp/console-with-logs.png b/.playwright-mcp/console-with-logs.png new file mode 100644 index 0000000..4c5fbd2 Binary files /dev/null and b/.playwright-mcp/console-with-logs.png differ diff --git a/.playwright-mcp/dashboard-after-nav.png b/.playwright-mcp/dashboard-after-nav.png new file mode 100644 index 0000000..bae4a70 Binary files /dev/null and b/.playwright-mcp/dashboard-after-nav.png differ diff --git a/.playwright-mcp/dashboard-after-wait.png b/.playwright-mcp/dashboard-after-wait.png new file mode 100644 index 0000000..5199a32 Binary files /dev/null and b/.playwright-mcp/dashboard-after-wait.png differ diff --git a/.playwright-mcp/dashboard-direct-nav.png b/.playwright-mcp/dashboard-direct-nav.png new file mode 100644 index 0000000..4bebb90 Binary files /dev/null and b/.playwright-mcp/dashboard-direct-nav.png differ diff --git a/.playwright-mcp/dashboard-refresh-fixed.png b/.playwright-mcp/dashboard-refresh-fixed.png new file mode 100644 index 0000000..fdaddb3 Binary files /dev/null and b/.playwright-mcp/dashboard-refresh-fixed.png differ diff --git a/.playwright-mcp/dashboard-test.png b/.playwright-mcp/dashboard-test.png new file mode 100644 index 0000000..c62f8fe Binary files /dev/null and b/.playwright-mcp/dashboard-test.png differ diff --git a/pkg/mining/miner.go b/pkg/mining/miner.go index 7dc1ebf..89bbdb0 100644 --- a/pkg/mining/miner.go +++ b/pkg/mining/miner.go @@ -92,6 +92,7 @@ type BaseMiner struct { API *API `json:"api"` mu sync.RWMutex cmd *exec.Cmd + stdinPipe io.WriteCloser `json:"-"` HashrateHistory []HashratePoint `json:"hashrateHistory"` LowResHashrateHistory []HashratePoint `json:"lowResHashrateHistory"` LastLowResAggregation time.Time `json:"-"` @@ -135,9 +136,33 @@ func (b *BaseMiner) Stop() error { return errors.New("miner is not running") } + // Close stdin pipe if open + if b.stdinPipe != nil { + b.stdinPipe.Close() + b.stdinPipe = nil + } + return b.cmd.Process.Kill() } +// WriteStdin sends input to the miner's stdin (for console commands). +func (b *BaseMiner) WriteStdin(input string) error { + b.mu.RLock() + defer b.mu.RUnlock() + + if !b.Running || b.stdinPipe == nil { + return errors.New("miner is not running or stdin not available") + } + + // Append newline if not present + if !strings.HasSuffix(input, "\n") { + input += "\n" + } + + _, err := b.stdinPipe.Write([]byte(input)) + return err +} + // Uninstall removes all files related to the miner. func (b *BaseMiner) Uninstall() error { return os.RemoveAll(b.GetPath()) diff --git a/pkg/mining/mining.go b/pkg/mining/mining.go index d7bbf1a..528639a 100644 --- a/pkg/mining/mining.go +++ b/pkg/mining/mining.go @@ -27,6 +27,7 @@ type Miner interface { AddHashratePoint(point HashratePoint) ReduceHashrateHistory(now time.Time) GetLogs() []string + WriteStdin(input string) error } // InstallationDetails contains information about an installed miner. diff --git a/pkg/mining/service.go b/pkg/mining/service.go index 8e9df6b..a18c470 100644 --- a/pkg/mining/service.go +++ b/pkg/mining/service.go @@ -2,6 +2,7 @@ package mining import ( "context" + "encoding/base64" "encoding/json" "fmt" "log" @@ -129,6 +130,7 @@ func (s *Service) SetupRoutes() { minersGroup.GET("/:miner_name/stats", s.handleGetMinerStats) minersGroup.GET("/:miner_name/hashrate-history", s.handleGetMinerHashrateHistory) minersGroup.GET("/:miner_name/logs", s.handleGetMinerLogs) + minersGroup.POST("/:miner_name/stdin", s.handleMinerStdin) } // Historical data endpoints (database-backed) @@ -471,11 +473,11 @@ func (s *Service) handleGetMinerHashrateHistory(c *gin.Context) { // handleGetMinerLogs godoc // @Summary Get miner log output -// @Description Get the captured stdout/stderr output from a running miner +// @Description Get the captured stdout/stderr output from a running miner. Log lines are base64 encoded to preserve ANSI escape codes and special characters. // @Tags miners // @Produce json // @Param miner_name path string true "Miner Name" -// @Success 200 {array} string +// @Success 200 {array} string "Base64 encoded log lines" // @Router /miners/{miner_name}/logs [get] func (s *Service) handleGetMinerLogs(c *gin.Context) { minerName := c.Param("miner_name") @@ -485,7 +487,51 @@ func (s *Service) handleGetMinerLogs(c *gin.Context) { return } logs := miner.GetLogs() - c.JSON(http.StatusOK, logs) + // Base64 encode each log line to preserve ANSI escape codes and special characters + encodedLogs := make([]string, len(logs)) + for i, line := range logs { + encodedLogs[i] = base64.StdEncoding.EncodeToString([]byte(line)) + } + c.JSON(http.StatusOK, encodedLogs) +} + +// StdinInput represents input to send to miner's stdin +type StdinInput struct { + Input string `json:"input" binding:"required"` +} + +// handleMinerStdin godoc +// @Summary Send input to miner stdin +// @Description Send console commands to a running miner's stdin (e.g., 'h' for hashrate, 'p' for pause) +// @Tags miners +// @Accept json +// @Produce json +// @Param miner_name path string true "Miner Name" +// @Param input body StdinInput true "Input to send" +// @Success 200 {object} map[string]string +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /miners/{miner_name}/stdin [post] +func (s *Service) handleMinerStdin(c *gin.Context) { + minerName := c.Param("miner_name") + miner, err := s.Manager.GetMiner(minerName) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "miner not found"}) + return + } + + var input StdinInput + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid input: " + err.Error()}) + return + } + + if err := miner.WriteStdin(input.Input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "sent", "input": input.Input}) } // handleListProfiles godoc diff --git a/pkg/mining/ttminer_start.go b/pkg/mining/ttminer_start.go index 5e075db..ac0bdbb 100644 --- a/pkg/mining/ttminer_start.go +++ b/pkg/mining/ttminer_start.go @@ -39,6 +39,13 @@ func (m *TTMiner) Start(config *Config) error { m.cmd = exec.Command(m.MinerBinary, args...) + // Create stdin pipe for console commands + stdinPipe, err := m.cmd.StdinPipe() + if err != nil { + return fmt.Errorf("failed to create stdin pipe: %w", err) + } + m.stdinPipe = stdinPipe + // Always capture output to LogBuffer if m.LogBuffer != nil { m.cmd.Stdout = m.LogBuffer diff --git a/pkg/mining/xmrig_start.go b/pkg/mining/xmrig_start.go index 6d5180d..f9fbdfc 100644 --- a/pkg/mining/xmrig_start.go +++ b/pkg/mining/xmrig_start.go @@ -62,6 +62,13 @@ func (m *XMRigMiner) Start(config *Config) error { m.cmd = exec.Command(m.MinerBinary, args...) + // Create stdin pipe for console commands + stdinPipe, err := m.cmd.StdinPipe() + if err != nil { + return fmt.Errorf("failed to create stdin pipe: %w", err) + } + m.stdinPipe = stdinPipe + // Always capture output to LogBuffer if m.LogBuffer != nil { m.cmd.Stdout = m.LogBuffer diff --git a/ui/e2e/ui/console.e2e.spec.ts b/ui/e2e/ui/console.e2e.spec.ts new file mode 100644 index 0000000..1e922ac --- /dev/null +++ b/ui/e2e/ui/console.e2e.spec.ts @@ -0,0 +1,251 @@ +import { test, expect } from '@playwright/test'; +import { MainLayoutPage } from '../page-objects/main-layout.page'; +import { ConsolePage } from '../page-objects/console.page'; + +test.describe('Console Page', () => { + let layout: MainLayoutPage; + let consolePage: ConsolePage; + + test.beforeEach(async ({ page }) => { + layout = new MainLayoutPage(page); + consolePage = new ConsolePage(page); + await layout.goto(); + await layout.waitForLayoutLoad(); + await layout.navigateToConsole(); + }); + + test.describe('Console Layout', () => { + test('should display console page container', async () => { + await expect(consolePage.consolePage).toBeVisible(); + }); + + test('should display console header', async () => { + await expect(consolePage.tabsContainer).toBeVisible(); + }); + + test('should display console output area', async () => { + await expect(consolePage.consoleOutput).toBeVisible(); + }); + + test('should display console controls', async () => { + await expect(consolePage.autoScrollCheckbox).toBeVisible(); + await expect(consolePage.clearButton).toBeVisible(); + }); + }); + + test.describe('Worker Selection', () => { + test('should show worker dropdown when miners are running', async ({ page }) => { + // Check if there's a worker select dropdown or no miners message + const workerSelect = page.locator('.worker-select'); + const noMinersMsg = page.locator('.no-miners-msg'); + + // Either worker select or no miners message should be visible + const hasWorkerSelect = await workerSelect.isVisible(); + const hasNoMiners = await noMinersMsg.isVisible(); + + expect(hasWorkerSelect || hasNoMiners).toBe(true); + }); + + test('should auto-select first miner on load', async ({ page }) => { + const workerSelect = page.locator('.worker-select'); + + if (await workerSelect.isVisible()) { + // A miner should be selected (value should not be empty) + const selectedValue = await workerSelect.inputValue(); + expect(selectedValue).toBeTruthy(); + } + }); + + test('should display miner tabs when multiple miners running', async ({ page }) => { + // This test checks if tabs appear when there are multiple miners + // We just verify the tabs container exists in the header + const consoleTabs = page.locator('.console-tabs'); + // Tabs only show when multiple miners, so this may or may not be visible + // Just ensure no errors + await consolePage.tabsContainer.isVisible(); + }); + }); + + test.describe('Console Output', () => { + test('should display logs when miner is running', async ({ page }) => { + const workerSelect = page.locator('.worker-select'); + + if (await workerSelect.isVisible()) { + // Wait for logs to load (poll happens every 2 seconds) + await page.waitForTimeout(3000); + + // Check if logs are displayed or waiting message + const logLines = await consolePage.getLogLineCount(); + const waitingMsg = page.getByText(/Waiting for logs from/); + const hasWaitingMsg = await waitingMsg.isVisible(); + + // Either logs should be present or waiting message + expect(logLines > 0 || hasWaitingMsg).toBe(true); + } + }); + + test('should show empty state when no miners running', async ({ page }) => { + const noMinersMsg = page.locator('.no-miners-msg'); + + if (await noMinersMsg.isVisible()) { + // When no miners, empty state should show + const emptyState = consolePage.emptyState; + await expect(emptyState).toBeVisible(); + } + }); + + test('should style error lines correctly', async ({ page }) => { + // Wait for logs + await page.waitForTimeout(3000); + + const errorLines = page.locator('.log-line.error'); + const errorCount = await errorLines.count(); + + // If there are error lines, verify they have error styling + if (errorCount > 0) { + const firstError = errorLines.first(); + await expect(firstError).toHaveClass(/error/); + } + }); + + test('should style warning lines correctly', async ({ page }) => { + // Wait for logs + await page.waitForTimeout(3000); + + const warningLines = page.locator('.log-line.warning'); + const warningCount = await warningLines.count(); + + // If there are warning lines, verify they have warning styling + if (warningCount > 0) { + const firstWarning = warningLines.first(); + await expect(firstWarning).toHaveClass(/warning/); + } + }); + }); + + test.describe('Console Controls', () => { + test('should have auto-scroll enabled by default', async () => { + const isEnabled = await consolePage.isAutoScrollEnabled(); + expect(isEnabled).toBe(true); + }); + + test('should toggle auto-scroll when checkbox clicked', async () => { + // Initially enabled + expect(await consolePage.isAutoScrollEnabled()).toBe(true); + + // Toggle off + await consolePage.toggleAutoScroll(); + expect(await consolePage.isAutoScrollEnabled()).toBe(false); + + // Toggle back on + await consolePage.toggleAutoScroll(); + expect(await consolePage.isAutoScrollEnabled()).toBe(true); + }); + + test('should clear logs when clear button clicked', async ({ page }) => { + const workerSelect = page.locator('.worker-select'); + + if (await workerSelect.isVisible()) { + // Wait for logs to load + await page.waitForTimeout(3000); + + const initialLogCount = await consolePage.getLogLineCount(); + + if (initialLogCount > 0) { + // Clear logs + await consolePage.clearLogs(); + + // Verify logs are cleared + const finalLogCount = await consolePage.getLogLineCount(); + expect(finalLogCount).toBe(0); + } + } + }); + + test('should disable clear button when no logs', async ({ page }) => { + const workerSelect = page.locator('.worker-select'); + + if (await workerSelect.isVisible()) { + // Wait for logs + await page.waitForTimeout(3000); + + const logCount = await consolePage.getLogLineCount(); + + if (logCount > 0) { + // Clear logs + await consolePage.clearLogs(); + + // Clear button should be disabled now + const isEnabled = await consolePage.isClearButtonEnabled(); + expect(isEnabled).toBe(false); + } + } + }); + }); + + test.describe('Log Polling', () => { + test('should update logs periodically', async ({ page }) => { + const workerSelect = page.locator('.worker-select'); + + if (await workerSelect.isVisible()) { + // Wait for initial logs + await page.waitForTimeout(3000); + + const initialLogs = await consolePage.getLogContent(); + + // Wait for another poll cycle (2+ seconds) + await page.waitForTimeout(3000); + + // Check if new logs appeared (miner generates output) + // We can't guarantee new logs, but verify no errors + const finalLogs = await consolePage.getLogContent(); + + // Logs array should still be valid + expect(Array.isArray(finalLogs)).toBe(true); + } + }); + }); + + test.describe('Worker Switching', () => { + test('should switch miner and clear logs when selection changes', async ({ page }) => { + const workerSelect = page.locator('.worker-select'); + + if (await workerSelect.isVisible()) { + // Get available options + const options = await workerSelect.locator('option').allTextContents(); + + if (options.length > 1) { + // Wait for initial logs + await page.waitForTimeout(3000); + + // Get initial selected value + const initialValue = await workerSelect.inputValue(); + + // Find a different miner to select + const otherMiner = options.find(opt => opt !== initialValue); + + if (otherMiner) { + // Select different miner + await workerSelect.selectOption(otherMiner); + + // Logs should be cleared momentarily (new miner selected) + // Then new logs should load + await page.waitForTimeout(500); + } + } + } + }); + }); + + test.describe('Responsive Behavior', () => { + test('should maintain layout when resized', async ({ page }) => { + // Resize to smaller viewport + await page.setViewportSize({ width: 800, height: 600 }); + + // Console should still be visible and functional + await expect(consolePage.consolePage).toBeVisible(); + await expect(consolePage.consoleOutput).toBeVisible(); + await expect(consolePage.clearButton).toBeVisible(); + }); + }); +}); diff --git a/ui/playwright-report/data/0edcb92400b64a8fa50036f5c425739793f50026.md b/ui/playwright-report/data/0edcb92400b64a8fa50036f5c425739793f50026.md deleted file mode 100644 index 8ec7276..0000000 --- a/ui/playwright-report/data/0edcb92400b64a8fa50036f5c425739793f50026.md +++ /dev/null @@ -1,78 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "Quick Test 1767041846199" - - option "Mining Test 1767041844401" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "Long Test 1767041847141" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/0f6d9ded6e423e26cbd76440b0a22264786ed6c5.md b/ui/playwright-report/data/0f6d9ded6e423e26cbd76440b0a22264786ed6c5.md deleted file mode 100644 index af32bfc..0000000 --- a/ui/playwright-report/data/0f6d9ded6e423e26cbd76440b0a22264786ed6c5.md +++ /dev/null @@ -1,80 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "Long Test 1767041847141" - - option "E2E List Test Profile" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - option "Quick Test 1767041846199" - - option "Mining Test 1767041844401" - - option "E2E Delete Test Profile" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/1132e270e6c1ca34a22bd0b099232dd63ac5f896.md b/ui/playwright-report/data/1132e270e6c1ca34a22bd0b099232dd63ac5f896.md deleted file mode 100644 index 7e91955..0000000 --- a/ui/playwright-report/data/1132e270e6c1ca34a22bd0b099232dd63ac5f896.md +++ /dev/null @@ -1,21 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - generic [ref=e6]: - - img [ref=e7] - - text: Setup Required - - paragraph [ref=e9]: To begin, please install a miner from the list below. - - heading "Available Miners" [level=4] [ref=e10] - - generic [ref=e11]: - - generic [ref=e12]: - - text: xmrig - - button "Install" [ref=e13]: - - img [ref=e14] - - text: Install - - generic [ref=e16]: - - text: tt-miner - - button "Install" [ref=e17]: - - img [ref=e18] - - text: Install -``` \ No newline at end of file diff --git a/ui/playwright-report/data/14eef6a11d1dacfd76cb123962d7741922305d2f.md b/ui/playwright-report/data/14eef6a11d1dacfd76cb123962d7741922305d2f.md deleted file mode 100644 index d0d40e1..0000000 --- a/ui/playwright-report/data/14eef6a11d1dacfd76cb123962d7741922305d2f.md +++ /dev/null @@ -1,78 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-editform-1767041826397" - - option "Quick Test 1767041846199" - - option "Mining Test 1767041844401" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "Long Test 1767041847141" - - option "FT-start-1767041826205" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/157a13cabd61a71672732473d3eecbb55653ce4e.md b/ui/playwright-report/data/157a13cabd61a71672732473d3eecbb55653ce4e.md deleted file mode 100644 index 6ceb340..0000000 --- a/ui/playwright-report/data/157a13cabd61a71672732473d3eecbb55653ce4e.md +++ /dev/null @@ -1,75 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/28b64b5f69d99b85092bec002f6b42d2f7d8422d.md b/ui/playwright-report/data/28b64b5f69d99b85092bec002f6b42d2f7d8422d.md deleted file mode 100644 index 3250859..0000000 --- a/ui/playwright-report/data/28b64b5f69d99b85092bec002f6b42d2f7d8422d.md +++ /dev/null @@ -1,69 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "E2E Test 1767041854325" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/2ace2b0b01522664d72feb6a7d1e95707d32712c.png b/ui/playwright-report/data/2ace2b0b01522664d72feb6a7d1e95707d32712c.png deleted file mode 100644 index fe0a46a..0000000 Binary files a/ui/playwright-report/data/2ace2b0b01522664d72feb6a7d1e95707d32712c.png and /dev/null differ diff --git a/ui/playwright-report/data/2b004b9308b8a58e2a6df6e1502ed6b865d05e27.png b/ui/playwright-report/data/2b004b9308b8a58e2a6df6e1502ed6b865d05e27.png deleted file mode 100644 index 231fba9..0000000 Binary files a/ui/playwright-report/data/2b004b9308b8a58e2a6df6e1502ed6b865d05e27.png and /dev/null differ diff --git a/ui/playwright-report/data/354fb4abb943582a02bd5adc3477c7f8f9892c96.md b/ui/playwright-report/data/354fb4abb943582a02bd5adc3477c7f8f9892c96.md deleted file mode 100644 index 7058ac9..0000000 --- a/ui/playwright-report/data/354fb4abb943582a02bd5adc3477c7f8f9892c96.md +++ /dev/null @@ -1,74 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/3addb24abc0e7cd9cd0ed5adc118bb1d5ef11aac.md b/ui/playwright-report/data/3addb24abc0e7cd9cd0ed5adc118bb1d5ef11aac.md deleted file mode 100644 index c499d8e..0000000 --- a/ui/playwright-report/data/3addb24abc0e7cd9cd0ed5adc118bb1d5ef11aac.md +++ /dev/null @@ -1,70 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 8s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "1" - - generic [ref=e76]: Workers - - button "All Workers (1)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (1) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - generic [ref=e92]: "Worker:" - - combobox [ref=e93] [cursor=pointer]: - - option "xmrig-279" [selected] - - paragraph [ref=e96]: Waiting for logs from xmrig-279... - - generic [ref=e97]: - - generic [ref=e98] [cursor=pointer]: - - checkbox "Auto-scroll" [checked] [ref=e99] - - generic [ref=e100]: Auto-scroll - - button "Clear" [disabled] [ref=e101]: - - img - - text: Clear -``` \ No newline at end of file diff --git a/ui/playwright-report/data/4b3aab4582248365a19b6ac5b13f630ccb9feff1.md b/ui/playwright-report/data/4b3aab4582248365a19b6ac5b13f630ccb9feff1.md deleted file mode 100644 index 079e523..0000000 --- a/ui/playwright-report/data/4b3aab4582248365a19b6ac5b13f630ccb9feff1.md +++ /dev/null @@ -1,73 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-start-1767041826205" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/4cf99ef5b45a3f0d273c7e4090fa00abacace34d.md b/ui/playwright-report/data/4cf99ef5b45a3f0d273c7e4090fa00abacace34d.md deleted file mode 100644 index ad49250..0000000 --- a/ui/playwright-report/data/4cf99ef5b45a3f0d273c7e4090fa00abacace34d.md +++ /dev/null @@ -1,74 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/657e34b175b5302541c51d1a4b41609f8e2a3663.png b/ui/playwright-report/data/657e34b175b5302541c51d1a4b41609f8e2a3663.png deleted file mode 100644 index a9591d9..0000000 Binary files a/ui/playwright-report/data/657e34b175b5302541c51d1a4b41609f8e2a3663.png and /dev/null differ diff --git a/ui/playwright-report/data/66fe8c8c17fe290d90f58e438d1fcf42e522de0d.md b/ui/playwright-report/data/66fe8c8c17fe290d90f58e438d1fcf42e522de0d.md deleted file mode 100644 index 302215a..0000000 --- a/ui/playwright-report/data/66fe8c8c17fe290d90f58e438d1fcf42e522de0d.md +++ /dev/null @@ -1,68 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/790a9ab81aaaa8fde650345152992a578a2be1e8.png b/ui/playwright-report/data/790a9ab81aaaa8fde650345152992a578a2be1e8.png deleted file mode 100644 index a6b2ee7..0000000 Binary files a/ui/playwright-report/data/790a9ab81aaaa8fde650345152992a578a2be1e8.png and /dev/null differ diff --git a/ui/playwright-report/data/7af2399d286704615aea1c24d7982e529a826e2d.png b/ui/playwright-report/data/7af2399d286704615aea1c24d7982e529a826e2d.png deleted file mode 100644 index 2fedd2a..0000000 Binary files a/ui/playwright-report/data/7af2399d286704615aea1c24d7982e529a826e2d.png and /dev/null differ diff --git a/ui/playwright-report/data/878ef902a6d7914af479f8f650843ddd08a43a8e.md b/ui/playwright-report/data/878ef902a6d7914af479f8f650843ddd08a43a8e.md deleted file mode 100644 index bdb2655..0000000 --- a/ui/playwright-report/data/878ef902a6d7914af479f8f650843ddd08a43a8e.md +++ /dev/null @@ -1,114 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 1s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "2" - - generic [ref=e76]: Workers - - button "All Workers (2)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (2) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e90]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - option "Quick Test 1767041846199" - - option "Mining Test 1767041844401" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - button "Stop All" [ref=e95] [cursor=pointer]: - - img [ref=e96] - - text: Stop All - - table [ref=e100]: - - rowgroup [ref=e101]: - - row "Worker Hashrate Shares Efficiency Uptime Pool Actions" [ref=e102]: - - columnheader "Worker" [ref=e103] - - columnheader "Hashrate" [ref=e104] - - columnheader "Shares" [ref=e105] - - columnheader "Efficiency" [ref=e106] - - columnheader "Uptime" [ref=e107] - - columnheader "Pool" [ref=e108] - - columnheader "Actions" [ref=e109] - - rowgroup [ref=e110]: - - row "xmrig-607 0H/s 0 100.0% 1s N/A" [ref=e111]: - - cell "xmrig-607" [ref=e112]: - - generic [ref=e115]: xmrig-607 - - cell "0H/s" [ref=e116]: 0H/s - - cell "0" [ref=e118] - - cell "100.0%" [ref=e119] - - cell "1s" [ref=e120] - - cell "N/A" [ref=e121] - - cell [ref=e122]: - - button "View logs" [ref=e123] [cursor=pointer]: - - img [ref=e124] - - button "Stop worker" [ref=e126] [cursor=pointer]: - - img [ref=e127] - - row "xmrig-51 0H/s 0 100.0% 0s N/A" [ref=e129]: - - cell "xmrig-51" [ref=e130]: - - generic [ref=e133]: xmrig-51 - - cell "0H/s" [ref=e134]: 0H/s - - cell "0" [ref=e136] - - cell "100.0%" [ref=e137] - - cell "0s" [ref=e138] - - cell "N/A" [ref=e139] - - cell [ref=e140]: - - button "View logs" [ref=e141] [cursor=pointer]: - - img [ref=e142] - - button "Stop worker" [ref=e144] [cursor=pointer]: - - img [ref=e145] -``` \ No newline at end of file diff --git a/ui/playwright-report/data/a0b03910af80ffb4bc7fabc54e8f1ff5bda0a5e8.md b/ui/playwright-report/data/a0b03910af80ffb4bc7fabc54e8f1ff5bda0a5e8.md deleted file mode 100644 index 4348454..0000000 --- a/ui/playwright-report/data/a0b03910af80ffb4bc7fabc54e8f1ff5bda0a5e8.md +++ /dev/null @@ -1,80 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "Long Test 1767041847141" - - option "E2E List Test Profile" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - option "Quick Test 1767041846199" - - option "Mining Test 1767041844401" - - option "E2E Delete Test Profile" - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/c7ec0997ccf643886ad272910fb941c14ca574bf.md b/ui/playwright-report/data/c7ec0997ccf643886ad272910fb941c14ca574bf.md deleted file mode 100644 index 4f418ab..0000000 --- a/ui/playwright-report/data/c7ec0997ccf643886ad272910fb941c14ca574bf.md +++ /dev/null @@ -1,75 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-cancel-1767041832358" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/cfe28b9421da79bca80340a4079378e4eedbb45f.png b/ui/playwright-report/data/cfe28b9421da79bca80340a4079378e4eedbb45f.png deleted file mode 100644 index ca15556..0000000 Binary files a/ui/playwright-report/data/cfe28b9421da79bca80340a4079378e4eedbb45f.png and /dev/null differ diff --git a/ui/playwright-report/data/d40dccfcfa44dcfb0d7b3b03a2db5ce4f1c4458b.png b/ui/playwright-report/data/d40dccfcfa44dcfb0d7b3b03a2db5ce4f1c4458b.png deleted file mode 100644 index e162e77..0000000 Binary files a/ui/playwright-report/data/d40dccfcfa44dcfb0d7b3b03a2db5ce4f1c4458b.png and /dev/null differ diff --git a/ui/playwright-report/data/e434fb46af5e4e85f54a838ba5314eb607fff231.md b/ui/playwright-report/data/e434fb46af5e4e85f54a838ba5314eb607fff231.md deleted file mode 100644 index 915dcb6..0000000 --- a/ui/playwright-report/data/e434fb46af5e4e85f54a838ba5314eb607fff231.md +++ /dev/null @@ -1,74 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "Mining Test 1767031630070" - - option "FT-display-1767041826192" - - option "FT-delete-1767041826329" - - option "FT-edit-1767041826330" - - option "FT-start-1767041826205" - - option "FT-editform-1767041826397" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/data/e998700c59e379b3706797d56a179af6f01b4dbb.md b/ui/playwright-report/data/e998700c59e379b3706797d56a179af6f01b4dbb.md deleted file mode 100644 index 8a55f34..0000000 --- a/ui/playwright-report/data/e998700c59e379b3706797d56a179af6f01b4dbb.md +++ /dev/null @@ -1,69 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e5]: - - complementary [ref=e7]: - - generic [ref=e8]: - - generic [ref=e9]: - - img - - generic [ref=e11]: Mining - - button [ref=e12] [cursor=pointer]: - - img [ref=e13] - - navigation [ref=e15]: - - button "Workers" [ref=e16] [cursor=pointer]: - - generic [ref=e18]: Workers - - button "Graphs" [ref=e19] [cursor=pointer]: - - generic [ref=e21]: Graphs - - button "Console" [ref=e22] [cursor=pointer]: - - generic [ref=e24]: Console - - button "Pools" [ref=e25] [cursor=pointer]: - - generic [ref=e27]: Pools - - button "Profiles" [ref=e28] [cursor=pointer]: - - generic [ref=e30]: Profiles - - button "Miners" [ref=e31] [cursor=pointer]: - - generic [ref=e33]: Miners - - generic [ref=e37]: Mining Active - - generic [ref=e38]: - - generic [ref=e39]: - - generic [ref=e41]: - - generic [ref=e42]: - - img [ref=e43] - - generic [ref=e45]: - - generic [ref=e46]: "0" - - generic [ref=e47]: H/s - - generic [ref=e48]: Hashrate - - generic [ref=e50]: - - img [ref=e51] - - generic [ref=e54]: "0" - - generic [ref=e55]: Shares - - generic [ref=e57]: - - img [ref=e58] - - generic [ref=e61]: 0s - - generic [ref=e62]: Uptime - - generic [ref=e64]: - - img [ref=e65] - - generic [ref=e68]: Not connected - - generic [ref=e69]: Pool - - generic [ref=e71]: - - img [ref=e72] - - generic [ref=e75]: "0" - - generic [ref=e76]: Workers - - button "All Workers (0)" [ref=e79] [cursor=pointer]: - - generic [ref=e80]: - - img [ref=e81] - - generic [ref=e83]: All Workers - - generic [ref=e84]: (0) - - img [ref=e85] - - generic [ref=e89]: - - generic [ref=e91]: - - combobox [ref=e92]: - - option "Select profile..." [disabled] [selected] - - option "Mining Test 1767031630070" - - button "Start" [disabled] [ref=e93]: - - img - - text: Start - - generic [ref=e95]: - - img [ref=e96] - - heading "No Active Workers" [level=3] [ref=e98] - - paragraph [ref=e99]: Select a profile and start mining to see workers here. -``` \ No newline at end of file diff --git a/ui/playwright-report/index.html b/ui/playwright-report/index.html index 2d878d8..41f218e 100644 --- a/ui/playwright-report/index.html +++ b/ui/playwright-report/index.html @@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
- \ No newline at end of file + \ No newline at end of file diff --git a/ui/src/app/app.routes.ts b/ui/src/app/app.routes.ts index a4c5130..840ec46 100644 --- a/ui/src/app/app.routes.ts +++ b/ui/src/app/app.routes.ts @@ -1,29 +1,24 @@ import { Routes } from '@angular/router'; -import { MainLayoutComponent } from './layouts/main-layout.component'; import { WorkersComponent } from './pages/workers/workers.component'; import { GraphsComponent } from './pages/graphs/graphs.component'; import { ConsoleComponent } from './pages/console/console.component'; import { PoolsComponent } from './pages/pools/pools.component'; import { ProfilesComponent } from './pages/profiles/profiles.component'; import { MinersComponent } from './pages/miners/miners.component'; +import { NodesComponent } from './pages/nodes/nodes.component'; import { SystemTrayComponent } from './pages/system-tray/system-tray.component'; export const routes: Routes = [ // System tray is standalone without layout { path: 'system-tray', component: SystemTrayComponent }, - // All other routes use the main layout - { - path: '', - component: MainLayoutComponent, - children: [ - { path: '', redirectTo: 'workers', pathMatch: 'full' }, - { path: 'workers', component: WorkersComponent }, - { path: 'graphs', component: GraphsComponent }, - { path: 'console', component: ConsoleComponent }, - { path: 'pools', component: PoolsComponent }, - { path: 'profiles', component: ProfilesComponent }, - { path: 'miners', component: MinersComponent }, - ] - }, + // Main app routes - MainLayoutComponent is rendered directly and contains router-outlet + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: GraphsComponent }, + { path: 'workers', component: WorkersComponent }, + { path: 'console', component: ConsoleComponent }, + { path: 'pools', component: PoolsComponent }, + { path: 'profiles', component: ProfilesComponent }, + { path: 'miners', component: MinersComponent }, + { path: 'nodes', component: NodesComponent }, ]; diff --git a/ui/src/app/app.ts b/ui/src/app/app.ts index c13e037..09605d6 100644 --- a/ui/src/app/app.ts +++ b/ui/src/app/app.ts @@ -10,7 +10,7 @@ import { MainLayoutComponent } from './layouts/main-layout.component'; imports: [ CommonModule, SetupWizardComponent, - MainLayoutComponent + MainLayoutComponent, ], templateUrl: './app.html', styleUrls: ['./app.css'], diff --git a/ui/src/app/chart.component.css b/ui/src/app/chart.component.css index 88bf219..bb2692c 100644 --- a/ui/src/app/chart.component.css +++ b/ui/src/app/chart.component.css @@ -1,11 +1,55 @@ .chart-container { width: 100%; - height: 300px; min-height: 200px; display: flex; flex-direction: column; } +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0.75rem; + gap: 1rem; +} + +.chart-title { + margin: 0; + font-size: 0.875rem; + font-weight: 600; + color: #e2e8f0; +} + +.time-range-selector { + display: flex; + gap: 0.25rem; + flex-wrap: wrap; +} + +.time-range-btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + color: #94a3b8; + background: transparent; + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 0.25rem; + cursor: pointer; + transition: all 0.15s ease; +} + +.time-range-btn:hover { + color: #e2e8f0; + background: rgba(148, 163, 184, 0.1); + border-color: rgba(148, 163, 184, 0.3); +} + +.time-range-btn.active { + color: var(--color-accent-400, #00d4ff); + background: rgba(0, 212, 255, 0.1); + border-color: var(--color-accent-500, #00d4ff); +} + .hashrate-chart { width: 100%; height: 300px; diff --git a/ui/src/app/chart.component.html b/ui/src/app/chart.component.html index b2887da..89b3574 100644 --- a/ui/src/app/chart.component.html +++ b/ui/src/app/chart.component.html @@ -1,4 +1,17 @@
+
+

Hashrate History

+
+ @for (range of minerService.timeRanges; track range.minutes) { + + } +
+
{ - const historyMap = this.minerService.hashrateHistory(); - - // Skip if no data - if (historyMap.size === 0) { - return; - } + // Use 24-hour historical data from database + const historyMap = this.minerService.historicalHashrate(); // Clean up colors for miners no longer active const activeNames = new Set(historyMap.keys()); @@ -107,7 +103,7 @@ export class ChartComponent { // Build new chart options this.chartOptions = { ...this.createBaseChartOptions(), - title: { text: 'Total Hashrate' }, + title: { text: '' }, chart: { ...this.createBaseChartOptions().chart, type: 'area' diff --git a/ui/src/app/components/sidebar/sidebar.component.ts b/ui/src/app/components/sidebar/sidebar.component.ts index 334721b..10924e4 100644 --- a/ui/src/app/components/sidebar/sidebar.component.ts +++ b/ui/src/app/components/sidebar/sidebar.component.ts @@ -1,10 +1,11 @@ -import { Component, signal, output, input } from '@angular/core'; +import { Component, signal, output, input, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; interface NavItem { id: string; label: string; - icon: string; + icon: SafeHtml; route: string; } @@ -210,55 +211,61 @@ interface NavItem { `] }) export class SidebarComponent { + private sanitizer = inject(DomSanitizer); + collapsed = signal(false); - currentRoute = input('workers'); + currentRoute = input('dashboard'); routeChange = output(); navItems: NavItem[] = [ + { + id: 'dashboard', + label: 'Dashboard', + route: 'dashboard', + icon: this.trustIcon('') + }, { id: 'workers', label: 'Workers', route: 'workers', - icon: '' - }, - { - id: 'graphs', - label: 'Graphs', - route: 'graphs', - icon: '' + icon: this.trustIcon('') }, { id: 'console', label: 'Console', route: 'console', - icon: '' + icon: this.trustIcon('') }, { id: 'pools', label: 'Pools', route: 'pools', - icon: '' + icon: this.trustIcon('') }, { id: 'profiles', label: 'Profiles', route: 'profiles', - icon: '' + icon: this.trustIcon('') }, { id: 'miners', label: 'Miners', route: 'miners', - icon: '' + icon: this.trustIcon('') }, { id: 'nodes', label: 'Nodes', route: 'nodes', - icon: '' + icon: this.trustIcon('') } ]; + private trustIcon(svg: string): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(svg); + } + toggleCollapse() { this.collapsed.update(v => !v); } diff --git a/ui/src/app/layouts/main-layout.component.ts b/ui/src/app/layouts/main-layout.component.ts index d836762..3747771 100644 --- a/ui/src/app/layouts/main-layout.component.ts +++ b/ui/src/app/layouts/main-layout.component.ts @@ -1,31 +1,21 @@ -import { Component, signal } from '@angular/core'; +import { Component, inject, AfterViewInit } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { Router, RouterOutlet, NavigationEnd } from '@angular/router'; +import { filter, map } from 'rxjs/operators'; +import { toSignal } from '@angular/core/rxjs-interop'; import { SidebarComponent } from '../components/sidebar/sidebar.component'; import { StatsPanelComponent } from '../components/stats-panel/stats-panel.component'; import { MinerSwitcherComponent } from '../components/miner-switcher/miner-switcher.component'; -import { WorkersComponent } from '../pages/workers/workers.component'; -import { GraphsComponent } from '../pages/graphs/graphs.component'; -import { ConsoleComponent } from '../pages/console/console.component'; -import { PoolsComponent } from '../pages/pools/pools.component'; -import { ProfilesComponent } from '../pages/profiles/profiles.component'; -import { MinersComponent } from '../pages/miners/miners.component'; -import { NodesComponent } from '../pages/nodes/nodes.component'; @Component({ selector: 'app-main-layout', standalone: true, imports: [ CommonModule, + RouterOutlet, SidebarComponent, StatsPanelComponent, MinerSwitcherComponent, - WorkersComponent, - GraphsComponent, - ConsoleComponent, - PoolsComponent, - ProfilesComponent, - MinersComponent, - NodesComponent ], template: `
@@ -38,32 +28,7 @@ import { NodesComponent } from '../pages/nodes/nodes.component';
- @switch (currentRoute()) { - @case ('workers') { - - } - @case ('graphs') { - - } - @case ('console') { - - } - @case ('pools') { - - } - @case ('profiles') { - - } - @case ('miners') { - - } - @case ('nodes') { - - } - @default { - - } - } +
@@ -102,17 +67,42 @@ import { NodesComponent } from '../pages/nodes/nodes.component'; } `] }) -export class MainLayoutComponent { - currentRoute = signal('workers'); - private editingProfileId: string | null = null; +export class MainLayoutComponent implements AfterViewInit { + private router = inject(Router); + + // Track current route from router events + currentRoute = toSignal( + this.router.events.pipe( + filter((event): event is NavigationEnd => event instanceof NavigationEnd), + map(event => { + // Extract route from URL like "/#/workers" or "/workers" + const url = event.urlAfterRedirects; + const segments = url.split('/').filter(s => s && s !== '#'); + return segments[0] || 'dashboard'; + }) + ), + { initialValue: this.getInitialRoute() } + ); + + private getInitialRoute(): string { + const url = this.router.url; + const segments = url.split('/').filter(s => s && s !== '#'); + return segments[0] || 'dashboard'; + } + + ngAfterViewInit() { + // Re-trigger navigation after router-outlet is available + // This handles the case where router tried to navigate before outlet existed + const route = this.getInitialRoute(); + setTimeout(() => this.router.navigate(['/', route]), 0); + } onRouteChange(route: string) { - this.currentRoute.set(route); + this.router.navigate(['/', route]); } navigateToProfiles(profileId: string) { - this.editingProfileId = profileId; - this.currentRoute.set('profiles'); - // TODO: Could emit event to profiles page to open edit modal for this profile + // TODO: Could pass profileId via query params or state + this.router.navigate(['/', 'profiles']); } } diff --git a/ui/src/app/miner.service.ts b/ui/src/app/miner.service.ts index aa0fcf6..6c1d60d 100644 --- a/ui/src/app/miner.service.ts +++ b/ui/src/app/miner.service.ts @@ -63,6 +63,24 @@ export class MinerService implements OnDestroy { // Separate signal for hashrate history as it updates frequently public hashrateHistory = signal>(new Map()); + // Historical hashrate data from database with configurable time range + public historicalHashrate = signal>(new Map()); + public selectedTimeRange = signal(60); // Default 60 minutes + private historyPollingSubscription?: Subscription; + + // Available time ranges in minutes + public readonly timeRanges = [ + { label: '5m', minutes: 5 }, + { label: '15m', minutes: 15 }, + { label: '30m', minutes: 30 }, + { label: '45m', minutes: 45 }, + { label: '1h', minutes: 60 }, + { label: '3h', minutes: 180 }, + { label: '6h', minutes: 360 }, + { label: '12h', minutes: 720 }, + { label: '24h', minutes: 1440 }, + ]; + // --- View Mode Signals (single/multi miner view) --- public viewMode = signal<'all' | 'single'>('all'); public selectedMinerName = signal(null); @@ -92,10 +110,12 @@ export class MinerService implements OnDestroy { constructor(private http: HttpClient) { this.forceRefreshState(); this.startPollingLive_Data(); + this.startPollingHistoricalData(); } ngOnDestroy(): void { this.stopPolling(); + this.historyPollingSubscription?.unsubscribe(); } // --- Data Loading and Polling Logic --- @@ -116,6 +136,8 @@ export class MinerService implements OnDestroy { if (initialState) { this.state.set(initialState); this.updateHashrateHistory(initialState.runningMiners); + // Fetch historical data now that we know which miners are running + this.fetchHistoricalHashrate(); } }); } @@ -132,6 +154,66 @@ export class MinerService implements OnDestroy { }); } + /** + * Starts a polling interval to fetch historical data from database. + * Polls every 30 seconds. Initial fetch happens in forceRefreshState after miners are loaded. + */ + private startPollingHistoricalData() { + // Poll every 30 seconds (initial fetch happens in forceRefreshState) + this.historyPollingSubscription = interval(30000).subscribe(() => { + this.fetchHistoricalHashrate(); + }); + } + + /** + * Fetches 24-hour historical hashrate data for all running miners from the database. + */ + private fetchHistoricalHashrate() { + const runningMiners = this.state().runningMiners; + if (runningMiners.length === 0) { + this.historicalHashrate.set(new Map()); + return; + } + + // Fetch historical data for each running miner + const requests = runningMiners.map(miner => + this.getHistoricalHashrateForMiner(miner.name).pipe( + map(data => ({ name: miner.name, data })), + catchError(() => of({ name: miner.name, data: [] as HashratePoint[] })) + ) + ); + + forkJoin(requests).subscribe(results => { + const newHistory = new Map(); + results.forEach(result => { + if (result.data && result.data.length > 0) { + newHistory.set(result.name, result.data); + } + }); + this.historicalHashrate.set(newHistory); + }); + } + + /** + * Fetches historical hashrate for a specific miner based on selected time range. + */ + private getHistoricalHashrateForMiner(minerName: string) { + const minutes = this.selectedTimeRange(); + const since = new Date(Date.now() - minutes * 60 * 1000).toISOString(); + const until = new Date().toISOString(); + return this.http.get( + `${this.apiBaseUrl}/history/miners/${minerName}/hashrate?since=${since}&until=${until}` + ); + } + + /** + * Sets the time range for historical data and refreshes immediately. + */ + public setTimeRange(minutes: number) { + this.selectedTimeRange.set(minutes); + this.fetchHistoricalHashrate(); + } + private stopPolling() { this.pollingSubscription?.unsubscribe(); } @@ -185,7 +267,24 @@ export class MinerService implements OnDestroy { } getMinerLogs(minerName: string) { - return this.http.get(`${this.apiBaseUrl}/miners/${minerName}/logs`); + return this.http.get(`${this.apiBaseUrl}/miners/${minerName}/logs`).pipe( + map(logs => logs.map(line => { + try { + // Decode base64 encoded log lines + return atob(line); + } catch { + // If decoding fails, return the original line + return line; + } + })) + ); + } + + sendStdin(minerName: string, input: string) { + return this.http.post<{status: string, input: string}>( + `${this.apiBaseUrl}/miners/${minerName}/stdin`, + { input } + ); } createProfile(profile: MiningProfile) { diff --git a/ui/src/app/pages/console/console.component.ts b/ui/src/app/pages/console/console.component.ts index bf9bd9d..180bac0 100644 --- a/ui/src/app/pages/console/console.component.ts +++ b/ui/src/app/pages/console/console.component.ts @@ -1,7 +1,7 @@ import { Component, inject, computed, signal, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewChecked } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { MinerService } from '../../miner.service'; -import { HttpClient } from '@angular/common/http'; import { interval, Subscription, switchMap } from 'rxjs'; @Component({ @@ -58,7 +58,7 @@ import { interval, Subscription, switchMap } from 'rxjs'; @if (logs().length > 0) { @for (line of logs(); track $index) {
- {{ line }} +
} } @else if (activeMiner()) { @@ -75,6 +75,19 @@ import { interval, Subscription, switchMap } from 'rxjs'; } + +
+ > + +
+