Add admin console access and platform pages (trust, users, keys, workloads, settings)
This commit is contained in:
@@ -54,6 +54,8 @@ func expectedMigrationName(version int) string {
|
||||
return "0012_worker_trust.up.sql"
|
||||
case 13:
|
||||
return "0013_task_results.up.sql"
|
||||
case 14:
|
||||
return "0014_workload_settings.up.sql"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS workload_settings;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
BEGIN;
|
||||
|
||||
-- Per-workload enable/disable. Absence of a row means "enabled" (the catalog
|
||||
-- default); a row only exists once an admin flipped a workload off or back on.
|
||||
CREATE TABLE workload_settings (
|
||||
workload text NOT NULL PRIMARY KEY,
|
||||
enabled boolean NOT NULL,
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -91,6 +91,24 @@ func (r *WorkerRepo) MarkStaleOffline(ctx context.Context, cutoff time.Time) (in
|
||||
return tag.RowsAffected(), nil
|
||||
}
|
||||
|
||||
func (r *WorkerRepo) SetTrust(ctx context.Context, id uuid.UUID, trust domain.WorkerTrust) error {
|
||||
sql, args, err := psql.Update("workers").
|
||||
SetMap(map[string]any{"trust_level": string(trust), "updated_at": time.Now()}).
|
||||
Where(sq.Eq{"id": id}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tag, err := conn(ctx, r.pool).Exec(ctx, sql, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set worker trust: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return domain.ErrWorkerNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanWorker(row pgx.Row) (*domain.Worker, error) {
|
||||
var (
|
||||
w domain.Worker
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/usecase"
|
||||
)
|
||||
|
||||
// WorkloadSettingsRepo persists the per-workload enable/disable overrides.
|
||||
// Absence of a row means the workload is enabled (the catalog default).
|
||||
type WorkloadSettingsRepo struct{ pool *pgxpool.Pool }
|
||||
|
||||
func NewWorkloadSettingsRepo(pool *pgxpool.Pool) *WorkloadSettingsRepo {
|
||||
return &WorkloadSettingsRepo{pool: pool}
|
||||
}
|
||||
|
||||
var _ usecase.WorkloadSettingsRepository = (*WorkloadSettingsRepo)(nil)
|
||||
|
||||
func (r *WorkloadSettingsRepo) GetEnabled(ctx context.Context, workload string) (bool, error) {
|
||||
var enabled bool
|
||||
err := conn(ctx, r.pool).QueryRow(ctx,
|
||||
"SELECT enabled FROM workload_settings WHERE workload = $1", workload).Scan(&enabled)
|
||||
if err != nil && err.Error() == "no rows in result set" {
|
||||
return true, nil // no override: catalog default enabled
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get workload setting: %w", err)
|
||||
}
|
||||
return enabled, nil
|
||||
}
|
||||
|
||||
func (r *WorkloadSettingsRepo) List(ctx context.Context) ([]usecase.WorkloadSetting, error) {
|
||||
rows, err := conn(ctx, r.pool).Query(ctx,
|
||||
"SELECT workload, enabled, updated_at FROM workload_settings ORDER BY workload ASC")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list workload settings: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []usecase.WorkloadSetting
|
||||
for rows.Next() {
|
||||
var s usecase.WorkloadSetting
|
||||
if err := rows.Scan(&s.Workload, &s.Enabled, &s.UpdatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *WorkloadSettingsRepo) SetEnabled(ctx context.Context, workload string, enabled bool, now time.Time) error {
|
||||
sql, args, err := psql.Insert("workload_settings").
|
||||
Columns("workload", "enabled", "updated_at").
|
||||
Values(workload, enabled, now).
|
||||
Suffix(`ON CONFLICT (workload) DO UPDATE SET enabled = EXCLUDED.enabled, updated_at = EXCLUDED.updated_at`).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := conn(ctx, r.pool).Exec(ctx, sql, args...); err != nil {
|
||||
return fmt.Errorf("set workload setting: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
|
||||
)
|
||||
|
||||
func TestWorkloadSettingsRepoRoundTrip(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ctx := context.Background()
|
||||
repo := NewWorkloadSettingsRepo(db)
|
||||
|
||||
// No override: enabled by default.
|
||||
enabled, err := repo.GetEnabled(ctx, "similarity-search")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !enabled {
|
||||
t.Error("workload without an override must be enabled")
|
||||
}
|
||||
|
||||
now := time.Date(2026, 8, 2, 12, 0, 0, 0, time.UTC)
|
||||
if err := repo.SetEnabled(ctx, "similarity-search", false, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enabled, err = repo.GetEnabled(ctx, "similarity-search")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if enabled {
|
||||
t.Error("workload must be disabled after the override")
|
||||
}
|
||||
|
||||
// Upsert flips it back and updates the timestamp.
|
||||
later := now.Add(time.Hour)
|
||||
if err := repo.SetEnabled(ctx, "similarity-search", true, later); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enabled, err = repo.GetEnabled(ctx, "similarity-search")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !enabled {
|
||||
t.Error("workload must be re-enabled after the upsert")
|
||||
}
|
||||
|
||||
list, err := repo.List(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(list) != 1 || list[0].Workload != "similarity-search" || !list[0].Enabled {
|
||||
t.Errorf("list = %+v, want the single re-enabled override", list)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkerSetTrust(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ctx := context.Background()
|
||||
repo := NewWorkerRepo(db)
|
||||
|
||||
worker, err := domain.NewWorker("lab-node", []string{"similarity-search"}, fixedTime())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repo.Insert(ctx, worker); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repo.SetTrust(ctx, worker.ID, domain.WorkerUntrusted); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := repo.Get(ctx, worker.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.TrustLevel != domain.WorkerUntrusted {
|
||||
t.Errorf("trust = %q, want untrusted", got.TrustLevel)
|
||||
}
|
||||
if err := repo.SetTrust(ctx, worker.ID, domain.WorkerTrusted); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repo.SetTrust(ctx, uuid.New(), domain.WorkerTrusted); err != domain.ErrWorkerNotFound {
|
||||
t.Errorf("unknown worker trust err = %v, want ErrWorkerNotFound", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 0002: per-workload enable/disable. Absence of a row means "enabled" (the
|
||||
-- catalog default); a row only exists once an admin flipped a workload off or
|
||||
-- back on.
|
||||
CREATE TABLE IF NOT EXISTS workload_settings (
|
||||
workload TEXT NOT NULL PRIMARY KEY,
|
||||
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
@@ -69,8 +69,8 @@ func TestMigrateIsIdempotent(t *testing.T) {
|
||||
if err := db.QueryRowContext(ctx, "PRAGMA user_version").Scan(&version); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != 1 {
|
||||
t.Errorf("user_version = %d, want 1", version)
|
||||
if version != 2 {
|
||||
t.Errorf("user_version = %d, want 2", version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,3 +90,20 @@ func (r *WorkerRepo) MarkStaleOffline(ctx context.Context, cutoff time.Time) (in
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
func (r *WorkerRepo) SetTrust(ctx context.Context, id uuid.UUID, trust domain.WorkerTrust) error {
|
||||
res, err := conn(ctx, r.db).ExecContext(ctx,
|
||||
"UPDATE workers SET trust_level = ?, updated_at = ? WHERE id = ?",
|
||||
string(trust), encodeTime(time.Now()), id.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected == 0 {
|
||||
return domain.ErrWorkerNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/usecase"
|
||||
)
|
||||
|
||||
// WorkloadSettingsRepo persists the per-workload enable/disable overrides.
|
||||
// Absence of a row means the workload is enabled (the catalog default).
|
||||
type WorkloadSettingsRepo struct{ db *sql.DB }
|
||||
|
||||
func NewWorkloadSettingsRepo(db *sql.DB) *WorkloadSettingsRepo { return &WorkloadSettingsRepo{db: db} }
|
||||
|
||||
var _ usecase.WorkloadSettingsRepository = (*WorkloadSettingsRepo)(nil)
|
||||
|
||||
func (r *WorkloadSettingsRepo) GetEnabled(ctx context.Context, workload string) (bool, error) {
|
||||
var enabled int
|
||||
err := conn(ctx, r.db).QueryRowContext(ctx,
|
||||
"SELECT enabled FROM workload_settings WHERE workload = ?", workload).Scan(&enabled)
|
||||
if err == sql.ErrNoRows {
|
||||
return true, nil // no override: catalog default enabled
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get workload setting: %w", err)
|
||||
}
|
||||
return enabled == 1, nil
|
||||
}
|
||||
|
||||
func (r *WorkloadSettingsRepo) List(ctx context.Context) ([]usecase.WorkloadSetting, error) {
|
||||
rows, err := conn(ctx, r.db).QueryContext(ctx,
|
||||
"SELECT workload, enabled, updated_at FROM workload_settings ORDER BY workload ASC")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list workload settings: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
var out []usecase.WorkloadSetting
|
||||
for rows.Next() {
|
||||
var (
|
||||
name string
|
||||
enabled int
|
||||
updatedAt int64
|
||||
)
|
||||
if err := rows.Scan(&name, &enabled, &updatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, usecase.WorkloadSetting{
|
||||
Workload: name,
|
||||
Enabled: enabled == 1,
|
||||
UpdatedAt: decodeTime(updatedAt),
|
||||
})
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *WorkloadSettingsRepo) SetEnabled(ctx context.Context, workload string, enabled bool, now time.Time) error {
|
||||
_, err := conn(ctx, r.db).ExecContext(ctx, `
|
||||
INSERT INTO workload_settings (workload, enabled, updated_at) VALUES (?, ?, ?)
|
||||
ON CONFLICT (workload) DO UPDATE SET enabled = excluded.enabled, updated_at = excluded.updated_at`,
|
||||
workload, boolInt(enabled), now.UnixNano())
|
||||
if err != nil {
|
||||
return fmt.Errorf("set workload setting: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func boolInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user