Record project continuation changes

This commit is contained in:
2026-05-12 21:02:29 +03:00
parent 3059d1d7a3
commit 8f69d53193
339 changed files with 101111 additions and 1769 deletions
+814 -3
View File
@@ -1,5 +1,6 @@
import type {
AuditEvent,
AuditSummary,
AuthResult,
BootstrapOwnerResult,
Cluster,
@@ -12,19 +13,49 @@ import type {
FabricEntryPointNode,
FabricEgressPool,
FabricEgressPoolNode,
FabricServiceChannelAdaptivePolicy,
FabricServiceChannelBreadcrumbWindowPolicy,
FabricServiceChannelPoolPolicy,
FabricServiceChannelRecoveryPolicy,
FabricServiceChannelRouteFeedbackObservation,
FabricServiceChannelRouteRebuildAlertSilence,
FabricServiceChannelRouteRebuildAttempt,
FabricServiceChannelRouteRebuildHealthSummary,
FabricServiceChannelRouteRebuildIncident,
FabricServiceChannelRebuildInvestigationBreadcrumbs,
ExpireFabricServiceChannelRouteFeedbackResult,
FabricServiceChannelReadiness,
FabricServiceChannelRebuildSnapshotMaintenanceHealth,
FabricServiceChannelRebuildSnapshotWarmup,
FabricServiceChannelLeaseMaintenance,
FabricServiceChannelAccessTelemetry,
FabricServiceChannelSchemaStatus,
FabricTestingFlag,
InstallationStatus,
JoinRequest,
MeshLink,
MeshRouteIntent,
NodeJoinToken,
NodeHeartbeat,
NodeSyntheticMeshConfig,
NodeTelemetryObservation,
NodeUpdatePlan,
NodeUpdatePolicy,
NodeUpdateStatus,
NodeWorkloadDesiredState,
OrganizationAdminSummary,
Organization,
OrganizationMembership,
QoSPolicy,
ReleaseVersion,
Resource,
RoleAssignment,
UserAccount,
VPNClientDiagnosticCommand,
VPNClientDiagnosticStatus,
VPNConnection,
VPNConnectionLease,
VPNPacketStats,
WorkloadStatus,
} from "../types";
@@ -69,6 +100,83 @@ export type CreateVPNConnectionPayload = {
placementPolicy: Record<string, unknown>;
};
export type UpsertNodeUpdatePolicyPayload = {
product: string;
channel?: string;
targetVersion?: string | null;
strategy?: string;
enabled?: boolean;
rollbackAllowed?: boolean;
healthWindowSeconds?: number;
};
export type UpdateFabricServiceChannelRecoveryPolicyPayload = {
hysteresisPenalty?: number;
promotionMinSamples?: number;
demotionFailureThreshold?: number;
demotionDropThreshold?: number;
demotionSlowThreshold?: number;
demotionRebuildEnabled?: boolean;
demotionFencedEnabled?: boolean;
};
export type UpdateFabricServiceChannelAdaptivePolicyPayload = {
maxParallelWindow?: number;
bulkPressureChannelThreshold?: number;
queuePressureHighWatermark?: number;
queuePressureMaxInFlight?: number;
classWindows?: Record<string, number>;
};
export type UpdateFabricServiceChannelPoolPolicyPayload = {
entryPoolNodeIds?: string[];
exitPoolNodeIds?: string[];
preferredEntryNodeId?: string;
preferredExitNodeId?: string;
selectionStrategy?: string;
routeRebuild?: string;
entryFailover?: string;
exitFailover?: string;
backendFallbackAllowed?: boolean;
stickySession?: boolean;
};
export type UpdateFabricServiceChannelBreadcrumbWindowPolicyPayload = {
currentWindowSeconds?: number;
historyWindowSeconds?: number;
};
export type CreateOrganizationPayload = {
slug: string;
name: string;
metadata?: Record<string, unknown>;
};
export type CreateUserPayload = {
email: string;
password: string;
platformRole?: string;
};
export type CreateResourcePayload = {
organizationId: string;
name: string;
address: string;
protocol: string;
secretRef?: string | null;
certificateVerificationMode?: string;
renderQualityProfile?: string;
clipboardMode?: string;
fileTransferMode?: string;
metadata?: Record<string, unknown>;
};
export type UpsertResourceSecretPayload = {
username?: string;
password?: string;
domain?: string;
};
export type CreateFabricEntryPointPayload = {
name: string;
endpointType: string;
@@ -110,6 +218,12 @@ export class AdminApiClient {
});
}
async refresh(input: { refreshToken: string }): Promise<AuthResult> {
return this.post<AuthResult>("/auth/refresh", {
refresh_token: input.refreshToken,
});
}
async getInstallationStatus(): Promise<InstallationStatus> {
const payload = await this.request<{ installation: InstallationStatus }>("/installation/status", {
method: "GET",
@@ -244,6 +358,13 @@ export class AdminApiClient {
});
}
async deleteClusterNode(clusterId: string, nodeId: string, reason: string): Promise<void> {
await this.delete(`/clusters/${clusterId}/nodes/${nodeId}`, {
actor_user_id: this.actorUserId,
reason,
});
}
async listJoinRequests(clusterId: string): Promise<JoinRequest[]> {
const payload = await this.get<{ join_requests: JoinRequest[] }>(`/clusters/${clusterId}/join-requests`);
return payload.join_requests ?? [];
@@ -260,6 +381,18 @@ export class AdminApiClient {
return payload.join_token;
}
async listJoinTokens(clusterId: string): Promise<NodeJoinToken[]> {
const payload = await this.get<{ join_tokens: NodeJoinToken[] }>(`/clusters/${clusterId}/join-tokens`);
return payload.join_tokens ?? [];
}
async revokeJoinToken(clusterId: string, tokenId: string): Promise<NodeJoinToken> {
const payload = await this.post<{ join_token: NodeJoinToken }>(`/clusters/${clusterId}/join-tokens/${tokenId}/revoke`, {
actor_user_id: this.actorUserId,
});
return payload.join_token;
}
async approveJoinRequest(clusterId: string, requestId: string): Promise<void> {
await this.post(`/clusters/${clusterId}/join-requests/${requestId}/approve`, {
actor_user_id: this.actorUserId,
@@ -323,6 +456,49 @@ export class AdminApiClient {
return payload.telemetry ?? [];
}
async listReleaseVersions(clusterId: string, product = "rap-node-agent", channel = "dev"): Promise<ReleaseVersion[]> {
const params = new URLSearchParams({ product, channel });
const payload = await this.get<{ release_versions: ReleaseVersion[] }>(`/clusters/${clusterId}/updates/releases?${params.toString()}`);
return payload.release_versions ?? [];
}
async getNodeUpdatePlan(
clusterId: string,
nodeId: string,
input: { product?: string; currentVersion?: string | null; os?: string; arch?: string; installType?: string; channel?: string },
): Promise<NodeUpdatePlan> {
const params = new URLSearchParams({
product: input.product || "rap-node-agent",
current_version: input.currentVersion || "",
os: input.os || "linux",
arch: input.arch || "amd64",
install_type: input.installType || "docker",
channel: input.channel || "dev",
});
const payload = await this.get<{ node_update_plan: NodeUpdatePlan }>(`/clusters/${clusterId}/nodes/${nodeId}/updates/plan?${params.toString()}`);
return payload.node_update_plan;
}
async upsertNodeUpdatePolicy(clusterId: string, nodeId: string, input: UpsertNodeUpdatePolicyPayload): Promise<NodeUpdatePolicy> {
const payload = await this.put<{ node_update_policy: NodeUpdatePolicy }>(`/clusters/${clusterId}/nodes/${nodeId}/updates/policy`, {
actor_user_id: this.actorUserId,
product: input.product,
channel: input.channel || "dev",
target_version: input.targetVersion ?? null,
strategy: input.strategy || "rolling",
enabled: input.enabled ?? true,
rollback_allowed: input.rollbackAllowed ?? true,
health_window_seconds: input.healthWindowSeconds || 180,
});
return payload.node_update_policy;
}
async listNodeUpdateStatuses(clusterId: string, nodeId: string, limit = 80): Promise<NodeUpdateStatus[]> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId, limit: String(limit) });
const payload = await this.get<{ node_update_statuses: NodeUpdateStatus[] }>(`/clusters/${clusterId}/nodes/${nodeId}/updates/statuses?${params.toString()}`);
return payload.node_update_statuses ?? [];
}
async listFabricTestingFlags(): Promise<FabricTestingFlag[]> {
const payload = await this.get<{ testing_flags: FabricTestingFlag[] }>("/fabric/testing-flags");
return payload.testing_flags ?? [];
@@ -374,6 +550,28 @@ export class AdminApiClient {
return payload.mesh_links ?? [];
}
async listRouteIntents(clusterId: string): Promise<MeshRouteIntent[]> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ route_intents: MeshRouteIntent[] }>(`/clusters/${clusterId}/mesh/route-intents?${params.toString()}`);
return payload.route_intents ?? [];
}
async expireRouteIntent(clusterId: string, routeIntentId: string, reason: string): Promise<MeshRouteIntent> {
const payload = await this.post<{ route_intent: MeshRouteIntent }>(`/clusters/${clusterId}/mesh/route-intents/${routeIntentId}/expire`, {
actor_user_id: this.actorUserId,
reason,
});
return payload.route_intent;
}
async disableRouteIntent(clusterId: string, routeIntentId: string, reason: string): Promise<MeshRouteIntent> {
const payload = await this.post<{ route_intent: MeshRouteIntent }>(`/clusters/${clusterId}/mesh/route-intents/${routeIntentId}/disable`, {
actor_user_id: this.actorUserId,
reason,
});
return payload.route_intent;
}
async getNodeSyntheticMeshConfig(clusterId: string, nodeId: string): Promise<NodeSyntheticMeshConfig> {
const payload = await this.get<{ synthetic_mesh_config: NodeSyntheticMeshConfig }>(
`/clusters/${clusterId}/nodes/${nodeId}/mesh/synthetic-config`,
@@ -381,6 +579,443 @@ export class AdminApiClient {
return payload.synthetic_mesh_config;
}
async listFabricServiceChannelRouteFeedback(
clusterId: string,
input: { reporterNodeId?: string; routeId?: string; serviceClass?: string; feedbackStatus?: string; includeExpired?: boolean } = {},
): Promise<FabricServiceChannelRouteFeedbackObservation[]> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.reporterNodeId) {
params.set("reporter_node_id", input.reporterNodeId);
}
if (input.routeId) {
params.set("route_id", input.routeId);
}
if (input.serviceClass) {
params.set("service_class", input.serviceClass);
}
if (input.feedbackStatus) {
params.set("feedback_status", input.feedbackStatus);
}
if (input.includeExpired) {
params.set("include_expired", "true");
}
const payload = await this.get<{ route_feedback: FabricServiceChannelRouteFeedbackObservation[] }>(
`/clusters/${clusterId}/fabric/service-channels/route-feedback?${params.toString()}`,
);
return payload.route_feedback ?? [];
}
async expireFabricServiceChannelRouteFeedback(
clusterId: string,
input: { routeId: string; reporterNodeId?: string; serviceClass?: string; reason?: string },
): Promise<ExpireFabricServiceChannelRouteFeedbackResult> {
const payload = await this.post<{ route_feedback_expire: ExpireFabricServiceChannelRouteFeedbackResult }>(
`/clusters/${clusterId}/fabric/service-channels/route-feedback/expire`,
{
actor_user_id: this.actorUserId,
route_id: input.routeId,
reporter_node_id: input.reporterNodeId || "",
service_class: input.serviceClass || "",
reason: input.reason || "expired from admin fabric diagnostics",
},
);
return payload.route_feedback_expire;
}
async listFabricServiceChannelRouteRebuildAttempts(
clusterId: string,
input: {
reporterNodeId?: string;
routeId?: string;
replacementRouteId?: string;
serviceClass?: string;
rebuildStatus?: string;
rebuildRequestId?: string;
generation?: string;
feedbackSource?: string;
feedbackChannelId?: string;
feedbackViolationStatus?: string;
enrichment?: "summary" | "deep";
limit?: number;
offset?: number;
} = {},
): Promise<FabricServiceChannelRouteRebuildAttempt[]> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.reporterNodeId) {
params.set("reporter_node_id", input.reporterNodeId);
}
if (input.routeId) {
params.set("route_id", input.routeId);
}
if (input.replacementRouteId) {
params.set("replacement_route_id", input.replacementRouteId);
}
if (input.serviceClass) {
params.set("service_class", input.serviceClass);
}
if (input.rebuildStatus) {
params.set("rebuild_status", input.rebuildStatus);
}
if (input.rebuildRequestId) {
params.set("rebuild_request_id", input.rebuildRequestId);
}
if (input.generation) {
params.set("generation", input.generation);
}
if (input.feedbackSource) {
params.set("feedback_source", input.feedbackSource);
}
if (input.feedbackChannelId) {
params.set("feedback_channel_id", input.feedbackChannelId);
}
if (input.feedbackViolationStatus) {
params.set("feedback_violation_status", input.feedbackViolationStatus);
}
if (input.enrichment) {
params.set("enrichment", input.enrichment);
}
if (input.limit) {
params.set("limit", String(input.limit));
}
if (input.offset) {
params.set("offset", String(input.offset));
}
const payload = await this.get<{ rebuild_attempts: FabricServiceChannelRouteRebuildAttempt[] }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-attempts?${params.toString()}`,
);
return payload.rebuild_attempts ?? [];
}
async getFabricServiceChannelRouteRebuildHealthSummary(clusterId: string, input: { limit?: number } = {}): Promise<FabricServiceChannelRouteRebuildHealthSummary> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
const payload = await this.get<{ rebuild_health: FabricServiceChannelRouteRebuildHealthSummary }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-health?${params.toString()}`,
);
return payload.rebuild_health;
}
async getFabricServiceChannelReadiness(clusterId: string, input: { limit?: number } = {}): Promise<FabricServiceChannelReadiness> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
const payload = await this.get<{ fabric_service_channel_readiness: FabricServiceChannelReadiness }>(
`/clusters/${clusterId}/fabric/service-channels/readiness?${params.toString()}`,
);
return payload.fabric_service_channel_readiness;
}
async getFabricServiceChannelSchemaStatus(clusterId: string): Promise<FabricServiceChannelSchemaStatus> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ fabric_service_channel_schema_status: FabricServiceChannelSchemaStatus }>(
`/clusters/${clusterId}/fabric/service-channels/schema-status?${params.toString()}`,
);
return payload.fabric_service_channel_schema_status;
}
async getFabricServiceChannelRebuildSnapshotMaintenanceHealth(
clusterId: string,
input: { limit?: number; minAgeSeconds?: number; heartbeatThreshold?: number } = {},
): Promise<FabricServiceChannelRebuildSnapshotMaintenanceHealth> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
if (input.minAgeSeconds) {
params.set("min_age_seconds", String(input.minAgeSeconds));
}
if (input.heartbeatThreshold) {
params.set("heartbeat_threshold", String(input.heartbeatThreshold));
}
const payload = await this.get<{ rebuild_snapshot_health: FabricServiceChannelRebuildSnapshotMaintenanceHealth }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-snapshots/health?${params.toString()}`,
);
return payload.rebuild_snapshot_health;
}
async warmupFabricServiceChannelRebuildSnapshots(
clusterId: string,
input: { limit?: number; staleAfterSeconds?: number } = {},
): Promise<FabricServiceChannelRebuildSnapshotWarmup> {
const payload = await this.post<{ rebuild_snapshot_warmup: FabricServiceChannelRebuildSnapshotWarmup }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-snapshots/warmup`,
{
actor_user_id: this.actorUserId,
limit: input.limit || 10,
stale_after_seconds: input.staleAfterSeconds || 60,
},
);
return payload.rebuild_snapshot_warmup;
}
async getFabricServiceChannelLeaseMaintenance(
clusterId: string,
input: { limit?: number; includeExpired?: boolean } = {},
): Promise<FabricServiceChannelLeaseMaintenance> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
if (input.includeExpired) {
params.set("include_expired", "true");
}
const payload = await this.get<{ fabric_service_channel_lease_maintenance: FabricServiceChannelLeaseMaintenance }>(
`/clusters/${clusterId}/fabric/service-channels/leases?${params.toString()}`,
);
return payload.fabric_service_channel_lease_maintenance;
}
async cleanupFabricServiceChannelLeases(clusterId: string, input: { limit?: number } = {}): Promise<FabricServiceChannelLeaseMaintenance> {
const payload = await this.post<{ fabric_service_channel_lease_maintenance: FabricServiceChannelLeaseMaintenance }>(
`/clusters/${clusterId}/fabric/service-channels/leases/cleanup`,
{
actor_user_id: this.actorUserId,
limit: input.limit || 100,
},
);
return payload.fabric_service_channel_lease_maintenance;
}
async getFabricServiceChannelAccessTelemetry(clusterId: string, input: { limit?: number } = {}): Promise<FabricServiceChannelAccessTelemetry> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
const payload = await this.get<{ fabric_service_channel_access_telemetry: FabricServiceChannelAccessTelemetry }>(
`/clusters/${clusterId}/fabric/service-channels/access-telemetry?${params.toString()}`,
);
return payload.fabric_service_channel_access_telemetry;
}
async listFabricServiceChannelRouteRebuildIncidents(clusterId: string, input: { limit?: number } = {}): Promise<FabricServiceChannelRouteRebuildIncident[]> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
const payload = await this.get<{ rebuild_incidents: FabricServiceChannelRouteRebuildIncident[] }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-incidents?${params.toString()}`,
);
return payload.rebuild_incidents ?? [];
}
async getFabricServiceChannelRebuildInvestigationBreadcrumbs(
clusterId: string,
input: { limit?: number; currentWindowSeconds?: number; historyWindowSeconds?: number } = {},
): Promise<FabricServiceChannelRebuildInvestigationBreadcrumbs> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
if (input.limit) {
params.set("limit", String(input.limit));
}
if (input.currentWindowSeconds) {
params.set("current_window_seconds", String(input.currentWindowSeconds));
}
if (input.historyWindowSeconds) {
params.set("history_window_seconds", String(input.historyWindowSeconds));
}
const payload = await this.get<{ rebuild_investigation_breadcrumbs: FabricServiceChannelRebuildInvestigationBreadcrumbs }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-investigations/breadcrumbs?${params.toString()}`,
);
return payload.rebuild_investigation_breadcrumbs;
}
async recordFabricServiceChannelRouteRebuildInvestigation(
clusterId: string,
input: {
reporterNodeId?: string;
routeId?: string;
serviceClass?: string;
generation?: string;
guardStatus?: string;
incidentId?: string;
feedbackSource?: string;
feedbackChannelId?: string;
feedbackViolationStatus?: string;
drilldownSource?: string;
reason?: string;
},
): Promise<void> {
await this.post(`/clusters/${clusterId}/fabric/service-channels/rebuild-incidents/investigations`, {
actor_user_id: this.actorUserId,
reporter_node_id: input.reporterNodeId,
route_id: input.routeId,
service_class: input.serviceClass || "",
generation: input.generation || "",
guard_status: input.guardStatus || "",
incident_id: input.incidentId || "",
feedback_source: input.feedbackSource || "",
feedback_channel_id: input.feedbackChannelId || "",
feedback_violation_status: input.feedbackViolationStatus || "",
drilldown_source: input.drilldownSource || "",
reason: input.reason || "operator opened deep rebuild ledger",
});
}
async silenceFabricServiceChannelRouteRebuildAlert(
clusterId: string,
input: {
incidentSource?: string;
channelId?: string;
reporterNodeId: string;
routeId: string;
guardStatus: string;
generation?: string;
reason?: string;
ttlSeconds?: number;
},
): Promise<FabricServiceChannelRouteRebuildAlertSilence> {
const payload = await this.post<{ rebuild_alert_silence: FabricServiceChannelRouteRebuildAlertSilence }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-health/silences`,
{
actor_user_id: this.actorUserId,
incident_source: input.incidentSource || "",
channel_id: input.channelId || "",
reporter_node_id: input.reporterNodeId,
route_id: input.routeId,
guard_status: input.guardStatus,
generation: input.generation || "",
reason: input.reason || "operator acknowledged rebuild alert",
ttl_seconds: input.ttlSeconds || 21600,
},
);
return payload.rebuild_alert_silence;
}
async listFabricServiceChannelRouteRebuildAlertSilences(
clusterId: string,
): Promise<FabricServiceChannelRouteRebuildAlertSilence[]> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ rebuild_alert_silences: FabricServiceChannelRouteRebuildAlertSilence[] }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-health/silences?${params.toString()}`,
);
return payload.rebuild_alert_silences ?? [];
}
async unsilenceFabricServiceChannelRouteRebuildAlert(
clusterId: string,
silenceId: string,
reason?: string,
): Promise<FabricServiceChannelRouteRebuildAlertSilence> {
const payload = await this.delete<{ rebuild_alert_silence: FabricServiceChannelRouteRebuildAlertSilence }>(
`/clusters/${clusterId}/fabric/service-channels/rebuild-health/silences/${encodeURIComponent(silenceId)}`,
{
actor_user_id: this.actorUserId,
reason: reason || "operator removed rebuild alert silence",
},
);
return payload.rebuild_alert_silence;
}
async getFabricServiceChannelRecoveryPolicy(clusterId: string): Promise<FabricServiceChannelRecoveryPolicy> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ fabric_service_channel_recovery_policy: FabricServiceChannelRecoveryPolicy }>(
`/clusters/${clusterId}/fabric/service-channels/recovery-policy?${params.toString()}`,
);
return payload.fabric_service_channel_recovery_policy;
}
async updateFabricServiceChannelRecoveryPolicy(
clusterId: string,
input: UpdateFabricServiceChannelRecoveryPolicyPayload,
): Promise<FabricServiceChannelRecoveryPolicy> {
const payload = await this.put<{ fabric_service_channel_recovery_policy: FabricServiceChannelRecoveryPolicy }>(
`/clusters/${clusterId}/fabric/service-channels/recovery-policy`,
{
actor_user_id: this.actorUserId,
hysteresis_penalty: input.hysteresisPenalty,
promotion_min_samples: input.promotionMinSamples,
demotion_failure_threshold: input.demotionFailureThreshold,
demotion_drop_threshold: input.demotionDropThreshold,
demotion_slow_threshold: input.demotionSlowThreshold,
demotion_rebuild_enabled: input.demotionRebuildEnabled,
demotion_fenced_enabled: input.demotionFencedEnabled,
},
);
return payload.fabric_service_channel_recovery_policy;
}
async getFabricServiceChannelAdaptivePolicy(clusterId: string): Promise<FabricServiceChannelAdaptivePolicy> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ fabric_service_channel_adaptive_policy: FabricServiceChannelAdaptivePolicy }>(
`/clusters/${clusterId}/fabric/service-channels/adaptive-policy?${params.toString()}`,
);
return payload.fabric_service_channel_adaptive_policy;
}
async updateFabricServiceChannelAdaptivePolicy(
clusterId: string,
input: UpdateFabricServiceChannelAdaptivePolicyPayload,
): Promise<FabricServiceChannelAdaptivePolicy> {
const payload = await this.put<{ fabric_service_channel_adaptive_policy: FabricServiceChannelAdaptivePolicy }>(
`/clusters/${clusterId}/fabric/service-channels/adaptive-policy`,
{
actor_user_id: this.actorUserId,
max_parallel_window: input.maxParallelWindow,
bulk_pressure_channel_threshold: input.bulkPressureChannelThreshold,
queue_pressure_high_watermark: input.queuePressureHighWatermark,
queue_pressure_max_in_flight: input.queuePressureMaxInFlight,
class_windows: input.classWindows,
},
);
return payload.fabric_service_channel_adaptive_policy;
}
async getFabricServiceChannelPoolPolicy(clusterId: string): Promise<FabricServiceChannelPoolPolicy> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ fabric_service_channel_pool_policy: FabricServiceChannelPoolPolicy }>(
`/clusters/${clusterId}/fabric/service-channels/pool-policy?${params.toString()}`,
);
return payload.fabric_service_channel_pool_policy;
}
async updateFabricServiceChannelPoolPolicy(
clusterId: string,
input: UpdateFabricServiceChannelPoolPolicyPayload,
): Promise<FabricServiceChannelPoolPolicy> {
const payload = await this.put<{ fabric_service_channel_pool_policy: FabricServiceChannelPoolPolicy }>(
`/clusters/${clusterId}/fabric/service-channels/pool-policy`,
{
actor_user_id: this.actorUserId,
entry_pool_node_ids: input.entryPoolNodeIds,
exit_pool_node_ids: input.exitPoolNodeIds,
preferred_entry_node_id: input.preferredEntryNodeId,
preferred_exit_node_id: input.preferredExitNodeId,
selection_strategy: input.selectionStrategy,
route_rebuild: input.routeRebuild,
entry_failover: input.entryFailover,
exit_failover: input.exitFailover,
backend_fallback_allowed: input.backendFallbackAllowed,
sticky_session: input.stickySession,
},
);
return payload.fabric_service_channel_pool_policy;
}
async getFabricServiceChannelBreadcrumbWindowPolicy(clusterId: string): Promise<FabricServiceChannelBreadcrumbWindowPolicy> {
const params = new URLSearchParams({ actor_user_id: this.actorUserId });
const payload = await this.get<{ fabric_service_channel_breadcrumb_window_policy: FabricServiceChannelBreadcrumbWindowPolicy }>(
`/clusters/${clusterId}/fabric/service-channels/breadcrumb-window-policy?${params.toString()}`,
);
return payload.fabric_service_channel_breadcrumb_window_policy;
}
async updateFabricServiceChannelBreadcrumbWindowPolicy(
clusterId: string,
input: UpdateFabricServiceChannelBreadcrumbWindowPolicyPayload,
): Promise<FabricServiceChannelBreadcrumbWindowPolicy> {
const payload = await this.put<{ fabric_service_channel_breadcrumb_window_policy: FabricServiceChannelBreadcrumbWindowPolicy }>(
`/clusters/${clusterId}/fabric/service-channels/breadcrumb-window-policy`,
{
actor_user_id: this.actorUserId,
current_window_seconds: input.currentWindowSeconds,
history_window_seconds: input.historyWindowSeconds,
},
);
return payload.fabric_service_channel_breadcrumb_window_policy;
}
async listQoSPolicies(clusterId: string): Promise<QoSPolicy[]> {
const payload = await this.get<{ qos_policies: QoSPolicy[] }>(`/clusters/${clusterId}/mesh/qos-policies`);
return payload.qos_policies ?? [];
@@ -519,6 +1154,46 @@ export class AdminApiClient {
}
}
async getVPNPacketStats(clusterId: string, vpnConnectionId: string): Promise<VPNPacketStats> {
const payload = await this.get<{ vpn_packet_stats: VPNPacketStats }>(
`/clusters/${clusterId}/vpn-connections/${vpnConnectionId}/tunnel/stats`,
);
return payload.vpn_packet_stats ?? {};
}
async getVPNClientDiagnosticStatus(clusterId: string, deviceId: string): Promise<VPNClientDiagnosticStatus | null> {
if (!deviceId.trim()) {
return null;
}
try {
const payload = await this.get<{ vpn_client_diagnostic_status: VPNClientDiagnosticStatus }>(
`/clusters/${clusterId}/vpn/client-diagnostics/${encodeURIComponent(deviceId.trim())}/status`,
);
return payload.vpn_client_diagnostic_status ?? null;
} catch {
return null;
}
}
async listVPNClientDiagnosticStatuses(clusterId: string): Promise<VPNClientDiagnosticStatus[]> {
const payload = await this.get<{ vpn_client_diagnostic_statuses: VPNClientDiagnosticStatus[] }>(
`/clusters/${clusterId}/vpn/client-diagnostics`,
);
return payload.vpn_client_diagnostic_statuses ?? [];
}
async enqueueVPNClientDiagnosticCommand(
clusterId: string,
deviceId: string,
command: Record<string, unknown>,
): Promise<VPNClientDiagnosticCommand> {
const payload = await this.post<{ vpn_client_diagnostic_command: VPNClientDiagnosticCommand }>(
`/clusters/${clusterId}/vpn/client-diagnostics/${encodeURIComponent(deviceId.trim())}/commands`,
command,
);
return payload.vpn_client_diagnostic_command;
}
async expireStaleVPNLeases(clusterId: string): Promise<VPNConnectionLease[]> {
const payload = await this.post<{ expired_leases: VPNConnectionLease[] }>(
`/clusters/${clusterId}/vpn-connection-leases/expire-stale`,
@@ -529,9 +1204,47 @@ export class AdminApiClient {
return payload.expired_leases ?? [];
}
async listAudit(clusterId: string): Promise<AuditEvent[]> {
const payload = await this.get<{ audit_events: AuditEvent[] }>(`/clusters/${clusterId}/audit?limit=100`);
return payload.audit_events ?? [];
async listAudit(
clusterId: string,
input: {
eventTypes?: string[];
targetTypes?: string[];
correlation?: string;
limit?: number;
} = {},
): Promise<AuditEvent[]> {
return (await this.listAuditDetailed(clusterId, input)).events;
}
async listAuditDetailed(
clusterId: string,
input: {
eventTypes?: string[];
targetTypes?: string[];
correlation?: string;
limit?: number;
} = {},
): Promise<{ events: AuditEvent[]; summary?: AuditSummary }> {
const params = new URLSearchParams({ limit: String(input.limit || 100) });
for (const eventType of input.eventTypes || []) {
if (eventType) {
params.append("event_type", eventType);
}
}
for (const targetType of input.targetTypes || []) {
if (targetType) {
params.append("target_type", targetType);
}
}
if (input.correlation) {
params.set("correlation", input.correlation);
}
const payload = await this.get<{ audit_events: AuditEvent[]; audit_summary?: AuditSummary }>(`/clusters/${clusterId}/audit?${params.toString()}`);
return { events: payload.audit_events ?? [], summary: payload.audit_summary };
}
clusterEventsURL(clusterId: string): string {
return `${this.baseUrl}/clusters/${encodeURIComponent(clusterId)}/events?actor_user_id=${encodeURIComponent(this.actorUserId)}`;
}
async getOrganizationAdminSummary(organizationId: string): Promise<OrganizationAdminSummary> {
@@ -541,6 +1254,96 @@ export class AdminApiClient {
return payload.admin_summary;
}
async listOrganizations(): Promise<Organization[]> {
const payload = await this.request<{ organizations: Organization[] }>(
`/organizations?user_id=${encodeURIComponent(this.actorUserId)}`,
{ method: "GET" },
);
return payload.organizations ?? [];
}
async createOrganization(input: CreateOrganizationPayload): Promise<Organization> {
const payload = await this.post<{ organization: Organization }>("/organizations/", {
actor_user_id: this.actorUserId,
slug: input.slug,
name: input.name,
metadata: input.metadata || {},
});
return payload.organization;
}
async listUsers(): Promise<UserAccount[]> {
const payload = await this.get<{ users: UserAccount[] }>("/users/");
return payload.users ?? [];
}
async createUser(input: CreateUserPayload): Promise<UserAccount> {
const payload = await this.post<{ user: UserAccount }>("/users/", {
actor_user_id: this.actorUserId,
email: input.email,
password: input.password,
platform_role: input.platformRole || "user",
});
return payload.user;
}
async listOrganizationMemberships(organizationId: string): Promise<OrganizationMembership[]> {
const payload = await this.request<{ memberships: OrganizationMembership[] }>(
`/organizations/${organizationId}/memberships?user_id=${encodeURIComponent(this.actorUserId)}`,
{ method: "GET" },
);
return payload.memberships ?? [];
}
async addOrganizationMembership(organizationId: string, input: { userId: string; roleId: string }): Promise<OrganizationMembership> {
const payload = await this.post<{ membership: OrganizationMembership }>(`/organizations/${organizationId}/memberships`, {
actor_user_id: this.actorUserId,
user_id: input.userId,
role_id: input.roleId,
});
return payload.membership;
}
async listResources(organizationId?: string): Promise<Resource[]> {
const params = new URLSearchParams({ user_id: this.actorUserId });
if (organizationId) {
params.set("organization_id", organizationId);
}
const payload = await this.request<{ resources: Resource[] }>(`/resources?${params.toString()}`, { method: "GET" });
return payload.resources ?? [];
}
async createResource(input: CreateResourcePayload): Promise<Resource> {
const payload = await this.post<{ resource: Resource }>("/resources/", {
actor_user_id: this.actorUserId,
organization_id: input.organizationId,
name: input.name,
address: input.address,
protocol: input.protocol || "rdp",
secret_ref: input.secretRef || null,
certificate_verification_mode: input.certificateVerificationMode || "strict",
render_quality_profile: input.renderQualityProfile || "balanced",
clipboard_mode: input.clipboardMode || "disabled",
file_transfer_mode: input.fileTransferMode || "disabled",
metadata: input.metadata || {},
});
return payload.resource;
}
async upsertResourceSecret(resourceId: string, input: UpsertResourceSecretPayload): Promise<void> {
await this.put(`/resources/${resourceId}/secret`, {
actor_user_id: this.actorUserId,
payload: {
username: input.username || "",
password: input.password || "",
domain: input.domain || "",
},
metadata: {
source: "web-admin",
},
});
}
private async get<T>(path: string): Promise<T> {
const separator = path.includes("?") ? "&" : "?";
return this.request<T>(`${path}${separator}actor_user_id=${encodeURIComponent(this.actorUserId)}`, {
@@ -564,6 +1367,14 @@ export class AdminApiClient {
});
}
private async delete<T>(path: string, body: unknown): Promise<T> {
return this.request<T>(path, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
private async request<T>(path: string, init: RequestInit): Promise<T> {
const response = await fetch(`${this.baseUrl}${path}`, init);
if (!response.ok) {