// -- API response types ------------------------------------------------------
///|
/// Flat row for the `GET /api/tasks` listing. One row per running task
/// instance (keyed by `task_uuid`), joined with its parent machine's display
/// info. `machine_id` groups tasks that belong to the same long-lived machine;
/// `task_uuid` is the identity used everywhere else on the wire.
pub(all) struct TaskEntry {
task_uuid : @uuid.UUID
machine_id : Int
hostname : String
cwd : String
/// Latest terminal title (OSC 0/2) reported by the task's server, if any.
title : String?
owner : String
online : Bool
last_seen_at : Int64 // unix millis
} derive(Eq)
///|
pub impl ToJson for TaskEntry with fn to_json(self) {
let obj : Map[String, Json] = {
"task_uuid": self.task_uuid.to_string(),
"machine_id": self.machine_id,
"hostname": self.hostname,
"cwd": self.cwd,
"owner": self.owner,
"online": self.online,
"last_seen_at": Json::number(
self.last_seen_at.to_double(),
repr=self.last_seen_at.to_string(),
),
}
if self.title is Some(title) {
obj["title"] = Json::string(title)
}
Json::object(obj)
}
///|
pub impl @json.FromJson for TaskEntry with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("task_uuid") is Some(String(uuid_str)) else {
raise JsonDecodeError((path, "missing field 'task_uuid'"))
}
let task_uuid = @uuid.from_hex(uuid_str) catch {
_ => raise JsonDecodeError((path, "invalid task_uuid: \{uuid_str}"))
}
guard fields.get("machine_id") is Some(Number(machine_id, ..)) else {
raise JsonDecodeError((path, "missing field 'machine_id'"))
}
guard fields.get("hostname") is Some(String(hostname)) else {
raise JsonDecodeError((path, "missing field 'hostname'"))
}
guard fields.get("cwd") is Some(String(cwd)) else {
raise JsonDecodeError((path, "missing field 'cwd'"))
}
guard fields.get("owner") is Some(String(owner)) else {
raise JsonDecodeError((path, "missing field 'owner'"))
}
let online = match fields.get("online") {
Some(True) => true
Some(False) => false
_ => raise JsonDecodeError((path, "missing field 'online'"))
}
guard fields.get("last_seen_at") is Some(Number(last_seen_at, ..)) else {
raise JsonDecodeError((path, "missing field 'last_seen_at'"))
}
{
task_uuid,
machine_id: machine_id.to_int(),
hostname,
cwd,
title: parse_optional_string(fields, "title", path),
owner,
online,
last_seen_at: last_seen_at.to_int64(),
}
}
///|
pub(all) struct MachineAccessGroup {
group_id : Int
name : String
}
///|
pub impl ToJson for MachineAccessGroup with fn to_json(self) {
{ "group_id": self.group_id, "name": self.name }
}
///|
pub impl @json.FromJson for MachineAccessGroup with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("group_id") is Some(Number(group_id, ..)) else {
raise JsonDecodeError((path, "missing field 'group_id'"))
}
guard fields.get("name") is Some(String(name)) else {
raise JsonDecodeError((path, "missing field 'name'"))
}
{ group_id: group_id.to_int(), name }
}
///|
pub(all) struct CreateMachineRequest {
name : String
}
///|
pub impl ToJson for CreateMachineRequest with fn to_json(self) {
{ "name": self.name }
}
///|
pub impl @json.FromJson for CreateMachineRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("name") is Some(String(name)) else {
raise JsonDecodeError((path, "missing field 'name'"))
}
{ name, }
}
///|
pub(all) struct CreateMachineResponse {
token : String
machine_id : Int
}
///|
pub impl ToJson for CreateMachineResponse with fn to_json(self) {
{ "token": self.token, "server_id": self.machine_id }
}
///|
pub impl @json.FromJson for CreateMachineResponse with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("token") is Some(String(token)) else {
raise JsonDecodeError((path, "missing field 'token'"))
}
guard fields.get("server_id") is Some(Number(server_id, ..)) else {
raise JsonDecodeError((path, "missing field 'server_id'"))
}
{ token, machine_id: server_id.to_int() }
}
///|
pub(all) struct GroupInfo {
id : Int
name : String
owner_id : Int64
}
///|
pub impl ToJson for GroupInfo with fn to_json(self) {
{
"id": self.id,
"name": self.name,
"owner_id": Json::number(
self.owner_id.to_double(),
repr=self.owner_id.to_string(),
),
}
}
///|
pub impl @json.FromJson for GroupInfo with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("id") is Some(Number(id, ..)) else {
raise JsonDecodeError((path, "missing field 'id'"))
}
guard fields.get("name") is Some(String(name)) else {
raise JsonDecodeError((path, "missing field 'name'"))
}
guard fields.get("owner_id") is Some(Number(owner_id, ..)) else {
raise JsonDecodeError((path, "missing field 'owner_id'"))
}
{ id: id.to_int(), name, owner_id: owner_id.to_int64() }
}
///|
pub(all) struct CreatedGroupResponse {
id : Int
}
///|
pub impl ToJson for CreatedGroupResponse with fn to_json(self) {
{ "id": self.id }
}
///|
pub impl @json.FromJson for CreatedGroupResponse with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("id") is Some(Number(id, ..)) else {
raise JsonDecodeError((path, "missing field 'id'"))
}
{ id: id.to_int() }
}
///|
pub(all) struct GroupMember {
github_id : Int64
login : String
avatar_url : String
}
///|
pub impl ToJson for GroupMember with fn to_json(self) {
{
"github_id": Json::number(
self.github_id.to_double(),
repr=self.github_id.to_string(),
),
"login": self.login,
"avatar_url": self.avatar_url,
}
}
///|
pub impl @json.FromJson for GroupMember with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("github_id") is Some(Number(github_id, ..)) else {
raise JsonDecodeError((path, "missing field 'github_id'"))
}
guard fields.get("login") is Some(String(login)) else {
raise JsonDecodeError((path, "missing field 'login'"))
}
guard fields.get("avatar_url") is Some(String(avatar_url)) else {
raise JsonDecodeError((path, "missing field 'avatar_url'"))
}
{ github_id: github_id.to_int64(), login, avatar_url }
}
// -- API request types -------------------------------------------------------
///|
pub(all) struct GrantAccessRequest {
group_id : Int
}
///|
pub impl ToJson for GrantAccessRequest with fn to_json(self) {
{ "group_id": self.group_id }
}
///|
pub impl @json.FromJson for GrantAccessRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("group_id") is Some(Number(group_id, ..)) else {
raise JsonDecodeError((path, "missing field 'group_id'"))
}
{ group_id: group_id.to_int() }
}
///|
pub(all) struct CreateGroupRequest {
name : String
}
///|
pub impl ToJson for CreateGroupRequest with fn to_json(self) {
{ "name": self.name }
}
///|
pub impl @json.FromJson for CreateGroupRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("name") is Some(String(name)) else {
raise JsonDecodeError((path, "missing field 'name'"))
}
{ name, }
}
///|
pub(all) struct TransferGroupRequest {
new_owner_id : Int64
}
///|
pub impl @json.FromJson for TransferGroupRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("new_owner_id") is Some(Number(n, ..)) else {
raise JsonDecodeError((path, "missing field 'new_owner_id'"))
}
{ new_owner_id: n.to_int64() }
}
///|
pub(all) struct AddMemberRequest {
login : String
}
///|
pub impl ToJson for AddMemberRequest with fn to_json(self) {
{ "login": self.login }
}
///|
pub impl @json.FromJson for AddMemberRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("login") is Some(String(login)) else {
raise JsonDecodeError((path, "missing field 'login'"))
}
{ login, }
}
// -- Device-code approval types ----------------------------------------------
///|
/// Response body of `GET /device/pending?code=`. Returned to the
/// SPA approval page so it can render the hostname the agent reported.
pub(all) struct DevicePendingResponse {
hostname : String
}
///|
pub impl ToJson for DevicePendingResponse with fn to_json(self) {
{ "hostname": self.hostname }
}
///|
pub impl @json.FromJson for DevicePendingResponse with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("hostname") is Some(String(hostname)) else {
raise JsonDecodeError((path, "missing field 'hostname'"))
}
{ hostname, }
}
///|
/// Request body of `POST /device/approve`. Sent by the SPA when the user
/// clicks Approve on the pairing page.
pub(all) struct DeviceApproveRequest {
code : String
}
///|
pub impl ToJson for DeviceApproveRequest with fn to_json(self) {
{ "code": self.code }
}
///|
pub impl @json.FromJson for DeviceApproveRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("code") is Some(String(code)) else {
raise JsonDecodeError((path, "missing field 'code'"))
}
{ code, }
}
///|
/// Request body of `POST /device/deny`. Sent by the SPA when the user clicks
/// Deny on the pairing page.
pub(all) struct DeviceDenyRequest {
code : String
}
///|
pub impl ToJson for DeviceDenyRequest with fn to_json(self) {
{ "code": self.code }
}
///|
pub impl @json.FromJson for DeviceDenyRequest with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected JSON object"))
}
guard fields.get("code") is Some(String(code)) else {
raise JsonDecodeError((path, "missing field 'code'"))
}
{ code, }
}