328 lines
8.0 KiB
TypeScript
328 lines
8.0 KiB
TypeScript
export type ServerStatus = "pending" | "active" | "offline";
|
|
export type KeySource = "uploaded" | "generated";
|
|
|
|
export interface PackageUpdate {
|
|
name: string;
|
|
current_version?: string;
|
|
new_version: string;
|
|
}
|
|
|
|
export interface Server {
|
|
id: string;
|
|
server_id: string;
|
|
hostname: string;
|
|
ip_address: string;
|
|
os_info: string;
|
|
status: ServerStatus;
|
|
agent_version?: string;
|
|
last_seen: string;
|
|
created_at: string;
|
|
available_updates?: PackageUpdate[];
|
|
updates_checked_at?: string;
|
|
console_protocols?: string[];
|
|
}
|
|
|
|
export interface ConsoleConnectRequest {
|
|
server_id: string;
|
|
protocol: string;
|
|
key_id?: string;
|
|
rdp_username?: string;
|
|
rdp_password?: string;
|
|
ssh_username?: string;
|
|
}
|
|
|
|
export interface ConsoleConnectResponse {
|
|
session_id: string;
|
|
token: string;
|
|
ws_path: string;
|
|
}
|
|
|
|
export interface Key {
|
|
id: string;
|
|
key_id: string;
|
|
label: string;
|
|
public_key: string;
|
|
fingerprint: string;
|
|
source: KeySource;
|
|
generated_by_server_id?: string;
|
|
has_private_key: boolean;
|
|
has_passphrase?: boolean;
|
|
created_at: string;
|
|
assigned_count?: number;
|
|
}
|
|
|
|
export interface Assignment {
|
|
id: string;
|
|
key_id: string;
|
|
server_id: string;
|
|
assigned_at: string;
|
|
revoked_at: string | null;
|
|
}
|
|
|
|
export interface AuditEvent {
|
|
id: string;
|
|
event_type: string;
|
|
actor: string;
|
|
server_id?: string;
|
|
key_id?: string;
|
|
details: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface AlertSettings {
|
|
enabled: boolean;
|
|
webhook_url: string;
|
|
offline_threshold_minutes: number;
|
|
}
|
|
|
|
export interface EmailSettings {
|
|
enabled: boolean;
|
|
smtp_host: string;
|
|
smtp_port: number;
|
|
username: string;
|
|
password: string;
|
|
from_addr: string;
|
|
to_addrs: string[];
|
|
use_tls: boolean;
|
|
}
|
|
|
|
export interface SecretsSettings {
|
|
read_token_set: boolean;
|
|
rotated_at?: string;
|
|
}
|
|
|
|
export interface Settings {
|
|
alerts: AlertSettings;
|
|
email: EmailSettings;
|
|
secrets: SecretsSettings;
|
|
}
|
|
|
|
export interface SecretGroupSummary {
|
|
group: string;
|
|
key_count: number;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface Secret {
|
|
group: string;
|
|
key: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface NewServerResponse {
|
|
server_id: string;
|
|
pre_reg_token: string;
|
|
install_command: string;
|
|
install_command_ps: string;
|
|
}
|
|
|
|
export interface GenerateKeyOptions {
|
|
label: string;
|
|
key_type: "ed25519" | "rsa" | "ecdsa";
|
|
key_size?: number;
|
|
passphrase?: string;
|
|
comment?: string;
|
|
}
|
|
|
|
export interface KeyWithAssignments extends Key {
|
|
assignments: (Assignment & { server: Server })[];
|
|
}
|
|
|
|
export interface ServerWithKeys extends Server {
|
|
keys: (Assignment & { key: Key })[];
|
|
}
|
|
|
|
class ApiError extends Error {
|
|
constructor(
|
|
public status: number,
|
|
message: string
|
|
) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
}
|
|
}
|
|
|
|
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
|
const res = await fetch(`/api${path}`, {
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...options?.headers,
|
|
},
|
|
...options,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => res.statusText);
|
|
throw new ApiError(res.status, text || `HTTP ${res.status}`);
|
|
}
|
|
|
|
if (res.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
return res.json();
|
|
}
|
|
|
|
export const api = {
|
|
// Servers
|
|
listServers(): Promise<Server[]> {
|
|
return request<Server[]>("/servers");
|
|
},
|
|
|
|
getServer(serverId: string): Promise<ServerWithKeys> {
|
|
return request<ServerWithKeys>(`/servers/${serverId}`);
|
|
},
|
|
|
|
createServer(): Promise<NewServerResponse> {
|
|
return request<NewServerResponse>("/servers/new", { method: "POST" });
|
|
},
|
|
|
|
deleteServer(serverId: string): Promise<void> {
|
|
return request<void>(`/servers/${serverId}`, { method: "DELETE" });
|
|
},
|
|
|
|
generateKeyForServer(serverId: string, opts: GenerateKeyOptions): Promise<{ command_id: string }> {
|
|
return request<{ command_id: string }>(`/servers/${serverId}/generate-key`, {
|
|
method: "POST",
|
|
body: JSON.stringify(opts),
|
|
});
|
|
},
|
|
|
|
getUpdateCommand(osInfo?: string): string {
|
|
if (osInfo && osInfo.toLowerCase().includes("windows")) {
|
|
return `irm "${window.location.origin}/update.ps1" | iex`;
|
|
}
|
|
return `curl -fsSL "${window.location.origin}/update" | bash`;
|
|
},
|
|
|
|
getLatestAgentVersion(): Promise<{ version: string }> {
|
|
return request<{ version: string }>("/agent/latest-version");
|
|
},
|
|
|
|
updateAgent(serverId: string): Promise<{ message: string; version: string }> {
|
|
return request<{ message: string; version: string }>(`/servers/${serverId}/update-agent`, {
|
|
method: "POST",
|
|
});
|
|
},
|
|
|
|
applyUpdates(serverId: string): Promise<{ message: string }> {
|
|
return request<{ message: string }>(`/servers/${serverId}/apply-updates`, {
|
|
method: "POST",
|
|
});
|
|
},
|
|
|
|
// Audit
|
|
listAuditEvents(limit?: number): Promise<AuditEvent[]> {
|
|
const qs = limit ? `?limit=${limit}` : "";
|
|
return request<AuditEvent[]>(`/audit${qs}`);
|
|
},
|
|
|
|
// Settings
|
|
getSettings(): Promise<Settings> {
|
|
return request<Settings>("/settings");
|
|
},
|
|
|
|
saveSettings(settings: { alerts: AlertSettings; email: EmailSettings }): Promise<{ saved: boolean }> {
|
|
return request<{ saved: boolean }>("/settings", {
|
|
method: "PUT",
|
|
body: JSON.stringify(settings),
|
|
});
|
|
},
|
|
|
|
rotateSecretsToken(): Promise<{ token: string }> {
|
|
return request<{ token: string }>("/settings/secrets-token", { method: "POST" });
|
|
},
|
|
|
|
// Secrets
|
|
listSecretGroups(): Promise<SecretGroupSummary[]> {
|
|
return request<SecretGroupSummary[]>("/secrets");
|
|
},
|
|
|
|
createSecretGroup(group: string, values: Record<string, string>): Promise<{ group: string }> {
|
|
return request<{ group: string }>("/secrets", {
|
|
method: "POST",
|
|
body: JSON.stringify({ group, values }),
|
|
});
|
|
},
|
|
|
|
getSecretGroup(group: string): Promise<{ group: string; secrets: Secret[] }> {
|
|
return request<{ group: string; secrets: Secret[] }>(`/secrets/${encodeURIComponent(group)}`);
|
|
},
|
|
|
|
putSecrets(group: string, values: Record<string, string>): Promise<{ saved: boolean }> {
|
|
return request<{ saved: boolean }>(`/secrets/${encodeURIComponent(group)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(values),
|
|
});
|
|
},
|
|
|
|
revealSecret(group: string, key: string): Promise<{ value: string }> {
|
|
return request<{ value: string }>(`/secrets/${encodeURIComponent(group)}/reveal`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ key }),
|
|
});
|
|
},
|
|
|
|
deleteSecret(group: string, key: string): Promise<void> {
|
|
return request<void>(`/secrets/${encodeURIComponent(group)}/${encodeURIComponent(key)}`, {
|
|
method: "DELETE",
|
|
});
|
|
},
|
|
|
|
deleteSecretGroup(group: string): Promise<void> {
|
|
return request<void>(`/secrets/${encodeURIComponent(group)}`, { method: "DELETE" });
|
|
},
|
|
|
|
// Keys
|
|
listKeys(): Promise<Key[]> {
|
|
return request<Key[]>("/keys");
|
|
},
|
|
|
|
getKey(keyId: string): Promise<KeyWithAssignments> {
|
|
return request<KeyWithAssignments>(`/keys/${keyId}`);
|
|
},
|
|
|
|
uploadKey(label: string, public_key: string, private_key?: string, passphrase?: string): Promise<Key> {
|
|
return request<Key>("/keys", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
label,
|
|
public_key,
|
|
private_key: private_key || undefined,
|
|
passphrase: passphrase || undefined,
|
|
}),
|
|
});
|
|
},
|
|
|
|
getPrivateKey(keyId: string): Promise<{ private_key: string }> {
|
|
return request<{ private_key: string }>(`/keys/${keyId}/private-key`);
|
|
},
|
|
|
|
deleteKey(keyId: string): Promise<void> {
|
|
return request<void>(`/keys/${keyId}`, { method: "DELETE" });
|
|
},
|
|
|
|
// Assignments
|
|
assignKey(keyId: string, serverId: string): Promise<Assignment> {
|
|
return request<Assignment>(`/keys/${keyId}/assign`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ server_id: serverId }),
|
|
});
|
|
},
|
|
|
|
revokeKey(keyId: string, serverId: string): Promise<void> {
|
|
return request<void>(`/keys/${keyId}/assign/${serverId}`, {
|
|
method: "DELETE",
|
|
});
|
|
},
|
|
|
|
// Console
|
|
connectConsole(body: ConsoleConnectRequest): Promise<ConsoleConnectResponse> {
|
|
return request<ConsoleConnectResponse>("/console/connect", {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
});
|
|
},
|
|
};
|