stackport 0.4.2__tar.gz → 0.4.3__tar.gz
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.
- {stackport-0.4.2/stackport.egg-info → stackport-0.4.3}/PKG-INFO +31 -8
- {stackport-0.4.2 → stackport-0.4.3}/README.md +29 -6
- {stackport-0.4.2 → stackport-0.4.3}/backend/cli.py +37 -35
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/resources.py +1 -0
- {stackport-0.4.2 → stackport-0.4.3}/pyproject.toml +2 -2
- {stackport-0.4.2 → stackport-0.4.3/stackport.egg-info}/PKG-INFO +31 -8
- {stackport-0.4.2 → stackport-0.4.3}/stackport.egg-info/SOURCES.txt +26 -26
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_cli.py +65 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_registries.py +17 -1
- stackport-0.4.2/ui/dist/assets/CloudscapeAbout-DccVyQFu.js → stackport-0.4.3/ui/dist/assets/CloudscapeAbout-BRbsDskR.js +1 -1
- stackport-0.4.2/ui/dist/assets/CloudscapeDashboard-DtV4-eQq.js → stackport-0.4.3/ui/dist/assets/CloudscapeDashboard-j2f4Elz2.js +1 -1
- stackport-0.4.2/ui/dist/assets/CloudscapeLearn-DW6RZAj4.js → stackport-0.4.3/ui/dist/assets/CloudscapeLearn-CVW3k3X0.js +1 -1
- stackport-0.4.2/ui/dist/assets/CloudscapeResourceBrowser-FpsXtjf_.js → stackport-0.4.3/ui/dist/assets/CloudscapeResourceBrowser-CgWdgn20.js +10 -10
- stackport-0.4.2/ui/dist/assets/CloudscapeSettings-UdgiI6dm.js → stackport-0.4.3/ui/dist/assets/CloudscapeSettings-CIXFvPKv.js +1 -1
- stackport-0.4.2/ui/dist/assets/CloudscapeShell-Be6uFzsz.js → stackport-0.4.3/ui/dist/assets/CloudscapeShell-DNfhSg0O.js +1 -1
- stackport-0.4.2/ui/dist/assets/StateMachineGraph-G76o42iO.js → stackport-0.4.3/ui/dist/assets/StateMachineGraph-DvF_kIOO.js +1 -1
- stackport-0.4.2/ui/dist/assets/index-B3zGVRXf.js → stackport-0.4.3/ui/dist/assets/index-BC7E-Fm4.js +1 -1
- stackport-0.4.2/ui/dist/assets/index-hhBdqMqs.js → stackport-0.4.3/ui/dist/assets/index-BLdRlH9d.js +2 -2
- stackport-0.4.2/ui/dist/assets/index-B3bka_BF.js → stackport-0.4.3/ui/dist/assets/index-Ba-oOcd3.js +1 -1
- stackport-0.4.2/ui/dist/assets/index-DhD1oh4N.js → stackport-0.4.3/ui/dist/assets/index-BkSgVQ0X.js +1 -1
- stackport-0.4.2/ui/dist/assets/index-CNWD72L_.js → stackport-0.4.3/ui/dist/assets/index-C5z3cir-.js +1 -1
- stackport-0.4.2/ui/dist/assets/index-B33aGwWE.js → stackport-0.4.3/ui/dist/assets/index-Dw6TGweb.js +1 -1
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/index.html +1 -1
- {stackport-0.4.2 → stackport-0.4.3}/LICENSE +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/MANIFEST.in +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/__init__.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/aws_client.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/cache.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/config.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/endpoint_store.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/learn/__init__.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/learn/content.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/learn/hotspots.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/learn/trails/aws-first-steps.json +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/learn/verify.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/learn_store.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/main.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/__init__.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/common.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/dynamodb.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/ec2.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/endpoints.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/iam.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/lambda_svc.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/learn.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/logs.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/monitoring.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/rds.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/s3.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/secretsmanager.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/sns.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/sqs.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/stats.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/stepfunctions.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/routes/tags.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/__init__.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/dynamodb.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/endpoints.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/lambda_svc.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/learn.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/logs.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/monitoring.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/s3.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/secretsmanager.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/sns.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/sqs.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/stepfunctions.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/schemas/tags.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/backend/websocket.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/setup.cfg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/stackport.egg-info/dependency_links.txt +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/stackport.egg-info/entry_points.txt +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/stackport.egg-info/requires.txt +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/stackport.egg-info/top_level.txt +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_cache.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_client.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_config.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_create_resources.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_dynamodb_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_ec2_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_endpoint_store.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_endpoints.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_iam_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_lambda_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_learn_content.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_learn_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_learn_store.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_logs_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_logs_tail.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_monitoring_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_multi_endpoint.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_rds_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_readonly_middleware.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_s3_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_s3_upload_limit_env.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_secretsmanager_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_sns_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_sqs_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_tags_routes.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/tests/test_websocket.py +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/CloudscapeResourceBrowser-CzwePG-V.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/CloudscapeShell-BXHVWjDE.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/index-DE--A2EE.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/index-DKIdXFX0.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/index-DNBdeKId.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/index-DoY0HscW.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/index-DrkkXAoG.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/index-Vf0UFiCT.css +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/stepfunctions-graph-B6nvU4OB.js +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/assets/utils-Cfmwk2UT.js +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/acm.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/apigateway.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/appsync.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/athena.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/cloudformation.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/cloudfront.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/cognito-idp.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/dynamodb.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/ec2.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/ecr.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/ecs.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/elasticache.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/elasticfilesystem.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/elasticloadbalancing.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/elasticmapreduce.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/events.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/firehose.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/glue.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/iam.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/kinesis.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/kms.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/lambda.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/logs.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/monitoring.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/rds.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/route53.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/s3.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/secretsmanager.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/ses.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/sns.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/sqs.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/ssm.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/stepfunctions.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/aws-icons/wafv2.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/favicon.png +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/favicon.svg +0 -0
- {stackport-0.4.2 → stackport-0.4.3}/ui/dist/stackport-logo.svg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackport
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Universal AWS resource browser for local emulators
|
|
5
5
|
Author: Davi Reis Vieira
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/DaviReisVieira/stackport
|
|
8
8
|
Project-URL: Repository, https://github.com/DaviReisVieira/stackport
|
|
9
9
|
Project-URL: Issues, https://github.com/DaviReisVieira/stackport/issues
|
|
10
|
-
Keywords: aws,ministack,localstack,browser,devtools,emulator
|
|
10
|
+
Keywords: aws,ministack,floci,localstack,browser,devtools,emulator
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Framework :: FastAPI
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -75,15 +75,18 @@ Dynamic: license-file
|
|
|
75
75
|
- **CLI** — `stackport status`, `list`, `describe`, `export` with JSON/CSV/table output
|
|
76
76
|
- **Real-time dashboard** with WebSocket-powered live updates
|
|
77
77
|
- **Keyboard shortcuts** — 16 shortcuts for fast navigation (press `?` to view)
|
|
78
|
-
- Single Docker image, works with MiniStack, LocalStack, Moto, or any AWS-compatible endpoint
|
|
78
|
+
- Single Docker image, works with [MiniStack](https://github.com/ministackorg/ministack), [Floci](https://github.com/floci-io/floci), LocalStack, Moto, or any AWS-compatible endpoint
|
|
79
79
|
|
|
80
80
|
## Quick Start
|
|
81
81
|
|
|
82
|
-
### With a local emulator
|
|
82
|
+
### With a local emulator
|
|
83
|
+
|
|
84
|
+
Start whichever emulator you use, then point StackPort at it:
|
|
83
85
|
|
|
84
86
|
```bash
|
|
85
|
-
# Start
|
|
86
|
-
|
|
87
|
+
# Start an emulator on :4566 (pick one)
|
|
88
|
+
docker run -d -p 4566:4566 ministackorg/ministack # MiniStack
|
|
89
|
+
docker run -d -p 4566:4566 floci/floci # Floci
|
|
87
90
|
|
|
88
91
|
# Start StackPort
|
|
89
92
|
pip install stackport
|
|
@@ -108,16 +111,32 @@ You can also configure per-endpoint authentication from the Settings UI — sele
|
|
|
108
111
|
|
|
109
112
|
When connected to real AWS, StackPort shows a warning banner and operates in read-only mode unless writes are explicitly enabled.
|
|
110
113
|
|
|
111
|
-
### Docker Compose
|
|
114
|
+
### Docker Compose
|
|
115
|
+
|
|
116
|
+
Each example runs the emulator, StackPort, and a one-shot `seed` container that creates a few resources (S3, SQS, SNS, DynamoDB, Secrets Manager, IAM, Logs, Step Functions) so the dashboard has something to show on first load. Both use the same seed. Swap in LocalStack, Moto, or any AWS-compatible endpoint by replacing the emulator service and updating `AWS_ENDPOINT_URL`.
|
|
112
117
|
|
|
113
|
-
|
|
118
|
+
**MiniStack + StackPort** — [`examples/docker-compose.yml`](examples/docker-compose.yml)
|
|
114
119
|
|
|
115
120
|
```bash
|
|
116
121
|
curl -O https://raw.githubusercontent.com/DaviReisVieira/stackport/main/examples/docker-compose.yml
|
|
117
122
|
docker compose up -d
|
|
118
123
|
# Open http://localhost:8080
|
|
124
|
+
# If the dashboard is empty: docker compose logs seed
|
|
119
125
|
```
|
|
120
126
|
|
|
127
|
+
[MiniStack](https://github.com/ministackorg/ministack) derives the URLs it returns (SQS queue URLs, pre-signed URLs) from the request `Host`, so they resolve from other containers and from the host without extra configuration.
|
|
128
|
+
|
|
129
|
+
**Floci + StackPort** — [`examples/docker-compose.floci.yml`](examples/docker-compose.floci.yml)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl -O https://raw.githubusercontent.com/DaviReisVieira/stackport/main/examples/docker-compose.floci.yml
|
|
133
|
+
docker compose -f docker-compose.floci.yml up -d
|
|
134
|
+
# Open http://localhost:8080
|
|
135
|
+
# If the dashboard is empty: docker compose -f docker-compose.floci.yml logs seed
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
[Floci](https://github.com/floci-io/floci) takes the hostname for the URLs it returns from `FLOCI_HOSTNAME`, which the example sets to `floci` so they resolve from other containers. From the host, reach the same resources through `http://localhost:4566`.
|
|
139
|
+
|
|
121
140
|
### Docker (standalone)
|
|
122
141
|
|
|
123
142
|
```bash
|
|
@@ -351,6 +370,10 @@ ui/src/
|
|
|
351
370
|
lib/ API client, types, service icons, utils
|
|
352
371
|
```
|
|
353
372
|
|
|
373
|
+
## Origin
|
|
374
|
+
|
|
375
|
+
StackPort started as a web UI pull request to [MiniStack](https://github.com/ministackorg/ministack) ([ministack#116](https://github.com/ministackorg/ministack/pull/116)). The MiniStack maintainers suggested it would be more useful as a standalone tool that talks to any emulator over the standard AWS API, and that is what it became. Thanks to [@Nahuel990](https://github.com/Nahuel990) and [@McDoit](https://github.com/McDoit) for pushing it in that direction.
|
|
376
|
+
|
|
354
377
|
## Contributors
|
|
355
378
|
|
|
356
379
|
<a href="https://github.com/DaviReisVieira/stackport/graphs/contributors">
|
|
@@ -39,15 +39,18 @@
|
|
|
39
39
|
- **CLI** — `stackport status`, `list`, `describe`, `export` with JSON/CSV/table output
|
|
40
40
|
- **Real-time dashboard** with WebSocket-powered live updates
|
|
41
41
|
- **Keyboard shortcuts** — 16 shortcuts for fast navigation (press `?` to view)
|
|
42
|
-
- Single Docker image, works with MiniStack, LocalStack, Moto, or any AWS-compatible endpoint
|
|
42
|
+
- Single Docker image, works with [MiniStack](https://github.com/ministackorg/ministack), [Floci](https://github.com/floci-io/floci), LocalStack, Moto, or any AWS-compatible endpoint
|
|
43
43
|
|
|
44
44
|
## Quick Start
|
|
45
45
|
|
|
46
|
-
### With a local emulator
|
|
46
|
+
### With a local emulator
|
|
47
|
+
|
|
48
|
+
Start whichever emulator you use, then point StackPort at it:
|
|
47
49
|
|
|
48
50
|
```bash
|
|
49
|
-
# Start
|
|
50
|
-
|
|
51
|
+
# Start an emulator on :4566 (pick one)
|
|
52
|
+
docker run -d -p 4566:4566 ministackorg/ministack # MiniStack
|
|
53
|
+
docker run -d -p 4566:4566 floci/floci # Floci
|
|
51
54
|
|
|
52
55
|
# Start StackPort
|
|
53
56
|
pip install stackport
|
|
@@ -72,16 +75,32 @@ You can also configure per-endpoint authentication from the Settings UI — sele
|
|
|
72
75
|
|
|
73
76
|
When connected to real AWS, StackPort shows a warning banner and operates in read-only mode unless writes are explicitly enabled.
|
|
74
77
|
|
|
75
|
-
### Docker Compose
|
|
78
|
+
### Docker Compose
|
|
79
|
+
|
|
80
|
+
Each example runs the emulator, StackPort, and a one-shot `seed` container that creates a few resources (S3, SQS, SNS, DynamoDB, Secrets Manager, IAM, Logs, Step Functions) so the dashboard has something to show on first load. Both use the same seed. Swap in LocalStack, Moto, or any AWS-compatible endpoint by replacing the emulator service and updating `AWS_ENDPOINT_URL`.
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
**MiniStack + StackPort** — [`examples/docker-compose.yml`](examples/docker-compose.yml)
|
|
78
83
|
|
|
79
84
|
```bash
|
|
80
85
|
curl -O https://raw.githubusercontent.com/DaviReisVieira/stackport/main/examples/docker-compose.yml
|
|
81
86
|
docker compose up -d
|
|
82
87
|
# Open http://localhost:8080
|
|
88
|
+
# If the dashboard is empty: docker compose logs seed
|
|
83
89
|
```
|
|
84
90
|
|
|
91
|
+
[MiniStack](https://github.com/ministackorg/ministack) derives the URLs it returns (SQS queue URLs, pre-signed URLs) from the request `Host`, so they resolve from other containers and from the host without extra configuration.
|
|
92
|
+
|
|
93
|
+
**Floci + StackPort** — [`examples/docker-compose.floci.yml`](examples/docker-compose.floci.yml)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
curl -O https://raw.githubusercontent.com/DaviReisVieira/stackport/main/examples/docker-compose.floci.yml
|
|
97
|
+
docker compose -f docker-compose.floci.yml up -d
|
|
98
|
+
# Open http://localhost:8080
|
|
99
|
+
# If the dashboard is empty: docker compose -f docker-compose.floci.yml logs seed
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
[Floci](https://github.com/floci-io/floci) takes the hostname for the URLs it returns from `FLOCI_HOSTNAME`, which the example sets to `floci` so they resolve from other containers. From the host, reach the same resources through `http://localhost:4566`.
|
|
103
|
+
|
|
85
104
|
### Docker (standalone)
|
|
86
105
|
|
|
87
106
|
```bash
|
|
@@ -315,6 +334,10 @@ ui/src/
|
|
|
315
334
|
lib/ API client, types, service icons, utils
|
|
316
335
|
```
|
|
317
336
|
|
|
337
|
+
## Origin
|
|
338
|
+
|
|
339
|
+
StackPort started as a web UI pull request to [MiniStack](https://github.com/ministackorg/ministack) ([ministack#116](https://github.com/ministackorg/ministack/pull/116)). The MiniStack maintainers suggested it would be more useful as a standalone tool that talks to any emulator over the standard AWS API, and that is what it became. Thanks to [@Nahuel990](https://github.com/Nahuel990) and [@McDoit](https://github.com/McDoit) for pushing it in that direction.
|
|
340
|
+
|
|
318
341
|
## Contributors
|
|
319
342
|
|
|
320
343
|
<a href="https://github.com/DaviReisVieira/stackport/graphs/contributors">
|
|
@@ -10,13 +10,37 @@ import click
|
|
|
10
10
|
import uvicorn
|
|
11
11
|
|
|
12
12
|
from backend.aws_client import get_client
|
|
13
|
-
from backend.config import
|
|
14
|
-
|
|
13
|
+
from backend.config import (
|
|
14
|
+
AWS_ENDPOINT_URL,
|
|
15
|
+
AWS_REGION,
|
|
16
|
+
LOG_LEVEL,
|
|
17
|
+
STACKPORT_PORT,
|
|
18
|
+
STACKPORT_SERVICES,
|
|
19
|
+
endpoint_store,
|
|
20
|
+
)
|
|
21
|
+
from backend.routes.resources import (
|
|
22
|
+
DESCRIBE_REGISTRY,
|
|
23
|
+
_PREFERRED_ID_FIELD,
|
|
24
|
+
_extract_id,
|
|
25
|
+
_serialize,
|
|
26
|
+
)
|
|
15
27
|
from backend.routes.stats import SERVICE_REGISTRY, _METHOD_KWARGS, _count_items, _probe_service
|
|
16
28
|
|
|
17
29
|
logger = logging.getLogger(__name__)
|
|
18
30
|
|
|
19
31
|
|
|
32
|
+
def _client_kwargs(endpoint: str | None, region: str | None) -> dict:
|
|
33
|
+
"""Resolve where a CLI command talks to.
|
|
34
|
+
|
|
35
|
+
An explicit --endpoint (or AWS_ENDPOINT_URL at startup) wins. Without one,
|
|
36
|
+
fall back to the saved default endpoint, the same one the web UI uses.
|
|
37
|
+
Passing the result to get_client explicitly is what makes the flag work
|
|
38
|
+
once ~/.stackport/endpoints.json exists.
|
|
39
|
+
"""
|
|
40
|
+
url = endpoint if endpoint is not None else endpoint_store.get_default_url()
|
|
41
|
+
return {"endpoint_url": url, "region": region}
|
|
42
|
+
|
|
43
|
+
|
|
20
44
|
@click.group(invoke_without_command=True)
|
|
21
45
|
@click.version_option(package_name="stackport")
|
|
22
46
|
@click.pass_context
|
|
@@ -39,22 +63,14 @@ def serve(port):
|
|
|
39
63
|
@click.option("--output", type=click.Choice(["json", "table"]), default="table", help="Output format")
|
|
40
64
|
def status(endpoint, region, output):
|
|
41
65
|
"""Show all services with availability and resource counts."""
|
|
42
|
-
|
|
43
|
-
if endpoint != AWS_ENDPOINT_URL:
|
|
44
|
-
import os
|
|
45
|
-
|
|
46
|
-
os.environ["AWS_ENDPOINT_URL"] = endpoint
|
|
47
|
-
if region != AWS_REGION:
|
|
48
|
-
import os
|
|
49
|
-
|
|
50
|
-
os.environ["AWS_REGION"] = region
|
|
66
|
+
client_kwargs = _client_kwargs(endpoint, region)
|
|
51
67
|
|
|
52
68
|
enabled_services = [s.strip() for s in STACKPORT_SERVICES.split(",") if s.strip()]
|
|
53
69
|
services = {}
|
|
54
70
|
|
|
55
71
|
try:
|
|
56
72
|
with ThreadPoolExecutor(max_workers=min(len(enabled_services), 10)) as executor:
|
|
57
|
-
futures = {executor.submit(_probe_service, svc): svc for svc in enabled_services}
|
|
73
|
+
futures = {executor.submit(_probe_service, svc, **client_kwargs): svc for svc in enabled_services}
|
|
58
74
|
for future in as_completed(futures):
|
|
59
75
|
svc_name, result = future.result()
|
|
60
76
|
services[svc_name] = result
|
|
@@ -89,15 +105,7 @@ def status(endpoint, region, output):
|
|
|
89
105
|
@click.option("--output", type=click.Choice(["json", "table", "csv"]), default="table", help="Output format")
|
|
90
106
|
def list(service, endpoint, region, output):
|
|
91
107
|
"""List resources for a service."""
|
|
92
|
-
|
|
93
|
-
if endpoint != AWS_ENDPOINT_URL:
|
|
94
|
-
import os
|
|
95
|
-
|
|
96
|
-
os.environ["AWS_ENDPOINT_URL"] = endpoint
|
|
97
|
-
if region != AWS_REGION:
|
|
98
|
-
import os
|
|
99
|
-
|
|
100
|
-
os.environ["AWS_REGION"] = region
|
|
108
|
+
client_kwargs = _client_kwargs(endpoint, region)
|
|
101
109
|
|
|
102
110
|
registry_entries = SERVICE_REGISTRY.get(service)
|
|
103
111
|
if not registry_entries:
|
|
@@ -109,7 +117,7 @@ def list(service, endpoint, region, output):
|
|
|
109
117
|
resources = {}
|
|
110
118
|
for resource_type, boto3_service, method_name, response_key in registry_entries:
|
|
111
119
|
try:
|
|
112
|
-
client = get_client(boto3_service)
|
|
120
|
+
client = get_client(boto3_service, **client_kwargs)
|
|
113
121
|
method = getattr(client, method_name)
|
|
114
122
|
kwargs = _METHOD_KWARGS.get((boto3_service, method_name), {})
|
|
115
123
|
resp = method(**kwargs)
|
|
@@ -129,8 +137,9 @@ def list(service, endpoint, region, output):
|
|
|
129
137
|
writer = csv.writer(sys.stdout)
|
|
130
138
|
writer.writerow(["service", "resource_type", "resource_id", "name"])
|
|
131
139
|
for resource_type, items in resources.items():
|
|
140
|
+
preferred = _PREFERRED_ID_FIELD.get((service, resource_type))
|
|
132
141
|
for item in items:
|
|
133
|
-
resource_id = _extract_id(item)
|
|
142
|
+
resource_id = _extract_id(item, preferred)
|
|
134
143
|
name = item.get("Name", item.get("FunctionName", item.get("TableName", resource_id))) if isinstance(item, dict) else resource_id
|
|
135
144
|
writer.writerow([service, resource_type, resource_id, name])
|
|
136
145
|
else:
|
|
@@ -139,8 +148,9 @@ def list(service, endpoint, region, output):
|
|
|
139
148
|
if items:
|
|
140
149
|
click.echo(f"\n{resource_type.upper()} ({len(items)}):")
|
|
141
150
|
click.echo("-" * 60)
|
|
151
|
+
preferred = _PREFERRED_ID_FIELD.get((service, resource_type))
|
|
142
152
|
for item in items[:20]: # Limit to first 20 for readability
|
|
143
|
-
resource_id = _extract_id(item)
|
|
153
|
+
resource_id = _extract_id(item, preferred)
|
|
144
154
|
if isinstance(item, dict):
|
|
145
155
|
name = item.get("Name", item.get("FunctionName", item.get("TableName", "")))
|
|
146
156
|
if name and name != resource_id:
|
|
@@ -164,20 +174,12 @@ def list(service, endpoint, region, output):
|
|
|
164
174
|
@click.option("--output", type=click.Choice(["json", "table"]), default="json", help="Output format")
|
|
165
175
|
def describe(service, resource_type, resource_id, endpoint, region, output):
|
|
166
176
|
"""Describe a specific resource."""
|
|
167
|
-
|
|
168
|
-
if endpoint != AWS_ENDPOINT_URL:
|
|
169
|
-
import os
|
|
170
|
-
|
|
171
|
-
os.environ["AWS_ENDPOINT_URL"] = endpoint
|
|
172
|
-
if region != AWS_REGION:
|
|
173
|
-
import os
|
|
174
|
-
|
|
175
|
-
os.environ["AWS_REGION"] = region
|
|
177
|
+
client_kwargs = _client_kwargs(endpoint, region)
|
|
176
178
|
|
|
177
179
|
# Special case for WAFv2
|
|
178
180
|
if (service, resource_type) == ("wafv2", "web_acls"):
|
|
179
181
|
try:
|
|
180
|
-
client = get_client("wafv2")
|
|
182
|
+
client = get_client("wafv2", **client_kwargs)
|
|
181
183
|
acls = client.list_web_acls(Scope="REGIONAL").get("WebACLs", [])
|
|
182
184
|
match = next((a for a in acls if a.get("Name") == resource_id), None)
|
|
183
185
|
if not match:
|
|
@@ -211,7 +213,7 @@ def describe(service, resource_type, resource_id, endpoint, region, output):
|
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
try:
|
|
214
|
-
client = get_client(boto3_service)
|
|
216
|
+
client = get_client(boto3_service, **client_kwargs)
|
|
215
217
|
method = getattr(client, method_name)
|
|
216
218
|
if id_param in _LIST_PARAMS:
|
|
217
219
|
resp = method(**{id_param: [resource_id]})
|
|
@@ -84,6 +84,7 @@ _PREFERRED_ID_FIELD: dict[tuple[str, str], str] = {
|
|
|
84
84
|
("elasticmapreduce", "clusters"): "Id",
|
|
85
85
|
("cognito-idp", "user_pools"): "Id",
|
|
86
86
|
("apigateway", "rest_apis"): "id",
|
|
87
|
+
("apigateway", "apis"): "ApiId",
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
# Known ID field names for extracting a resource identifier from list results
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stackport"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3"
|
|
4
4
|
description = "Universal AWS resource browser for local emulators"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
7
7
|
requires-python = ">=3.10"
|
|
8
8
|
authors = [{name = "Davi Reis Vieira"}]
|
|
9
|
-
keywords = ["aws", "ministack", "localstack", "browser", "devtools", "emulator"]
|
|
9
|
+
keywords = ["aws", "ministack", "floci", "localstack", "browser", "devtools", "emulator"]
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Development Status :: 4 - Beta",
|
|
12
12
|
"Framework :: FastAPI",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackport
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Universal AWS resource browser for local emulators
|
|
5
5
|
Author: Davi Reis Vieira
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/DaviReisVieira/stackport
|
|
8
8
|
Project-URL: Repository, https://github.com/DaviReisVieira/stackport
|
|
9
9
|
Project-URL: Issues, https://github.com/DaviReisVieira/stackport/issues
|
|
10
|
-
Keywords: aws,ministack,localstack,browser,devtools,emulator
|
|
10
|
+
Keywords: aws,ministack,floci,localstack,browser,devtools,emulator
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Framework :: FastAPI
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -75,15 +75,18 @@ Dynamic: license-file
|
|
|
75
75
|
- **CLI** — `stackport status`, `list`, `describe`, `export` with JSON/CSV/table output
|
|
76
76
|
- **Real-time dashboard** with WebSocket-powered live updates
|
|
77
77
|
- **Keyboard shortcuts** — 16 shortcuts for fast navigation (press `?` to view)
|
|
78
|
-
- Single Docker image, works with MiniStack, LocalStack, Moto, or any AWS-compatible endpoint
|
|
78
|
+
- Single Docker image, works with [MiniStack](https://github.com/ministackorg/ministack), [Floci](https://github.com/floci-io/floci), LocalStack, Moto, or any AWS-compatible endpoint
|
|
79
79
|
|
|
80
80
|
## Quick Start
|
|
81
81
|
|
|
82
|
-
### With a local emulator
|
|
82
|
+
### With a local emulator
|
|
83
|
+
|
|
84
|
+
Start whichever emulator you use, then point StackPort at it:
|
|
83
85
|
|
|
84
86
|
```bash
|
|
85
|
-
# Start
|
|
86
|
-
|
|
87
|
+
# Start an emulator on :4566 (pick one)
|
|
88
|
+
docker run -d -p 4566:4566 ministackorg/ministack # MiniStack
|
|
89
|
+
docker run -d -p 4566:4566 floci/floci # Floci
|
|
87
90
|
|
|
88
91
|
# Start StackPort
|
|
89
92
|
pip install stackport
|
|
@@ -108,16 +111,32 @@ You can also configure per-endpoint authentication from the Settings UI — sele
|
|
|
108
111
|
|
|
109
112
|
When connected to real AWS, StackPort shows a warning banner and operates in read-only mode unless writes are explicitly enabled.
|
|
110
113
|
|
|
111
|
-
### Docker Compose
|
|
114
|
+
### Docker Compose
|
|
115
|
+
|
|
116
|
+
Each example runs the emulator, StackPort, and a one-shot `seed` container that creates a few resources (S3, SQS, SNS, DynamoDB, Secrets Manager, IAM, Logs, Step Functions) so the dashboard has something to show on first load. Both use the same seed. Swap in LocalStack, Moto, or any AWS-compatible endpoint by replacing the emulator service and updating `AWS_ENDPOINT_URL`.
|
|
112
117
|
|
|
113
|
-
|
|
118
|
+
**MiniStack + StackPort** — [`examples/docker-compose.yml`](examples/docker-compose.yml)
|
|
114
119
|
|
|
115
120
|
```bash
|
|
116
121
|
curl -O https://raw.githubusercontent.com/DaviReisVieira/stackport/main/examples/docker-compose.yml
|
|
117
122
|
docker compose up -d
|
|
118
123
|
# Open http://localhost:8080
|
|
124
|
+
# If the dashboard is empty: docker compose logs seed
|
|
119
125
|
```
|
|
120
126
|
|
|
127
|
+
[MiniStack](https://github.com/ministackorg/ministack) derives the URLs it returns (SQS queue URLs, pre-signed URLs) from the request `Host`, so they resolve from other containers and from the host without extra configuration.
|
|
128
|
+
|
|
129
|
+
**Floci + StackPort** — [`examples/docker-compose.floci.yml`](examples/docker-compose.floci.yml)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl -O https://raw.githubusercontent.com/DaviReisVieira/stackport/main/examples/docker-compose.floci.yml
|
|
133
|
+
docker compose -f docker-compose.floci.yml up -d
|
|
134
|
+
# Open http://localhost:8080
|
|
135
|
+
# If the dashboard is empty: docker compose -f docker-compose.floci.yml logs seed
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
[Floci](https://github.com/floci-io/floci) takes the hostname for the URLs it returns from `FLOCI_HOSTNAME`, which the example sets to `floci` so they resolve from other containers. From the host, reach the same resources through `http://localhost:4566`.
|
|
139
|
+
|
|
121
140
|
### Docker (standalone)
|
|
122
141
|
|
|
123
142
|
```bash
|
|
@@ -351,6 +370,10 @@ ui/src/
|
|
|
351
370
|
lib/ API client, types, service icons, utils
|
|
352
371
|
```
|
|
353
372
|
|
|
373
|
+
## Origin
|
|
374
|
+
|
|
375
|
+
StackPort started as a web UI pull request to [MiniStack](https://github.com/ministackorg/ministack) ([ministack#116](https://github.com/ministackorg/ministack/pull/116)). The MiniStack maintainers suggested it would be more useful as a standalone tool that talks to any emulator over the standard AWS API, and that is what it became. Thanks to [@Nahuel990](https://github.com/Nahuel990) and [@McDoit](https://github.com/McDoit) for pushing it in that direction.
|
|
376
|
+
|
|
354
377
|
## Contributors
|
|
355
378
|
|
|
356
379
|
<a href="https://github.com/DaviReisVieira/stackport/graphs/contributors">
|
|
@@ -15,27 +15,27 @@ backend/../ui/dist/favicon.png
|
|
|
15
15
|
backend/../ui/dist/favicon.svg
|
|
16
16
|
backend/../ui/dist/index.html
|
|
17
17
|
backend/../ui/dist/stackport-logo.svg
|
|
18
|
-
backend/../ui/dist/assets/CloudscapeAbout-
|
|
19
|
-
backend/../ui/dist/assets/CloudscapeDashboard-
|
|
20
|
-
backend/../ui/dist/assets/CloudscapeLearn-
|
|
18
|
+
backend/../ui/dist/assets/CloudscapeAbout-BRbsDskR.js
|
|
19
|
+
backend/../ui/dist/assets/CloudscapeDashboard-j2f4Elz2.js
|
|
20
|
+
backend/../ui/dist/assets/CloudscapeLearn-CVW3k3X0.js
|
|
21
|
+
backend/../ui/dist/assets/CloudscapeResourceBrowser-CgWdgn20.js
|
|
21
22
|
backend/../ui/dist/assets/CloudscapeResourceBrowser-CzwePG-V.css
|
|
22
|
-
backend/../ui/dist/assets/
|
|
23
|
-
backend/../ui/dist/assets/CloudscapeSettings-UdgiI6dm.js
|
|
23
|
+
backend/../ui/dist/assets/CloudscapeSettings-CIXFvPKv.js
|
|
24
24
|
backend/../ui/dist/assets/CloudscapeShell-BXHVWjDE.css
|
|
25
|
-
backend/../ui/dist/assets/CloudscapeShell-
|
|
26
|
-
backend/../ui/dist/assets/StateMachineGraph-
|
|
27
|
-
backend/../ui/dist/assets/index-
|
|
28
|
-
backend/../ui/dist/assets/index-
|
|
29
|
-
backend/../ui/dist/assets/index-
|
|
30
|
-
backend/../ui/dist/assets/index-
|
|
25
|
+
backend/../ui/dist/assets/CloudscapeShell-DNfhSg0O.js
|
|
26
|
+
backend/../ui/dist/assets/StateMachineGraph-DvF_kIOO.js
|
|
27
|
+
backend/../ui/dist/assets/index-BC7E-Fm4.js
|
|
28
|
+
backend/../ui/dist/assets/index-BLdRlH9d.js
|
|
29
|
+
backend/../ui/dist/assets/index-Ba-oOcd3.js
|
|
30
|
+
backend/../ui/dist/assets/index-BkSgVQ0X.js
|
|
31
|
+
backend/../ui/dist/assets/index-C5z3cir-.js
|
|
31
32
|
backend/../ui/dist/assets/index-DE--A2EE.css
|
|
32
33
|
backend/../ui/dist/assets/index-DKIdXFX0.css
|
|
33
34
|
backend/../ui/dist/assets/index-DNBdeKId.css
|
|
34
|
-
backend/../ui/dist/assets/index-DhD1oh4N.js
|
|
35
35
|
backend/../ui/dist/assets/index-DoY0HscW.css
|
|
36
36
|
backend/../ui/dist/assets/index-DrkkXAoG.css
|
|
37
|
+
backend/../ui/dist/assets/index-Dw6TGweb.js
|
|
37
38
|
backend/../ui/dist/assets/index-Vf0UFiCT.css
|
|
38
|
-
backend/../ui/dist/assets/index-hhBdqMqs.js
|
|
39
39
|
backend/../ui/dist/assets/stepfunctions-graph-B6nvU4OB.js
|
|
40
40
|
backend/../ui/dist/assets/utils-Cfmwk2UT.js
|
|
41
41
|
backend/../ui/dist/aws-icons/acm.svg
|
|
@@ -148,27 +148,27 @@ ui/dist/favicon.png
|
|
|
148
148
|
ui/dist/favicon.svg
|
|
149
149
|
ui/dist/index.html
|
|
150
150
|
ui/dist/stackport-logo.svg
|
|
151
|
-
ui/dist/assets/CloudscapeAbout-
|
|
152
|
-
ui/dist/assets/CloudscapeDashboard-
|
|
153
|
-
ui/dist/assets/CloudscapeLearn-
|
|
151
|
+
ui/dist/assets/CloudscapeAbout-BRbsDskR.js
|
|
152
|
+
ui/dist/assets/CloudscapeDashboard-j2f4Elz2.js
|
|
153
|
+
ui/dist/assets/CloudscapeLearn-CVW3k3X0.js
|
|
154
|
+
ui/dist/assets/CloudscapeResourceBrowser-CgWdgn20.js
|
|
154
155
|
ui/dist/assets/CloudscapeResourceBrowser-CzwePG-V.css
|
|
155
|
-
ui/dist/assets/
|
|
156
|
-
ui/dist/assets/CloudscapeSettings-UdgiI6dm.js
|
|
156
|
+
ui/dist/assets/CloudscapeSettings-CIXFvPKv.js
|
|
157
157
|
ui/dist/assets/CloudscapeShell-BXHVWjDE.css
|
|
158
|
-
ui/dist/assets/CloudscapeShell-
|
|
159
|
-
ui/dist/assets/StateMachineGraph-
|
|
160
|
-
ui/dist/assets/index-
|
|
161
|
-
ui/dist/assets/index-
|
|
162
|
-
ui/dist/assets/index-
|
|
163
|
-
ui/dist/assets/index-
|
|
158
|
+
ui/dist/assets/CloudscapeShell-DNfhSg0O.js
|
|
159
|
+
ui/dist/assets/StateMachineGraph-DvF_kIOO.js
|
|
160
|
+
ui/dist/assets/index-BC7E-Fm4.js
|
|
161
|
+
ui/dist/assets/index-BLdRlH9d.js
|
|
162
|
+
ui/dist/assets/index-Ba-oOcd3.js
|
|
163
|
+
ui/dist/assets/index-BkSgVQ0X.js
|
|
164
|
+
ui/dist/assets/index-C5z3cir-.js
|
|
164
165
|
ui/dist/assets/index-DE--A2EE.css
|
|
165
166
|
ui/dist/assets/index-DKIdXFX0.css
|
|
166
167
|
ui/dist/assets/index-DNBdeKId.css
|
|
167
|
-
ui/dist/assets/index-DhD1oh4N.js
|
|
168
168
|
ui/dist/assets/index-DoY0HscW.css
|
|
169
169
|
ui/dist/assets/index-DrkkXAoG.css
|
|
170
|
+
ui/dist/assets/index-Dw6TGweb.js
|
|
170
171
|
ui/dist/assets/index-Vf0UFiCT.css
|
|
171
|
-
ui/dist/assets/index-hhBdqMqs.js
|
|
172
172
|
ui/dist/assets/stepfunctions-graph-B6nvU4OB.js
|
|
173
173
|
ui/dist/assets/utils-Cfmwk2UT.js
|
|
174
174
|
ui/dist/aws-icons/acm.svg
|
|
@@ -120,3 +120,68 @@ class TestCLI:
|
|
|
120
120
|
result = runner.invoke(cli, ["export", "s3", "--format", format_type])
|
|
121
121
|
# Should either succeed or fail gracefully
|
|
122
122
|
assert result.exit_code in [0, 1, 2]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _FakeClient:
|
|
126
|
+
"""Answers any boto3 method with an empty response."""
|
|
127
|
+
|
|
128
|
+
def __getattr__(self, name):
|
|
129
|
+
return lambda **kwargs: {}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TestEndpointResolution:
|
|
133
|
+
"""--endpoint must reach get_client even when ~/.stackport has a saved default (#171)."""
|
|
134
|
+
|
|
135
|
+
def _capture_get_client(self, monkeypatch):
|
|
136
|
+
calls = []
|
|
137
|
+
|
|
138
|
+
def fake_get_client(service_name, **kwargs):
|
|
139
|
+
calls.append((service_name, kwargs))
|
|
140
|
+
return _FakeClient()
|
|
141
|
+
|
|
142
|
+
monkeypatch.setattr("backend.cli.get_client", fake_get_client)
|
|
143
|
+
return calls
|
|
144
|
+
|
|
145
|
+
def test_list_passes_explicit_endpoint(self, monkeypatch):
|
|
146
|
+
calls = self._capture_get_client(monkeypatch)
|
|
147
|
+
monkeypatch.setattr("backend.cli.endpoint_store.get_default_url", lambda: "http://saved:4566")
|
|
148
|
+
result = CliRunner().invoke(cli, ["list", "s3", "--endpoint", "http://flag:4599", "--region", "eu-west-1"])
|
|
149
|
+
assert result.exit_code == 0, result.output
|
|
150
|
+
assert calls, "list never created a client"
|
|
151
|
+
for _, kwargs in calls:
|
|
152
|
+
assert kwargs["endpoint_url"] == "http://flag:4599"
|
|
153
|
+
assert kwargs["region"] == "eu-west-1"
|
|
154
|
+
|
|
155
|
+
def test_list_falls_back_to_saved_default(self, monkeypatch):
|
|
156
|
+
calls = self._capture_get_client(monkeypatch)
|
|
157
|
+
# Other test modules put AWS_ENDPOINT_URL back into the environment; click reads it via envvar.
|
|
158
|
+
monkeypatch.delenv("AWS_ENDPOINT_URL", raising=False)
|
|
159
|
+
monkeypatch.setattr("backend.cli.AWS_ENDPOINT_URL", None)
|
|
160
|
+
monkeypatch.setattr("backend.cli.endpoint_store.get_default_url", lambda: "http://saved:4566")
|
|
161
|
+
# Rebuild the option default: click captured AWS_ENDPOINT_URL at import time.
|
|
162
|
+
for param in cli.commands["list"].params:
|
|
163
|
+
if param.name == "endpoint":
|
|
164
|
+
monkeypatch.setattr(param, "default", None)
|
|
165
|
+
result = CliRunner().invoke(cli, ["list", "s3"])
|
|
166
|
+
assert result.exit_code == 0, result.output
|
|
167
|
+
assert calls and all(kwargs["endpoint_url"] == "http://saved:4566" for _, kwargs in calls)
|
|
168
|
+
|
|
169
|
+
def test_describe_passes_explicit_endpoint(self, monkeypatch):
|
|
170
|
+
calls = self._capture_get_client(monkeypatch)
|
|
171
|
+
result = CliRunner().invoke(cli, ["describe", "s3", "buckets", "demo", "--endpoint", "http://flag:4599"])
|
|
172
|
+
assert result.exit_code == 0, result.output
|
|
173
|
+
assert calls and all(kwargs["endpoint_url"] == "http://flag:4599" for _, kwargs in calls)
|
|
174
|
+
|
|
175
|
+
def test_status_probes_every_service_at_the_given_endpoint(self, monkeypatch):
|
|
176
|
+
probed = []
|
|
177
|
+
|
|
178
|
+
def fake_probe(service, endpoint_url, region=None, **_):
|
|
179
|
+
probed.append((service, endpoint_url, region))
|
|
180
|
+
return service, {"status": "available", "resources": {}}
|
|
181
|
+
|
|
182
|
+
monkeypatch.setattr("backend.cli._probe_service", fake_probe)
|
|
183
|
+
result = CliRunner().invoke(cli, ["status", "--endpoint", "http://flag:4599", "--output", "json"])
|
|
184
|
+
assert result.exit_code == 0, result.output
|
|
185
|
+
assert probed, "status never probed anything"
|
|
186
|
+
assert {url for _, url, _ in probed} == {"http://flag:4599"}
|
|
187
|
+
assert isinstance(json.loads(result.output), dict)
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"""Validate SERVICE_REGISTRY, DESCRIBE_REGISTRY, and _METHOD_KWARGS consistency."""
|
|
2
2
|
|
|
3
|
-
from backend.routes.resources import
|
|
3
|
+
from backend.routes.resources import (
|
|
4
|
+
DESCRIBE_REGISTRY,
|
|
5
|
+
_ID_FIELDS,
|
|
6
|
+
_PREFERRED_ID_FIELD,
|
|
7
|
+
_extract_id,
|
|
8
|
+
_summarize_item,
|
|
9
|
+
)
|
|
4
10
|
from backend.routes.stats import SERVICE_REGISTRY, _METHOD_KWARGS
|
|
5
11
|
|
|
6
12
|
|
|
@@ -67,3 +73,13 @@ class TestIdFields:
|
|
|
67
73
|
|
|
68
74
|
def test_no_duplicates(self):
|
|
69
75
|
assert len(_ID_FIELDS) == len(set(_ID_FIELDS))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestPreferredIdField:
|
|
79
|
+
def test_apigatewayv2_api_id_wins_over_name(self):
|
|
80
|
+
"""get_api takes ApiId, but the list item also has Name, which ranks first
|
|
81
|
+
in _ID_FIELDS — without the override the detail lookup 404s."""
|
|
82
|
+
item = {"ApiId": "acf2a85b", "Name": "my-api", "ProtocolType": "HTTP"}
|
|
83
|
+
preferred = _PREFERRED_ID_FIELD.get(("apigateway", "apis"))
|
|
84
|
+
assert _extract_id(item, preferred) == "acf2a85b"
|
|
85
|
+
assert _summarize_item(item, preferred)["id"] == "acf2a85b"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as j,r as m,dY as f,u as y,j as e,S as o,B as p,A as b,e as i,d as u,L as v}from"./index-
|
|
1
|
+
import{b as j,r as m,dY as f,u as y,j as e,S as o,B as p,A as b,e as i,d as u,L as v}from"./index-BLdRlH9d.js";import{C as S,H as a,K as d,B as h}from"./CloudscapeShell-DNfhSg0O.js";import{C}from"./index-C5z3cir-.js";import{f as g}from"./utils-Cfmwk2UT.js";const k=[{label:"GitHub Repository",url:"https://github.com/DaviReisVieira/stackport"},{label:"Report an Issue",url:"https://github.com/DaviReisVieira/stackport/issues"}];function r(t,n){return e.jsxs("div",{children:[e.jsx(u,{variant:"awsui-key-label",children:t}),e.jsx(u,{fontSize:"body-s",children:n})]},t)}function z(){const{activeEndpoint:t}=j(),n=m.useCallback(()=>f(t),[t]),{data:s,error:c,refresh:x}=y(n,1e4);return e.jsx(S,{activeHref:"/about",children:e.jsxs(o,{size:"l",children:[e.jsx(a,{variant:"h1",description:"System information and project details",actions:e.jsx(p,{iconName:"refresh",onClick:()=>x(),ariaLabel:"Refresh system info"}),children:"About"}),c&&!s&&e.jsx(b,{type:"error",header:"Failed to load system info",action:e.jsx(p,{onClick:()=>x(),children:"Retry"}),children:c}),!s&&!c&&e.jsx(i,{type:"loading",children:"Loading system info"}),s&&e.jsxs(C,{columns:3,children:[e.jsx(d,{header:e.jsx(a,{variant:"h3",children:"Project"}),children:e.jsxs(o,{size:"s",children:[r("Name","StackPort"),r("Version",e.jsx(h,{color:"blue",children:s.version})),r("License","MIT"),e.jsx(u,{color:"text-body-secondary",fontSize:"body-s",children:"Universal AWS resource browser for local emulators"})]})}),e.jsx(d,{header:e.jsx(a,{variant:"h3",children:"Connection"}),children:e.jsxs(o,{size:"s",children:[r("Mode",s.connection_type==="local"?e.jsx(h,{color:"grey",children:"Local Emulator"}):e.jsx(h,{color:"severity-medium",children:"Real AWS"})),r("Endpoint",s.endpoint_url??"AWS (default)"),r("Region",s.region),r("Services",String(s.services_count)),r("Writes",s.writes_enabled?e.jsx(i,{type:"success",children:"Enabled"}):e.jsx(i,{type:"stopped",children:"Disabled"})),r("Uptime",g(s.uptime_seconds)),r("Status",s.status==="ok"?e.jsx(i,{type:"success",children:"ok"}):e.jsx(i,{type:"error",children:s.status}))]})}),e.jsx(d,{header:e.jsx(a,{variant:"h3",children:"Links"}),children:e.jsx(o,{size:"s",children:k.map(l=>e.jsx(v,{href:l.url,external:!0,children:l.label},l.url))})})]})]})})}export{z as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,u as L,a as W,b as O,c as A,f as D,j as t,S as $,A as E,B as R,d,e as F,L as M}from"./index-
|
|
1
|
+
import{r as a,u as L,a as W,b as O,c as A,f as D,j as t,S as $,A as E,B as R,d,e as F,L as M}from"./index-BLdRlH9d.js";import{u as P,a as H,H as B,C as U,B as _,g as V}from"./CloudscapeShell-DNfhSg0O.js";import{S as J,C as G,T as K}from"./index-BkSgVQ0X.js";import{C as q}from"./index-C5z3cir-.js";import{C as Q}from"./index-Ba-oOcd3.js";import{T as X}from"./index-BC7E-Fm4.js";import{f as Y}from"./utils-Cfmwk2UT.js";function Z({fallbackFetcher:n,fallbackInterval:r=5e3,messageType:i,endpoint:x}){const[s,g]=a.useState(null),[y,j]=a.useState(!1),[p,C]=a.useState(!1),h=a.useRef(null),c=a.useRef(0),b=a.useRef(null),l=a.useRef(),k=3;a.useEffect(()=>{let v=!1;c.current=0;function S(){var o;if(v||((o=h.current)==null?void 0:o.readyState)===WebSocket.OPEN)return;const m=window.location.protocol==="https:"?"wss:":"ws:",e=new WebSocket(`${m}//${window.location.host}/ws`);h.current=e,e.onopen=()=>{if(v){e.close();return}j(!0),c.current=0,e.send(JSON.stringify({type:"subscribe",services:["all"],endpoint:x??void 0}))},e.onmessage=w=>{try{const f=JSON.parse(w.data);f.type===i&&f.data&&g(f.data)}catch{}},e.onclose=()=>{if(j(!1),h.current=null,!v)if(c.current++,c.current>=k)C(!0);else{const w=Math.min(1e3*Math.pow(2,c.current),3e4);b.current=setTimeout(()=>{var f;return(f=l.current)==null?void 0:f.call(l)},w)}},e.onerror=()=>{e.close()}}return l.current=S,S(),()=>{var m;v=!0,b.current&&clearTimeout(b.current),(m=h.current)==null||m.close(),g(null),C(!1)}},[i,x]);const u=L(n,p?r:void 0),T=s??u.data,N=!T&&u.loading;return{data:T,loading:N,error:p?u.error:null,connected:y,refresh:u.refresh}}function ee(){return localStorage.getItem("stackport:view-mode")==="list"?"list":"grid"}function z(n,r){const i=V(n.name);return t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(i,{className:"h-5 w-5 flex-shrink-0"}),t.jsx(M,{fontSize:"heading-m",href:`/resources/${n.name}`,onFollow:x=>{x.preventDefault(),r(n.name)},children:n.name})]})}function I(n){return n.total===0?t.jsx(d,{color:"text-status-inactive",children:"No resources"}):t.jsx($,{direction:"horizontal",size:"xs",children:Object.entries(n.resources).filter(([,r])=>r>0).map(([r,i])=>t.jsxs(_,{color:"grey",children:[r,": ",i]},r))})}function ce(){const n=W(),{activeEndpoint:r}=O(),{data:i}=A(),x=a.useCallback(()=>D(r),[r]),{data:s,loading:g,error:y,connected:j,refresh:p}=Z({fallbackFetcher:x,fallbackInterval:5e3,messageType:"stats",endpoint:r}),{toggleFavorite:C,isFavorite:h}=P(),[c,b]=a.useState(""),[l,k]=a.useState(ee);a.useEffect(()=>{localStorage.setItem("stackport:view-mode",l)},[l]),H([{key:"r",handler:()=>p()},{key:"v",handler:()=>k(e=>e==="grid"?"list":"grid")}]);const u=a.useMemo(()=>s?Object.entries(s.services).map(([e,o])=>({name:e,status:o.status,resources:o.resources,total:Object.values(o.resources).reduce((w,f)=>w+f,0),favorite:h(e)})).filter(e=>e.name.toLowerCase().includes(c.toLowerCase())).sort((e,o)=>Number(o.favorite)-Number(e.favorite)||o.total-e.total||e.name.localeCompare(o.name)):[],[s,c,h]),T=s?Object.values(s.services).filter(e=>e.status==="available").length:0,N=a.useCallback(e=>n(`/resources/${e}`),[n]),v=e=>t.jsx(R,{variant:"inline-icon",iconName:e.favorite?"star-filled":"star",ariaLabel:e.favorite?`Remove ${e.name} from favorites`:`Add ${e.name} to favorites`,onClick:()=>C(e.name)}),S=t.jsx(B,{variant:"h2",counter:s?`(${u.length})`:void 0,actions:t.jsx(J,{selectedId:l,onChange:({detail:e})=>k(e.selectedId),label:"View mode",options:[{id:"grid",text:"Grid"},{id:"list",text:"List"}]}),children:"Services"}),m=t.jsx(K,{filteringText:c,filteringPlaceholder:"Find a service",onChange:({detail:e})=>b(e.filteringText)});return t.jsx(U,{activeHref:"/",children:t.jsx(Q,{header:t.jsx(B,{variant:"h1",description:r?`Connected to ${r}`:i!=null&&i.endpoint_url?`Connected to ${i.endpoint_url}`:"Using the default endpoint",actions:t.jsxs($,{direction:"horizontal",size:"xs",children:[t.jsx(F,{type:j?"success":"in-progress",children:j?"Live":"Polling"}),t.jsx(R,{iconName:"refresh",onClick:()=>p(),loading:g,children:"Refresh"})]}),children:"Dashboard"}),children:t.jsxs($,{size:"l",children:[!s&&y&&t.jsx(E,{type:"error",header:"Could not reach the endpoint",action:t.jsx(R,{onClick:()=>p(),children:"Retry"}),children:y}),t.jsxs(q,{columns:3,variant:"text-grid",children:[t.jsxs("div",{children:[t.jsx(d,{variant:"awsui-key-label",children:"Services available"}),t.jsx(d,{fontSize:"display-l",fontWeight:"bold",children:s?`${T}/${Object.keys(s.services).length}`:"—"})]}),t.jsxs("div",{children:[t.jsx(d,{variant:"awsui-key-label",children:"Total resources"}),t.jsx(d,{fontSize:"display-l",fontWeight:"bold",children:s?s.total_resources:"—"})]}),t.jsxs("div",{children:[t.jsx(d,{variant:"awsui-key-label",children:"Uptime"}),t.jsx(d,{fontSize:"display-l",fontWeight:"bold",children:s?Y(s.uptime_seconds):"—"})]})]}),l==="grid"?t.jsx(G,{items:u,loading:g&&!s,loadingText:"Probing services",trackBy:"name",cardsPerRow:[{cards:1},{minWidth:500,cards:2},{minWidth:900,cards:3}],header:S,filter:m,cardDefinition:{header:e=>t.jsxs("div",{className:"flex items-center justify-between gap-2",children:[z(e,N),v(e)]}),sections:[{id:"status",content:e=>t.jsx(F,{type:e.status==="available"?"success":"error",children:e.status==="available"?"Available":"Unavailable"})},{id:"resources",header:"Resources",content:e=>I(e)}]},empty:t.jsx(d,{textAlign:"center",children:"No services match the filter"})}):t.jsx(X,{items:u,loading:g&&!s,loadingText:"Probing services",trackBy:"name",header:S,filter:m,variant:"container",columnDefinitions:[{id:"favorite",header:"",width:60,cell:e=>v(e)},{id:"service",header:"Service",cell:e=>z(e,N),sortingField:"name"},{id:"status",header:"Status",cell:e=>t.jsx(F,{type:e.status==="available"?"success":"error",children:e.status==="available"?"Available":"Unavailable"})},{id:"resources",header:"Resources",cell:e=>I(e)},{id:"total",header:"Total",width:90,cell:e=>e.total}],empty:t.jsx(d,{textAlign:"center",children:"No services match the filter"})})]})})})}export{ce as default};
|