emulate 0.4.0 → 0.5.0
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 +59 -10
- package/dist/api.d.ts +2 -1
- package/dist/api.js +232 -96
- package/dist/api.js.map +1 -1
- package/dist/{chunk-TEPNEZ63.js → chunk-AQ2CLRU3.js} +26 -23
- package/dist/chunk-AQ2CLRU3.js.map +1 -0
- package/dist/chunk-WVQMFHQM.js +83 -0
- package/dist/chunk-WVQMFHQM.js.map +1 -0
- package/dist/{dist-RDFBZ5O6.js → dist-4X2KPMAJ.js} +212 -47
- package/dist/dist-4X2KPMAJ.js.map +1 -0
- package/dist/{dist-OTJZRQ3Q.js → dist-5JVGPOL3.js} +217 -75
- package/dist/dist-5JVGPOL3.js.map +1 -0
- package/dist/{dist-G7WQPZ3Y.js → dist-CE6BUCWQ.js} +211 -60
- package/dist/dist-CE6BUCWQ.js.map +1 -0
- package/dist/{dist-6JFNJPUU.js → dist-CFST4X4K.js} +172 -22
- package/dist/dist-CFST4X4K.js.map +1 -0
- package/dist/{dist-YOVM5HEY.js → dist-ENKE2S7V.js} +521 -60
- package/dist/dist-ENKE2S7V.js.map +1 -0
- package/dist/{dist-RMK3BS5M.js → dist-ETHHYBGF.js} +197 -33
- package/dist/dist-ETHHYBGF.js.map +1 -0
- package/dist/{dist-QMOJM6DV.js → dist-IBXD3O6A.js} +239 -54
- package/dist/dist-IBXD3O6A.js.map +1 -0
- package/dist/dist-J6LHUR52.js +1899 -0
- package/dist/dist-J6LHUR52.js.map +1 -0
- package/dist/{dist-6EW7SSOZ.js → dist-KKTYBE5S.js} +391 -222
- package/dist/dist-KKTYBE5S.js.map +1 -0
- package/dist/{dist-VVXVP5EZ.js → dist-LDUHEJAN.js} +553 -91
- package/dist/dist-LDUHEJAN.js.map +1 -0
- package/dist/{dist-B674PYKV.js → dist-PWGOAQC6.js} +22 -43
- package/dist/dist-PWGOAQC6.js.map +1 -0
- package/dist/{dist-H6JYGQM4.js → dist-REDHDZ3V.js} +272 -157
- package/dist/dist-REDHDZ3V.js.map +1 -0
- package/dist/fonts/favicon.ico +0 -0
- package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
- package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
- package/dist/index.js +365 -108
- package/dist/index.js.map +1 -1
- package/package.json +17 -14
- package/dist/chunk-TEPNEZ63.js.map +0 -1
- package/dist/dist-6EW7SSOZ.js.map +0 -1
- package/dist/dist-6JFNJPUU.js.map +0 -1
- package/dist/dist-B674PYKV.js.map +0 -1
- package/dist/dist-G7WQPZ3Y.js.map +0 -1
- package/dist/dist-H6JYGQM4.js.map +0 -1
- package/dist/dist-OTJZRQ3Q.js.map +0 -1
- package/dist/dist-QMOJM6DV.js.map +0 -1
- package/dist/dist-RDFBZ5O6.js.map +0 -1
- package/dist/dist-RMK3BS5M.js.map +0 -1
- package/dist/dist-VVXVP5EZ.js.map +0 -1
- package/dist/dist-YOVM5HEY.js.map +0 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import "./chunk-AQ2CLRU3.js";
|
|
2
|
+
|
|
1
3
|
// ../@emulators/aws/dist/index.js
|
|
2
4
|
import { randomBytes, createHash } from "crypto";
|
|
3
5
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { dirname, join } from "path";
|
|
4
9
|
function getAwsStore(store) {
|
|
5
10
|
return {
|
|
6
11
|
s3Buckets: store.collection("aws.s3_buckets", ["bucket_name"]),
|
|
@@ -65,7 +70,7 @@ function parseQueryString(body) {
|
|
|
65
70
|
function s3Routes(ctx) {
|
|
66
71
|
const { app, store, baseUrl } = ctx;
|
|
67
72
|
const aws = () => getAwsStore(store);
|
|
68
|
-
|
|
73
|
+
const handleListBuckets = (c) => {
|
|
69
74
|
const buckets = aws().s3Buckets.all();
|
|
70
75
|
const bucketXml = buckets.map(
|
|
71
76
|
(b) => ` <Bucket>
|
|
@@ -84,12 +89,17 @@ ${bucketXml}
|
|
|
84
89
|
</Buckets>
|
|
85
90
|
</ListAllMyBucketsResult>`;
|
|
86
91
|
return awsXmlResponse(c, xml);
|
|
87
|
-
}
|
|
88
|
-
|
|
92
|
+
};
|
|
93
|
+
const handleCreateBucket = (c) => {
|
|
89
94
|
const bucketName = c.req.param("bucket");
|
|
90
95
|
const existing = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
91
96
|
if (existing) {
|
|
92
|
-
return awsErrorXml(
|
|
97
|
+
return awsErrorXml(
|
|
98
|
+
c,
|
|
99
|
+
"BucketAlreadyOwnedByYou",
|
|
100
|
+
"Your previous request to create the named bucket succeeded and you already own it.",
|
|
101
|
+
409
|
|
102
|
+
);
|
|
93
103
|
}
|
|
94
104
|
aws().s3Buckets.insert({
|
|
95
105
|
bucket_name: bucketName,
|
|
@@ -99,8 +109,8 @@ ${bucketXml}
|
|
|
99
109
|
versioning_enabled: false
|
|
100
110
|
});
|
|
101
111
|
return c.text("", 200, { Location: `/${bucketName}` });
|
|
102
|
-
}
|
|
103
|
-
|
|
112
|
+
};
|
|
113
|
+
const handleDeleteBucket = (c) => {
|
|
104
114
|
const bucketName = c.req.param("bucket");
|
|
105
115
|
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
106
116
|
if (!bucket) {
|
|
@@ -112,16 +122,16 @@ ${bucketXml}
|
|
|
112
122
|
}
|
|
113
123
|
aws().s3Buckets.delete(bucket.id);
|
|
114
124
|
return c.body(null, 204);
|
|
115
|
-
}
|
|
116
|
-
|
|
125
|
+
};
|
|
126
|
+
const handleHeadBucket = (c) => {
|
|
117
127
|
const bucketName = c.req.param("bucket");
|
|
118
128
|
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
119
129
|
if (!bucket) {
|
|
120
130
|
return c.text("", 404);
|
|
121
131
|
}
|
|
122
132
|
return c.text("", 200, { "x-amz-bucket-region": bucket.region });
|
|
123
|
-
}
|
|
124
|
-
|
|
133
|
+
};
|
|
134
|
+
const handleListObjects = (c) => {
|
|
125
135
|
const bucketName = c.req.param("bucket");
|
|
126
136
|
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
127
137
|
if (!bucket) {
|
|
@@ -130,10 +140,18 @@ ${bucketXml}
|
|
|
130
140
|
const prefix = c.req.query("prefix") ?? "";
|
|
131
141
|
const delimiter = c.req.query("delimiter") ?? "";
|
|
132
142
|
const maxKeys = Math.min(parseInt(c.req.query("max-keys") ?? "1000", 10), 1e3);
|
|
143
|
+
const continuationToken = c.req.query("continuation-token");
|
|
144
|
+
const startAfter = c.req.query("start-after");
|
|
133
145
|
let objects = aws().s3Objects.findBy("bucket_name", bucketName);
|
|
134
146
|
if (prefix) {
|
|
135
147
|
objects = objects.filter((o) => o.key.startsWith(prefix));
|
|
136
148
|
}
|
|
149
|
+
objects.sort((a, b) => a.key.localeCompare(b.key));
|
|
150
|
+
const marker = continuationToken ?? startAfter;
|
|
151
|
+
if (marker) {
|
|
152
|
+
const startIndex = objects.findIndex((o) => o.key > marker);
|
|
153
|
+
objects = startIndex >= 0 ? objects.slice(startIndex) : [];
|
|
154
|
+
}
|
|
137
155
|
const commonPrefixes = [];
|
|
138
156
|
let contents = objects;
|
|
139
157
|
if (delimiter) {
|
|
@@ -152,6 +170,7 @@ ${bucketXml}
|
|
|
152
170
|
}
|
|
153
171
|
const truncated = contents.length > maxKeys;
|
|
154
172
|
const page = contents.slice(0, maxKeys);
|
|
173
|
+
const nextToken = truncated ? page[page.length - 1].key : void 0;
|
|
155
174
|
const contentsXml = page.map(
|
|
156
175
|
(o) => ` <Contents>
|
|
157
176
|
<Key>${escapeXml(o.key)}</Key>
|
|
@@ -168,13 +187,109 @@ ${bucketXml}
|
|
|
168
187
|
<Prefix>${escapeXml(prefix)}</Prefix>
|
|
169
188
|
<MaxKeys>${maxKeys}</MaxKeys>
|
|
170
189
|
<IsTruncated>${truncated}</IsTruncated>
|
|
171
|
-
<KeyCount>${page.length}</KeyCount
|
|
190
|
+
<KeyCount>${page.length}</KeyCount>${continuationToken ? `
|
|
191
|
+
<ContinuationToken>${escapeXml(continuationToken)}</ContinuationToken>` : ""}${nextToken ? `
|
|
192
|
+
<NextContinuationToken>${escapeXml(nextToken)}</NextContinuationToken>` : ""}${startAfter ? `
|
|
193
|
+
<StartAfter>${escapeXml(startAfter)}</StartAfter>` : ""}
|
|
172
194
|
${contentsXml}
|
|
173
195
|
${prefixesXml}
|
|
174
196
|
</ListBucketResult>`;
|
|
175
197
|
return awsXmlResponse(c, xml);
|
|
176
|
-
}
|
|
177
|
-
|
|
198
|
+
};
|
|
199
|
+
const handlePresignedPost = async (c) => {
|
|
200
|
+
const bucketName = c.req.param("bucket");
|
|
201
|
+
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
202
|
+
if (!bucket) {
|
|
203
|
+
return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
|
|
204
|
+
}
|
|
205
|
+
const body = await c.req.parseBody();
|
|
206
|
+
const key = body["key"];
|
|
207
|
+
if (!key) {
|
|
208
|
+
return awsErrorXml(c, "InvalidArgument", "Bucket POST must contain a field named 'key'.", 400);
|
|
209
|
+
}
|
|
210
|
+
const file = body["file"];
|
|
211
|
+
if (!file || !(file instanceof File)) {
|
|
212
|
+
return awsErrorXml(c, "InvalidArgument", "Bucket POST must contain a file field.", 400);
|
|
213
|
+
}
|
|
214
|
+
const policyB64 = body["Policy"];
|
|
215
|
+
if (policyB64) {
|
|
216
|
+
let policy;
|
|
217
|
+
try {
|
|
218
|
+
policy = JSON.parse(Buffer.from(policyB64, "base64").toString());
|
|
219
|
+
} catch {
|
|
220
|
+
return awsErrorXml(c, "InvalidPolicyDocument", "Invalid Policy: Invalid JSON.", 400);
|
|
221
|
+
}
|
|
222
|
+
if (policy.expiration) {
|
|
223
|
+
const expDate = new Date(policy.expiration);
|
|
224
|
+
if (expDate.getTime() < Date.now()) {
|
|
225
|
+
return awsErrorXml(c, "AccessDenied", "Invalid according to Policy: Policy expired.", 403);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(policy.conditions)) {
|
|
229
|
+
for (const condition of policy.conditions) {
|
|
230
|
+
if (!Array.isArray(condition)) continue;
|
|
231
|
+
if (condition[0] === "content-length-range") {
|
|
232
|
+
const min = condition[1];
|
|
233
|
+
const max = condition[2];
|
|
234
|
+
if (file.size < min || file.size > max) {
|
|
235
|
+
return awsErrorXml(c, "EntityTooLarge", "Your proposed upload exceeds the maximum allowed size.", 400);
|
|
236
|
+
}
|
|
237
|
+
} else if (condition[0] === "starts-with") {
|
|
238
|
+
const field = condition[1].replace(/^\$/, "");
|
|
239
|
+
const prefix = condition[2];
|
|
240
|
+
const value = body[field] ?? "";
|
|
241
|
+
if (!value.startsWith(prefix)) {
|
|
242
|
+
return awsErrorXml(
|
|
243
|
+
c,
|
|
244
|
+
"AccessDenied",
|
|
245
|
+
`Invalid according to Policy: Policy Condition failed: ["starts-with", "$${field}", "${prefix}"]`,
|
|
246
|
+
403
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const fileContent = await file.text();
|
|
254
|
+
const contentType = body["Content-Type"] ?? file.type ?? "application/octet-stream";
|
|
255
|
+
const etag = md5(fileContent);
|
|
256
|
+
const contentLength = new TextEncoder().encode(fileContent).byteLength;
|
|
257
|
+
const existing = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
258
|
+
if (existing) {
|
|
259
|
+
aws().s3Objects.update(existing.id, {
|
|
260
|
+
body: fileContent,
|
|
261
|
+
content_type: contentType,
|
|
262
|
+
content_length: contentLength,
|
|
263
|
+
etag,
|
|
264
|
+
last_modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
265
|
+
metadata: {}
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
aws().s3Objects.insert({
|
|
269
|
+
bucket_name: bucketName,
|
|
270
|
+
key,
|
|
271
|
+
body: fileContent,
|
|
272
|
+
content_type: contentType,
|
|
273
|
+
content_length: contentLength,
|
|
274
|
+
etag,
|
|
275
|
+
last_modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
276
|
+
metadata: {}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const successStatus = parseInt(body["success_action_status"], 10);
|
|
280
|
+
if (successStatus === 201) {
|
|
281
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
282
|
+
<PostResponse>
|
|
283
|
+
<Location>${escapeXml(baseUrl)}/${escapeXml(bucketName)}/${escapeXml(key)}</Location>
|
|
284
|
+
<Bucket>${escapeXml(bucketName)}</Bucket>
|
|
285
|
+
<Key>${escapeXml(key)}</Key>
|
|
286
|
+
<ETag>"${etag}"</ETag>
|
|
287
|
+
</PostResponse>`;
|
|
288
|
+
return awsXmlResponse(c, xml, 201);
|
|
289
|
+
}
|
|
290
|
+
return c.body(null, 204);
|
|
291
|
+
};
|
|
292
|
+
const handlePutObject = async (c) => {
|
|
178
293
|
const bucketName = c.req.param("bucket");
|
|
179
294
|
const key = c.req.param("key");
|
|
180
295
|
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
@@ -223,7 +338,10 @@ ${prefixesXml}
|
|
|
223
338
|
<ETag>"${etag2}"</ETag>
|
|
224
339
|
<LastModified>${now}</LastModified>
|
|
225
340
|
</CopyObjectResult>`;
|
|
226
|
-
return
|
|
341
|
+
return c.text(xml, 200, {
|
|
342
|
+
"Content-Type": "application/xml",
|
|
343
|
+
"Last-Modified": new Date(now).toUTCString()
|
|
344
|
+
});
|
|
227
345
|
}
|
|
228
346
|
const body = await c.req.text();
|
|
229
347
|
const contentType = c.req.header("Content-Type") ?? "application/octet-stream";
|
|
@@ -257,8 +375,8 @@ ${prefixesXml}
|
|
|
257
375
|
});
|
|
258
376
|
}
|
|
259
377
|
return c.text("", 200, { ETag: `"${etag}"` });
|
|
260
|
-
}
|
|
261
|
-
|
|
378
|
+
};
|
|
379
|
+
const handleGetObject = (c) => {
|
|
262
380
|
const bucketName = c.req.param("bucket");
|
|
263
381
|
const key = c.req.param("key");
|
|
264
382
|
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
@@ -273,14 +391,14 @@ ${prefixesXml}
|
|
|
273
391
|
"Content-Type": obj.content_type,
|
|
274
392
|
"Content-Length": String(obj.content_length),
|
|
275
393
|
ETag: `"${obj.etag}"`,
|
|
276
|
-
"Last-Modified": obj.last_modified
|
|
394
|
+
"Last-Modified": new Date(obj.last_modified).toUTCString()
|
|
277
395
|
};
|
|
278
396
|
for (const [k, v] of Object.entries(obj.metadata)) {
|
|
279
397
|
headers[`x-amz-meta-${k}`] = v;
|
|
280
398
|
}
|
|
281
399
|
return c.text(obj.body, 200, headers);
|
|
282
|
-
}
|
|
283
|
-
|
|
400
|
+
};
|
|
401
|
+
const handleHeadObject = (c) => {
|
|
284
402
|
const bucketName = c.req.param("bucket");
|
|
285
403
|
const key = c.req.param("key");
|
|
286
404
|
const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
@@ -291,10 +409,10 @@ ${prefixesXml}
|
|
|
291
409
|
"Content-Type": obj.content_type,
|
|
292
410
|
"Content-Length": String(obj.content_length),
|
|
293
411
|
ETag: `"${obj.etag}"`,
|
|
294
|
-
"Last-Modified": obj.last_modified
|
|
412
|
+
"Last-Modified": new Date(obj.last_modified).toUTCString()
|
|
295
413
|
});
|
|
296
|
-
}
|
|
297
|
-
|
|
414
|
+
};
|
|
415
|
+
const handleDeleteObject = (c) => {
|
|
298
416
|
const bucketName = c.req.param("bucket");
|
|
299
417
|
const key = c.req.param("key");
|
|
300
418
|
const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
@@ -302,7 +420,32 @@ ${prefixesXml}
|
|
|
302
420
|
aws().s3Objects.delete(obj.id);
|
|
303
421
|
}
|
|
304
422
|
return c.body(null, 204);
|
|
305
|
-
}
|
|
423
|
+
};
|
|
424
|
+
app.get("/s3/", handleListBuckets);
|
|
425
|
+
app.put("/s3/:bucket", handleCreateBucket);
|
|
426
|
+
app.delete("/s3/:bucket", handleDeleteBucket);
|
|
427
|
+
app.on("HEAD", "/s3/:bucket", handleHeadBucket);
|
|
428
|
+
app.get("/s3/:bucket", handleListObjects);
|
|
429
|
+
app.post("/s3/:bucket", handlePresignedPost);
|
|
430
|
+
app.put("/s3/:bucket/:key{.+}", handlePutObject);
|
|
431
|
+
app.get("/s3/:bucket/:key{.+}", handleGetObject);
|
|
432
|
+
app.on("HEAD", "/s3/:bucket/:key{.+}", handleHeadObject);
|
|
433
|
+
app.delete("/s3/:bucket/:key{.+}", handleDeleteObject);
|
|
434
|
+
app.get("/", handleListBuckets);
|
|
435
|
+
app.put("/:bucket", handleCreateBucket);
|
|
436
|
+
app.put("/:bucket/", handleCreateBucket);
|
|
437
|
+
app.delete("/:bucket", handleDeleteBucket);
|
|
438
|
+
app.delete("/:bucket/", handleDeleteBucket);
|
|
439
|
+
app.on("HEAD", "/:bucket", handleHeadBucket);
|
|
440
|
+
app.on("HEAD", "/:bucket/", handleHeadBucket);
|
|
441
|
+
app.get("/:bucket", handleListObjects);
|
|
442
|
+
app.get("/:bucket/", handleListObjects);
|
|
443
|
+
app.post("/:bucket", handlePresignedPost);
|
|
444
|
+
app.post("/:bucket/", handlePresignedPost);
|
|
445
|
+
app.put("/:bucket/:key{.+}", handlePutObject);
|
|
446
|
+
app.get("/:bucket/:key{.+}", handleGetObject);
|
|
447
|
+
app.on("HEAD", "/:bucket/:key{.+}", handleHeadObject);
|
|
448
|
+
app.delete("/:bucket/:key{.+}", handleDeleteObject);
|
|
306
449
|
}
|
|
307
450
|
function sqsRoutes(ctx) {
|
|
308
451
|
const { app, store, baseUrl } = ctx;
|
|
@@ -465,7 +608,12 @@ ${queueUrlsXml}
|
|
|
465
608
|
}
|
|
466
609
|
const bodyBytes = new TextEncoder().encode(messageBody).byteLength;
|
|
467
610
|
if (bodyBytes > queue.max_message_size) {
|
|
468
|
-
return awsErrorXml(
|
|
611
|
+
return awsErrorXml(
|
|
612
|
+
c,
|
|
613
|
+
"InvalidParameterValue",
|
|
614
|
+
`One or more parameters are invalid. Reason: Message must be shorter than ${queue.max_message_size} bytes.`,
|
|
615
|
+
400
|
|
616
|
+
);
|
|
469
617
|
}
|
|
470
618
|
const messageId = generateMessageId();
|
|
471
619
|
const bodyMd5 = md5(messageBody);
|
|
@@ -731,7 +879,10 @@ ${usersXml}
|
|
|
731
879
|
}
|
|
732
880
|
const accessKeyId = "AKIA" + randomBytes2(8).toString("hex").toUpperCase();
|
|
733
881
|
const secretAccessKey = randomBytes2(30).toString("base64");
|
|
734
|
-
const keys = [
|
|
882
|
+
const keys = [
|
|
883
|
+
...user.access_keys,
|
|
884
|
+
{ access_key_id: accessKeyId, secret_access_key: secretAccessKey, status: "Active" }
|
|
885
|
+
];
|
|
735
886
|
aws().iamUsers.update(user.id, { access_keys: keys });
|
|
736
887
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
737
888
|
<CreateAccessKeyResponse>
|
|
@@ -934,10 +1085,347 @@ ${rolesXml}
|
|
|
934
1085
|
return awsXmlResponse(c, xml);
|
|
935
1086
|
}
|
|
936
1087
|
}
|
|
1088
|
+
function createErrorHandler(documentationUrl) {
|
|
1089
|
+
return async (c, next) => {
|
|
1090
|
+
if (documentationUrl) {
|
|
1091
|
+
c.set("docsUrl", documentationUrl);
|
|
1092
|
+
}
|
|
1093
|
+
await next();
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
var errorHandler = createErrorHandler();
|
|
1097
|
+
var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1");
|
|
1098
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1099
|
+
var FONTS = {
|
|
1100
|
+
"geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
|
|
1101
|
+
"GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
|
|
1102
|
+
};
|
|
1103
|
+
var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
|
|
1104
|
+
function escapeHtml(s) {
|
|
1105
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1106
|
+
}
|
|
1107
|
+
function escapeAttr(s) {
|
|
1108
|
+
return escapeHtml(s).replace(/'/g, "'");
|
|
1109
|
+
}
|
|
1110
|
+
var CSS = `
|
|
1111
|
+
@font-face{
|
|
1112
|
+
font-family:'Geist';font-style:normal;font-weight:100 900;font-display:swap;
|
|
1113
|
+
src:url('/_emulate/fonts/geist-sans.woff2') format('woff2');
|
|
1114
|
+
}
|
|
1115
|
+
@font-face{
|
|
1116
|
+
font-family:'Geist Pixel';font-style:normal;font-weight:400;font-display:swap;
|
|
1117
|
+
src:url('/_emulate/fonts/GeistPixel-Square.woff2') format('woff2');
|
|
1118
|
+
}
|
|
1119
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
1120
|
+
body{
|
|
1121
|
+
font-family:'Geist',-apple-system,BlinkMacSystemFont,sans-serif;
|
|
1122
|
+
background:#000;color:#33ff00;min-height:100vh;
|
|
1123
|
+
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
|
1124
|
+
}
|
|
1125
|
+
.emu-bar{
|
|
1126
|
+
border-bottom:1px solid #0a3300;padding:10px 20px;
|
|
1127
|
+
display:flex;align-items:center;gap:10px;font-size:.8125rem;color:#1a8c00;
|
|
1128
|
+
}
|
|
1129
|
+
.emu-bar-title{font-weight:600;color:#33ff00;font-family:'Geist Pixel',monospace;}
|
|
1130
|
+
.emu-bar-links{margin-left:auto;display:flex;gap:16px;}
|
|
1131
|
+
.emu-bar-links a{
|
|
1132
|
+
color:#1a8c00;font-size:.75rem;text-decoration:none;transition:color .15s;
|
|
1133
|
+
}
|
|
1134
|
+
.emu-bar-links a:hover{color:#33ff00;}
|
|
1135
|
+
.emu-bar-links a .full{display:inline;}
|
|
1136
|
+
.emu-bar-links a .short{display:none;}
|
|
1137
|
+
@media(max-width:600px){
|
|
1138
|
+
.emu-bar-links a .full{display:none;}
|
|
1139
|
+
.emu-bar-links a .short{display:inline;}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.content{
|
|
1143
|
+
display:flex;align-items:center;justify-content:center;
|
|
1144
|
+
min-height:calc(100vh - 42px);padding:24px 16px;
|
|
1145
|
+
}
|
|
1146
|
+
.content-inner{width:100%;max-width:420px;}
|
|
1147
|
+
.card-title{
|
|
1148
|
+
font-family:'Geist Pixel',monospace;
|
|
1149
|
+
font-size:1.125rem;font-weight:600;margin-bottom:4px;color:#33ff00;
|
|
1150
|
+
}
|
|
1151
|
+
.card-subtitle{color:#1a8c00;font-size:.8125rem;margin-bottom:18px;line-height:1.45;}
|
|
1152
|
+
.powered-by{
|
|
1153
|
+
position:fixed;bottom:0;left:0;right:0;
|
|
1154
|
+
text-align:center;padding:12px;font-size:.6875rem;color:#0a3300;
|
|
1155
|
+
font-family:'Geist Pixel',monospace;
|
|
1156
|
+
}
|
|
1157
|
+
.powered-by a{color:#1a8c00;text-decoration:none;transition:color .15s;}
|
|
1158
|
+
.powered-by a:hover{color:#33ff00;}
|
|
1159
|
+
|
|
1160
|
+
.error-title{
|
|
1161
|
+
font-family:'Geist Pixel',monospace;
|
|
1162
|
+
color:#ff4444;font-size:1.125rem;font-weight:600;margin-bottom:8px;
|
|
1163
|
+
}
|
|
1164
|
+
.error-msg{color:#1a8c00;font-size:.875rem;line-height:1.5;}
|
|
1165
|
+
.error-card{text-align:center;}
|
|
1166
|
+
|
|
1167
|
+
.user-form{margin-bottom:8px;}
|
|
1168
|
+
.user-form:last-of-type{margin-bottom:0;}
|
|
1169
|
+
.user-btn{
|
|
1170
|
+
width:100%;display:flex;align-items:center;gap:12px;
|
|
1171
|
+
padding:10px 12px;border:1px solid #0a3300;border-radius:8px;
|
|
1172
|
+
background:#000;color:inherit;cursor:pointer;text-align:left;
|
|
1173
|
+
font:inherit;transition:border-color .15s;
|
|
1174
|
+
}
|
|
1175
|
+
.user-btn:hover{border-color:#33ff00;}
|
|
1176
|
+
.avatar{
|
|
1177
|
+
width:36px;height:36px;border-radius:50%;
|
|
1178
|
+
background:#0a3300;color:#33ff00;font-weight:600;font-size:.875rem;
|
|
1179
|
+
display:flex;align-items:center;justify-content:center;flex-shrink:0;
|
|
1180
|
+
font-family:'Geist Pixel',monospace;
|
|
1181
|
+
}
|
|
1182
|
+
.user-text{min-width:0;}
|
|
1183
|
+
.user-login{font-weight:600;font-size:.875rem;display:block;color:#33ff00;}
|
|
1184
|
+
.user-meta{color:#1a8c00;font-size:.75rem;margin-top:1px;}
|
|
1185
|
+
.user-email{font-size:.6875rem;color:#116600;word-break:break-all;margin-top:1px;}
|
|
1186
|
+
|
|
1187
|
+
.settings-layout{
|
|
1188
|
+
max-width:920px;margin:0 auto;padding:28px 20px;
|
|
1189
|
+
display:flex;gap:28px;
|
|
1190
|
+
}
|
|
1191
|
+
.settings-sidebar{width:200px;flex-shrink:0;}
|
|
1192
|
+
.settings-sidebar a{
|
|
1193
|
+
display:block;padding:6px 10px;border-radius:6px;color:#1a8c00;
|
|
1194
|
+
text-decoration:none;font-size:.8125rem;transition:color .15s;
|
|
1195
|
+
}
|
|
1196
|
+
.settings-sidebar a:hover{color:#33ff00;}
|
|
1197
|
+
.settings-sidebar a.active{color:#33ff00;font-weight:600;}
|
|
1198
|
+
.settings-main{flex:1;min-width:0;}
|
|
1199
|
+
|
|
1200
|
+
.s-card{
|
|
1201
|
+
padding:18px 0;margin-bottom:14px;border-bottom:1px solid #0a3300;
|
|
1202
|
+
}
|
|
1203
|
+
.s-card:last-child{border-bottom:none;}
|
|
1204
|
+
.s-card-header{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
|
|
1205
|
+
.s-icon{
|
|
1206
|
+
width:42px;height:42px;border-radius:8px;
|
|
1207
|
+
background:#0a3300;display:flex;align-items:center;justify-content:center;
|
|
1208
|
+
font-size:1.125rem;font-weight:700;color:#116600;flex-shrink:0;
|
|
1209
|
+
font-family:'Geist Pixel',monospace;
|
|
1210
|
+
}
|
|
1211
|
+
.s-title{
|
|
1212
|
+
font-family:'Geist Pixel',monospace;
|
|
1213
|
+
font-size:1.25rem;font-weight:600;color:#33ff00;
|
|
1214
|
+
}
|
|
1215
|
+
.s-subtitle{font-size:.75rem;color:#1a8c00;margin-top:2px;}
|
|
1216
|
+
.section-heading{
|
|
1217
|
+
font-size:.9375rem;font-weight:600;margin-bottom:10px;color:#33ff00;
|
|
1218
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
1219
|
+
}
|
|
1220
|
+
.perm-list{list-style:none;}
|
|
1221
|
+
.perm-list li{padding:5px 0;font-size:.8125rem;display:flex;align-items:center;gap:6px;color:#1a8c00;}
|
|
1222
|
+
.check{color:#33ff00;}
|
|
1223
|
+
.org-row{
|
|
1224
|
+
display:flex;align-items:center;gap:8px;padding:7px 0;
|
|
1225
|
+
border-bottom:1px solid #0a3300;font-size:.8125rem;
|
|
1226
|
+
}
|
|
1227
|
+
.org-row:last-child{border-bottom:none;}
|
|
1228
|
+
.org-icon{
|
|
1229
|
+
width:22px;height:22px;border-radius:4px;background:#0a3300;
|
|
1230
|
+
display:flex;align-items:center;justify-content:center;
|
|
1231
|
+
font-size:.625rem;font-weight:700;color:#116600;flex-shrink:0;
|
|
1232
|
+
font-family:'Geist Pixel',monospace;
|
|
1233
|
+
}
|
|
1234
|
+
.org-name{font-weight:600;color:#33ff00;}
|
|
1235
|
+
.badge{font-size:.6875rem;padding:1px 7px;border-radius:999px;font-weight:500;}
|
|
1236
|
+
.badge-granted{background:#0a3300;color:#33ff00;}
|
|
1237
|
+
.badge-denied{background:#1a0a0a;color:#ff4444;}
|
|
1238
|
+
.badge-requested{background:#0a3300;color:#1a8c00;}
|
|
1239
|
+
.btn-revoke{
|
|
1240
|
+
display:inline-block;padding:5px 14px;border-radius:6px;
|
|
1241
|
+
border:1px solid #0a3300;background:transparent;color:#ff4444;
|
|
1242
|
+
font-size:.75rem;font-weight:600;cursor:pointer;transition:border-color .15s;
|
|
1243
|
+
}
|
|
1244
|
+
.btn-revoke:hover{border-color:#ff4444;}
|
|
1245
|
+
.info-text{color:#1a8c00;font-size:.75rem;line-height:1.5;margin-top:10px;}
|
|
1246
|
+
.app-link{
|
|
1247
|
+
display:flex;align-items:center;gap:12px;padding:12px;
|
|
1248
|
+
border:1px solid #0a3300;border-radius:8px;background:#000;
|
|
1249
|
+
text-decoration:none;color:inherit;margin-bottom:8px;transition:border-color .15s;
|
|
1250
|
+
}
|
|
1251
|
+
.app-link:hover{border-color:#33ff00;}
|
|
1252
|
+
.app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
|
|
1253
|
+
.app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
|
|
1254
|
+
.empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
|
|
1255
|
+
|
|
1256
|
+
.inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
|
|
1257
|
+
.inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
|
|
1258
|
+
.inspector-tabs a{
|
|
1259
|
+
padding:7px 16px;border-radius:6px;text-decoration:none;
|
|
1260
|
+
font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
|
|
1261
|
+
transition:color .15s,border-color .15s;
|
|
1262
|
+
}
|
|
1263
|
+
.inspector-tabs a:hover{color:#33ff00;}
|
|
1264
|
+
.inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
|
|
1265
|
+
.inspector-section{margin-bottom:24px;}
|
|
1266
|
+
.inspector-section h2{
|
|
1267
|
+
font-family:'Geist Pixel',monospace;
|
|
1268
|
+
font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
|
|
1269
|
+
}
|
|
1270
|
+
.inspector-section h3{
|
|
1271
|
+
font-family:'Geist Pixel',monospace;
|
|
1272
|
+
font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
|
|
1273
|
+
}
|
|
1274
|
+
.inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
|
|
1275
|
+
.inspector-table th,.inspector-table td{
|
|
1276
|
+
text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
|
|
1277
|
+
font-size:.8125rem;
|
|
1278
|
+
}
|
|
1279
|
+
.inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
|
|
1280
|
+
.inspector-table td{color:#33ff00;}
|
|
1281
|
+
.inspector-table tbody tr{transition:background .1s;}
|
|
1282
|
+
.inspector-table tbody tr:hover{background:#0a3300;}
|
|
1283
|
+
.inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
|
|
1284
|
+
|
|
1285
|
+
.checkout-layout{
|
|
1286
|
+
display:flex;min-height:calc(100vh - 42px);
|
|
1287
|
+
}
|
|
1288
|
+
.checkout-summary{
|
|
1289
|
+
flex:1;background:#020;padding:48px 40px 48px 10%;
|
|
1290
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
1291
|
+
border-right:1px solid #0a3300;
|
|
1292
|
+
}
|
|
1293
|
+
.checkout-form-side{
|
|
1294
|
+
flex:1;background:#000;padding:48px 10% 48px 40px;
|
|
1295
|
+
display:flex;flex-direction:column;justify-content:center;
|
|
1296
|
+
}
|
|
1297
|
+
.checkout-merchant{
|
|
1298
|
+
display:flex;align-items:center;gap:10px;margin-bottom:6px;
|
|
1299
|
+
}
|
|
1300
|
+
.checkout-merchant-name{
|
|
1301
|
+
font-family:'Geist Pixel',monospace;
|
|
1302
|
+
font-size:.9375rem;font-weight:600;color:#33ff00;
|
|
1303
|
+
}
|
|
1304
|
+
.checkout-test-badge{
|
|
1305
|
+
font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
|
|
1306
|
+
background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
|
|
1307
|
+
}
|
|
1308
|
+
.checkout-total{
|
|
1309
|
+
font-family:'Geist Pixel',monospace;
|
|
1310
|
+
font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
|
|
1311
|
+
}
|
|
1312
|
+
.checkout-line-item{
|
|
1313
|
+
display:flex;align-items:center;gap:14px;padding:14px 0;
|
|
1314
|
+
border-bottom:1px solid #0a3300;
|
|
1315
|
+
}
|
|
1316
|
+
.checkout-line-item:first-child{border-top:1px solid #0a3300;}
|
|
1317
|
+
.checkout-item-icon{
|
|
1318
|
+
width:42px;height:42px;border-radius:6px;background:#0a3300;
|
|
1319
|
+
display:flex;align-items:center;justify-content:center;flex-shrink:0;
|
|
1320
|
+
font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
|
|
1321
|
+
}
|
|
1322
|
+
.checkout-item-details{flex:1;min-width:0;}
|
|
1323
|
+
.checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
|
|
1324
|
+
.checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
|
|
1325
|
+
.checkout-item-price{
|
|
1326
|
+
font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
|
|
1327
|
+
}
|
|
1328
|
+
.checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
|
|
1329
|
+
.checkout-totals{margin-top:20px;}
|
|
1330
|
+
.checkout-totals-row{
|
|
1331
|
+
display:flex;justify-content:space-between;padding:6px 0;
|
|
1332
|
+
font-size:.8125rem;color:#1a8c00;
|
|
1333
|
+
}
|
|
1334
|
+
.checkout-totals-row.total{
|
|
1335
|
+
border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
|
|
1336
|
+
font-size:.9375rem;font-weight:600;color:#33ff00;
|
|
1337
|
+
}
|
|
1338
|
+
.checkout-form-section{margin-bottom:24px;}
|
|
1339
|
+
.checkout-form-label{
|
|
1340
|
+
font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
|
|
1341
|
+
}
|
|
1342
|
+
.checkout-input{
|
|
1343
|
+
width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
|
|
1344
|
+
background:#020;color:#33ff00;font:inherit;font-size:.875rem;
|
|
1345
|
+
transition:border-color .15s;outline:none;
|
|
1346
|
+
}
|
|
1347
|
+
.checkout-input:focus{border-color:#33ff00;}
|
|
1348
|
+
.checkout-input::placeholder{color:#116600;}
|
|
1349
|
+
.checkout-card-box{
|
|
1350
|
+
border:1px solid #0a3300;border-radius:6px;padding:14px;
|
|
1351
|
+
background:#020;
|
|
1352
|
+
}
|
|
1353
|
+
.checkout-card-row{
|
|
1354
|
+
display:flex;gap:12px;margin-top:10px;
|
|
1355
|
+
}
|
|
1356
|
+
.checkout-card-row .checkout-input{flex:1;}
|
|
1357
|
+
.checkout-sim-note{
|
|
1358
|
+
font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
|
|
1359
|
+
font-style:italic;
|
|
1360
|
+
}
|
|
1361
|
+
.checkout-pay-btn{
|
|
1362
|
+
width:100%;padding:14px;border:none;border-radius:8px;
|
|
1363
|
+
background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
|
|
1364
|
+
cursor:pointer;transition:background .15s;
|
|
1365
|
+
font-family:'Geist Pixel',monospace;
|
|
1366
|
+
}
|
|
1367
|
+
.checkout-pay-btn:hover{background:#44ff22;}
|
|
1368
|
+
.checkout-cancel{
|
|
1369
|
+
text-align:center;margin-top:14px;
|
|
1370
|
+
}
|
|
1371
|
+
.checkout-cancel a{
|
|
1372
|
+
color:#1a8c00;text-decoration:none;font-size:.8125rem;
|
|
1373
|
+
transition:color .15s;
|
|
1374
|
+
}
|
|
1375
|
+
.checkout-cancel a:hover{color:#33ff00;}
|
|
1376
|
+
@media(max-width:768px){
|
|
1377
|
+
.checkout-layout{flex-direction:column;}
|
|
1378
|
+
.checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
|
|
1379
|
+
.checkout-form-side{padding:32px 20px;}
|
|
1380
|
+
}
|
|
1381
|
+
`;
|
|
1382
|
+
var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
|
|
1383
|
+
function emuBar(service) {
|
|
1384
|
+
const title = service ? `${escapeHtml(service)} Emulator` : "Emulator";
|
|
1385
|
+
return `<div class="emu-bar">
|
|
1386
|
+
<span class="emu-bar-title">${title}</span>
|
|
1387
|
+
<nav class="emu-bar-links">
|
|
1388
|
+
<a href="https://github.com/vercel-labs/emulate/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
|
|
1389
|
+
<a href="https://github.com/vercel-labs/emulate" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
|
|
1390
|
+
<a href="https://emulate.dev" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
|
|
1391
|
+
</nav>
|
|
1392
|
+
</div>`;
|
|
1393
|
+
}
|
|
1394
|
+
function head(title) {
|
|
1395
|
+
return `<!DOCTYPE html>
|
|
1396
|
+
<html lang="en">
|
|
1397
|
+
<head>
|
|
1398
|
+
<meta charset="utf-8"/>
|
|
1399
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
1400
|
+
<link rel="icon" href="/_emulate/favicon.ico"/>
|
|
1401
|
+
<title>${escapeHtml(title)} | emulate</title>
|
|
1402
|
+
<style>${CSS}</style>
|
|
1403
|
+
</head>`;
|
|
1404
|
+
}
|
|
1405
|
+
function renderInspectorPage(title, tabs, activeTab, body, service) {
|
|
1406
|
+
const tabLinks = tabs.map(
|
|
1407
|
+
(t) => `<a href="${escapeAttr(t.href)}" class="${t.id === activeTab ? "active" : ""}">${escapeHtml(t.label)}</a>`
|
|
1408
|
+
).join("");
|
|
1409
|
+
return `${head(title)}
|
|
1410
|
+
<body>
|
|
1411
|
+
${emuBar(service)}
|
|
1412
|
+
<div class="inspector-layout">
|
|
1413
|
+
<nav class="inspector-tabs">${tabLinks}</nav>
|
|
1414
|
+
${body}
|
|
1415
|
+
</div>
|
|
1416
|
+
${POWERED_BY}
|
|
1417
|
+
</body></html>`;
|
|
1418
|
+
}
|
|
1419
|
+
var SERVICE_LABEL = "AWS";
|
|
1420
|
+
var TABS = [
|
|
1421
|
+
{ id: "s3", label: "S3", href: "/_inspector?tab=s3" },
|
|
1422
|
+
{ id: "sqs", label: "SQS", href: "/_inspector?tab=sqs" },
|
|
1423
|
+
{ id: "iam", label: "IAM", href: "/_inspector?tab=iam" }
|
|
1424
|
+
];
|
|
937
1425
|
function inspectorRoutes(ctx) {
|
|
938
1426
|
const { app, store } = ctx;
|
|
939
1427
|
const aws = () => getAwsStore(store);
|
|
940
|
-
app.get("/", (c) => {
|
|
1428
|
+
app.get("/_inspector", (c) => {
|
|
941
1429
|
const tab = c.req.query("tab") ?? "s3";
|
|
942
1430
|
const s3Store = aws();
|
|
943
1431
|
const buckets = s3Store.s3Buckets.all();
|
|
@@ -956,11 +1444,13 @@ function inspectorRoutes(ctx) {
|
|
|
956
1444
|
</tr>`;
|
|
957
1445
|
}).join("\n");
|
|
958
1446
|
contentHtml = `
|
|
959
|
-
<
|
|
960
|
-
|
|
961
|
-
<
|
|
962
|
-
|
|
963
|
-
|
|
1447
|
+
<div class="inspector-section">
|
|
1448
|
+
<h2>S3 Buckets (${buckets.length})</h2>
|
|
1449
|
+
<table class="inspector-table">
|
|
1450
|
+
<thead><tr><th>Bucket</th><th>Objects</th><th>Region</th><th>Created</th></tr></thead>
|
|
1451
|
+
<tbody>${rows || `<tr><td colspan="4"><div class="inspector-empty">No buckets</div></td></tr>`}</tbody>
|
|
1452
|
+
</table>
|
|
1453
|
+
</div>`;
|
|
964
1454
|
for (const bucket of buckets) {
|
|
965
1455
|
const objects = s3Store.s3Objects.findBy("bucket_name", bucket.bucket_name);
|
|
966
1456
|
if (objects.length > 0) {
|
|
@@ -973,11 +1463,13 @@ function inspectorRoutes(ctx) {
|
|
|
973
1463
|
</tr>`
|
|
974
1464
|
).join("\n");
|
|
975
1465
|
contentHtml += `
|
|
976
|
-
<
|
|
977
|
-
|
|
978
|
-
<
|
|
979
|
-
|
|
980
|
-
|
|
1466
|
+
<div class="inspector-section">
|
|
1467
|
+
<h3>${escapeXml(bucket.bucket_name)} objects</h3>
|
|
1468
|
+
<table class="inspector-table">
|
|
1469
|
+
<thead><tr><th>Key</th><th>Size</th><th>Type</th><th>Last Modified</th></tr></thead>
|
|
1470
|
+
<tbody>${objRows}</tbody>
|
|
1471
|
+
</table>
|
|
1472
|
+
</div>`;
|
|
981
1473
|
}
|
|
982
1474
|
}
|
|
983
1475
|
} else if (tab === "sqs") {
|
|
@@ -991,11 +1483,13 @@ function inspectorRoutes(ctx) {
|
|
|
991
1483
|
</tr>`;
|
|
992
1484
|
}).join("\n");
|
|
993
1485
|
contentHtml = `
|
|
994
|
-
<
|
|
995
|
-
|
|
996
|
-
<
|
|
997
|
-
|
|
998
|
-
|
|
1486
|
+
<div class="inspector-section">
|
|
1487
|
+
<h2>SQS Queues (${queues.length})</h2>
|
|
1488
|
+
<table class="inspector-table">
|
|
1489
|
+
<thead><tr><th>Queue</th><th>Messages</th><th>FIFO</th><th>Visibility Timeout</th></tr></thead>
|
|
1490
|
+
<tbody>${rows || `<tr><td colspan="4"><div class="inspector-empty">No queues</div></td></tr>`}</tbody>
|
|
1491
|
+
</table>
|
|
1492
|
+
</div>`;
|
|
999
1493
|
} else if (tab === "iam") {
|
|
1000
1494
|
const userRows = users.map(
|
|
1001
1495
|
(u) => `<tr>
|
|
@@ -1014,54 +1508,22 @@ function inspectorRoutes(ctx) {
|
|
|
1014
1508
|
</tr>`
|
|
1015
1509
|
).join("\n");
|
|
1016
1510
|
contentHtml = `
|
|
1017
|
-
<
|
|
1018
|
-
|
|
1019
|
-
<
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
<
|
|
1026
|
-
|
|
1511
|
+
<div class="inspector-section">
|
|
1512
|
+
<h2>IAM Users (${users.length})</h2>
|
|
1513
|
+
<table class="inspector-table">
|
|
1514
|
+
<thead><tr><th>User</th><th>User ID</th><th>Access Keys</th><th>ARN</th></tr></thead>
|
|
1515
|
+
<tbody>${userRows || `<tr><td colspan="4"><div class="inspector-empty">No users</div></td></tr>`}</tbody>
|
|
1516
|
+
</table>
|
|
1517
|
+
</div>
|
|
1518
|
+
<div class="inspector-section">
|
|
1519
|
+
<h2>IAM Roles (${roles.length})</h2>
|
|
1520
|
+
<table class="inspector-table">
|
|
1521
|
+
<thead><tr><th>Role</th><th>Role ID</th><th>Description</th><th>ARN</th></tr></thead>
|
|
1522
|
+
<tbody>${roleRows || `<tr><td colspan="4"><div class="inspector-empty">No roles</div></td></tr>`}</tbody>
|
|
1523
|
+
</table>
|
|
1524
|
+
</div>`;
|
|
1027
1525
|
}
|
|
1028
|
-
|
|
1029
|
-
<html>
|
|
1030
|
-
<head>
|
|
1031
|
-
<meta charset="UTF-8">
|
|
1032
|
-
<title>AWS Emulator - Inspector</title>
|
|
1033
|
-
<style>
|
|
1034
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
|
|
1035
|
-
.header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
1036
|
-
.header h1 { margin: 0; font-size: 24px; }
|
|
1037
|
-
.badge { background: #ff9900; color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
|
|
1038
|
-
.tabs { display: flex; gap: 4px; margin-bottom: 20px; }
|
|
1039
|
-
.tabs a { padding: 8px 16px; border-radius: 6px 6px 0 0; text-decoration: none; color: #333; background: #e0e0e0; }
|
|
1040
|
-
.tabs a.active { background: #fff; font-weight: 600; }
|
|
1041
|
-
.content { background: #fff; border-radius: 8px; padding: 20px; }
|
|
1042
|
-
table { width: 100%; border-collapse: collapse; margin-bottom: 16px; }
|
|
1043
|
-
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #eee; }
|
|
1044
|
-
th { background: #f9f9f9; font-weight: 600; }
|
|
1045
|
-
h2 { margin-top: 0; }
|
|
1046
|
-
h3 { margin-top: 16px; color: #555; }
|
|
1047
|
-
</style>
|
|
1048
|
-
</head>
|
|
1049
|
-
<body>
|
|
1050
|
-
<div class="header">
|
|
1051
|
-
<h1>AWS Emulator</h1>
|
|
1052
|
-
<span class="badge">Inspector</span>
|
|
1053
|
-
</div>
|
|
1054
|
-
<div class="tabs">
|
|
1055
|
-
<a href="/?tab=s3" class="${tab === "s3" ? "active" : ""}">S3</a>
|
|
1056
|
-
<a href="/?tab=sqs" class="${tab === "sqs" ? "active" : ""}">SQS</a>
|
|
1057
|
-
<a href="/?tab=iam" class="${tab === "iam" ? "active" : ""}">IAM</a>
|
|
1058
|
-
</div>
|
|
1059
|
-
<div class="content">
|
|
1060
|
-
${contentHtml}
|
|
1061
|
-
</div>
|
|
1062
|
-
</body>
|
|
1063
|
-
</html>`;
|
|
1064
|
-
return c.html(html);
|
|
1526
|
+
return c.html(renderInspectorPage("Inspector", TABS, tab, contentHtml, SERVICE_LABEL));
|
|
1065
1527
|
});
|
|
1066
1528
|
}
|
|
1067
1529
|
function seedDefaults(store, baseUrl) {
|
|
@@ -1180,10 +1642,10 @@ var awsPlugin = {
|
|
|
1180
1642
|
name: "aws",
|
|
1181
1643
|
register(app, store, webhooks, baseUrl, tokenMap) {
|
|
1182
1644
|
const ctx = { app, store, webhooks, baseUrl, tokenMap };
|
|
1183
|
-
|
|
1645
|
+
inspectorRoutes(ctx);
|
|
1184
1646
|
sqsRoutes(ctx);
|
|
1185
1647
|
iamRoutes(ctx);
|
|
1186
|
-
|
|
1648
|
+
s3Routes(ctx);
|
|
1187
1649
|
},
|
|
1188
1650
|
seed(store, baseUrl) {
|
|
1189
1651
|
seedDefaults(store, baseUrl);
|
|
@@ -1196,4 +1658,4 @@ export {
|
|
|
1196
1658
|
getAwsStore,
|
|
1197
1659
|
seedFromConfig
|
|
1198
1660
|
};
|
|
1199
|
-
//# sourceMappingURL=dist-
|
|
1661
|
+
//# sourceMappingURL=dist-LDUHEJAN.js.map
|