From a4ee97471b526b59471956609223c76aa673622d Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Sun, 29 Jun 2025 15:20:14 -0300 Subject: [PATCH] feat: add SES integration with email management features --- docker-compose.yml | 2 +- package-lock.json | 373 +++++++++++++++++++++++++++++++++++++ package.json | 9 +- src/App.vue | 1 + src/router/routes.js | 6 + src/stores/app.js | 4 + src/utils/api.js | 25 +++ src/utils/formatDate.js | 4 + src/views/Dashboard.vue | 58 +++++- src/views/DynamoDBView.vue | 5 +- src/views/KMSView.vue | 6 +- src/views/KinesisView.vue | 5 +- src/views/LambdaView.vue | 4 - src/views/S3View.vue | 5 +- src/views/SESView.vue | 364 ++++++++++++++++++++++++++++++++++++ src/views/SQSView.vue | 7 +- 16 files changed, 841 insertions(+), 37 deletions(-) create mode 100644 src/utils/api.js create mode 100644 src/utils/formatDate.js create mode 100644 src/views/SESView.vue diff --git a/docker-compose.yml b/docker-compose.yml index 06f4211..c79b452 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - "127.0.0.1:4566:4566" # LocalStack Edge Proxy environment: - DEBUG=${DEBUG:-0} - - SERVICES=sns,sqs,s3,lambda,dynamodb,kinesis,kms + - SERVICES=sns,sqs,s3,ses,lambda,dynamodb,kinesis,kms - DISABLE_CORS_CHECKS=1 - DISABLE_CORS_HANDLERS=1 - SKIP_CORS_PREFLIGHT=1 diff --git a/package-lock.json b/package-lock.json index 5a165a5..c0d5cc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/client-kms": "^3.400.0", "@aws-sdk/client-lambda": "^3.400.0", "@aws-sdk/client-s3": "^3.400.0", + "@aws-sdk/client-ses": "^3.839.0", "@aws-sdk/client-sns": "^3.400.0", "@aws-sdk/client-sqs": "^3.400.0", "@mdi/font": "^7.4.0", @@ -546,6 +547,378 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@aws-sdk/client-ses": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.839.0.tgz", + "integrity": "sha512-y6JqloPDS1yin71nH5r9KnBBBaJRXtYC1h6WzLBm99nfHjP9qK00JBjO3gRAds+eii9U4kTN2bfT0+1jWMNEuw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-node": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/client-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.839.0.tgz", + "integrity": "sha512-AZABysUhbfcwXVlMo97/vwHgsfJNF81wypCAowpqAJkSjP2KrqsqHpb71/RoR2w8JGmEnBBXRD4wIxDhnmifWg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/core": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.839.0.tgz", + "integrity": "sha512-KdwL5RaK7eUIlOpdOoZ5u+2t4X1rdX/MTZgz3IV/aBzjVUoGsp+uUnbyqXomLQSUitPHp72EE/NHDsvWW/IHvQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.6.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.839.0.tgz", + "integrity": "sha512-cWTadewPPz1OvObZJB+olrgh8VwcgIVcT293ZUT9V0CMF0UU7QaPwJP7uNXcNxltTh+sk1yhjH4UlcnJigZZbA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.839.0.tgz", + "integrity": "sha512-fv0BZwrDhWDju4D1MCLT4I2aPjr0dVQ6P+MpqvcGNOA41Oa9UdRhYTV5iuy5NLXzIzoCmnS+XfSq5Kbsf6//xw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.839.0.tgz", + "integrity": "sha512-GHm0hF4CiDxIDR7TauMaA6iI55uuSqRxMBcqTAHaTPm6+h1A+MS+ysQMxZ+Jvwtoy8WmfTIGrJVxSCw0sK2hvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.839.0.tgz", + "integrity": "sha512-7bR+U2h+ft0V8chyeu9Bh/pvau4ZkQMeRt5f0dAULoepZQ77QQVRP4H04yJPTg9DCtqbVULQ3uf5YOp1/08vQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.839.0", + "@aws-sdk/credential-provider-http": "3.839.0", + "@aws-sdk/credential-provider-ini": "3.839.0", + "@aws-sdk/credential-provider-process": "3.839.0", + "@aws-sdk/credential-provider-sso": "3.839.0", + "@aws-sdk/credential-provider-web-identity": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.839.0.tgz", + "integrity": "sha512-qShpekjociUZ+isyQNa0P7jo+0q3N2+0eJDg8SGyP6K6hHTcGfiqxTDps+IKl6NreCPhZCBzyI9mWkP0xSDR6g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.839.0.tgz", + "integrity": "sha512-w10zBLHhU8SBQcdrSPMI02haLoRGZg+gP7mH/Er8VhIXfHefbr7o4NirmB0hwdw/YAH8MLlC9jj7c2SJlsNhYA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.839.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/token-providers": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.839.0.tgz", + "integrity": "sha512-EvqTc7J1kgmiuxknpCp1S60hyMQvmKxsI5uXzQtcogl/N55rxiXEqnCLI5q6p33q91PJegrcMCM5Q17Afhm5qA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.839.0.tgz", + "integrity": "sha512-2u74uRM1JWq6Sf7+3YpjejPM9YkomGt4kWhrmooIBEq1k5r2GTbkH7pNCxBQwBueXM21jAGVDxxeClpTx+5hig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.6.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/nested-clients": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.839.0.tgz", + "integrity": "sha512-Glic0pg2THYP3aRhJORwJJBe1JLtJoEdWV/MFZNyzCklfMwEzpWtZAyxy+tQyFmMeW50uBAnh2R0jhMMcf257w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.839.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.839.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.6.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-retry": "^4.1.14", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.21", + "@smithy/util-defaults-mode-node": "^4.0.21", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/token-providers": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.839.0.tgz", + "integrity": "sha512-2nlafqdSbet/2WtYIoZ7KEGFowFonPBDYlTjrUvwU2yooE10VhvzhLSCTB2aKIVzo2Z2wL5WGFQsqAY5QwK6Bw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.839.0", + "@aws-sdk/nested-clients": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.839.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.839.0.tgz", + "integrity": "sha512-MuunkIG1bJVMtTH7MbjXOrhHleU5wjHz5eCAUc6vj7M9rwol71nqjj9b8RLnkO5gsJcKc29Qk8iV6xQuzKWNMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.839.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@aws-sdk/client-sns": { "version": "3.835.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.835.0.tgz", diff --git a/package.json b/package.json index 2304d7b..70254be 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,13 @@ }, "dependencies": { "@aws-sdk/client-dynamodb": "^3.400.0", + "@aws-sdk/client-kinesis": "^3.400.0", + "@aws-sdk/client-kms": "^3.400.0", + "@aws-sdk/client-lambda": "^3.400.0", "@aws-sdk/client-s3": "^3.400.0", - "@aws-sdk/client-sqs": "^3.400.0", + "@aws-sdk/client-ses": "^3.839.0", "@aws-sdk/client-sns": "^3.400.0", - "@aws-sdk/client-lambda": "^3.400.0", - "@aws-sdk/client-kms": "^3.400.0", - "@aws-sdk/client-kinesis": "^3.400.0", + "@aws-sdk/client-sqs": "^3.400.0", "@mdi/font": "^7.4.0", "axios": "^1.6.0", "jszip": "^3.10.1", diff --git a/src/App.vue b/src/App.vue index 11e7b08..c4b969e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -170,6 +170,7 @@ const menuItems = [ { title: 'KMS Keys', icon: 'mdi-key', to: '/kms' }, { title: 'Lambda Functions', icon: 'mdi-function', to: '/lambda' }, { title: 'S3 Buckets', icon: 'mdi-folder-multiple', to: '/s3' }, + { title: 'SES Emails', icon: 'mdi-email-multiple', to: '/ses' }, { title: 'SNS Topics', icon: 'mdi-forum', to: '/sns' }, { title: 'SQS Queues', icon: 'mdi-format-list-bulleted', to: '/sqs' }, ] diff --git a/src/router/routes.js b/src/router/routes.js index de25dda..dbcdecb 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -7,6 +7,7 @@ import KinesisView from '@/views/KinesisView.vue' import SNSView from '@/views/SNSView.vue' import KMSView from '@/views/KMSView.vue' import AboutView from '@/views/AboutView.vue' +import SESView from '@/views/SESView.vue' export default [ { @@ -19,6 +20,11 @@ export default [ name: 'S3', component: S3View }, + { + path: '/ses', + name: 'SES', + component: SESView + }, { path: '/sqs', name: 'SQS', diff --git a/src/stores/app.js b/src/stores/app.js index e8defe7..a6b6249 100644 --- a/src/stores/app.js +++ b/src/stores/app.js @@ -1,6 +1,7 @@ import { defineStore } from 'pinia' import { ref } from 'vue' import { S3Client } from '@aws-sdk/client-s3' +import { SESClient} from '@aws-sdk/client-ses' import { SNSClient } from '@aws-sdk/client-sns' import { SQSClient } from '@aws-sdk/client-sqs' import { DynamoDBClient } from '@aws-sdk/client-dynamodb' @@ -35,6 +36,7 @@ export const useAppStore = defineStore('app', () => { // Initialize AWS services const s3 = ref(null) + const ses = ref(null) const sns = ref(null) const sqs = ref(null) const dynamodb = ref(null) @@ -53,6 +55,7 @@ export const useAppStore = defineStore('app', () => { } s3.value = new S3Client(config) + ses.value = new SESClient(config) sns.value = new SNSClient(config) sqs.value = new SQSClient(config) dynamodb.value = new DynamoDBClient(config) @@ -106,6 +109,7 @@ export const useAppStore = defineStore('app', () => { connectionStatus, snackbar, s3, + ses, sns, sqs, dynamodb, diff --git a/src/utils/api.js b/src/utils/api.js new file mode 100644 index 0000000..27bcd49 --- /dev/null +++ b/src/utils/api.js @@ -0,0 +1,25 @@ +import axios from 'axios' + +const api = axios.create({ + baseURL: import.meta.env.VITE_LOCALSTACK_ENDPOINT || 'http://localhost:4566', + timeout: 10000, // 10 seconds +}) + +export const getSesData = async (params = {}) => { + try { + const response = await api.get('/_aws/ses', { params }) + return response.data + } catch (error) { + console.error('Error fetching SES data:', error); + throw error + } +} + +export const deleteSesMessage = async (params = {}) => { + try { + await api.delete('/_aws/ses', { params }) + } catch (error) { + console.error('Error deleting SES message:', error); + throw error + } +} diff --git a/src/utils/formatDate.js b/src/utils/formatDate.js new file mode 100644 index 0000000..5d72e20 --- /dev/null +++ b/src/utils/formatDate.js @@ -0,0 +1,4 @@ +export const formatDate = (date) => { + if (!date) return 'N/A' + return new Date(date).toLocaleString('pt-BR') +} diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 1252360..efb2556 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -15,7 +15,7 @@ :color="service.status === 'active' ? 'primary' : 'grey-lighten-2'" :variant="service.status === 'active' ? 'elevated' : 'outlined'" class="pa-4" - height="120" + height="150" @click="service.status === 'active' ? navigateToService(service.route): null" style="cursor: pointer" > @@ -127,10 +127,12 @@ import { ListFunctionsCommand } from '@aws-sdk/client-lambda' import { ListBucketsCommand, ListObjectsV2Command, DeleteObjectsCommand, DeleteBucketCommand } from '@aws-sdk/client-s3' import { ListTopicsCommand } from '@aws-sdk/client-sns' import { ListQueuesCommand, DeleteQueueCommand } from '@aws-sdk/client-sqs' +import { ListIdentitiesCommand } from '@aws-sdk/client-ses' +import { deleteSesMessage, getSesData } from '@/utils/api.js' const router = useRouter() const appStore = useAppStore() -const { s3, sns, sqs, dynamodb, lambda, kinesis, kms } = storeToRefs(appStore) +const { s3, ses, sns, sqs, dynamodb, lambda, kinesis, kms } = storeToRefs(appStore) const services = ref([ { @@ -168,6 +170,13 @@ const services = ref([ status: 'inactive', stats: null }, + { + name: 'SES', + icon: 'mdi-email', + route: '/ses', + status: 'inactive', + stats: null + }, { name: 'SNS', icon: 'mdi-forum', @@ -206,6 +215,13 @@ const quickActions = ref([ action: 'clearDynamoDB', loading: false }, + { + title: 'Limpar SES', + icon: 'mdi-email-remove', + color: 'error', + action: 'clearSES', + loading: false + }, { title: 'Atualizar Status', icon: 'mdi-refresh', @@ -290,28 +306,42 @@ const loadServiceStats = async () => { services.value[4].status = 'inactive' } - // SNS Stats + // SES Stats try { - const snsTopics = await sns.value.send(new ListTopicsCommand({})) + const sesIdentities = await ses.value.send(new ListIdentitiesCommand({ IdentityType: 'EmailAddress' })) + const sesData = await getSesData(); services.value[5].status = 'active' services.value[5].stats = { + 'E-mails': sesData.messages ? sesData.messages.length : 0, + 'Identidades': sesIdentities.Identities ? sesIdentities.Identities.length : 0 + } + } catch (error) { + console.error('SES error:', error) + services.value[5].status = 'inactive' + } + + // SNS Stats + try { + const snsTopics = await sns.value.send(new ListTopicsCommand({})) + services.value[6].status = 'active' + services.value[6].stats = { 'Tópicos': snsTopics.Topics ? snsTopics.Topics.length : 0 } } catch (error) { console.error('SNS error:', error) - services.value[5].status = 'inactive' + services.value[6].status = 'inactive' } // SQS Stats try { const sqsQueues = await sqs.value.send(new ListQueuesCommand({})) - services.value[6].status = 'active' - services.value[6].stats = { + services.value[7].status = 'active' + services.value[7].stats = { 'Filas': sqsQueues.QueueUrls ? sqsQueues.QueueUrls.length : 0 } } catch (error) { console.error('SQS error:', error) - services.value[6].status = 'inactive' + services.value[7].status = 'inactive' } } catch (error) { @@ -344,6 +374,11 @@ const executeQuickAction = (action) => { text = 'Esta ação irá deletar todas as filas SQS. Deseja continuar?' color = 'warning' break + case 'clearSES': + title = 'Limpar todos os emails SES' + text = 'Esta ação irá deletar todos os emails do SES. Deseja continuar?' + color = 'error' + break case 'clearDynamoDB': title = 'Limpar todas as tabelas DynamoDB' text = 'Esta ação irá deletar todas as tabelas DynamoDB. Deseja continuar?' @@ -373,6 +408,9 @@ const performClearAction = async (action) => { case 'clearSQS': await clearAllSQSQueues() break + case 'clearSES': + await clearAllSESData() + break case 'clearDynamoDB': await clearAllDynamoDBTables() break @@ -422,6 +460,10 @@ const clearAllSQSQueues = async () => { } } +const clearAllSESData = async () => { + await deleteSesMessage() +} + const clearAllDynamoDBTables = async () => { const tables = await dynamodb.value.send(new ListTablesCommand({})) diff --git a/src/views/DynamoDBView.vue b/src/views/DynamoDBView.vue index 48000ab..b87503d 100644 --- a/src/views/DynamoDBView.vue +++ b/src/views/DynamoDBView.vue @@ -304,6 +304,7 @@ import { DeleteItemCommand, PutItemCommand } from '@aws-sdk/client-dynamodb' +import { formatDate } from '../utils/formatDate.js' const appStore = useAppStore() const { dynamodb } = storeToRefs(appStore) @@ -622,10 +623,6 @@ const getStatusColor = (status) => { } } -const formatDate = (date) => { - return new Date(date).toLocaleString('pt-BR') -} - const formatBytes = (bytes) => { if (bytes === 0) return '0 Bytes' const k = 1024 diff --git a/src/views/KMSView.vue b/src/views/KMSView.vue index 2225db1..e47b3fa 100644 --- a/src/views/KMSView.vue +++ b/src/views/KMSView.vue @@ -308,6 +308,7 @@ import { DecryptCommand, ScheduleKeyDeletionCommand } from '@aws-sdk/client-kms' +import { formatDate } from '../utils/formatDate.js' const appStore = useAppStore() const { kms } = storeToRefs(appStore) @@ -381,11 +382,6 @@ const getKeyStateColor = (state) => { } } -const formatDate = (dateString) => { - if (!dateString) return 'N/A' - return new Date(dateString).toLocaleString('pt-BR') -} - // KMS API functions const loadKeys = async () => { try { diff --git a/src/views/KinesisView.vue b/src/views/KinesisView.vue index ce68115..18e9e90 100644 --- a/src/views/KinesisView.vue +++ b/src/views/KinesisView.vue @@ -301,6 +301,7 @@ import { GetShardIteratorCommand, GetRecordsCommand } from '@aws-sdk/client-kinesis' +import { formatDate } from '../utils/formatDate.js' const appStore = useAppStore() const { kinesis } = storeToRefs(appStore) @@ -511,10 +512,6 @@ const getStatusColor = (status) => { } } -const formatDate = (date) => { - return new Date(date).toLocaleString('pt-BR') -} - const decodeData = (data) => { try { // Try to decode as UTF-8 string diff --git a/src/views/LambdaView.vue b/src/views/LambdaView.vue index 5645417..f281934 100644 --- a/src/views/LambdaView.vue +++ b/src/views/LambdaView.vue @@ -659,10 +659,6 @@ const getStateColor = (state) => { } } -const formatDate = (date) => { - return new Date(date).toLocaleString('pt-BR') -} - const decodeLogs = (logResult) => { try { return atob(logResult) diff --git a/src/views/S3View.vue b/src/views/S3View.vue index ceb89b2..a1d3136 100644 --- a/src/views/S3View.vue +++ b/src/views/S3View.vue @@ -211,6 +211,7 @@ import { DeleteObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3' +import { formatDate } from '../utils/formatDate.js' const appStore = useAppStore() const { s3 } = storeToRefs(appStore) @@ -370,10 +371,6 @@ const uploadFile = async () => { } } -const formatDate = (date) => { - return new Date(date).toLocaleString('pt-BR') -} - const formatBytes = (bytes) => { if (bytes === 0) return '0 Bytes' const k = 1024 diff --git a/src/views/SESView.vue b/src/views/SESView.vue new file mode 100644 index 0000000..0638c30 --- /dev/null +++ b/src/views/SESView.vue @@ -0,0 +1,364 @@ + + + diff --git a/src/views/SQSView.vue b/src/views/SQSView.vue index 644e930..9627d95 100644 --- a/src/views/SQSView.vue +++ b/src/views/SQSView.vue @@ -185,7 +185,7 @@
Mensagem {{ index + 1 }} - {{ formatDate(message.Attributes?.SentTimestamp) }} + {{ parseAndFormatDate(message.Attributes?.SentTimestamp) }}
@@ -278,6 +278,7 @@ import { SendMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs' +import { formatDate } from '@/utils/formatDate.js' const appStore = useAppStore() const { sqs } = storeToRefs(appStore) @@ -484,9 +485,9 @@ const deleteMessage = async (message) => { } } -const formatDate = (timestamp) => { +const parseAndFormatDate = (timestamp) => { if (!timestamp) return 'N/A' - return new Date(parseInt(timestamp)).toLocaleString('pt-BR') + return formatDate(parseInt(timestamp)) } onMounted(() => {