Refactor RDP proxy handling and update related tests

This commit is contained in:
2026-05-17 20:38:35 +03:00
parent 8e9402580f
commit d551e57fd5
172 changed files with 22117 additions and 2509 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -4,8 +4,8 @@
<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-DU4b34gj.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BeytaWKC.css">
<script type="module" crossorigin src="/assets/index-gMV--oab.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cur_BAkX.css">
</head>
<body>
<div id="root"></div>
+579 -498
View File
File diff suppressed because it is too large Load Diff
-112
View File
@@ -9,10 +9,6 @@ import type {
ClusterAuthorityState,
ClusterNode,
CreatedJoinToken,
FabricEntryPoint,
FabricEntryPointNode,
FabricEgressPool,
FabricEgressPoolNode,
FabricServiceChannelAdaptivePolicy,
FabricServiceChannelBreadcrumbWindowPolicy,
FabricServiceChannelPoolPolicy,
@@ -177,28 +173,6 @@ export type UpsertResourceSecretPayload = {
domain?: string;
};
export type CreateFabricEntryPointPayload = {
name: string;
endpointType: string;
publicEndpoint?: string | null;
policy?: Record<string, unknown>;
metadata?: Record<string, unknown>;
};
export type CreateFabricEgressPoolPayload = {
name: string;
description?: string | null;
routeScope?: Record<string, unknown>;
policy?: Record<string, unknown>;
metadata?: Record<string, unknown>;
};
export type SetFabricEndpointNodePayload = {
status?: string;
priority?: number;
metadata?: Record<string, unknown>;
};
export class AdminApiClient {
private readonly baseUrl: string;
private readonly actorUserId: string;
@@ -1021,92 +995,6 @@ export class AdminApiClient {
return payload.qos_policies ?? [];
}
async listFabricEntryPoints(clusterId: string): Promise<FabricEntryPoint[]> {
const payload = await this.get<{ entry_points: FabricEntryPoint[] }>(`/clusters/${clusterId}/fabric/entry-points`);
return payload.entry_points ?? [];
}
async createFabricEntryPoint(clusterId: string, input: CreateFabricEntryPointPayload): Promise<FabricEntryPoint> {
const payload = await this.post<{ entry_point: FabricEntryPoint }>(`/clusters/${clusterId}/fabric/entry-points`, {
actor_user_id: this.actorUserId,
name: input.name,
status: "active",
endpoint_type: input.endpointType || "client_access",
public_endpoint: input.publicEndpoint || null,
policy: input.policy || {},
metadata: input.metadata || {},
});
return payload.entry_point;
}
async listFabricEntryPointNodes(clusterId: string, entryPointId: string): Promise<FabricEntryPointNode[]> {
const payload = await this.get<{ entry_point_nodes: FabricEntryPointNode[] }>(
`/clusters/${clusterId}/fabric/entry-points/${entryPointId}/nodes`,
);
return payload.entry_point_nodes ?? [];
}
async setFabricEntryPointNode(
clusterId: string,
entryPointId: string,
nodeId: string,
input: SetFabricEndpointNodePayload = {},
): Promise<FabricEntryPointNode> {
const payload = await this.put<{ entry_point_node: FabricEntryPointNode }>(
`/clusters/${clusterId}/fabric/entry-points/${entryPointId}/nodes/${nodeId}`,
{
actor_user_id: this.actorUserId,
status: input.status || "active",
priority: input.priority || 100,
metadata: input.metadata || {},
},
);
return payload.entry_point_node;
}
async listFabricEgressPools(clusterId: string): Promise<FabricEgressPool[]> {
const payload = await this.get<{ egress_pools: FabricEgressPool[] }>(`/clusters/${clusterId}/fabric/egress-pools`);
return payload.egress_pools ?? [];
}
async createFabricEgressPool(clusterId: string, input: CreateFabricEgressPoolPayload): Promise<FabricEgressPool> {
const payload = await this.post<{ egress_pool: FabricEgressPool }>(`/clusters/${clusterId}/fabric/egress-pools`, {
actor_user_id: this.actorUserId,
name: input.name,
status: "active",
description: input.description || null,
route_scope: input.routeScope || {},
policy: input.policy || {},
metadata: input.metadata || {},
});
return payload.egress_pool;
}
async listFabricEgressPoolNodes(clusterId: string, egressPoolId: string): Promise<FabricEgressPoolNode[]> {
const payload = await this.get<{ egress_pool_nodes: FabricEgressPoolNode[] }>(
`/clusters/${clusterId}/fabric/egress-pools/${egressPoolId}/nodes`,
);
return payload.egress_pool_nodes ?? [];
}
async setFabricEgressPoolNode(
clusterId: string,
egressPoolId: string,
nodeId: string,
input: SetFabricEndpointNodePayload = {},
): Promise<FabricEgressPoolNode> {
const payload = await this.put<{ egress_pool_node: FabricEgressPoolNode }>(
`/clusters/${clusterId}/fabric/egress-pools/${egressPoolId}/nodes/${nodeId}`,
{
actor_user_id: this.actorUserId,
status: input.status || "active",
priority: input.priority || 100,
metadata: input.metadata || {},
},
);
return payload.egress_pool_node;
}
async listVPNConnections(clusterId: string): Promise<VPNConnection[]> {
const payload = await this.get<{ vpn_connections: VPNConnection[] }>(`/clusters/${clusterId}/vpn-connections`);
return payload.vpn_connections ?? [];
+168 -43
View File
@@ -227,7 +227,7 @@ button.danger {
}
.workspace {
width: min(1500px, calc(100vw - 340px));
width: calc(100vw - 310px);
padding: 28px 28px 54px;
}
@@ -406,6 +406,33 @@ summary {
grid-template-columns: repeat(2, minmax(280px, 1fr));
}
.fabricTransportView {
display: grid;
gap: 16px;
margin-top: 18px;
}
.fabricMapCard {
min-height: calc(100vh - 176px);
}
.fabricMapCard .cardHead {
align-items: center;
}
.fabricMapCard .topologyShell {
min-height: calc(100vh - 310px);
}
.fabricMapCard .topologySvg {
min-height: calc(100vh - 360px);
max-height: none;
}
.fabricDiagnostics {
margin-top: 10px;
}
.span2 {
grid-column: span 2;
}
@@ -983,54 +1010,32 @@ summary {
.topologySvg {
width: 100%;
min-height: 520px;
min-height: 640px;
max-height: 78vh;
border: 1px solid var(--line);
border-radius: 22px;
color: rgba(24, 32, 24, 0.42);
background:
radial-gradient(circle at 50% 50%, rgba(47, 111, 79, 0.12), transparent 18rem),
linear-gradient(135deg, rgba(255, 255, 255, 0.56), rgba(255, 250, 240, 0.32));
}
.topologyRing {
fill: none;
stroke: rgba(47, 111, 79, 0.14);
stroke-width: 2;
stroke-dasharray: 12 10;
}
.topologyZone {
fill: rgba(255, 252, 239, 0.44);
stroke: rgba(24, 32, 24, 0.08);
stroke-width: 2;
}
.topologyZone.ingress {
fill: rgba(67, 122, 146, 0.11);
}
.topologyZone.core {
fill: rgba(47, 111, 79, 0.09);
}
.topologyZone.egress {
fill: rgba(176, 122, 50, 0.11);
}
.topologyLayerLabel {
fill: var(--green);
font-size: 22px;
font-weight: 950;
letter-spacing: 0.08em;
text-anchor: middle;
text-transform: uppercase;
linear-gradient(rgba(24, 32, 24, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(24, 32, 24, 0.035) 1px, transparent 1px),
linear-gradient(135deg, rgba(255, 255, 255, 0.56), rgba(248, 250, 246, 0.42));
background-size: 34px 34px, 34px 34px, auto;
}
.topologyLink {
stroke-width: 4;
stroke-width: 3;
stroke-linecap: round;
opacity: 0.84;
fill: none;
}
.topologyLinkGroup {
cursor: help;
}
.topologyLinkGroup:hover .topologyLink {
stroke-width: 5;
opacity: 1;
}
.topologyLink.good {
@@ -1049,6 +1054,17 @@ summary {
stroke-dasharray: 4 7;
}
.topologyLink.relay {
stroke-dasharray: 8 7;
}
.topologyLink.route {
color: var(--steel);
stroke: var(--steel);
stroke-dasharray: 3 8;
opacity: 0.7;
}
.topologyLink.bad {
color: var(--red);
stroke: var(--red);
@@ -1105,7 +1121,7 @@ summary {
.topologyLinkLabel {
fill: var(--muted);
font-size: 22px;
font-size: 16px;
}
.topologyEndpointRect {
@@ -1140,14 +1156,53 @@ summary {
.topologyNodeCircle {
fill: rgba(255, 252, 239, 0.94);
stroke: var(--steel);
stroke-width: 4;
stroke-width: 3;
filter: drop-shadow(0 14px 22px rgba(24, 32, 24, 0.14));
}
.topologyNode {
cursor: help;
}
.topologyNode:hover .topologyNodeCircle {
stroke-width: 5;
filter: drop-shadow(0 16px 26px rgba(24, 32, 24, 0.22));
}
.topologyNodeCircle.healthy {
stroke: var(--green);
}
.topologyNodeCircle.active {
stroke-width: 5;
}
.topologyNodeCircle.passive {
stroke: var(--amber);
stroke-dasharray: 10 7;
}
.topologyNodeCircle.mixed {
stroke: var(--steel);
stroke-dasharray: 16 6 4 6;
}
.topologyNodeCircle.unknown {
stroke-dasharray: 4 8;
}
.topologyNodeCircle.web-ready {
fill: rgba(232, 248, 239, 0.98);
}
.topologyNodeCircle.web-degraded {
fill: rgba(255, 246, 224, 0.98);
}
.topologyNodeCircle.web-blocked {
fill: rgba(255, 235, 230, 0.98);
}
.topologyNodeCircle.critical,
.topologyNodeCircle.offline,
.topologyNodeCircle.failed {
@@ -1156,12 +1211,12 @@ summary {
.topologyNodeName {
fill: var(--ink);
font-size: 23px;
font-size: 20px;
}
.topologyNodeMeta {
fill: var(--muted);
font-size: 18px;
font-size: 13px;
}
.topologyEmpty {
@@ -1169,6 +1224,36 @@ summary {
font-size: 24px;
}
.topologyTooltipObject {
pointer-events: none;
}
.topologyTooltip {
display: grid;
gap: 4px;
max-width: 360px;
padding: 12px 14px;
color: var(--ink);
background: rgba(255, 252, 239, 0.96);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: 0 18px 42px rgba(24, 32, 24, 0.18);
font-size: 13px;
line-height: 1.3;
}
.topologyTooltip strong,
.topologyTooltip span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.topologyTooltip strong {
font-size: 14px;
}
.topologyLegend {
display: flex;
flex-wrap: wrap;
@@ -1205,6 +1290,16 @@ summary {
border-color: var(--amber);
}
.legendLine.relay {
border-top-style: dashed;
border-color: var(--amber);
}
.legendLine.route {
border-top-style: dashed;
border-color: var(--steel);
}
.legendLine.stale,
.legendLine.problem {
border-top-style: dashed;
@@ -1216,12 +1311,42 @@ summary {
border-color: var(--steel);
}
.legendDot {
display: inline-block;
width: 14px;
height: 14px;
border: 1px solid var(--line);
border-radius: 50%;
}
.legendDot.webReady {
background: rgba(232, 248, 239, 0.98);
}
.legendDot.webDegraded {
background: rgba(255, 246, 224, 0.98);
}
.legendDot.webBlocked {
background: rgba(255, 235, 230, 0.98);
}
.serviceTags {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
}
.sectionBlock {
display: grid;
gap: 12px;
margin-top: 18px;
}
.sectionBlock h4 {
margin: 0;
}
.serviceTag {
display: grid;
gap: 5px;
-50
View File
@@ -1210,56 +1210,6 @@ export type NodeSyntheticMeshConfig = {
production_forwarding: boolean;
};
export type FabricEntryPoint = {
id: string;
cluster_id: string;
name: string;
status: string;
endpoint_type: string;
public_endpoint?: string | null;
policy?: Record<string, unknown>;
metadata?: Record<string, unknown>;
created_by_user_id?: string | null;
created_at: string;
updated_at?: string;
};
export type FabricEntryPointNode = {
entry_point_id: string;
cluster_id: string;
node_id: string;
status: string;
priority: number;
metadata?: Record<string, unknown>;
added_by_user_id?: string | null;
added_at: string;
};
export type FabricEgressPool = {
id: string;
cluster_id: string;
name: string;
status: string;
description?: string | null;
route_scope?: Record<string, unknown>;
policy?: Record<string, unknown>;
metadata?: Record<string, unknown>;
created_by_user_id?: string | null;
created_at: string;
updated_at?: string;
};
export type FabricEgressPoolNode = {
egress_pool_id: string;
cluster_id: string;
node_id: string;
status: string;
priority: number;
metadata?: Record<string, unknown>;
added_by_user_id?: string | null;
added_at: string;
};
export type QoSPolicy = {
id: string;
cluster_id: string;