mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-03 19:20:07 +02:00
feat(runtime): add owner tracking, filtering, and delete (#535)
Add owner_id to agent_runtime table to track who registered each runtime. Backend: new delete endpoint with role-based permissions (owner/admin can delete any, members only their own), list filtering by owner (?owner=me), and agent dependency check before deletion. Frontend: Mine/All filter toggle in runtime list, owner display in list items and detail view, delete button with AlertDialog confirmation. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,26 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const countAgentsByRuntime = `-- name: CountAgentsByRuntime :one
|
||||
SELECT count(*) FROM agent WHERE runtime_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) CountAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countAgentsByRuntime, runtimeID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const deleteAgentRuntime = `-- name: DeleteAgentRuntime :exec
|
||||
DELETE FROM agent_runtime WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteAgentRuntime(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteAgentRuntime, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const failTasksForOfflineRuntimes = `-- name: FailTasksForOfflineRuntimes :many
|
||||
UPDATE agent_task_queue
|
||||
SET status = 'failed', completed_at = now(), error = 'runtime went offline'
|
||||
@@ -50,7 +70,7 @@ func (q *Queries) FailTasksForOfflineRuntimes(ctx context.Context) ([]FailTasksF
|
||||
}
|
||||
|
||||
const getAgentRuntime = `-- name: GetAgentRuntime :one
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at FROM agent_runtime
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
@@ -70,12 +90,13 @@ func (q *Queries) GetAgentRuntime(ctx context.Context, id pgtype.UUID) (AgentRun
|
||||
&i.LastSeenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getAgentRuntimeForWorkspace = `-- name: GetAgentRuntimeForWorkspace :one
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at FROM agent_runtime
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
|
||||
WHERE id = $1 AND workspace_id = $2
|
||||
`
|
||||
|
||||
@@ -100,12 +121,13 @@ func (q *Queries) GetAgentRuntimeForWorkspace(ctx context.Context, arg GetAgentR
|
||||
&i.LastSeenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listAgentRuntimes = `-- name: ListAgentRuntimes :many
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at FROM agent_runtime
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
|
||||
WHERE workspace_id = $1
|
||||
ORDER BY created_at ASC
|
||||
`
|
||||
@@ -132,6 +154,52 @@ func (q *Queries) ListAgentRuntimes(ctx context.Context, workspaceID pgtype.UUID
|
||||
&i.LastSeenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listAgentRuntimesByOwner = `-- name: ListAgentRuntimesByOwner :many
|
||||
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id FROM agent_runtime
|
||||
WHERE workspace_id = $1 AND owner_id = $2
|
||||
ORDER BY created_at ASC
|
||||
`
|
||||
|
||||
type ListAgentRuntimesByOwnerParams struct {
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
OwnerID pgtype.UUID `json:"owner_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListAgentRuntimesByOwner(ctx context.Context, arg ListAgentRuntimesByOwnerParams) ([]AgentRuntime, error) {
|
||||
rows, err := q.db.Query(ctx, listAgentRuntimesByOwner, arg.WorkspaceID, arg.OwnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []AgentRuntime{}
|
||||
for rows.Next() {
|
||||
var i AgentRuntime
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.WorkspaceID,
|
||||
&i.DaemonID,
|
||||
&i.Name,
|
||||
&i.RuntimeMode,
|
||||
&i.Provider,
|
||||
&i.Status,
|
||||
&i.DeviceInfo,
|
||||
&i.Metadata,
|
||||
&i.LastSeenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -191,7 +259,7 @@ const updateAgentRuntimeHeartbeat = `-- name: UpdateAgentRuntimeHeartbeat :one
|
||||
UPDATE agent_runtime
|
||||
SET status = 'online', last_seen_at = now(), updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at
|
||||
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateAgentRuntimeHeartbeat(ctx context.Context, id pgtype.UUID) (AgentRuntime, error) {
|
||||
@@ -210,6 +278,7 @@ func (q *Queries) UpdateAgentRuntimeHeartbeat(ctx context.Context, id pgtype.UUI
|
||||
&i.LastSeenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -224,8 +293,9 @@ INSERT INTO agent_runtime (
|
||||
status,
|
||||
device_info,
|
||||
metadata,
|
||||
owner_id,
|
||||
last_seen_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, now())
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now())
|
||||
ON CONFLICT (workspace_id, daemon_id, provider)
|
||||
DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
@@ -233,9 +303,10 @@ DO UPDATE SET
|
||||
status = EXCLUDED.status,
|
||||
device_info = EXCLUDED.device_info,
|
||||
metadata = EXCLUDED.metadata,
|
||||
owner_id = COALESCE(EXCLUDED.owner_id, agent_runtime.owner_id),
|
||||
last_seen_at = now(),
|
||||
updated_at = now()
|
||||
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at
|
||||
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id
|
||||
`
|
||||
|
||||
type UpsertAgentRuntimeParams struct {
|
||||
@@ -247,6 +318,7 @@ type UpsertAgentRuntimeParams struct {
|
||||
Status string `json:"status"`
|
||||
DeviceInfo string `json:"device_info"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
OwnerID pgtype.UUID `json:"owner_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntimeParams) (AgentRuntime, error) {
|
||||
@@ -259,6 +331,7 @@ func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntime
|
||||
arg.Status,
|
||||
arg.DeviceInfo,
|
||||
arg.Metadata,
|
||||
arg.OwnerID,
|
||||
)
|
||||
var i AgentRuntime
|
||||
err := row.Scan(
|
||||
@@ -274,6 +347,7 @@ func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntime
|
||||
&i.LastSeenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user