feat: Build Manager, Crucible ops deck, branding, and portable Cloudflare tunnel
Add Build Manager with pin-to-dropper, Crucible multi-node terminal with SSH probe/wake, Command Deck chart balance and pretty stats, AetherForge logo and sacred geometry UI, Field Guide refresh, and LAUNCH.bat Cloudflare MSI + token service install flow.
This commit is contained in:
@@ -115,6 +115,7 @@ func (d *Database) migrate() error {
|
||||
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN bundle_size INTEGER NOT NULL DEFAULT 0`)
|
||||
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN file_name TEXT NOT NULL DEFAULT ''`)
|
||||
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN download_url TEXT NOT NULL DEFAULT ''`)
|
||||
_, _ = d.Exec(`ALTER TABLE builds ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0`)
|
||||
_, _ = d.Exec(`ALTER TABLE agents ADD COLUMN notes TEXT NOT NULL DEFAULT ''`)
|
||||
_, _ = d.Exec(`ALTER TABLE agents ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`)
|
||||
_, _ = d.Exec(`ALTER TABLE agents ADD COLUMN platform TEXT NOT NULL DEFAULT ''`)
|
||||
@@ -251,14 +252,16 @@ func (d *Database) GetHashrateHistory(agentID string, limit int) ([]*models.Hash
|
||||
|
||||
// Build operations
|
||||
|
||||
const buildSelectCols = `id, worker_name, server_url, wallet, threads, file_size, bundle_size, file_path, file_name, download_url, platform, created_at, pool_host, pool_port, pool_tls, pool_pass`
|
||||
const buildSelectCols = `id, worker_name, server_url, wallet, threads, file_size, bundle_size, file_path, file_name, download_url, platform, created_at, pool_host, pool_port, pool_tls, pool_pass, pinned`
|
||||
|
||||
func scanBuild(row interface {
|
||||
Scan(...any) error
|
||||
}) (*models.BuildRecord, error) {
|
||||
b := &models.BuildRecord{}
|
||||
var pinnedInt int
|
||||
err := row.Scan(&b.ID, &b.WorkerName, &b.ServerURL, &b.Wallet, &b.Threads, &b.FileSize, &b.BundleSize,
|
||||
&b.FilePath, &b.FileName, &b.DownloadURL, &b.Platform, &b.CreatedAt, &b.PoolHost, &b.PoolPort, &b.PoolTLS, &b.PoolPass)
|
||||
&b.FilePath, &b.FileName, &b.DownloadURL, &b.Platform, &b.CreatedAt, &b.PoolHost, &b.PoolPort, &b.PoolTLS, &b.PoolPass, &pinnedInt)
|
||||
b.Pinned = pinnedInt == 1
|
||||
return b, err
|
||||
}
|
||||
|
||||
@@ -276,22 +279,47 @@ func (d *Database) GetBuild(id string) (*models.BuildRecord, error) {
|
||||
return scanBuild(d.QueryRow(`SELECT `+buildSelectCols+` FROM builds WHERE id = ?`, id))
|
||||
}
|
||||
|
||||
// GetLatestBuildForPlatform returns the pinned build for the given platform
|
||||
// (or any platform when empty), falling back to the most-recently-created build.
|
||||
func (d *Database) GetLatestBuildForPlatform(platform string) (*models.BuildRecord, error) {
|
||||
var query string
|
||||
var args []any
|
||||
if platform == "" || platform == "any" {
|
||||
query = `SELECT ` + buildSelectCols + ` FROM builds ORDER BY created_at DESC LIMIT 1`
|
||||
} else {
|
||||
query = `SELECT ` + buildSelectCols + ` FROM builds WHERE platform = ? ORDER BY created_at DESC LIMIT 1`
|
||||
args = []any{platform}
|
||||
// 1. Pinned build for this platform (exact match)
|
||||
if platform != "" && platform != "any" {
|
||||
b, err := scanBuild(d.QueryRow(`SELECT `+buildSelectCols+` FROM builds WHERE pinned = 1 AND platform = ? LIMIT 1`, platform))
|
||||
if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
// 2. Any pinned build (universal or first pinned regardless of platform)
|
||||
b, err := scanBuild(d.QueryRow(`SELECT ` + buildSelectCols + ` FROM builds WHERE pinned = 1 ORDER BY created_at DESC LIMIT 1`))
|
||||
if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
// 3. Latest by creation time, filtered by platform when given
|
||||
if platform == "" || platform == "any" {
|
||||
b, err = scanBuild(d.QueryRow(`SELECT ` + buildSelectCols + ` FROM builds ORDER BY created_at DESC LIMIT 1`))
|
||||
} else {
|
||||
b, err = scanBuild(d.QueryRow(`SELECT `+buildSelectCols+` FROM builds WHERE platform = ? ORDER BY created_at DESC LIMIT 1`, platform))
|
||||
}
|
||||
b, err := scanBuild(d.QueryRow(query, args...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// SetPinnedBuild unpins all builds then pins the one with the given id.
|
||||
// If id is empty, all builds are unpinned.
|
||||
func (d *Database) SetPinnedBuild(id string) error {
|
||||
_, err := d.Exec(`UPDATE builds SET pinned = 0`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if id == "" {
|
||||
return nil
|
||||
}
|
||||
_, err = d.Exec(`UPDATE builds SET pinned = 1 WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) ListBuilds(limit int) ([]*models.BuildRecord, error) {
|
||||
rows, err := d.Query(`SELECT `+buildSelectCols+` FROM builds ORDER BY created_at DESC LIMIT ?`, limit)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user