Add resource inspector to Quantum dashboard
This commit is contained in:
@@ -158,6 +158,150 @@ def send_sqs_message():
|
||||
return jsonify(ok("sqs-message", {"queue": QUEUE_NAME, "messageId": response.get("MessageId")}))
|
||||
|
||||
|
||||
@app.get("/api/s3/objects")
|
||||
def list_s3_objects():
|
||||
response = client("s3").list_objects_v2(Bucket=BUCKET_NAME)
|
||||
objects = [
|
||||
{
|
||||
"key": item.get("Key"),
|
||||
"size": item.get("Size"),
|
||||
"lastModified": item.get("LastModified").isoformat() if item.get("LastModified") else None,
|
||||
"etag": item.get("ETag"),
|
||||
}
|
||||
for item in response.get("Contents", [])
|
||||
]
|
||||
|
||||
return jsonify(ok("s3-objects", {"bucket": BUCKET_NAME, "objectCount": len(objects), "objects": objects}))
|
||||
|
||||
|
||||
@app.get("/api/sqs/details")
|
||||
def sqs_details():
|
||||
sqs = client("sqs")
|
||||
main_url = queue_url(QUEUE_NAME)
|
||||
dlq_url = queue_url(DLQ_NAME)
|
||||
attributes = [
|
||||
"ApproximateNumberOfMessages",
|
||||
"ApproximateNumberOfMessagesNotVisible",
|
||||
"ApproximateNumberOfMessagesDelayed",
|
||||
"CreatedTimestamp",
|
||||
"LastModifiedTimestamp",
|
||||
"VisibilityTimeout",
|
||||
]
|
||||
|
||||
main_attrs = sqs.get_queue_attributes(QueueUrl=main_url, AttributeNames=attributes)["Attributes"]
|
||||
dlq_attrs = sqs.get_queue_attributes(QueueUrl=dlq_url, AttributeNames=attributes)["Attributes"]
|
||||
messages = sqs.receive_message(
|
||||
QueueUrl=main_url,
|
||||
MaxNumberOfMessages=5,
|
||||
VisibilityTimeout=0,
|
||||
WaitTimeSeconds=0,
|
||||
AttributeNames=["All"],
|
||||
).get("Messages", [])
|
||||
|
||||
visible_messages = [
|
||||
{
|
||||
"messageId": message.get("MessageId"),
|
||||
"body": message.get("Body"),
|
||||
"attributes": message.get("Attributes", {}),
|
||||
}
|
||||
for message in messages
|
||||
]
|
||||
|
||||
return jsonify(
|
||||
ok(
|
||||
"sqs-details",
|
||||
{
|
||||
"queue": {"name": QUEUE_NAME, "url": main_url, "attributes": main_attrs},
|
||||
"dlq": {"name": DLQ_NAME, "url": dlq_url, "attributes": dlq_attrs},
|
||||
"visibleMessagesSample": visible_messages,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/lambda/details")
|
||||
def lambda_details():
|
||||
lambda_client = client("lambda")
|
||||
function = lambda_client.get_function(FunctionName=LAMBDA_NAME)
|
||||
configuration = function.get("Configuration", {})
|
||||
mappings = lambda_client.list_event_source_mappings(FunctionName=LAMBDA_NAME).get("EventSourceMappings", [])
|
||||
|
||||
return jsonify(
|
||||
ok(
|
||||
"lambda-details",
|
||||
{
|
||||
"functionName": configuration.get("FunctionName"),
|
||||
"runtime": configuration.get("Runtime"),
|
||||
"handler": configuration.get("Handler"),
|
||||
"state": configuration.get("State"),
|
||||
"lastModified": configuration.get("LastModified"),
|
||||
"role": configuration.get("Role"),
|
||||
"environment": configuration.get("Environment", {}).get("Variables", {}),
|
||||
"eventSourceMappings": [
|
||||
{
|
||||
"uuid": mapping.get("UUID"),
|
||||
"state": mapping.get("State"),
|
||||
"batchSize": mapping.get("BatchSize"),
|
||||
"eventSourceArn": mapping.get("EventSourceArn"),
|
||||
}
|
||||
for mapping in mappings
|
||||
],
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/iam/details")
|
||||
def iam_details():
|
||||
iam = client("iam")
|
||||
role = iam.get_role(RoleName=ROLE_NAME)["Role"]
|
||||
policies = iam.list_attached_role_policies(RoleName=ROLE_NAME).get("AttachedPolicies", [])
|
||||
|
||||
return jsonify(ok("iam-details", {"role": role, "attachedPolicies": policies}))
|
||||
|
||||
|
||||
@app.get("/api/secrets/details")
|
||||
def secrets_details():
|
||||
secrets = client("secretsmanager")
|
||||
description = secrets.describe_secret(SecretId=SECRET_NAME)
|
||||
value = secrets.get_secret_value(SecretId=SECRET_NAME)
|
||||
|
||||
return jsonify(
|
||||
ok(
|
||||
"secrets-details",
|
||||
{
|
||||
"name": description.get("Name"),
|
||||
"arn": description.get("ARN"),
|
||||
"versionIds": list(description.get("VersionIdsToStages", {}).keys()),
|
||||
"secretString": value.get("SecretString"),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/logs/details")
|
||||
def log_details():
|
||||
logs_client = client("logs")
|
||||
groups = logs_client.describe_log_groups(logGroupNamePrefix=LOG_GROUP_NAME).get("logGroups", [])
|
||||
streams = logs_client.describe_log_streams(
|
||||
logGroupName=LOG_GROUP_NAME,
|
||||
orderBy="LastEventTime",
|
||||
descending=True,
|
||||
limit=5,
|
||||
).get("logStreams", []) if groups else []
|
||||
|
||||
return jsonify(
|
||||
ok(
|
||||
"cloudwatch-logs-details",
|
||||
{
|
||||
"logGroup": LOG_GROUP_NAME,
|
||||
"groups": groups,
|
||||
"streams": streams,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/lambda/invoke")
|
||||
def invoke_lambda():
|
||||
response = client("lambda").invoke(
|
||||
|
||||
@@ -129,6 +129,10 @@
|
||||
.result {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.inspect {
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -146,16 +150,30 @@
|
||||
<button id="lambda" class="secondary" type="button">Check Lambda</button>
|
||||
</section>
|
||||
|
||||
<section class="actions" aria-label="Inspect resources">
|
||||
<button data-inspect="/api/s3/objects" class="secondary" type="button">Inspect S3</button>
|
||||
<button data-inspect="/api/sqs/details" class="secondary" type="button">Inspect SQS</button>
|
||||
<button data-inspect="/api/lambda/details" class="secondary" type="button">Inspect Lambda</button>
|
||||
<button data-inspect="/api/iam/details" class="secondary" type="button">Inspect IAM</button>
|
||||
<button data-inspect="/api/secrets/details" class="secondary" type="button">Inspect Secret</button>
|
||||
<button data-inspect="/api/logs/details" class="secondary" type="button">Inspect Logs</button>
|
||||
</section>
|
||||
|
||||
<section id="cards" class="grid" aria-live="polite"></section>
|
||||
<section class="card result">
|
||||
<h2>Last Action</h2>
|
||||
<pre id="result">Ready.</pre>
|
||||
</section>
|
||||
<section class="card inspect">
|
||||
<h2>Resource Inspector</h2>
|
||||
<pre id="inspect">Select a resource to inspect.</pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const cards = document.querySelector("#cards");
|
||||
const result = document.querySelector("#result");
|
||||
const inspect = document.querySelector("#inspect");
|
||||
|
||||
function renderJson(value) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
@@ -165,6 +183,10 @@
|
||||
result.textContent = typeof value === "string" ? value : renderJson(value);
|
||||
}
|
||||
|
||||
function setInspect(value) {
|
||||
inspect.textContent = typeof value === "string" ? value : renderJson(value);
|
||||
}
|
||||
|
||||
function renderChecks(data) {
|
||||
cards.innerHTML = "";
|
||||
|
||||
@@ -214,12 +236,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function inspectResource(path) {
|
||||
try {
|
||||
setInspect("Loading resource details...");
|
||||
const data = await request(path);
|
||||
setInspect(data);
|
||||
} catch (error) {
|
||||
setInspect({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector("#refresh").addEventListener("click", () => {
|
||||
refresh().catch((error) => setResult({ error: error.message }));
|
||||
});
|
||||
document.querySelector("#s3").addEventListener("click", () => runAction("/api/s3/marker"));
|
||||
document.querySelector("#sqs").addEventListener("click", () => runAction("/api/sqs/message"));
|
||||
document.querySelector("#lambda").addEventListener("click", () => runAction("/api/lambda/invoke"));
|
||||
document.querySelectorAll("[data-inspect]").forEach((button) => {
|
||||
button.addEventListener("click", () => inspectResource(button.dataset.inspect));
|
||||
});
|
||||
|
||||
refresh().catch((error) => setResult({ error: error.message }));
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user