Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,17 @@ jobs:

- name: Assert the UI is served
run: |
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 http://localhost:4500/)
echo "UI HTTP $code"
test "$code" = "200"
for i in $(seq 1 20); do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 http://localhost:4500/ || true)
echo "[$i] UI HTTP $code"
if [ "$code" = "200" ]; then
echo "UI reachable."
exit 0
fi
sleep 3
done
echo "::error::UI did not become reachable in time"
exit 1

- name: Dump container logs on failure
if: failure()
Expand Down
69 changes: 46 additions & 23 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,21 @@ for all new work; the legacy routes survive only for deep EC2 panels and Secrets

### The multi-cloud SPI (the part you will use most)

- `packages/api/src/cloud-spi/types.ts` — `CloudProvider` (`aws|azure|gcp`),
`CloudServiceType` (`storage|k8s|database|serverless|compute|networking|…`),
the `CloudServiceAdapter` interface, and `ServiceSchema`.
- `packages/api/src/cloud-spi/serviceCatalog.ts` — **the single source of truth for which
services exist.** `CloudServiceType` derives from its keys; nav metadata (display name,
icon hint, group, route) is served to the frontend from here.
- `packages/api/src/cloud-spi/types.ts` — `CloudProvider` (`aws|azure|gcp`), the
`CloudServiceAdapter` interface, `ServiceSchema`, and the status shapes.
- `packages/api/src/cloud-spi/errors.ts` — the typed errors adapters throw; mapped to HTTP
once in `routes/clouds.ts` (with `adapter-aws/awsErrors.ts` for SDK failures).
- `packages/api/src/registry/CloudAdapterRegistry.ts` — registry keyed by `"cloud:service"`.
Availability is derived from it, so registering an adapter is what lights up the nav.
- `packages/api/src/service/CloudProxyService.ts` — the single dispatcher.
- `packages/api/src/service/runtimeProbe.ts` — per-runtime liveness probes.
- `packages/api/src/cloudProxy.ts` — where adapters are instantiated and registered.
- `packages/api/src/routes/clouds.ts` — the generic `/api/clouds/...` REST surface.
- `packages/api/src/cloudProxy.test.ts` — guards that no schema advertises a capability its
adapter cannot perform.

A `ServiceSchema` (fields, `actions`, `capabilities`, `filters`, `columns`) drives the UI:
the frontend's `DynamicResourceView` renders list / create / delete / inspect generically
Expand All @@ -74,8 +82,9 @@ from the schema — most services need **no bespoke UI**.
### Frontend layout

