Add fleet ops dashboard, Calibrate enforcement, and dead-code cleanup.
Ship live alerts, pool status, AI monitor, remote agent commands, build manager, and uninstall flow; wire Calibrate settings (WS ping, pool traffic log, retention limits) at runtime and exclude server/data from git.
This commit is contained in:
41
server/internal/db/retention.go
Normal file
41
server/internal/db/retention.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"crypto-miner-server/internal/models"
|
||||
)
|
||||
|
||||
// PurgeHashrateSamplesBefore deletes samples older than cutoff.
|
||||
func (d *Database) PurgeHashrateSamplesBefore(cutoff time.Time) (int64, error) {
|
||||
res, err := d.Exec("DELETE FROM hashrate_samples WHERE timestamp < ?", cutoff)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// ListBuildsOlderThan returns build records created before cutoff.
|
||||
func (d *Database) ListBuildsOlderThan(cutoff time.Time) ([]*models.BuildRecord, error) {
|
||||
rows, err := d.Query(`SELECT id, worker_name, server_url, wallet, threads, file_size, file_path, created_at, pool_host, pool_port, pool_tls, pool_pass FROM builds WHERE created_at < ?`, cutoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []*models.BuildRecord
|
||||
for rows.Next() {
|
||||
b := &models.BuildRecord{}
|
||||
if err := rows.Scan(&b.ID, &b.WorkerName, &b.ServerURL, &b.Wallet, &b.Threads, &b.FileSize, &b.FilePath, &b.CreatedAt,
|
||||
&b.PoolHost, &b.PoolPort, &b.PoolTLS, &b.PoolPass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, b)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeleteBuild removes a build record by id.
|
||||
func (d *Database) DeleteBuild(id string) error {
|
||||
_, err := d.Exec("DELETE FROM builds WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
33
server/internal/db/retention_test.go
Normal file
33
server/internal/db/retention_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPurgeHashrateSamplesBefore(t *testing.T) {
|
||||
d, err := New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
_, err = d.Exec("INSERT INTO hashrate_samples (agent_id, hashrate, timestamp) VALUES ('a1', 100, ?)",
|
||||
time.Now().Add(-48*time.Hour))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = d.Exec("INSERT INTO hashrate_samples (agent_id, hashrate, timestamp) VALUES ('a1', 200, ?)",
|
||||
time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := d.PurgeHashrateSamplesBefore(time.Now().Add(-24 * time.Hour))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("expected 1 purged, got %d", n)
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
"crypto-miner-server/internal/models"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
@@ -190,9 +190,18 @@ func (d *Database) ListAgents() ([]*models.Agent, error) {
|
||||
|
||||
// Share operations
|
||||
|
||||
func (d *Database) InsertShare(s *models.Share) error {
|
||||
func (d *Database) InsertShare(s *models.Share) (int64, error) {
|
||||
query := `INSERT INTO shares (agent_id, job_id, difficulty, accepted, hash, nonce, error, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
_, err := d.Exec(query, s.AgentID, s.JobID, s.Difficulty, boolToInt(s.Accepted), s.Hash, s.Nonce, s.Error, s.Timestamp)
|
||||
res, err := d.Exec(query, s.AgentID, s.JobID, s.Difficulty, boolToInt(s.Accepted), s.Hash, s.Nonce, s.Error, s.Timestamp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *Database) UpdateShareResult(id int64, accepted bool, errMsg string) error {
|
||||
query := `UPDATE shares SET accepted = ?, error = ? WHERE id = ?`
|
||||
_, err := d.Exec(query, boolToInt(accepted), errMsg, id)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -285,13 +294,13 @@ func (d *Database) ListBuilds(limit int) ([]*models.BuildRecord, error) {
|
||||
// Stats
|
||||
|
||||
type FleetStats struct {
|
||||
TotalAgents int `json:"total_agents"`
|
||||
OnlineAgents int `json:"online_agents"`
|
||||
TotalHashrate float64 `json:"total_hashrate"`
|
||||
TotalShares int `json:"total_shares"`
|
||||
AcceptedShares int `json:"accepted_shares"`
|
||||
RejectedShares int `json:"rejected_shares"`
|
||||
AcceptRate float64 `json:"accept_rate"`
|
||||
TotalAgents int `json:"total_agents"`
|
||||
OnlineAgents int `json:"online_agents"`
|
||||
TotalHashrate float64 `json:"total_hashrate"`
|
||||
TotalShares int `json:"total_shares"`
|
||||
AcceptedShares int `json:"accepted_shares"`
|
||||
RejectedShares int `json:"rejected_shares"`
|
||||
AcceptRate float64 `json:"accept_rate"`
|
||||
}
|
||||
|
||||
func (d *Database) GetFleetStats() (*FleetStats, error) {
|
||||
|
||||
Reference in New Issue
Block a user