@gugananuvem/aws-local-simulator 1.0.34 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +662 -658
- package/package.json +11 -4
- package/src/config/default-config.js +71 -0
- package/src/config/env-loader.js +4 -2
- package/src/middleware/authorization.js +271 -0
- package/src/middleware/cloudtrail-logger.js +333 -0
- package/src/middleware/error-handler.js +138 -0
- package/src/middleware/index.js +79 -0
- package/src/middleware/policy-enforcement.js +407 -0
- package/src/server.js +74 -4
- package/src/services/acm/index.js +66 -0
- package/src/services/acm/models/certificate.js +202 -0
- package/src/services/acm/server.js +224 -0
- package/src/services/acm/simulator.js +293 -0
- package/src/services/alb/index.js +104 -0
- package/src/services/alb/models/loadBalancer.js +88 -0
- package/src/services/alb/models/targetGroup.js +128 -0
- package/src/services/alb/server.js +160 -0
- package/src/services/alb/simulator.js +262 -0
- package/src/services/apigateway/index.js +22 -0
- package/src/services/apigateway/server.js +247 -35
- package/src/services/apigateway/simulator.js +780 -26
- package/src/services/asg/index.js +100 -0
- package/src/services/asg/models/autoScalingGroup.js +110 -0
- package/src/services/asg/models/launchTemplate.js +41 -0
- package/src/services/asg/models/scalingPolicy.js +51 -0
- package/src/services/asg/server.js +236 -0
- package/src/services/asg/simulator.js +410 -0
- package/src/services/aurora/cluster-manager.js +731 -0
- package/src/services/aurora/data-api.js +514 -0
- package/src/services/aurora/db-engine.js +270 -0
- package/src/services/aurora/index.js +159 -0
- package/src/services/aurora/rds-api.js +189 -0
- package/src/services/aurora/server.js +213 -0
- package/src/services/aurora/sql-translator.js +334 -0
- package/src/services/cloudformation/index.js +2 -2
- package/src/services/cloudformation/server.js +1 -0
- package/src/services/cloudfront/cache-engine.js +225 -0
- package/src/services/cloudfront/geo-restrictions.js +377 -0
- package/src/services/cloudfront/index.js +220 -0
- package/src/services/cloudfront/models/behavior.js +158 -0
- package/src/services/cloudfront/models/distribution.js +284 -0
- package/src/services/cloudfront/models/index.js +99 -0
- package/src/services/cloudfront/models/invalidation.js +109 -0
- package/src/services/cloudfront/models/origin.js +155 -0
- package/src/services/cloudfront/origin-router.js +552 -0
- package/src/services/cloudfront/origin-validator.js +398 -0
- package/src/services/cloudfront/server.js +512 -0
- package/src/services/cloudfront/signed-url-generator.js +377 -0
- package/src/services/cloudfront/simulator.js +425 -0
- package/src/services/cloudfront/store.js +326 -0
- package/src/services/cloudshell/env-builder.js +27 -0
- package/src/services/cloudshell/index.js +48 -0
- package/src/services/cloudshell/routes.js +60 -0
- package/src/services/cloudshell/session-manager.js +226 -0
- package/src/services/cloudshell/static/.gitkeep +0 -0
- package/src/services/cloudshell/static/index.html +406 -0
- package/src/services/cloudshell/websocket-handler.js +107 -0
- package/src/services/cloudtrail/index.js +2 -3
- package/src/services/cloudtrail/server.js +68 -0
- package/src/services/cognito/server.js +4 -1
- package/src/services/dynamodb/server.js +4 -1
- package/src/services/dynamodb/simulator.js +2 -1
- package/src/services/ec2/index.js +101 -0
- package/src/services/ec2/instanceServer.js +311 -0
- package/src/services/ec2/models/ami.js +39 -0
- package/src/services/ec2/models/instance.js +98 -0
- package/src/services/ec2/models/networkInterface.js +74 -0
- package/src/services/ec2/models/securityGroup.js +71 -0
- package/src/services/ec2/server.js +350 -0
- package/src/services/ec2/simulator.js +436 -0
- package/src/services/eventbridge/server.js +36 -0
- package/src/services/eventbridge/simulator.js +230 -45
- package/src/services/iam/index.js +65 -0
- package/src/services/iam/middleware.js +99 -0
- package/src/services/iam/server.js +331 -0
- package/src/services/iam/simulator.js +1432 -0
- package/src/services/index.js +28 -0
- package/src/services/kinesis-analytics/index.js +105 -0
- package/src/services/kinesis-analytics/models/application.js +325 -0
- package/src/services/kinesis-analytics/models/index.js +12 -0
- package/src/services/kinesis-analytics/server.js +250 -0
- package/src/services/kinesis-analytics/simulator.js +407 -0
- package/src/services/kinesis-analytics/store.js +176 -0
- package/src/services/kinesis-data-streams/index.js +106 -0
- package/src/services/kinesis-data-streams/models/index.js +12 -0
- package/src/services/kinesis-data-streams/models/record.js +70 -0
- package/src/services/kinesis-data-streams/models/stream.js +280 -0
- package/src/services/kinesis-data-streams/server.js +255 -0
- package/src/services/kinesis-data-streams/simulator.js +514 -0
- package/src/services/kinesis-data-streams/store.js +163 -0
- package/src/services/kinesis-firehose/index.js +106 -0
- package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
- package/src/services/kinesis-firehose/models/index.js +10 -0
- package/src/services/kinesis-firehose/server.js +215 -0
- package/src/services/kinesis-firehose/simulator.js +388 -0
- package/src/services/kinesis-firehose/store.js +163 -0
- package/src/services/kms/server.js +4 -1
- package/src/services/lambda/handler-loader.js +50 -5
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +18 -12
- package/src/services/lambda/simulator.js +73 -17
- package/src/services/opensearch/index.js +105 -0
- package/src/services/opensearch/models/document.js +115 -0
- package/src/services/opensearch/models/index.js +73 -0
- package/src/services/opensearch/server.js +310 -0
- package/src/services/opensearch/simulator.js +605 -0
- package/src/services/opensearch/store.js +162 -0
- package/src/services/organizations/control-tower-simulator.js +321 -0
- package/src/services/organizations/control-tower-store.js +182 -0
- package/src/services/organizations/iam-integration.js +413 -0
- package/src/services/organizations/index.js +172 -0
- package/src/services/organizations/models/index.js +13 -0
- package/src/services/organizations/models/invitation.js +48 -0
- package/src/services/organizations/models/organization.js +566 -0
- package/src/services/organizations/models/role.js +244 -0
- package/src/services/organizations/models/scp.js +131 -0
- package/src/services/organizations/server.js +736 -0
- package/src/services/organizations/simulator.js +593 -0
- package/src/services/organizations/store.js +235 -0
- package/src/services/organizations/vpc-integration.js +453 -0
- package/src/services/route53/index.js +67 -0
- package/src/services/route53/models/healthCheck.js +101 -0
- package/src/services/route53/models/hostedZone.js +37 -0
- package/src/services/route53/models/resourceRecord.js +106 -0
- package/src/services/route53/server.js +841 -0
- package/src/services/route53/simulator.js +350 -0
- package/src/services/s3/index.js +62 -0
- package/src/services/s3/presigned-url-generator.js +337 -0
- package/src/services/s3/server.js +231 -0
- package/src/services/s3/simulator.js +551 -4
- package/src/services/sns/index.js +31 -0
- package/src/services/sns/server.js +9 -0
- package/src/services/sqs/index.js +38 -0
- package/src/services/sqs/server.js +57 -4
- package/src/services/sqs/simulator.js +423 -33
- package/src/services/sts/simulator.js +209 -17
- package/src/services/vpc/README.md +459 -0
- package/src/services/vpc/ec2ResponseFormatter.js +431 -0
- package/src/services/vpc/index.js +90 -0
- package/src/services/vpc/models/internetGateway.js +71 -0
- package/src/services/vpc/models/routeTable.js +160 -0
- package/src/services/vpc/models/subnet.js +48 -0
- package/src/services/vpc/models/vpc.js +37 -0
- package/src/services/vpc/models/vpcEndpoint.js +100 -0
- package/src/services/vpc/models/vpcGateway.js +134 -0
- package/src/services/vpc/server.js +836 -0
- package/src/services/vpc/simulator.js +613 -0
- package/src/utils/policy-cache.js +215 -0
- package/aws-config +0 -154
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>CloudShell - AWS Local Simulator</title>
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5/css/xterm.css">
|
|
8
|
+
<style>
|
|
9
|
+
* {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
background: #1e1e1e;
|
|
17
|
+
color: #d4d4d4;
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
19
|
+
height: 100vh;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#tab-bar {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
background: #252526;
|
|
29
|
+
border-bottom: 1px solid #3c3c3c;
|
|
30
|
+
height: 36px;
|
|
31
|
+
min-height: 36px;
|
|
32
|
+
overflow-x: auto;
|
|
33
|
+
scrollbar-width: thin;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#tab-bar::-webkit-scrollbar {
|
|
37
|
+
height: 4px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#tab-bar::-webkit-scrollbar-thumb {
|
|
41
|
+
background: #555;
|
|
42
|
+
border-radius: 2px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tab {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
padding: 0 12px;
|
|
49
|
+
height: 100%;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
border-right: 1px solid #3c3c3c;
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
font-size: 13px;
|
|
54
|
+
color: #969696;
|
|
55
|
+
user-select: none;
|
|
56
|
+
transition: background 0.1s;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.tab:hover {
|
|
60
|
+
background: #2d2d2d;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.tab.active {
|
|
64
|
+
background: #1e1e1e;
|
|
65
|
+
color: #ffffff;
|
|
66
|
+
border-bottom: 2px solid #007acc;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.tab .tab-title {
|
|
70
|
+
margin-right: 8px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.tab .tab-close {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
width: 18px;
|
|
78
|
+
height: 18px;
|
|
79
|
+
border-radius: 3px;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
line-height: 1;
|
|
82
|
+
color: #969696;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.tab .tab-close:hover {
|
|
86
|
+
background: #4a4a4a;
|
|
87
|
+
color: #ffffff;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#new-tab-btn {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
width: 36px;
|
|
95
|
+
height: 100%;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
color: #969696;
|
|
98
|
+
font-size: 18px;
|
|
99
|
+
border: none;
|
|
100
|
+
background: none;
|
|
101
|
+
transition: background 0.1s;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#new-tab-btn:hover {
|
|
105
|
+
background: #2d2d2d;
|
|
106
|
+
color: #ffffff;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#terminal-container {
|
|
110
|
+
flex: 1;
|
|
111
|
+
position: relative;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.terminal-wrapper {
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: 0;
|
|
118
|
+
left: 0;
|
|
119
|
+
right: 0;
|
|
120
|
+
bottom: 0;
|
|
121
|
+
display: none;
|
|
122
|
+
padding: 4px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.terminal-wrapper.active {
|
|
126
|
+
display: block;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.terminal-wrapper .xterm {
|
|
130
|
+
height: 100%;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
</head>
|
|
134
|
+
<body>
|
|
135
|
+
<div id="tab-bar">
|
|
136
|
+
<button id="new-tab-btn" title="New terminal session">+</button>
|
|
137
|
+
</div>
|
|
138
|
+
<div id="terminal-container"></div>
|
|
139
|
+
|
|
140
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm@5/lib/xterm.js"></script>
|
|
141
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0/lib/xterm-addon-fit.js"></script>
|
|
142
|
+
<script>
|
|
143
|
+
(function () {
|
|
144
|
+
const API_BASE = '/__admin/cloudshell/sessions';
|
|
145
|
+
const sessions = new Map(); // sessionId -> { term, fitAddon, ws, tabEl, wrapperEl }
|
|
146
|
+
let activeSessionId = null;
|
|
147
|
+
let tabCounter = 0;
|
|
148
|
+
|
|
149
|
+
const tabBar = document.getElementById('tab-bar');
|
|
150
|
+
const newTabBtn = document.getElementById('new-tab-btn');
|
|
151
|
+
const terminalContainer = document.getElementById('terminal-container');
|
|
152
|
+
|
|
153
|
+
// --- API Helpers ---
|
|
154
|
+
|
|
155
|
+
async function fetchSessions() {
|
|
156
|
+
const res = await fetch(API_BASE);
|
|
157
|
+
const data = await res.json();
|
|
158
|
+
return data.sessions || [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function createSessionAPI() {
|
|
162
|
+
const res = await fetch(API_BASE, { method: 'POST' });
|
|
163
|
+
return await res.json();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function deleteSessionAPI(sessionId) {
|
|
167
|
+
await fetch(`${API_BASE}/${sessionId}`, { method: 'DELETE' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --- WebSocket ---
|
|
171
|
+
|
|
172
|
+
function connectWebSocket(sessionId) {
|
|
173
|
+
const wsUrl = `ws://${window.location.host}/ws/cloudshell/${sessionId}`;
|
|
174
|
+
const ws = new WebSocket(wsUrl);
|
|
175
|
+
ws.binaryType = 'arraybuffer';
|
|
176
|
+
|
|
177
|
+
const session = sessions.get(sessionId);
|
|
178
|
+
if (!session) return null;
|
|
179
|
+
|
|
180
|
+
ws.onopen = function () {
|
|
181
|
+
session.ws = ws;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
ws.onmessage = function (event) {
|
|
185
|
+
const s = sessions.get(sessionId);
|
|
186
|
+
if (s && s.term) {
|
|
187
|
+
s.term.write(typeof event.data === 'string' ? event.data : new Uint8Array(event.data));
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
ws.onclose = function () {
|
|
192
|
+
const s = sessions.get(sessionId);
|
|
193
|
+
if (s) {
|
|
194
|
+
s.ws = null;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
ws.onerror = function () {
|
|
199
|
+
// WebSocket errors are followed by onclose
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return ws;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --- Terminal & Session Management ---
|
|
206
|
+
|
|
207
|
+
function createTerminalSession(sessionId) {
|
|
208
|
+
tabCounter++;
|
|
209
|
+
|
|
210
|
+
// Create xterm instance
|
|
211
|
+
const term = new Terminal({
|
|
212
|
+
theme: {
|
|
213
|
+
background: '#1e1e1e',
|
|
214
|
+
foreground: '#d4d4d4',
|
|
215
|
+
cursor: '#d4d4d4',
|
|
216
|
+
selectionBackground: '#264f78',
|
|
217
|
+
},
|
|
218
|
+
fontFamily: "'Cascadia Code', 'Fira Code', 'Consolas', monospace",
|
|
219
|
+
fontSize: 14,
|
|
220
|
+
cursorBlink: true,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const fitAddon = new FitAddon.FitAddon();
|
|
224
|
+
term.loadAddon(fitAddon);
|
|
225
|
+
|
|
226
|
+
// Create terminal wrapper element
|
|
227
|
+
const wrapperEl = document.createElement('div');
|
|
228
|
+
wrapperEl.className = 'terminal-wrapper';
|
|
229
|
+
wrapperEl.id = `terminal-${sessionId}`;
|
|
230
|
+
terminalContainer.appendChild(wrapperEl);
|
|
231
|
+
|
|
232
|
+
// Open terminal in wrapper
|
|
233
|
+
term.open(wrapperEl);
|
|
234
|
+
|
|
235
|
+
// Create tab element
|
|
236
|
+
const tabEl = document.createElement('div');
|
|
237
|
+
tabEl.className = 'tab';
|
|
238
|
+
tabEl.dataset.sessionId = sessionId;
|
|
239
|
+
tabEl.innerHTML = `
|
|
240
|
+
<span class="tab-title">Terminal ${tabCounter}</span>
|
|
241
|
+
<span class="tab-close" title="Close session">×</span>
|
|
242
|
+
`;
|
|
243
|
+
|
|
244
|
+
// Insert tab before the new-tab button
|
|
245
|
+
tabBar.insertBefore(tabEl, newTabBtn);
|
|
246
|
+
|
|
247
|
+
// Tab click handler (switch tab)
|
|
248
|
+
tabEl.addEventListener('click', function (e) {
|
|
249
|
+
if (e.target.classList.contains('tab-close')) return;
|
|
250
|
+
switchTab(sessionId);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Tab close handler
|
|
254
|
+
tabEl.querySelector('.tab-close').addEventListener('click', function (e) {
|
|
255
|
+
e.stopPropagation();
|
|
256
|
+
closeSession(sessionId);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Store session data
|
|
260
|
+
const sessionData = { term, fitAddon, ws: null, tabEl, wrapperEl };
|
|
261
|
+
sessions.set(sessionId, sessionData);
|
|
262
|
+
|
|
263
|
+
// Connect WebSocket
|
|
264
|
+
const ws = connectWebSocket(sessionId);
|
|
265
|
+
sessionData.ws = ws;
|
|
266
|
+
|
|
267
|
+
// Terminal data handler -> send to WebSocket
|
|
268
|
+
term.onData(function (data) {
|
|
269
|
+
const s = sessions.get(sessionId);
|
|
270
|
+
if (s && s.ws && s.ws.readyState === WebSocket.OPEN) {
|
|
271
|
+
s.ws.send(data);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Activate this tab
|
|
276
|
+
switchTab(sessionId);
|
|
277
|
+
|
|
278
|
+
// Fit terminal after it's visible
|
|
279
|
+
requestAnimationFrame(function () {
|
|
280
|
+
fitAddon.fit();
|
|
281
|
+
sendResize(sessionId);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return sessionData;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function switchTab(sessionId) {
|
|
288
|
+
// Deactivate all tabs and wrappers
|
|
289
|
+
for (const [id, session] of sessions) {
|
|
290
|
+
session.tabEl.classList.remove('active');
|
|
291
|
+
session.wrapperEl.classList.remove('active');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Activate selected session
|
|
295
|
+
const session = sessions.get(sessionId);
|
|
296
|
+
if (session) {
|
|
297
|
+
session.tabEl.classList.add('active');
|
|
298
|
+
session.wrapperEl.classList.add('active');
|
|
299
|
+
activeSessionId = sessionId;
|
|
300
|
+
|
|
301
|
+
// Re-fit after becoming visible
|
|
302
|
+
requestAnimationFrame(function () {
|
|
303
|
+
session.fitAddon.fit();
|
|
304
|
+
session.term.focus();
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function closeSession(sessionId) {
|
|
310
|
+
const session = sessions.get(sessionId);
|
|
311
|
+
if (!session) return;
|
|
312
|
+
|
|
313
|
+
// Close WebSocket
|
|
314
|
+
if (session.ws) {
|
|
315
|
+
session.ws.close();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Destroy terminal
|
|
319
|
+
session.term.dispose();
|
|
320
|
+
|
|
321
|
+
// Remove DOM elements
|
|
322
|
+
session.tabEl.remove();
|
|
323
|
+
session.wrapperEl.remove();
|
|
324
|
+
|
|
325
|
+
// Remove from map
|
|
326
|
+
sessions.delete(sessionId);
|
|
327
|
+
|
|
328
|
+
// Delete session on backend
|
|
329
|
+
await deleteSessionAPI(sessionId);
|
|
330
|
+
|
|
331
|
+
// If closed the active tab, switch to another
|
|
332
|
+
if (activeSessionId === sessionId) {
|
|
333
|
+
activeSessionId = null;
|
|
334
|
+
const remaining = Array.from(sessions.keys());
|
|
335
|
+
if (remaining.length > 0) {
|
|
336
|
+
switchTab(remaining[remaining.length - 1]);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function sendResize(sessionId) {
|
|
342
|
+
const session = sessions.get(sessionId);
|
|
343
|
+
if (!session || !session.ws || session.ws.readyState !== WebSocket.OPEN) return;
|
|
344
|
+
|
|
345
|
+
const cols = session.term.cols;
|
|
346
|
+
const rows = session.term.rows;
|
|
347
|
+
|
|
348
|
+
session.ws.send(JSON.stringify({
|
|
349
|
+
type: 'resize',
|
|
350
|
+
cols: cols,
|
|
351
|
+
rows: rows,
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// --- Window Resize Handler ---
|
|
356
|
+
|
|
357
|
+
let resizeTimeout = null;
|
|
358
|
+
window.addEventListener('resize', function () {
|
|
359
|
+
if (resizeTimeout) clearTimeout(resizeTimeout);
|
|
360
|
+
resizeTimeout = setTimeout(function () {
|
|
361
|
+
for (const [id, session] of sessions) {
|
|
362
|
+
session.fitAddon.fit();
|
|
363
|
+
}
|
|
364
|
+
if (activeSessionId) {
|
|
365
|
+
sendResize(activeSessionId);
|
|
366
|
+
}
|
|
367
|
+
}, 100);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// --- New Tab Button ---
|
|
371
|
+
|
|
372
|
+
newTabBtn.addEventListener('click', async function () {
|
|
373
|
+
try {
|
|
374
|
+
const data = await createSessionAPI();
|
|
375
|
+
createTerminalSession(data.id);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
console.error('Failed to create session:', err);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// --- Page Load: Initialize ---
|
|
382
|
+
|
|
383
|
+
async function initialize() {
|
|
384
|
+
try {
|
|
385
|
+
const existingSessions = await fetchSessions();
|
|
386
|
+
|
|
387
|
+
if (existingSessions.length > 0) {
|
|
388
|
+
// Reconnect to existing sessions
|
|
389
|
+
for (const sess of existingSessions) {
|
|
390
|
+
createTerminalSession(sess.id);
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
// Create a first session automatically
|
|
394
|
+
const data = await createSessionAPI();
|
|
395
|
+
createTerminalSession(data.id);
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
console.error('Failed to initialize CloudShell:', err);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
initialize();
|
|
403
|
+
})();
|
|
404
|
+
</script>
|
|
405
|
+
</body>
|
|
406
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { WebSocketServer } = require('ws');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extracts the session ID from a WebSocket upgrade request URL.
|
|
8
|
+
* Expected path format: /ws/cloudshell/:sessionId
|
|
9
|
+
*
|
|
10
|
+
* @param {string} requestUrl - the request URL path
|
|
11
|
+
* @returns {string|null} the session ID, or null if the path doesn't match
|
|
12
|
+
*/
|
|
13
|
+
function extractSessionId(requestUrl) {
|
|
14
|
+
const pathname = url.parse(requestUrl).pathname;
|
|
15
|
+
const match = pathname && pathname.match(/^\/ws\/cloudshell\/([^/]+)$/);
|
|
16
|
+
return match ? match[1] : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates and configures the WebSocket server for CloudShell.
|
|
21
|
+
* Attaches to the existing HTTP server using noServer mode and handles
|
|
22
|
+
* upgrade requests manually for session validation.
|
|
23
|
+
*
|
|
24
|
+
* @param {import('http').Server} httpServer - the Management API HTTP server
|
|
25
|
+
* @param {import('./session-manager').SessionManager} sessionManager - session manager instance
|
|
26
|
+
* @returns {import('ws').WebSocketServer}
|
|
27
|
+
*/
|
|
28
|
+
function createWebSocketServer(httpServer, sessionManager) {
|
|
29
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
30
|
+
|
|
31
|
+
httpServer.on('upgrade', (request, socket, head) => {
|
|
32
|
+
const sessionId = extractSessionId(request.url);
|
|
33
|
+
|
|
34
|
+
// Only handle upgrade requests for our path; ignore all others
|
|
35
|
+
if (!sessionId) {
|
|
36
|
+
// Not a CloudShell WebSocket path — do NOT destroy the socket!
|
|
37
|
+
// Let other upgrade handlers (like Vite HMR) process it.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('CloudShell: WebSocket upgrade request for session:', sessionId);
|
|
42
|
+
|
|
43
|
+
// Validate that the session exists
|
|
44
|
+
const session = sessionManager.getSession(sessionId);
|
|
45
|
+
if (!session) {
|
|
46
|
+
console.log('CloudShell: Session not found, rejecting WebSocket');
|
|
47
|
+
// Reject connection: complete the WebSocket handshake then close with 4004
|
|
48
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
49
|
+
ws.close(4004, 'Session not found');
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Accept connection
|
|
55
|
+
console.log('CloudShell: Accepting WebSocket connection');
|
|
56
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
57
|
+
wss.emit('connection', ws, request, sessionId);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
wss.on('connection', (ws, request, sessionId) => {
|
|
62
|
+
const session = sessionManager.getSession(sessionId);
|
|
63
|
+
if (!session) {
|
|
64
|
+
ws.close(4004, 'Session not found');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Attach WebSocket to session (pipes PTY output to WS)
|
|
69
|
+
sessionManager.attachWebSocket(sessionId, ws);
|
|
70
|
+
|
|
71
|
+
// Handle incoming messages from the client
|
|
72
|
+
ws.on('message', (data) => {
|
|
73
|
+
const currentSession = sessionManager.getSession(sessionId);
|
|
74
|
+
if (!currentSession) return;
|
|
75
|
+
|
|
76
|
+
const message = data.toString();
|
|
77
|
+
|
|
78
|
+
// Try to parse as JSON resize event
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(message);
|
|
81
|
+
if (parsed && parsed.type === 'resize') {
|
|
82
|
+
sessionManager.resizeSession(sessionId, parsed.cols, parsed.rows);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Not JSON; treat as raw terminal input
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Write raw input to PTY stdin
|
|
90
|
+
try {
|
|
91
|
+
currentSession.pty.write(message);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// PTY may have exited; ignore write errors
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// On WebSocket close: detach from session (keep PTY alive)
|
|
98
|
+
ws.on('close', () => {
|
|
99
|
+
// The attachWebSocket already sets up close listener to null out ws,
|
|
100
|
+
// so no additional detach logic needed here. PTY stays alive.
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return wss;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { createWebSocketServer, extractSessionId };
|
|
@@ -42,10 +42,9 @@ class CloudTrailService {
|
|
|
42
42
|
|
|
43
43
|
async start() {
|
|
44
44
|
if (this.isRunning) return;
|
|
45
|
-
|
|
45
|
+
this._server = createCloudTrailServer(this.simulator, this.logger);
|
|
46
46
|
return new Promise((resolve, reject) => {
|
|
47
|
-
this._server
|
|
48
|
-
this._server.listen(this.port, () => {
|
|
47
|
+
this._server.listen(this.port, '0.0.0.0', () => {
|
|
49
48
|
this.isRunning = true;
|
|
50
49
|
this.logger.debug(`CloudTrail rodando na porta ${this.port}`);
|
|
51
50
|
resolve();
|
|
@@ -129,6 +129,11 @@ function createCloudTrailServer(simulator, logger) {
|
|
|
129
129
|
return handleAdmin(req, res, simulator, logger);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// ── JSON routes for React frontend ──────────────────────────────────────
|
|
133
|
+
if (url.startsWith('/json/cloudtrail')) {
|
|
134
|
+
return handleJsonRoutes(req, res, simulator, logger);
|
|
135
|
+
}
|
|
136
|
+
|
|
132
137
|
// ── Internal route (cross-service) ──────────────────────────────────────
|
|
133
138
|
if (url === '/__internal/record-event' && method === 'POST') {
|
|
134
139
|
const body = await parseBody(req);
|
|
@@ -176,6 +181,69 @@ function createCloudTrailServer(simulator, logger) {
|
|
|
176
181
|
return server;
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
// ─── JSON routes for React frontend ──────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
async function handleJsonRoutes(req, res, simulator, logger) {
|
|
187
|
+
const { method, url } = req;
|
|
188
|
+
const path = url.split('?')[0];
|
|
189
|
+
|
|
190
|
+
// GET /json/cloudtrail/trails
|
|
191
|
+
if (path === '/json/cloudtrail/trails' && method === 'GET') {
|
|
192
|
+
try {
|
|
193
|
+
const result = simulator.describeTrails({});
|
|
194
|
+
const trails = result.trailList || [];
|
|
195
|
+
sendJson(res, 200, trails);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
logger.error('[CloudTrail] GET /json/cloudtrail/trails error:', err.message);
|
|
198
|
+
sendJson(res, 200, []);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// POST /json/cloudtrail/trails
|
|
204
|
+
if (path === '/json/cloudtrail/trails' && method === 'POST') {
|
|
205
|
+
try {
|
|
206
|
+
const body = await parseBody(req);
|
|
207
|
+
const result = simulator.createTrail(body);
|
|
208
|
+
sendJson(res, 201, result);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
logger.error('[CloudTrail] POST /json/cloudtrail/trails error:', err.message);
|
|
211
|
+
sendError(res, err);
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// DELETE /json/cloudtrail/trails/:trailName
|
|
217
|
+
if (path.startsWith('/json/cloudtrail/trails/') && method === 'DELETE') {
|
|
218
|
+
try {
|
|
219
|
+
const trailName = path.replace('/json/cloudtrail/trails/', '');
|
|
220
|
+
const result = simulator.deleteTrail({ Name: trailName });
|
|
221
|
+
sendJson(res, 200, result);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
logger.error('[CloudTrail] DELETE /json/cloudtrail/trails error:', err.message);
|
|
224
|
+
sendError(res, err);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// GET /json/cloudtrail/events
|
|
230
|
+
if (path === '/json/cloudtrail/events' && method === 'GET') {
|
|
231
|
+
try {
|
|
232
|
+
const urlObj = new URL(url, 'http://localhost');
|
|
233
|
+
const limit = parseInt(urlObj.searchParams.get('limit') || '50', 10);
|
|
234
|
+
const result = simulator.lookupEvents({ MaxResults: limit });
|
|
235
|
+
const events = result.Events || [];
|
|
236
|
+
sendJson(res, 200, events);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
logger.error('[CloudTrail] GET /json/cloudtrail/events error:', err.message);
|
|
239
|
+
sendJson(res, 200, []);
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
sendJson(res, 404, { message: `JSON route not found: ${path}` });
|
|
245
|
+
}
|
|
246
|
+
|
|
179
247
|
// ─── Admin handlers ───────────────────────────────────────────────────────────
|
|
180
248
|
|
|
181
249
|
async function handleAdmin(req, res, simulator, logger) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { setupServiceMiddleware } = require('../../middleware');
|
|
1
2
|
/**
|
|
2
3
|
* Cognito Server - Servidor HTTP para Cognito API
|
|
3
4
|
*/
|
|
@@ -7,7 +8,9 @@ const cors = require('cors');
|
|
|
7
8
|
const logger = require('../../utils/logger');
|
|
8
9
|
|
|
9
10
|
class CognitoServer {
|
|
10
|
-
constructor(port, config) {
|
|
11
|
+
constructor(port, config) {
|
|
12
|
+
this.organizationsService = null;
|
|
13
|
+
this.stsService = null;
|
|
11
14
|
this.port = port;
|
|
12
15
|
this.config = config;
|
|
13
16
|
this.app = express();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { setupServiceMiddleware } = require('../../middleware');
|
|
1
2
|
/**
|
|
2
3
|
* DynamoDB Server - Servidor HTTP para DynamoDB
|
|
3
4
|
*/
|
|
@@ -8,7 +9,9 @@ const DynamoDBSimulator = require('./simulator');
|
|
|
8
9
|
const logger = require('../../utils/logger');
|
|
9
10
|
|
|
10
11
|
class DynamoDBServer {
|
|
11
|
-
constructor(port, config) {
|
|
12
|
+
constructor(port, config) {
|
|
13
|
+
this.organizationsService = null;
|
|
14
|
+
this.stsService = null;
|
|
12
15
|
this.port = port;
|
|
13
16
|
this.config = config;
|
|
14
17
|
this.app = express();
|
|
@@ -625,7 +625,8 @@ class DynamoDBSimulator {
|
|
|
625
625
|
}
|
|
626
626
|
|
|
627
627
|
// Tenta match de operador infix: attr OP :val
|
|
628
|
-
const match = trimmedPart.match(/([^\s]+)\s*(
|
|
628
|
+
const match = trimmedPart.match(/([^\s]+)\s*(>=|<=|=|>|<|BEGINS_WITH|BETWEEN)\s*([^\s]+)(?:\s+AND\s+([^\s]+))?/i);//trimmedPart.match(/^([#\w.]+)\s*(>=|<=|=|>|<)\s*(:\w+)\s*$/);
|
|
629
|
+
|
|
629
630
|
if (match) {
|
|
630
631
|
const attrPlaceholder = match[1];
|
|
631
632
|
const operator = match[2].toUpperCase();
|