- `packages/frontend/src/App.tsx` — routes (`/console/:cloud`, `/cloud-explorer/:cloud/:service`)
- `packages/frontend/src/components/Layout.tsx` — nav (`CLOUD_SERVICE_ITEMS`, `CLOUD_SERVICE_ICONS`)
- `packages/frontend/src/pages/CloudExplorerPage.tsx` — `normalizeService()` route handling
- `packages/frontend/src/components/Layout.tsx` — nav, rendered from `GET /clouds/:cloud/services`
- `packages/frontend/src/api/queries/cloudQueries.ts` — shared cloud/service/status queries
- `packages/frontend/src/components/serviceIcons.ts` — `iconKey` -> component, with a fallback
- `packages/frontend/src/components/DynamicResourceView.tsx` — schema → table/form/inspector orchestrator
- Reusable: `ResourceTable`, `DynamicFormRenderer`, `ResourceInspector`, `StorageObjectBrowser`,
`CosmosNoSqlPanel`, `EmptyState`, `lib/capabilities.ts`
Expand Down Expand Up @@ -111,29 +120,40 @@ Requires a running Floci core (`:4566`) — see `README.md` / `docker compose` (

This is the canonical pattern (also referenced by the open service-coverage issues).

**Backend (`packages/api`):**
**Backend (`packages/api`) — this is the whole change:**

1. `src/cloud-spi/<service>Schema.ts` — export `<service>SchemaFor(cloud)` returning a
`ServiceSchema`. Model: `src/cloud-spi/storageSchema.ts`.
2. Add the literal to `CloudServiceType` in `src/cloud-spi/types.ts` (once per new category).
1. Add one row to `SERVICE_CATALOG` in `src/cloud-spi/serviceCatalog.ts` (only for a new
category). `CloudServiceType`, the route guard, and the nav metadata all derive from it.
2. `src/cloud-spi/<service>Schema.ts` — export a per-cloud `<cloud><Service>Schema()`
returning a `ServiceSchema`. Model: `src/cloud-spi/storageSchema.ts`. Use `path` on a
column to surface a `metadata.*` field.
3. `src/adapter-<cloud>/<Cloud><Service>Adapter.ts implements CloudServiceAdapter` with a
`.test.ts` alongside. Model: `src/adapter-aws/AwsStorageAdapter.ts`. AWS adapters use AWS
SDK v3 against `FLOCI_ENDPOINT`; Azure/GCP adapters call the local runtime over HTTP
(`adapter-azure/azure.ts`, `adapter-gcp/gcp.ts`).
4. Register `new <Cloud><Service>Adapter()` in `src/cloudProxy.ts`.
5. Add a `services.push({...})` entry + `schema()` fallback in `service/CloudProxyService.ts`,
and extend `isServiceType()` in `routes/clouds.ts`. The generic `/api/clouds/...` routes
then work with no new handler.
SDK v3 against `FLOCI_ENDPOINT`; Azure/GCP adapters take the shared runtime client
(`AzureRuntimeClient` in `azure.ts`, `GcpRuntimeClient` in `gcp.ts`) — do not hand-roll fetch.
4. Register it in `src/cloudProxy.ts`.

**Frontend (`packages/frontend`):**
That is it. `services()` derives availability from the registry, `schema()` serves only
registered adapters, and the generic `/api/clouds/...` routes need no new handler.

1. Extend `CloudServiceType` in `src/types/cloud.ts` (and `types/schema.ts` if new shapes).
2. Add a nav entry + icon and per-cloud gating in `components/Layout.tsx`.
3. Handle the literal in `normalizeService()` in `pages/CloudExplorerPage.tsx`.
4. `DynamicResourceView` renders it generically. Only add a `service === '<x>'` panel for
deep UX (models: `ComputePanel`, `NetworkingPanel`, `CosmosNoSqlPanel`).
**Frontend (`packages/frontend`): normally nothing.**

Rule: copy an existing adapter + schema before introducing a new shape.
The nav, Console Home, and Cloud Explorer render `GET /clouds/:cloud/services`. Optional:
add an `iconKey` to `components/serviceIcons.ts` (an unknown key falls back to a generic
icon, so this is cosmetic), and add a `service === '<x>'` panel in `DynamicResourceView`
only for deep UX (models: `ComputePanel`, `NetworkingPanel`, `CosmosNoSqlPanel`).

Rules:

- Copy an existing adapter + schema before introducing a new shape.
- Throw the typed errors in `src/cloud-spi/errors.ts`, never a bare `Error` —
`routes/clouds.ts` maps them to HTTP and no longer matches on message text.
- Never advertise a capability the adapter cannot perform. `src/cloudProxy.test.ts`
fails the build if a capability marked `available` has no adapter method, or if
anything not `available` lacks a `reason`. Use `descriptorOverride()` when the
adapter exists but the local runtime does not implement it.
- Regenerate the README table when navigation changes:
`cd packages/api && bun run scripts/service-matrix.ts`.

---

Expand Down Expand Up @@ -185,7 +205,10 @@ and push the multi-arch `floci/floci-ui` image. Treat release workflows as criti
- Calling cloud endpoints directly from the frontend instead of through `/api/*`
- Adding fake/sample data instead of real empty states
- Extending the legacy `ec2/rds/eks/secretsmanager` routes for new work
- Forgetting to register the adapter (`cloudProxy.ts`) or wire the nav (`Layout.tsx`)
- Forgetting to register the adapter in `cloudProxy.ts` — that registration *is* what makes
the service appear; do not hardcode availability in the frontend
- Advertising a schema capability the adapter cannot perform (`cloudProxy.test.ts` catches it)
- Throwing a bare `Error` from an adapter instead of a typed error from `cloud-spi/errors.ts`
- Skipping `pnpm type-check` / `pnpm test` before finishing

---
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- AWS Lambda invoke, including the tailed execution log and handler errors.
- Per-service status: `GET /api/clouds/:cloud/services/:service/status` and
`GET /api/clouds/:cloud/status?services=all`, with an `errorCode` that distinguishes a
runtime that does not implement a service from one that cannot be reached.
- `packages/api/scripts/service-matrix.ts` generates the README coverage table from the
service catalog and adapter registry.
- Grouped sidebar sections, a loading skeleton, and a tooltip explaining why a service is
unavailable.

### Changed

- Service availability is derived from one catalog plus the adapter registry and served to
the frontend. Registering an adapter is now the only step needed for a service to appear;
the sidebar, Console Home, and Cloud Explorer no longer hardcode it.
- Adapters throw typed errors that are mapped to HTTP in one place, so AWS SDK failures
return 400/403/404/409/429 instead of a blanket 502.
- Both GCP adapters share a runtime client that reports Google's error message instead of a
bare HTTP status.

### Fixed

- AWS Lambda creation failed against the runtime: inline code was sent as raw text where a
deployment archive is required, so "create" could never succeed.
- Azure serverless was advertised as available even though the Floci-AZ runtime answers 501
NotImplemented; it now reports `coming_soon` with that reason. GCP Cloud Functions invoke
is likewise advertised as `coming_soon` — contrary to the 0.2.0 note below, it was never
implemented.
- AWS networking advertised create and delete as available while the adapter threw, and its
`get()` always returned null so inspect never worked.
- A schema was served for services with no registered adapter, so the UI rendered a table
that then failed on every request.
- GCP runtime status reported "reachable" whenever the port was open, because the probe
ignored the HTTP status. Cloud status was also inferred solely from the storage adapter.
- Table columns bound to `metadata` fields — including Serverless "Runtime" and
"Last Updated" — rendered blank on every row.
- An unknown service slug silently redirected to Storage instead of reporting it.

## [0.2.0] - 2026-07-08

### Added
Expand Down
93 changes: 66 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,42 @@ Open [http://localhost:4500](http://localhost:4500).

## What The UI Actually Exposes Today

This table is the source of truth for the current UI surface.
The sidebar and Console Home are rendered from `GET /api/clouds/:cloud/services`,
so this table is derived from the service catalog and the adapter registry rather
than maintained by hand. Regenerate it after any change to either:

| Surface | AWS | Azure | GCP | Notes |
```bash
cd packages/api && bun run scripts/service-matrix.ts
```

| Group | Service | AWS | Azure | GCP |
|---|---|---|---|---|
| Console Home | Yes | Yes | Yes | Cloud-aware overview page with runtime status and service cards. |
| Cloud Explorer / Storage | Yes | Yes | Yes | Unified storage view with resource table, inspector, object browser, and schema-driven actions. |
| Cloud Explorer / k8s Engine | Yes | Placeholder | Placeholder | AWS EKS list/inspect is wired. |
| Cloud Explorer / Database | Yes | Yes | Placeholder | AWS RDS list/inspect and Azure Cosmos DB NoSQL workflows. |
| Cloud Explorer / Compute | Yes | Placeholder | Placeholder | AWS EC2 and AMI workflows. |
| Cloud Explorer / Networking | Yes | Placeholder | Placeholder | AWS VPC/networking workflows. |
| Cloud Explorer / Serverless | Yes | Not exposed in navigation | Not exposed in navigation | AWS Lambda flows through the unified shell. |
| Dedicated page / Secrets Manager | Yes | No | No | AWS-only page outside Cloud Explorer. |

Visible placeholders in the current sidebar:

- Queue
- Function
- Azure compute, networking, and k8s
- GCP non-storage services
- IAM, KMS, Cognito, Systems Manager, ElastiCache
| Compute | Compute | Yes (list, inspect, create, delete) | No | No |
| Compute | EKS / AKS / GKE | Yes (list, inspect) | No | Yes (list, create, inspect, delete) |
| Compute | Serverless | Yes (list, create, inspect, delete) | Runtime gap | Yes (list, create, inspect, delete) |
| Storage | Storage | Yes (list, create, delete, inspect) | Yes (list, create, delete, inspect) | Yes (list, create, delete, inspect) |
| Databases | Database | Yes (list, inspect) | Yes (list, create, delete, inspect) | Yes (list, create, inspect, delete) |
| Networking | Networking | Yes (list) | No | No |
| Security | Secrets Manager / Key Vault | Yes (legacy page) | Yes (list, create, delete, inspect) | No |

Console Home is available for all three clouds.

Runtime gaps — an adapter exists but the local runtime does not implement it:

- Azure Serverless: the Floci-AZ runtime returns 501 NotImplemented for the Azure Functions endpoint.

Services marked `No` render as a disabled sidebar row whose tooltip carries the
server-supplied reason. Adding one is a catalog row in
`packages/api/src/cloud-spi/serviceCatalog.ts` plus an adapter — no frontend change.

<p align="center">
<img src="docs/images/floci-ui-console-azure.png" alt="Azure console home, showing services grouped by category with per-cloud naming and coming-soon reasons" width="900" />
</p>

Azure on the same build: the nav is grouped by category, `k8s Engine` is labelled
`AKS` for this provider, and every unavailable service carries a reason — Serverless
reads `coming soon` because the Floci-AZ runtime answers 501 for Azure Functions,
even though an adapter is registered.

## Current Capability Snapshot

Expand Down Expand Up @@ -149,30 +165,37 @@ Current gaps:

AWS only, through the unified shell plus an AWS-specific networking panel.

- VPC list and inspect.
- VPC creation and delete.
- VPC wizard.
- Subnets, security groups, internet gateways, NAT gateways, route tables, and Elastic IP workflows.
- VPC list and inspect through the unified resource table.
- VPC creation and delete, the VPC wizard, subnets, security groups, internet
gateways, NAT gateways, route tables, and Elastic IP workflows — all in the
Networking panel.

Current gaps:

- No Azure VNet or GCP VPC adapter yet.
- Create and delete are advertised as `partial` in the unified schema and are
handled by the Networking panel, because they need dependent selectors that a
flat generic form cannot express.
- Advanced multi-cloud networking normalization is still pending.

</details>

<details>
<summary><strong>Serverless</strong></summary>

AWS only in the current navigation.
AWS and GCP, both through the unified shell.

- Lambda-oriented unified schema is wired through the Cloud Explorer serverless service.
- The backend already exposes serverless through the Cloud Proxy API.
- AWS Lambda and GCP Cloud Functions list, create, inspect, and delete.
- AWS Lambda invoke is wired, including the tailed execution log and handler errors.
- Lambda creation packages inline code into a real deployment archive.
- The navigation entry appears for any cloud with a registered adapter.

Current gaps:

- Azure Functions is not yet exposed in the left navigation.
- No GCP serverless adapter in the UI surface.
- Azure Functions is registered but the Floci-AZ runtime answers 501 NotImplemented,
so it reports `coming_soon` with that reason rather than appearing available.
- GCP Cloud Functions invoke is not wired yet; the capability is advertised as
`coming_soon` instead of being silently missing.
- Old AWS Lambda page is gone; all future work should stay in the unified model.

</details>
Expand Down Expand Up @@ -392,11 +415,27 @@ Check the runtime directly:

```bash
curl http://localhost:4566/_floci/health
curl http://localhost:4577/_floci/health
curl http://localhost:4588/_floci-gcp/health
curl http://localhost:4501/api/clouds/aws/status
curl http://localhost:4501/api/clouds/azure/status
curl http://localhost:4501/api/clouds/gcp/status
```

### A single service shows as unavailable while the cloud is connected

Cloud status reflects the runtime; each service is probed separately. Ask which
service is failing and why:

```bash
curl http://localhost:4501/api/clouds/azure/status?services=all
curl http://localhost:4501/api/clouds/azure/services/serverless/status
```

`errorCode` distinguishes the cases: `operation_not_implemented` means the local
runtime does not implement that service, `runtime_unavailable` means it cannot be
reached, and `operation_not_supported` means no adapter is registered.

### Credentials or endpoint mismatch

For AWS local development, keep API credentials aligned with the runtime:
Expand Down
Loading