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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Панель Secure Access Fabric</title>
<script type="module" crossorigin src="/assets/index-Bx8KW1vY.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BLOAmgHn.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+5589 -285
View File
File diff suppressed because it is too large Load Diff
+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) {
+478 -11
View File
@@ -149,7 +149,14 @@ button.danger {
min-height: 100vh;
}
.sideRail {
.portalShell {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
min-height: 100vh;
}
.sideRail,
.portalRail {
position: sticky;
top: 0;
height: 100vh;
@@ -160,6 +167,11 @@ button.danger {
radial-gradient(circle at 20% 20%, rgba(184, 111, 35, 0.36), transparent 16rem);
}
.portalRail button {
width: 100%;
margin-top: 16px;
}
.brandMark {
display: inline-grid;
width: 58px;
@@ -183,7 +195,8 @@ button.danger {
text-transform: uppercase;
}
.sideRail h1 {
.sideRail h1,
.portalRail h1 {
margin: 0 0 12px;
font-size: clamp(2.3rem, 4vw, 4.2rem);
line-height: 0.9;
@@ -218,9 +231,15 @@ button.danger {
padding: 28px 28px 54px;
}
.topBar {
.portalWorkspace {
width: min(1280px, calc(100vw - 310px));
padding: 28px 28px 54px;
}
.topBar,
.portalTop {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(260px, 360px) auto minmax(220px, auto);
grid-template-columns: minmax(0, 1fr) minmax(260px, 360px) auto minmax(150px, auto) minmax(220px, auto);
align-items: center;
gap: 20px;
padding: 28px;
@@ -231,6 +250,10 @@ button.danger {
backdrop-filter: blur(16px);
}
.portalTop {
grid-template-columns: minmax(0, 1fr) minmax(240px, 340px) auto;
}
.clusterPicker {
display: grid;
gap: 8px;
@@ -248,6 +271,25 @@ button.danger {
overflow-wrap: anywhere;
}
.refreshStatus {
display: grid;
gap: 4px;
min-width: 150px;
color: var(--muted);
font-size: 0.78rem;
font-weight: 800;
line-height: 1.35;
}
.refreshStatus strong {
color: var(--text);
font-size: 0.82rem;
}
.refreshStatus span {
overflow-wrap: anywhere;
}
.profilePanel {
display: grid;
grid-template-columns: 1fr;
@@ -441,6 +483,69 @@ summary {
gap: 16px;
}
.cardHead.compact {
gap: 10px;
}
.subPanel {
display: grid;
gap: 14px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(255, 255, 255, 0.45);
}
.portalInstallList {
display: grid;
gap: 10px;
margin-top: 14px;
}
.installTile {
display: grid;
gap: 6px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 18px;
color: var(--ink);
background: rgba(255, 255, 255, 0.52);
text-decoration: none;
}
.installTile:hover {
transform: translateY(-1px);
}
.installTile strong {
font-size: 1.05rem;
}
.installTile span,
.installTile small {
color: var(--muted);
overflow-wrap: anywhere;
}
.primaryInstall {
border-color: rgba(47, 111, 79, 0.35);
background: rgba(47, 111, 79, 0.1);
}
.portalRoadmap {
display: grid;
gap: 8px;
}
.portalRoadmap span {
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(255, 255, 255, 0.48);
color: var(--muted);
font-weight: 800;
}
.vpnCard {
grid-template-columns: minmax(0, 1.2fr) minmax(220px, 0.8fr) auto;
padding: 16px 0;
@@ -476,7 +581,7 @@ summary {
.nodeListRow {
display: grid;
grid-template-columns: minmax(240px, 1.2fr) auto minmax(110px, 0.55fr) minmax(130px, 0.7fr) auto auto;
grid-template-columns: minmax(220px, 1.1fr) auto minmax(220px, 0.9fr) minmax(150px, 0.7fr) auto minmax(180px, 0.8fr) minmax(130px, 0.7fr) auto auto;
align-items: center;
gap: 10px;
padding: 10px;
@@ -496,6 +601,43 @@ summary {
white-space: nowrap;
}
.runtimeBadges {
display: flex;
flex-wrap: wrap;
gap: 5px;
min-width: 0;
}
.runtimeBadges .pill {
padding: 0.24rem 0.52rem;
font-size: 0.68rem;
}
.nodeEndpointCell {
display: grid;
gap: 5px;
min-width: 0;
}
.nodeEndpointCell .pill {
width: fit-content;
max-width: 100%;
padding: 0.28rem 0.58rem;
font-size: 0.72rem;
}
.nodeEndpointCell strong,
.nodeEndpointCell small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nodeEndpointCell small {
color: var(--muted);
font-size: 0.74rem;
}
.functionList {
display: grid;
gap: 8px;
@@ -513,6 +655,41 @@ summary {
background: rgba(255, 255, 255, 0.42);
}
.functionState {
min-width: 96px;
display: grid;
gap: 2px;
padding: 8px 10px;
border-radius: 14px;
background: rgba(16, 43, 35, 0.06);
color: var(--ink);
}
.functionState small {
color: var(--muted);
font-size: 0.68rem;
font-weight: 800;
}
.functionState strong {
font-size: 0.76rem;
}
.functionState.good {
background: rgba(36, 118, 84, 0.14);
color: var(--green);
}
.functionState.info {
background: rgba(41, 80, 111, 0.12);
color: var(--blue);
}
.functionState.warn {
background: rgba(175, 110, 46, 0.15);
color: #8a4f19;
}
.nodePanel {
display: grid;
gap: 12px;
@@ -522,6 +699,111 @@ summary {
background: rgba(255, 255, 255, 0.42);
}
.nodeDetails {
display: grid;
gap: 14px;
}
.nodeDetailGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.nodeMetricGrid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.summaryChips {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
}
.inlineActions {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.stackedText {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.nodeTabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
position: sticky;
top: 0;
z-index: 2;
padding: 8px 0;
background: rgba(249, 247, 239, 0.94);
}
.nodeTabs button {
min-width: 92px;
padding: 0.55rem 0.8rem;
}
.nodeTabs button.active {
color: #fffaf0;
border-color: transparent;
background: #36556c;
}
.rawDetailsGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.rawBlock {
min-width: 0;
border: 1px solid var(--line);
border-radius: 12px;
background: rgba(255, 255, 255, 0.42);
}
.rawBlock summary {
cursor: pointer;
padding: 10px 12px;
font-weight: 700;
}
.rawBlock pre {
max-height: 320px;
margin: 0;
overflow: auto;
padding: 0 12px 12px;
font-size: 0.78rem;
line-height: 1.45;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.segmented {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.segmented button {
min-width: 84px;
padding: 0.52rem 0.85rem;
}
.segmented button.active {
color: #fffaf0;
border-color: transparent;
background: #36556c;
}
.stateList {
display: grid;
gap: 10px;
@@ -621,11 +903,21 @@ summary {
background: rgba(47, 111, 79, 0.12);
}
.pill.info {
color: var(--steel);
background: rgba(54, 85, 108, 0.1);
}
.pill.bad {
color: var(--red);
background: rgba(177, 68, 52, 0.12);
}
.pill.warn {
color: var(--amber);
background: rgba(184, 111, 35, 0.14);
}
.membershipList {
display: flex;
flex-wrap: wrap;
@@ -691,7 +983,8 @@ summary {
.topologySvg {
width: 100%;
min-height: 440px;
min-height: 520px;
max-height: 78vh;
border: 1px solid var(--line);
border-radius: 22px;
color: rgba(24, 32, 24, 0.42);
@@ -750,12 +1043,25 @@ summary {
stroke: var(--amber);
}
.topologyLink.oneWay {
color: var(--amber);
stroke: var(--amber);
stroke-dasharray: 4 7;
}
.topologyLink.bad {
color: var(--red);
stroke: var(--red);
stroke-dasharray: 10 8;
}
.topologyLink.stale {
color: var(--red);
stroke: var(--red);
stroke-dasharray: 2 8;
opacity: 0.42;
}
.topologyPlacementLink {
stroke: var(--steel);
stroke-width: 3;
@@ -774,6 +1080,15 @@ summary {
stroke: var(--amber);
}
.topologyConfiguredLink {
color: var(--steel);
stroke: var(--steel);
stroke-width: 2.5;
stroke-linecap: round;
stroke-dasharray: 4 7;
opacity: 0.72;
}
.topologyLinkLabel,
.topologyNodeName,
.topologyNodeMeta,
@@ -834,6 +1149,7 @@ summary {
}
.topologyNodeCircle.critical,
.topologyNodeCircle.offline,
.topologyNodeCircle.failed {
stroke: var(--red);
}
@@ -884,6 +1200,22 @@ summary {
border-color: var(--amber);
}
.legendLine.oneWay {
border-top-style: dashed;
border-color: var(--amber);
}
.legendLine.stale,
.legendLine.problem {
border-top-style: dashed;
border-color: var(--red);
}
.legendLine.configured {
border-top-style: dashed;
border-color: var(--steel);
}
.serviceTags {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
@@ -944,13 +1276,25 @@ summary {
.status.active,
.status.approved,
.status.healthy,
.status.connected,
.status.current,
.status.running,
.status.authoritative {
background: var(--green);
}
.status.outdated,
.status.no_policy {
background: var(--amber);
}
.status.rejected,
.status.failed,
.status.critical,
.status.missing,
.status.offline,
.status.stale,
.status.unreachable,
.status.disabled,
.status.revoked {
background: var(--red);
@@ -996,6 +1340,23 @@ th {
background: var(--green);
}
.noticePanel.goodPanel {
background: var(--green);
}
.noticePanel.warnPanel {
background: var(--amber);
}
.noticePanel.badPanel {
background: var(--red);
}
.noticePanel .muted,
.noticePanel .stateLine span {
color: rgba(255, 255, 255, 0.78);
}
.formGrid {
display: grid;
grid-template-columns: repeat(2, minmax(180px, 1fr));
@@ -1060,10 +1421,30 @@ th {
margin: 18px 0;
}
.inlineForm input {
.inlineForm input,
.inlineForm select {
flex: 1 1 320px;
}
.diagnosticCommandPanel {
display: grid;
gap: 12px;
margin: 12px 0 18px;
padding: 14px;
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255, 255, 255, 0.42);
}
.diagnosticCommandPanel label {
display: grid;
gap: 6px;
}
.diagnosticCommandPanel input {
min-width: min(520px, 100%);
}
.secretOnce {
display: grid;
gap: 8px;
@@ -1087,16 +1468,19 @@ code {
}
@media (max-width: 1100px) {
.consoleShell {
.consoleShell,
.portalShell {
grid-template-columns: 1fr;
}
.sideRail {
.sideRail,
.portalRail {
position: relative;
height: auto;
}
.workspace {
.workspace,
.portalWorkspace {
width: 100%;
}
@@ -1105,7 +1489,10 @@ code {
.grid.two,
.controlBar,
.topBar,
.portalTop,
.loginShell,
.nodeDetailGrid,
.rawDetailsGrid,
.vpnCard {
grid-template-columns: 1fr;
}
@@ -1121,11 +1508,91 @@ code {
}
@media (max-width: 720px) {
body {
background: #f4f1e7;
}
button,
input,
select,
textarea {
font-size: 16px;
}
button {
min-height: 46px;
}
.workspace,
.sideRail {
.portalWorkspace,
.sideRail,
.portalRail {
padding: 18px;
}
.loginShell {
align-items: start;
padding: 16px;
}
.loginCard {
padding: 18px;
border-radius: 18px;
}
.portalRail {
height: auto;
min-height: 0;
}
.portalRail .brandMark,
.portalRail .sideKicker,
.portalRail .sideText {
display: none;
}
.portalRail h1 {
margin: 0;
font-size: 2rem;
letter-spacing: 0;
}
.portalTop,
.card,
.metric,
.empty,
.errorPanel,
.noticePanel {
border-radius: 18px;
}
.portalTop {
padding: 18px;
}
.portalTop h2 {
font-size: 2rem;
letter-spacing: 0;
}
.portalInstallList,
.stack,
.grid {
gap: 12px;
}
.installTile {
border-radius: 14px;
}
.metric {
min-height: 96px;
}
.metric strong {
font-size: 2.2rem;
}
.formGrid,
.signalStrip,
.clusterCardMain,
+886
View File
@@ -148,6 +148,8 @@ export type CreatedJoinToken = {
token: string;
};
export type NodeJoinToken = Omit<CreatedJoinToken, "token">;
export type RoleAssignment = {
id: string;
cluster_id: string;
@@ -168,9 +170,58 @@ export type AuditEvent = {
target_type: string;
target_id?: string | null;
payload: Record<string, unknown>;
correlation_hints?: AuditCorrelationHints;
created_at: string;
};
export type AuditCorrelationHints = {
scope?: string;
current_diagnostic_status?: string;
breadcrumb_status?: string;
breadcrumb_age_seconds?: number;
breadcrumb_current_window_seconds?: number;
breadcrumb_history_window_seconds?: number;
feedback_breakdown?: FabricServiceChannelRouteRebuildFeedbackHealthBreakdown;
rebuild_incident?: FabricServiceChannelRouteRebuildIncident;
recommended_action?: string;
};
export type AuditSummary = {
total_count: number;
counts_by_event_type?: Record<string, number>;
counts_by_target_type?: Record<string, number>;
counts_by_current_diagnostic_status?: Record<string, number>;
counts_by_feedback_source?: Record<string, number>;
counts_by_feedback_violation_status?: Record<string, number>;
counts_by_breadcrumb_status?: Record<string, number>;
correlated_count?: number;
not_visible_count?: number;
latest_at?: string;
};
export type FabricServiceChannelRebuildInvestigationBreadcrumbs = {
cluster_id: string;
events: AuditEvent[];
summary: AuditSummary;
current_window_seconds?: number;
history_window_seconds?: number;
current_count?: number;
stale_count?: number;
expired_count?: number;
};
export type FabricServiceChannelBreadcrumbWindowPolicy = {
schema_version: string;
fingerprint?: string;
current_window_seconds: number;
history_window_seconds: number;
source: string;
updated_by_user_id?: string | null;
updated_at?: string;
control_plane_only: boolean;
production_forwarding: boolean;
};
export type WorkloadStatus = {
id: string;
cluster_id: string;
@@ -239,6 +290,90 @@ export type NodeTelemetryObservation = {
observed_at: string;
};
export type ReleaseArtifact = {
id: string;
release_id: string;
cluster_id: string;
product: string;
version: string;
os: string;
arch: string;
install_type: string;
kind: string;
url: string;
sha256: string;
size_bytes: number;
signature?: string | null;
metadata?: Record<string, unknown>;
created_at: string;
};
export type ReleaseVersion = {
id: string;
cluster_id: string;
product: string;
version: string;
channel: string;
status: string;
compatibility?: Record<string, unknown>;
changelog?: string | null;
created_by_user_id?: string | null;
created_at: string;
artifacts?: ReleaseArtifact[];
authority_payload?: Record<string, unknown>;
authority_signature?: ClusterSignature;
};
export type NodeUpdatePlan = {
schema_version: string;
cluster_id: string;
node_id: string;
product: string;
current_version?: string;
action: string;
reason: string;
target_version?: string;
channel?: string;
strategy?: string;
rollback_allowed: boolean;
health_window_seconds?: number;
artifact?: ReleaseArtifact;
authority_payload?: Record<string, unknown>;
authority_signature?: ClusterSignature;
production_forwarding: boolean;
};
export type NodeUpdatePolicy = {
id: string;
cluster_id: string;
node_id: string;
product: string;
channel: string;
target_version?: string | null;
strategy: string;
enabled: boolean;
rollback_allowed: boolean;
health_window_seconds: number;
updated_by_user_id?: string | null;
updated_at: string;
};
export type NodeUpdateStatus = {
id: string;
cluster_id: string;
node_id: string;
product: string;
current_version?: string;
target_version?: string;
phase: string;
status: string;
attempt_id?: string;
error_message?: string | null;
rollback_version?: string | null;
payload?: Record<string, unknown>;
observed_at: string;
};
export type MeshLink = {
id: string;
cluster_id: string;
@@ -251,6 +386,23 @@ export type MeshLink = {
observed_at: string;
};
export type MeshRouteIntent = {
id: string;
cluster_id: string;
source_selector: Record<string, unknown>;
destination_selector: Record<string, unknown>;
service_class: string;
priority: number;
status: string;
lifecycle_status?: string;
is_expired?: boolean;
policy_expires_at?: string | null;
policy: Record<string, unknown>;
created_by_user_id?: string | null;
created_at: string;
updated_at: string;
};
export type PeerEndpointCandidate = {
endpoint_id: string;
node_id: string;
@@ -330,6 +482,19 @@ export type RendezvousRelayPolicyReport = {
export type RoutePathDecision = {
decision_id: string;
route_id: string;
replacement_route_id?: string;
rebuild_request_id?: string;
rebuild_status?: string;
rebuild_reason?: string;
rebuild_attempt?: number;
feedback_observation_id?: string;
feedback_source?: string;
feedback_observed_at?: string;
feedback_expires_at?: string;
feedback_channel_id?: string;
feedback_resource_id?: string;
feedback_violation_status?: string;
feedback_violation_reason?: string;
cluster_id: string;
local_node_id: string;
source_node_id: string;
@@ -357,8 +522,15 @@ export type RoutePathDecisionReport = {
schema_version: string;
decision_mode: string;
generation: string;
recovery_policy?: FabricServiceChannelRecoveryPolicy;
decision_count: number;
replacement_decision_count: number;
degraded_decision_count?: number;
rebuild_request_count?: number;
rebuild_applied_count?: number;
recovery_hysteresis_count?: number;
recovery_promoted_count?: number;
recovery_demoted_count?: number;
control_plane_only: boolean;
production_forwarding: boolean;
decisions?: RoutePathDecision[];
@@ -379,6 +551,620 @@ export type SyntheticMeshRoute = {
peer_directory_version?: string;
};
export type FabricServiceChannelRouteFeedbackObservation = {
id?: string;
cluster_id: string;
reporter_node_id: string;
route_id: string;
service_class: string;
feedback_status: string;
score_adjustment: number;
reasons?: string[];
last_error?: string;
consecutive_failures?: number;
stall_count?: number;
last_send_duration_ms?: number;
payload?: Record<string, unknown>;
observed_at: string;
expires_at: string;
retry_cooldown_until?: string;
recovery_state?: string;
recovery_hysteresis_active?: boolean;
recovery_hysteresis_penalty?: number;
recovery_promoted?: boolean;
recovery_demoted?: boolean;
recovery_reason?: string;
observed_policy_fingerprint?: string;
effective_policy_fingerprint?: string;
observed_route_generation?: string;
effective_route_generation?: string;
provenance_missing?: boolean;
stale_policy?: boolean;
stale_generation?: boolean;
stale_reason?: string;
};
export type FabricServiceChannelRouteRebuildAttempt = {
id: string;
cluster_id: string;
reporter_node_id: string;
service_class: string;
route_id: string;
replacement_route_id?: string;
rebuild_request_id: string;
rebuild_status: string;
rebuild_reason?: string;
rebuild_attempt?: number;
decision_source: string;
outcome: string;
generation?: string;
policy_fingerprint?: string;
observed_policy_fingerprint?: string;
observed_route_generation?: string;
effective_route_generation?: string;
feedback_status?: string;
feedback_observation_id?: string;
feedback_source?: string;
feedback_observed_at?: string;
feedback_expires_at?: string;
feedback_channel_id?: string;
feedback_resource_id?: string;
feedback_violation_status?: string;
feedback_violation_reason?: string;
feedback_score_adjustment?: number;
feedback_effective_score_adjustment?: number;
feedback_reasons?: string[];
last_error?: string;
consecutive_failures?: number;
stall_count?: number;
last_send_duration_ms?: number;
quality_window_sample_count?: number;
quality_window_failure_count?: number;
quality_window_drop_count?: number;
quality_window_slow_count?: number;
old_hops?: string[];
replacement_hops?: string[];
node_transition_status?: string;
node_transition_generation?: string;
node_transition_observed_at?: string;
node_transition_matched?: boolean;
node_route_generation_status?: string;
node_route_generation_applied_at?: string;
node_route_generation_withdrawn_at?: string;
node_route_generation_matched?: boolean;
post_rebuild_selected_route_id?: string;
post_rebuild_send_packets?: number;
post_rebuild_send_failures?: number;
post_rebuild_send_flow_packets?: number;
post_rebuild_send_flow_dropped?: number;
guard_status?: string;
guard_severity?: string;
guard_reason?: string;
guard_age_seconds?: number;
guard_transition_deadline_seconds?: number;
guard_traffic_deadline_seconds?: number;
alert_silenced?: boolean;
alert_silence_id?: string;
alert_silence_reason?: string;
alert_silenced_until?: string;
alert_resurfaced?: boolean;
alert_resurfaced_from_silence_id?: string;
alert_resurfaced_previous_generation?: string;
alert_resurfaced_previous_until?: string;
timeline?: FabricServiceChannelRouteRebuildTimelineEvent[];
payload?: Record<string, unknown>;
created_at: string;
updated_at: string;
};
export type FabricServiceChannelRouteRebuildTimelineEvent = {
stage: string;
status: string;
at?: string;
route_id?: string;
generation?: string;
payload?: Record<string, unknown>;
};
export type FabricServiceChannelRouteRebuildHealthSummary = {
cluster_id: string;
observed_at: string;
window_limit: number;
total_attempts: number;
good_count: number;
warn_count: number;
bad_count: number;
unknown_count: number;
active_bad_count: number;
active_warn_count: number;
silenced_count: number;
resurfaced_count: number;
applied_count: number;
pending_count: number;
access_route_decision_count?: number;
access_replacement_count?: number;
access_applied_count?: number;
access_recovery_count?: number;
access_no_safe_count?: number;
counts_by_guard_status?: Record<string, number>;
counts_by_guard_severity?: Record<string, number>;
feedback_breakdowns?: FabricServiceChannelRouteRebuildFeedbackHealthBreakdown[];
affected_reporter_node_ids?: string[];
affected_route_ids?: string[];
most_recent_bad_attempts?: FabricServiceChannelRouteRebuildAttempt[];
resurfaced_attempts?: FabricServiceChannelRouteRebuildAttempt[];
recommended_operator_action?: string;
};
export type FabricServiceChannelRouteRebuildFeedbackHealthBreakdown = {
feedback_source?: string;
feedback_channel_id?: string;
feedback_violation_status?: string;
total_count: number;
good_count?: number;
warn_count?: number;
bad_count?: number;
unknown_count?: number;
active_warn_count?: number;
active_bad_count?: number;
silenced_count?: number;
latest_observed_at?: string;
affected_reporter_node_ids?: string[];
affected_route_ids?: string[];
};
export type FabricServiceChannelReadiness = {
cluster_id: string;
observed_at: string;
status: string;
reason: string;
active_alert_count: number;
active_bad_count: number;
active_warn_count: number;
resurfaced_count: number;
silenced_count: number;
missing_transition_count: number;
missing_route_generation_count: number;
missing_post_rebuild_traffic_count: number;
unexpected_route_count: number;
post_rebuild_degraded_count: number;
blocking_reasons?: string[];
degraded_reasons?: string[];
recommended_operator_action?: string;
};
export type FabricServiceChannelSchemaStatus = {
cluster_id: string;
observed_at: string;
status: string;
reason: string;
required_migration: string;
required_check_count: number;
passed_check_count: number;
missing_check_count: number;
required_checks: FabricServiceChannelSchemaCheck[];
missing_checks?: FabricServiceChannelSchemaCheck[];
recommended_operator_action?: string;
};
export type FabricServiceChannelSchemaCheck = {
check_id: string;
relation_name: string;
column_name?: string;
status: string;
required_by: string;
};
export type FabricServiceChannelRebuildSnapshotWarmup = {
cluster_id: string;
observed_at: string;
window_limit: number;
stale_after_seconds: number;
scanned_count: number;
warmed_count: number;
already_fresh_count: number;
missing_snapshot_count: number;
stale_snapshot_count: number;
deferred_stale_count: number;
error_count: number;
status: string;
reason: string;
recommended_operator_action?: string;
};
export type FabricServiceChannelRebuildSnapshotMaintenanceHealth = {
cluster_id: string;
observed_at: string;
status: string;
reason: string;
window_limit: number;
min_age_seconds: number;
heartbeat_threshold: number;
recent_attempt_count: number;
valid_snapshot_count: number;
missing_snapshot_count: number;
overdue_missing_snapshot_count: number;
auto_warmup_event_count: number;
auto_warmup_warmed_count: number;
auto_warmup_already_fresh_count: number;
auto_warmup_error_count: number;
latest_auto_warmup_at?: string;
nodes?: FabricServiceChannelRebuildSnapshotNodeHealth[];
overdue_missing_snapshot_attempts?: FabricServiceChannelRouteRebuildAttempt[];
recommended_operator_action?: string;
};
export type FabricServiceChannelRebuildSnapshotNodeHealth = {
node_id: string;
recent_attempt_count: number;
valid_snapshot_count: number;
missing_snapshot_count: number;
overdue_missing_snapshot_count: number;
heartbeat_after_attempt_count: number;
last_heartbeat_at?: string;
auto_warmup_event_count: number;
auto_warmup_warmed_count: number;
auto_warmup_error_count: number;
latest_auto_warmup_at?: string;
};
export type FabricServiceChannelLeaseSummary = {
cluster_id: string;
channel_id: string;
resource_id?: string;
service_class: string;
status: string;
selected_entry_node_id?: string;
selected_exit_node_id?: string;
allowed_channels?: string[];
primary_route_id?: string;
primary_route_status?: string;
data_plane?: FabricServiceChannelDataPlaneContract;
force_backend_fallback: boolean;
expired: boolean;
issued_at: string;
expires_at: string;
created_at: string;
updated_at: string;
};
export type FabricServiceChannelLeaseMaintenance = {
schema_version: string;
cluster_id: string;
status: string;
reason: string;
observed_at: string;
active_count: number;
expired_count: number;
scanned_count: number;
deleted_expired_count?: number;
window_limit: number;
recommended_operator_action?: string;
leases?: FabricServiceChannelLeaseSummary[];
};
export type FabricServiceChannelDataPlaneContract = {
schema_version: string;
mode: string;
control_plane_transport: string;
working_data_transport: string;
steady_state_transport: string;
backend_relay_policy: string;
production_forwarding_required: boolean;
service_neutral: boolean;
protocol_agnostic: boolean;
logical_flow_mode: string;
required_flow_isolation_classes?: string[];
route_selection_strategy: string;
entry_failover_mode: string;
exit_failover_mode: string;
route_rebuild_mode: string;
failure_detection_source: string;
degraded_fallback_visibility: string;
stable_contract_for_service_class?: string;
};
export type FabricServiceChannelAccessTelemetryNode = {
node_id: string;
node_name?: string;
observed_at: string;
total_accepted: number;
signed_accepted: number;
introspection_accepted: number;
legacy_unsigned_accepted: number;
backend_fallback_count: number;
backend_fallback_blocked_count?: number;
fabric_route_send_failure_count?: number;
data_plane_contract_count?: number;
last_data_plane_mode?: string;
last_working_data_transport?: string;
last_steady_state_transport?: string;
last_backend_relay_policy?: string;
last_logical_flow_mode?: string;
last_data_plane_violation_status?: string;
last_data_plane_violation_reason?: string;
traffic_class_counts?: Record<string, number>;
flow_channel_count?: number;
flow_dropped?: number;
flow_high_watermark?: number;
flow_max_in_flight?: number;
flow_health_status?: string;
flow_health_reason?: string;
recommended_parallel_windows?: Record<string, number>;
adaptive_backpressure_active?: boolean;
adaptive_backpressure_reason?: string;
adaptive_policy_fingerprint?: string;
last_accepted_at?: string;
};
export type FabricServiceChannelAccessTelemetryChannel = {
channel_id: string;
resource_id?: string;
service_class: string;
status: string;
selected_entry_node_id?: string;
selected_exit_node_id?: string;
primary_route_id?: string;
primary_route_status?: string;
force_backend_fallback: boolean;
entry_node_total_accepted: number;
entry_node_introspection_accepted: number;
entry_node_backend_fallback_count: number;
entry_node_backend_fallback_blocked_count?: number;
entry_node_fabric_route_send_failure_count?: number;
entry_node_data_plane_contract_count?: number;
entry_node_last_data_plane_mode?: string;
entry_node_last_working_data_transport?: string;
entry_node_last_steady_state_transport?: string;
entry_node_last_backend_relay_policy?: string;
entry_node_last_logical_flow_mode?: string;
entry_node_last_data_plane_violation_status?: string;
entry_node_last_data_plane_violation_reason?: string;
entry_node_traffic_class_counts?: Record<string, number>;
entry_node_flow_channel_count?: number;
entry_node_flow_dropped?: number;
entry_node_flow_high_watermark?: number;
entry_node_flow_max_in_flight?: number;
entry_node_flow_health_status?: string;
entry_node_flow_health_reason?: string;
entry_node_recommended_parallel_windows?: Record<string, number>;
entry_node_adaptive_backpressure_active?: boolean;
entry_node_adaptive_backpressure_reason?: string;
entry_node_adaptive_policy_fingerprint?: string;
route_feedback_status?: string;
route_feedback_observed_at?: string;
route_feedback_score_adjustment?: number;
route_feedback_effective_score_adjustment?: number;
route_feedback_reasons?: string[];
route_quality_window_sample_count?: number;
route_quality_window_failure_count?: number;
route_quality_window_drop_count?: number;
route_quality_window_slow_count?: number;
last_send_duration_ms?: number;
remediation_action?: string;
remediation_reason?: string;
remediation_route_id?: string;
remediation_route_status?: string;
remediation_guard_status?: string;
remediation_guard_reason?: string;
remediation_execution_status?: string;
remediation_execution_reason?: string;
remediation_execution_generation?: string;
remediation_execution_observed_at?: string;
route_decision_source?: string;
route_decision_route_id?: string;
route_decision_replacement_route_id?: string;
route_decision_rebuild_status?: string;
route_decision_rebuild_reason?: string;
route_decision_generation?: string;
route_decision_score_reasons?: string[];
pool_policy_fingerprint?: string;
data_plane?: FabricServiceChannelDataPlaneContract;
remediation_command?: {
schema_version: string;
command_id: string;
action: string;
cluster_id: string;
channel_id: string;
resource_id?: string;
service_class: string;
entry_node_id?: string;
exit_node_id?: string;
primary_route_id?: string;
replacement_route_id?: string;
replacement_route_status?: string;
pool_policy_fingerprint?: string;
guard_status?: string;
guard_reason?: string;
execution_status?: string;
execution_reason?: string;
execution_generation?: string;
execution_observed_at?: string;
reason?: string;
operator_action?: string;
issued_at: string;
expires_at: string;
};
recommended_operator_action?: string;
expires_at: string;
};
export type FabricServiceChannelAccessTelemetry = {
schema_version: string;
cluster_id: string;
status: string;
reason: string;
observed_at: string;
node_count: number;
reporting_node_count: number;
total_accepted: number;
signed_accepted: number;
introspection_accepted: number;
legacy_unsigned_accepted: number;
backend_fallback_count: number;
backend_fallback_blocked_count?: number;
fabric_route_send_failure_count?: number;
data_plane_contract_count?: number;
last_data_plane_mode?: string;
last_working_data_transport?: string;
last_steady_state_transport?: string;
last_backend_relay_policy?: string;
last_logical_flow_mode?: string;
last_data_plane_violation_status?: string;
last_data_plane_violation_reason?: string;
active_channel_count: number;
degraded_fallback_channel_count: number;
correlated_route_count: number;
degraded_route_count: number;
route_decision_channel_count?: number;
replacement_decision_count?: number;
applied_rebuild_decision_count?: number;
recovery_decision_count?: number;
no_safe_recovery_decision_count?: number;
traffic_class_counts?: Record<string, number>;
flow_channel_count?: number;
flow_dropped?: number;
flow_high_watermark?: number;
flow_max_in_flight?: number;
flow_health_status?: string;
flow_health_reason?: string;
recommended_parallel_windows?: Record<string, number>;
adaptive_backpressure_active?: boolean;
adaptive_backpressure_reason?: string;
adaptive_policy_fingerprint?: string;
latest_accepted_at?: string;
nodes?: FabricServiceChannelAccessTelemetryNode[];
active_channels?: FabricServiceChannelAccessTelemetryChannel[];
recommended_operator_action?: string;
};
export type FabricServiceChannelRouteRebuildIncident = {
fingerprint: string;
cluster_id: string;
reporter_node_id: string;
route_id: string;
service_class: string;
generation?: string;
incident_source?: string;
channel_id?: string;
guard_status: string;
guard_severity: string;
guard_reason?: string;
attempt_count: number;
first_seen_at: string;
last_seen_at: string;
latest_replacement_route_id?: string;
latest_rebuild_status?: string;
latest_outcome?: string;
alert_silenced?: boolean;
alert_resurfaced?: boolean;
alert_resurfaced_from_silence_id?: string;
alert_resurfaced_cause?: string;
alert_resurfaced_previous_route_id?: string;
alert_resurfaced_previous_channel_id?: string;
alert_resurfaced_previous_generation?: string;
alert_resurfaced_previous_until?: string;
recommended_operator_action?: string;
};
export type FabricServiceChannelRouteRebuildAlertSilence = {
id: string;
cluster_id: string;
incident_source?: string;
channel_id?: string;
reporter_node_id: string;
route_id: string;
display_route_id?: string;
guard_status: string;
generation?: string;
reason?: string;
created_by_user_id?: string | null;
created_at: string;
expires_at: string;
payload?: Record<string, unknown>;
};
export type ExpireFabricServiceChannelRouteFeedbackResult = {
cluster_id: string;
reporter_node_id?: string;
route_id: string;
service_class?: string;
expired_count: number;
expired_at: string;
cooldown_until: string;
};
export type FabricServiceChannelRouteFeedbackReport = {
schema_version: string;
generated_at: string;
feedback_max_age_seconds: number;
recovery_policy?: FabricServiceChannelRecoveryPolicy;
observation_count: number;
fenced_route_count: number;
degraded_route_count: number;
healthy_route_count: number;
recovered_route_count?: number;
recovery_hysteresis_count?: number;
recovery_promoted_count?: number;
recovery_demoted_count?: number;
missing_provenance_count?: number;
stale_policy_count?: number;
stale_generation_count?: number;
observations?: FabricServiceChannelRouteFeedbackObservation[];
};
export type FabricServiceChannelRecoveryPolicy = {
schema_version: string;
fingerprint?: string;
hysteresis_penalty: number;
promotion_min_samples: number;
demotion_failure_threshold: number;
demotion_drop_threshold: number;
demotion_slow_threshold: number;
demotion_rebuild_enabled: boolean;
demotion_fenced_enabled: boolean;
source: string;
updated_by_user_id?: string | null;
updated_at?: string;
control_plane_only: boolean;
production_forwarding: boolean;
};
export type FabricServiceChannelAdaptivePolicy = {
schema_version: string;
fingerprint?: string;
max_parallel_window: number;
bulk_pressure_channel_threshold: number;
queue_pressure_high_watermark: number;
queue_pressure_max_in_flight: number;
class_windows: Record<string, number>;
source: string;
updated_by_user_id?: string | null;
updated_at?: string;
control_plane_only: boolean;
production_forwarding: boolean;
};
export type FabricServiceChannelPoolPolicy = {
schema_version: string;
fingerprint?: string;
entry_pool_node_ids?: string[];
exit_pool_node_ids?: string[];
preferred_entry_node_id?: string;
preferred_exit_node_id?: string;
selection_strategy: string;
route_rebuild: string;
entry_failover: string;
exit_failover: string;
backend_fallback_allowed: boolean;
sticky_session: boolean;
source: string;
updated_by_user_id?: string | null;
updated_at?: string;
control_plane_only: boolean;
production_forwarding: boolean;
};
export type NodeSyntheticMeshConfig = {
enabled: boolean;
schema_version: string;
@@ -398,6 +1184,28 @@ export type NodeSyntheticMeshConfig = {
rendezvous_leases?: PeerRendezvousLease[];
rendezvous_relay_policy?: RendezvousRelayPolicyReport;
route_path_decisions?: RoutePathDecisionReport;
service_channel_route_feedback?: FabricServiceChannelRouteFeedbackReport;
service_channel_adaptive_policy?: FabricServiceChannelAdaptivePolicy;
service_channel_remediation_commands?: FabricServiceChannelAccessTelemetryChannel["remediation_command"][];
mesh_listener?: {
schema_version: string;
source: string;
desired_state: string;
listen_addr: string;
listen_port_mode: string;
auto_port_start?: number;
auto_port_end?: number;
advertise_endpoint?: string;
advertise_transport?: string;
connectivity_mode?: string;
nat_type?: string;
region?: string;
config_version?: string;
updated_by_user_id?: string;
updated_at?: string;
control_plane_only: boolean;
production_forwarding: boolean;
};
routes: SyntheticMeshRoute[];
production_forwarding: boolean;
};
@@ -489,6 +1297,53 @@ export type OrganizationAdminSummary = {
topology_exposure: string;
};
export type Organization = {
id: string;
slug: string;
name: string;
status: string;
metadata?: Record<string, unknown>;
created_at: string;
updated_at: string;
};
export type OrganizationMembership = {
id: string;
organization_id: string;
user_id: string;
role_id: string;
status: string;
invited_by_user_id?: string | null;
created_at: string;
updated_at: string;
};
export type UserAccount = {
id: string;
email: string;
mfa_enabled: boolean;
platform_role: string;
created_at: string;
updated_at: string;
};
export type Resource = {
id: string;
organization_id: string;
name: string;
address: string;
protocol: string;
secret_ref?: string | null;
has_secret?: boolean;
certificate_verification_mode: string;
render_quality_profile?: string;
clipboard_mode: string;
file_transfer_mode: string;
metadata?: Record<string, unknown>;
created_at: string;
updated_at: string;
};
export type VPNConnection = {
id: string;
cluster_id: string;
@@ -527,3 +1382,34 @@ export type VPNConnectionLease = {
fenced_at?: string | null;
metadata: Record<string, unknown>;
};
export type VPNPacketDirectionStats = {
pushed: number;
popped: number;
dropped: number;
queue_depth: number;
last_push_size: number;
last_pop_size: number;
last_push_at?: string;
last_pop_at?: string;
};
export type VPNPacketStats = {
client_to_gateway?: VPNPacketDirectionStats;
gateway_to_client?: VPNPacketDirectionStats;
};
export type VPNClientDiagnosticStatus = {
cluster_id: string;
device_id: string;
payload: Record<string, unknown>;
observed_at: string;
};
export type VPNClientDiagnosticCommand = {
id: string;
cluster_id: string;
device_id: string;
payload: Record<string, unknown>;
created_at: string;
};