80320ae698
Close audit findings: anti-spoof client IP for login rate limit, JWT/CORS enforce on startup, SSH host-key verification and sudo via stdin, optional WinRM HTTPS, SSE httpOnly cookie auth, ingest body limit, ILIKE escaping, and HMAC API key hashing with legacy SHA-256 fallback.
798 lines
22 KiB
TypeScript
798 lines
22 KiB
TypeScript
const TOKEN_KEY = "sac_token";
|
|
const ROLE_KEY = "sac_role";
|
|
|
|
export function getToken(): string | null {
|
|
return localStorage.getItem(TOKEN_KEY);
|
|
}
|
|
|
|
export function getRole(): string | null {
|
|
return localStorage.getItem(ROLE_KEY);
|
|
}
|
|
|
|
export function isAdmin(): boolean {
|
|
return getRole() === "admin";
|
|
}
|
|
|
|
export function setToken(token: string): void {
|
|
localStorage.setItem(TOKEN_KEY, token);
|
|
}
|
|
|
|
export function setRole(role: string): void {
|
|
localStorage.setItem(ROLE_KEY, role);
|
|
}
|
|
|
|
export function setAuth(token: string, role: string): void {
|
|
setToken(token);
|
|
setRole(role);
|
|
}
|
|
|
|
export function clearToken(): void {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
localStorage.removeItem(ROLE_KEY);
|
|
}
|
|
|
|
export async function logoutApi(): Promise<void> {
|
|
try {
|
|
await fetch("/api/v1/auth/logout", { method: "POST", credentials: "same-origin" });
|
|
} catch {
|
|
/* ignore network errors on logout */
|
|
}
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
status: number;
|
|
|
|
constructor(message: string, status: number) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
this.status = status;
|
|
}
|
|
}
|
|
|
|
export function redirectToErrorPage(status: 401 | 403 | 429): void {
|
|
window.location.href = `/${status}`;
|
|
}
|
|
|
|
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const headers = new Headers(init.headers);
|
|
if (!headers.has("Content-Type") && init.body) {
|
|
headers.set("Content-Type", "application/json");
|
|
}
|
|
const token = getToken();
|
|
const hadToken = !!token;
|
|
if (token) {
|
|
headers.set("Authorization", `Bearer ${token}`);
|
|
}
|
|
const res = await fetch(path, { ...init, headers, credentials: "same-origin" });
|
|
const text = await res.text();
|
|
if (res.status === 401) {
|
|
if (hadToken) {
|
|
clearToken();
|
|
redirectToErrorPage(401);
|
|
}
|
|
throw new ApiError(parseApiError(text, 401) || "Unauthorized", 401);
|
|
}
|
|
if (res.status === 403) {
|
|
if (hadToken) {
|
|
redirectToErrorPage(403);
|
|
}
|
|
throw new ApiError(parseApiError(text, 403) || "Forbidden", 403);
|
|
}
|
|
if (res.status === 429) {
|
|
redirectToErrorPage(429);
|
|
throw new ApiError(parseApiError(text, 429) || "Too many requests", 429);
|
|
}
|
|
if (!res.ok) {
|
|
throw new ApiError(parseApiError(text, res.status) || res.statusText, res.status);
|
|
}
|
|
if (!text) {
|
|
return undefined as T;
|
|
}
|
|
return JSON.parse(text) as T;
|
|
}
|
|
|
|
export interface TokenResponse {
|
|
access_token: string;
|
|
token_type: string;
|
|
username: string;
|
|
role: string;
|
|
}
|
|
|
|
export interface MeResponse {
|
|
username: string;
|
|
role: string;
|
|
}
|
|
|
|
export function fetchMe(): Promise<MeResponse> {
|
|
return apiFetch<MeResponse>("/api/v1/auth/me");
|
|
}
|
|
|
|
export function parseApiError(text: string, status?: number): string {
|
|
if (status === 502 || status === 503 || status === 504) {
|
|
if (/<title>\s*502 Bad Gateway/i.test(text) || /502 Bad Gateway/i.test(text)) {
|
|
return "Сервер SAC временно недоступен (перезапуск или обновление). Подождите 5–10 секунд и обновите страницу.";
|
|
}
|
|
if (/<title>\s*503 Service Unavailable/i.test(text) || /503 Service Unavailable/i.test(text)) {
|
|
return "SAC временно перегружен. Повторите запрос через несколько секунд.";
|
|
}
|
|
if (/<title>\s*504 Gateway Time-out/i.test(text) || /504 Gateway Time-out/i.test(text)) {
|
|
return "Превышено время ожидания ответа SAC. Долгая операция могла ещё выполняться на сервере.";
|
|
}
|
|
return "Временная ошибка шлюза SAC. Обновите страницу.";
|
|
}
|
|
if (!text) return "";
|
|
if (text.trimStart().startsWith("<")) {
|
|
return status ? `Ошибка HTTP ${status}` : "Ошибка сервера";
|
|
}
|
|
try {
|
|
const data = JSON.parse(text) as { detail?: string | Array<{ msg?: string }> };
|
|
if (typeof data.detail === "string") return data.detail;
|
|
if (Array.isArray(data.detail)) {
|
|
return data.detail.map((item) => item.msg ?? String(item)).join("; ");
|
|
}
|
|
} catch {
|
|
/* plain text */
|
|
}
|
|
return text;
|
|
}
|
|
|
|
export interface UserSummary {
|
|
id: number;
|
|
username: string;
|
|
role: string;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface UserListResponse {
|
|
items: UserSummary[];
|
|
}
|
|
|
|
export interface UpdateUserPayload {
|
|
username?: string;
|
|
role?: string;
|
|
password?: string;
|
|
is_active?: boolean;
|
|
}
|
|
|
|
export function fetchUsers(): Promise<UserListResponse> {
|
|
return apiFetch<UserListResponse>("/api/v1/users");
|
|
}
|
|
|
|
export function createUser(payload: {
|
|
username: string;
|
|
password: string;
|
|
role: string;
|
|
}): Promise<UserSummary> {
|
|
return apiFetch<UserSummary>("/api/v1/users", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export function updateUser(userId: number, payload: UpdateUserPayload): Promise<UserSummary> {
|
|
return apiFetch<UserSummary>(`/api/v1/users/${userId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export interface EventSummary {
|
|
id: number;
|
|
event_id: string;
|
|
host_id: number;
|
|
hostname: string;
|
|
display_name?: string | null;
|
|
product_version?: string | null;
|
|
occurred_at: string;
|
|
received_at: string;
|
|
category: string;
|
|
type: string;
|
|
severity: string;
|
|
title: string;
|
|
summary: string;
|
|
actor_user?: string | null;
|
|
rdg_flap?: boolean;
|
|
rdg_flap_pair_event_id?: number | null;
|
|
rdg_flap_qwinsta_event_id?: number | null;
|
|
rdg_access_path?: string | null;
|
|
rdg_qwinsta_enabled?: boolean;
|
|
session_terminated?: boolean;
|
|
}
|
|
|
|
export interface AgentCommandResponse {
|
|
command_uuid: string;
|
|
command_type: string;
|
|
status: string;
|
|
result_stdout?: string | null;
|
|
result_stderr?: string | null;
|
|
created_at?: string | null;
|
|
completed_at?: string | null;
|
|
target?: string | null;
|
|
client_hostname?: string | null;
|
|
internal_ip?: string | null;
|
|
}
|
|
|
|
export function postEventQwinsta(eventId: number): Promise<AgentCommandResponse> {
|
|
return apiFetch<AgentCommandResponse>(`/api/v1/events/${eventId}/actions/qwinsta`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export function postEventLogoff(eventId: number, sessionId: number): Promise<AgentCommandResponse> {
|
|
return apiFetch<AgentCommandResponse>(`/api/v1/events/${eventId}/actions/logoff`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ session_id: sessionId }),
|
|
});
|
|
}
|
|
|
|
export function fetchEventAction(eventId: number, commandUuid: string): Promise<AgentCommandResponse> {
|
|
return apiFetch<AgentCommandResponse>(`/api/v1/events/${eventId}/actions/${commandUuid}`);
|
|
}
|
|
|
|
export interface EventListResponse {
|
|
items: EventSummary[];
|
|
total: number;
|
|
page: number;
|
|
page_size: number;
|
|
}
|
|
|
|
export interface EventDetail extends EventSummary {
|
|
details: Record<string, unknown> | null;
|
|
raw: Record<string, unknown> | null;
|
|
payload: Record<string, unknown>;
|
|
}
|
|
|
|
export interface HostSummary {
|
|
id: number;
|
|
hostname: string;
|
|
display_name: string | null;
|
|
os_family: string;
|
|
os_version?: string | null;
|
|
product: string;
|
|
product_version: string | null;
|
|
ipv4: string | null;
|
|
last_seen_at: string;
|
|
event_count: number | null;
|
|
last_heartbeat_at: string | null;
|
|
last_daily_report_at: string | null;
|
|
last_inventory_at?: string | null;
|
|
agent_status: "online" | "stale" | "unknown";
|
|
version_status?: "ok" | "outdated" | "unknown";
|
|
pending_agent_update?: boolean;
|
|
ssh_admin_ok?: boolean | null;
|
|
ssh_admin_checked_at?: string | null;
|
|
}
|
|
|
|
export interface HostDetail extends HostSummary {
|
|
agent_instance_id: string | null;
|
|
ipv6: string | null;
|
|
tags: string[] | null;
|
|
use_sac_mode: string | null;
|
|
inventory: Record<string, unknown> | null;
|
|
inventory_updated_at: string | null;
|
|
created_at: string | null;
|
|
pending_update_requested_at?: string | null;
|
|
pending_update_target_version?: string | null;
|
|
agent_config_revision?: number;
|
|
agent_config?: Record<string, unknown> | null;
|
|
agent_update_state?: string | null;
|
|
agent_update_last_at?: string | null;
|
|
agent_update_last_error?: string | null;
|
|
}
|
|
|
|
export function fetchHost(id: number): Promise<HostDetail> {
|
|
return apiFetch<HostDetail>(`/api/v1/hosts/${id}`);
|
|
}
|
|
|
|
export function fetchRecentEvents(limit = 1): Promise<EventSummary[]> {
|
|
return apiFetch<EventSummary[]>(`/api/v1/dashboards/recent-events?limit=${limit}`);
|
|
}
|
|
|
|
export interface HostListResponse {
|
|
items: HostSummary[];
|
|
total: number;
|
|
page: number;
|
|
page_size: number;
|
|
latest_agent_versions?: Record<string, string>;
|
|
/** Эталон для «устарела»: max(git latest, min, max в fleet) */
|
|
reference_agent_versions?: Record<string, string>;
|
|
/** Последние версии из git (version.txt на main) */
|
|
git_latest_rdp_version?: string | null;
|
|
git_latest_ssh_version?: string | null;
|
|
}
|
|
|
|
export interface ProblemSummary {
|
|
id: number;
|
|
host_id: number | null;
|
|
hostname: string | null;
|
|
display_name?: string | null;
|
|
title: string;
|
|
summary: string;
|
|
severity: string;
|
|
status: string;
|
|
rule_id: string | null;
|
|
fingerprint?: string;
|
|
event_count?: number;
|
|
last_seen_at?: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ProblemListResponse {
|
|
items: ProblemSummary[];
|
|
total: number;
|
|
page: number;
|
|
page_size: number;
|
|
}
|
|
|
|
export interface ProblemEventItem {
|
|
id: number;
|
|
event_id: string;
|
|
occurred_at: string;
|
|
type: string;
|
|
severity: string;
|
|
title: string;
|
|
summary: string;
|
|
}
|
|
|
|
export interface ProblemDetail extends ProblemSummary {
|
|
events: ProblemEventItem[];
|
|
}
|
|
|
|
export function fetchProblem(id: number): Promise<ProblemDetail> {
|
|
return apiFetch<ProblemDetail>(`/api/v1/problems/${id}`);
|
|
}
|
|
|
|
export async function ackProblem(problemId: number): Promise<{ id: number; status: string }> {
|
|
return apiFetch(`/api/v1/problems/${problemId}/ack`, { method: "POST" });
|
|
}
|
|
|
|
export async function resolveProblem(problemId: number): Promise<{ id: number; status: string }> {
|
|
return apiFetch(`/api/v1/problems/${problemId}/resolve`, { method: "POST" });
|
|
}
|
|
|
|
export interface TopHostItem {
|
|
host_id: number;
|
|
hostname: string;
|
|
display_name?: string | null;
|
|
count: number;
|
|
}
|
|
|
|
export interface TopTypeItem {
|
|
type: string;
|
|
count: number;
|
|
}
|
|
|
|
export interface MobileSettings {
|
|
devices_allowed: boolean;
|
|
max_devices_per_user: number;
|
|
min_app_version: string | null;
|
|
fcm_enabled: boolean;
|
|
fcm_configured: boolean;
|
|
source: string;
|
|
}
|
|
|
|
export interface MobileEnrollmentCode {
|
|
id: number;
|
|
label: string;
|
|
code_prefix: string;
|
|
target_user_id: number | null;
|
|
target_username: string | null;
|
|
login_mode: string;
|
|
max_uses: number;
|
|
use_count: number;
|
|
expires_at: string;
|
|
revoked_at: string | null;
|
|
created_at: string;
|
|
is_active: boolean;
|
|
}
|
|
|
|
export interface MobileEnrollmentCodeCreated extends MobileEnrollmentCode {
|
|
enrollment_code: string;
|
|
}
|
|
|
|
export interface MobileDevice {
|
|
id: number;
|
|
user_id: number;
|
|
username: string;
|
|
device_uuid: string;
|
|
display_name: string;
|
|
platform: string;
|
|
app_version: string | null;
|
|
has_fcm_token: boolean;
|
|
enrolled_at: string;
|
|
last_seen_at: string | null;
|
|
revoked_at: string | null;
|
|
is_active: boolean;
|
|
}
|
|
|
|
export function fetchMobileSettings(): Promise<MobileSettings> {
|
|
return apiFetch<MobileSettings>("/api/v1/mobile/settings");
|
|
}
|
|
|
|
export function updateMobileSettings(payload: {
|
|
devices_allowed: boolean;
|
|
max_devices_per_user: number;
|
|
min_app_version?: string | null;
|
|
}): Promise<MobileSettings> {
|
|
return apiFetch<MobileSettings>("/api/v1/mobile/settings", {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export function fetchMobileEnrollmentCodes(): Promise<MobileEnrollmentCode[]> {
|
|
return apiFetch<MobileEnrollmentCode[]>("/api/v1/mobile/enrollment-codes");
|
|
}
|
|
|
|
export function createMobileEnrollmentCode(payload: {
|
|
label?: string;
|
|
target_user_id?: number | null;
|
|
login_mode?: string;
|
|
max_uses?: number;
|
|
expires_in_hours?: number;
|
|
}): Promise<MobileEnrollmentCodeCreated> {
|
|
return apiFetch<MobileEnrollmentCodeCreated>("/api/v1/mobile/enrollment-codes", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export function revokeMobileEnrollmentCode(codeId: number): Promise<void> {
|
|
return apiFetch<void>(`/api/v1/mobile/enrollment-codes/${codeId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function fetchMobileDevices(): Promise<MobileDevice[]> {
|
|
return apiFetch<MobileDevice[]>("/api/v1/mobile/devices");
|
|
}
|
|
|
|
export function revokeMobileDevice(deviceId: number): Promise<MobileDevice> {
|
|
return apiFetch<MobileDevice>(`/api/v1/mobile/devices/${deviceId}/revoke`, { method: "POST" });
|
|
}
|
|
|
|
export function deleteMobileDevice(deviceId: number): Promise<void> {
|
|
return apiFetch<void>(`/api/v1/mobile/devices/${deviceId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function testMobileDevicePush(deviceId: number): Promise<{ message: string }> {
|
|
return apiFetch(`/api/v1/mobile/devices/${deviceId}/test-push`, { method: "POST" });
|
|
}
|
|
|
|
export interface WinAdminSettings {
|
|
configured: boolean;
|
|
user: string | null;
|
|
password_hint: string | null;
|
|
source: string;
|
|
}
|
|
|
|
export function fetchWinAdminSettings(): Promise<WinAdminSettings> {
|
|
return apiFetch<WinAdminSettings>("/api/v1/settings/win-admin");
|
|
}
|
|
|
|
export function updateWinAdminSettings(payload: {
|
|
user?: string | null;
|
|
password?: string | null;
|
|
}): Promise<WinAdminSettings> {
|
|
return apiFetch<WinAdminSettings>("/api/v1/settings/win-admin", {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export interface RdpFlapSettings {
|
|
auto_disconnect: boolean;
|
|
win_admin_configured: boolean;
|
|
source: string;
|
|
}
|
|
|
|
export function fetchRdpFlapSettings(): Promise<RdpFlapSettings> {
|
|
return apiFetch<RdpFlapSettings>("/api/v1/settings/rdp-flap");
|
|
}
|
|
|
|
export function updateRdpFlapSettings(payload: { auto_disconnect: boolean }): Promise<RdpFlapSettings> {
|
|
return apiFetch<RdpFlapSettings>("/api/v1/settings/rdp-flap", {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export interface HostWinRmTestResult {
|
|
ok: boolean;
|
|
message: string;
|
|
target: string;
|
|
hostname: string | null;
|
|
}
|
|
|
|
export function testHostWinRm(hostId: number): Promise<HostWinRmTestResult> {
|
|
return apiFetch<HostWinRmTestResult>(`/api/v1/hosts/${hostId}/actions/winrm-test`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export interface LinuxAdminSettings {
|
|
configured: boolean;
|
|
user: string | null;
|
|
password_hint: string | null;
|
|
source: string;
|
|
}
|
|
|
|
export function fetchLinuxAdminSettings(): Promise<LinuxAdminSettings> {
|
|
return apiFetch<LinuxAdminSettings>("/api/v1/settings/linux-admin");
|
|
}
|
|
|
|
export function updateLinuxAdminSettings(payload: {
|
|
user?: string | null;
|
|
password?: string | null;
|
|
}): Promise<LinuxAdminSettings> {
|
|
return apiFetch<LinuxAdminSettings>("/api/v1/settings/linux-admin", {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export interface HostSshActionResult {
|
|
ok: boolean;
|
|
message: string;
|
|
target: string;
|
|
stdout: string | null;
|
|
stderr: string | null;
|
|
exit_code: number | null;
|
|
product_version?: string | null;
|
|
ssh_admin_ok?: boolean | null;
|
|
}
|
|
|
|
export function testHostSsh(hostId: number): Promise<HostSshActionResult> {
|
|
return apiFetch<HostSshActionResult>(`/api/v1/hosts/${hostId}/actions/ssh-test`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export interface HostRemoteActionStartResult {
|
|
status: string;
|
|
host_id: number;
|
|
title: string;
|
|
message: string;
|
|
}
|
|
|
|
export interface HostRemoteActionJobStatus {
|
|
active: boolean;
|
|
host_id: number;
|
|
hostname?: string | null;
|
|
status?: string | null;
|
|
title?: string;
|
|
message?: string;
|
|
stdout?: string | null;
|
|
stderr?: string | null;
|
|
output?: string | null;
|
|
ok?: boolean | null;
|
|
exit_code?: number | null;
|
|
product_version?: string | null;
|
|
agent_update_state?: string | null;
|
|
started_at?: string | null;
|
|
finished_at?: string | null;
|
|
}
|
|
|
|
export function updateHostAgentViaSsh(hostId: number): Promise<HostRemoteActionStartResult> {
|
|
return apiFetch<HostRemoteActionStartResult>(`/api/v1/hosts/${hostId}/actions/agent-update`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export interface HostAgentUpdateRequestResult {
|
|
status: string;
|
|
pending_agent_update: boolean;
|
|
target_version: string | null;
|
|
agent_update_state: string | null;
|
|
}
|
|
|
|
export function requestHostAgentUpdate(hostId: number): Promise<HostAgentUpdateRequestResult> {
|
|
return apiFetch<HostAgentUpdateRequestResult>(
|
|
`/api/v1/hosts/${hostId}/actions/request-agent-update`,
|
|
{ method: "POST" },
|
|
);
|
|
}
|
|
|
|
export function runHostAgentUpdateFallback(hostId: number): Promise<HostRemoteActionStartResult> {
|
|
return apiFetch<HostRemoteActionStartResult>(
|
|
`/api/v1/hosts/${hostId}/actions/agent-update-fallback`,
|
|
{ method: "POST" },
|
|
);
|
|
}
|
|
|
|
export interface HostManualAddRequest {
|
|
platform: "windows" | "linux";
|
|
target: string;
|
|
}
|
|
|
|
export function addHostManually(body: HostManualAddRequest): Promise<HostRemoteActionStartResult> {
|
|
return apiFetch<HostRemoteActionStartResult>("/api/v1/hosts/manual-add", {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
export function fetchHostRemoteJob(hostId: number): Promise<HostRemoteActionJobStatus> {
|
|
return apiFetch<HostRemoteActionJobStatus>(`/api/v1/hosts/${hostId}/actions/remote-job`);
|
|
}
|
|
|
|
export interface HostSessionItem {
|
|
session_id: string;
|
|
user: string;
|
|
tty?: string | null;
|
|
state?: string | null;
|
|
source_ip?: string | null;
|
|
session_name?: string | null;
|
|
}
|
|
|
|
export interface HostSessionsResponse {
|
|
ok: boolean;
|
|
message: string;
|
|
target?: string | null;
|
|
sessions: HostSessionItem[];
|
|
}
|
|
|
|
export interface HostSessionTerminateResponse {
|
|
ok: boolean;
|
|
message: string;
|
|
target?: string | null;
|
|
stdout?: string | null;
|
|
stderr?: string | null;
|
|
}
|
|
|
|
export function fetchHostSessions(hostId: number): Promise<HostSessionsResponse> {
|
|
return apiFetch<HostSessionsResponse>(`/api/v1/hosts/${hostId}/actions/sessions/list`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export function terminateHostSession(
|
|
hostId: number,
|
|
sessionId: string,
|
|
): Promise<HostSessionTerminateResponse> {
|
|
return apiFetch<HostSessionTerminateResponse>(`/api/v1/hosts/${hostId}/actions/sessions/terminate`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ session_id: sessionId }),
|
|
});
|
|
}
|
|
|
|
export function postEventTerminateSession(
|
|
eventId: number,
|
|
sessionId?: string,
|
|
): Promise<HostSessionTerminateResponse> {
|
|
return apiFetch<HostSessionTerminateResponse>(`/api/v1/events/${eventId}/actions/terminate-session`, {
|
|
method: "POST",
|
|
body: JSON.stringify(sessionId ? { session_id: sessionId } : {}),
|
|
});
|
|
}
|
|
|
|
export interface HostAgentConfigResponse {
|
|
config_revision: number;
|
|
settings: Record<string, unknown>;
|
|
}
|
|
|
|
export function patchHostAgentConfig(
|
|
hostId: number,
|
|
settings: Record<string, unknown>,
|
|
): Promise<HostAgentConfigResponse> {
|
|
return apiFetch<HostAgentConfigResponse>(`/api/v1/hosts/${hostId}/agent-config`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify({ settings }),
|
|
});
|
|
}
|
|
|
|
export interface AgentUpdateSettings {
|
|
mode: "gpo" | "sac";
|
|
fallback_enabled: boolean;
|
|
fallback_after_minutes: number;
|
|
recommended_rdp_version: string | null;
|
|
recommended_ssh_version: string | null;
|
|
min_rdp_version: string | null;
|
|
min_ssh_version: string | null;
|
|
win_agent_update_script: string | null;
|
|
rdp_git_repo_url: string | null;
|
|
ssh_git_repo_url: string | null;
|
|
git_branch: string;
|
|
git_latest_rdp_version: string | null;
|
|
git_latest_ssh_version: string | null;
|
|
git_fetched_at: string | null;
|
|
git_errors: Record<string, string>;
|
|
source: string;
|
|
}
|
|
|
|
export interface AgentGitTestResult {
|
|
git_latest_rdp_version: string | null;
|
|
git_latest_ssh_version: string | null;
|
|
git_fetched_at: string | null;
|
|
git_errors: Record<string, string>;
|
|
from_cache: boolean;
|
|
}
|
|
|
|
export function fetchAgentUpdateSettings(): Promise<AgentUpdateSettings> {
|
|
return apiFetch<AgentUpdateSettings>("/api/v1/settings/agent-updates");
|
|
}
|
|
|
|
export function saveAgentUpdateSettings(
|
|
payload: Partial<AgentUpdateSettings>,
|
|
): Promise<AgentUpdateSettings> {
|
|
return apiFetch<AgentUpdateSettings>("/api/v1/settings/agent-updates", {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export interface AgentGitTestPayload {
|
|
rdp_git_repo_url?: string | null;
|
|
ssh_git_repo_url?: string | null;
|
|
git_branch?: string | null;
|
|
}
|
|
|
|
export function testAgentGitRelease(payload?: AgentGitTestPayload): Promise<AgentGitTestResult> {
|
|
return apiFetch<AgentGitTestResult>("/api/v1/settings/agent-updates/test-git", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload ?? {}),
|
|
});
|
|
}
|
|
|
|
export interface LoginSecuritySettings {
|
|
ip_whitelist: string[];
|
|
ip_whitelist_text: string;
|
|
sync_fail2ban: boolean;
|
|
max_failures: number;
|
|
window_minutes: number;
|
|
fail2ban_available: boolean;
|
|
source: string;
|
|
}
|
|
|
|
export interface LoginBlockItem {
|
|
ip_address: string;
|
|
scope: string;
|
|
failure_count: number | null;
|
|
usernames: string[];
|
|
blocked_until: string | null;
|
|
reason: string;
|
|
}
|
|
|
|
export function fetchLoginSecuritySettings(): Promise<LoginSecuritySettings> {
|
|
return apiFetch<LoginSecuritySettings>("/api/v1/settings/login-security");
|
|
}
|
|
|
|
export function saveLoginSecuritySettings(payload: {
|
|
ip_whitelist_text: string;
|
|
sync_fail2ban: boolean;
|
|
}): Promise<LoginSecuritySettings> {
|
|
return apiFetch<LoginSecuritySettings>("/api/v1/settings/login-security", {
|
|
method: "PUT",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export function fetchLoginSecurityBlocks(): Promise<LoginBlockItem[]> {
|
|
return apiFetch<LoginBlockItem[]>("/api/v1/settings/login-security/blocks");
|
|
}
|
|
|
|
export function unblockLoginSecurityIp(payload: {
|
|
ip_address: string;
|
|
scope: "web" | "ssh" | "both";
|
|
}): Promise<{ messages: string[] }> {
|
|
return apiFetch<{ messages: string[] }>("/api/v1/settings/login-security/unblock", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export interface DashboardSummary {
|
|
events_last_24h: number;
|
|
hosts_total: number;
|
|
hosts_stale: number;
|
|
heartbeats_24h: number;
|
|
daily_reports_24h: number;
|
|
problems_open: number;
|
|
problems_opened_24h: number;
|
|
problems_resolved_24h: number;
|
|
severity_24h: Record<string, number>;
|
|
top_hosts: TopHostItem[];
|
|
top_event_types: TopTypeItem[];
|
|
recent_events: EventSummary[];
|
|
}
|