──────
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
### Summary of Changes
1. Documentation Consolidation:
• Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
• Updated document links in README.md.
2. AI Agent Project Map:
• Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
schemas, environment variables, and developer workflows.
• Added PROJECT_MAP.md to .gitignore.
3. Monitoring UI & Infrastructure Integration:
• Added monitoring_ui service to docker-compose.yml (port 5175).
• Configured Gitea workflow docker-build.yml for monitoring_ui builds.
• Added CORS middleware headers in index.js.
• Added nginx.conf copy step in Dockerfile.
──────
### Recommended Commit Message
docs: consolidate project documentation into monitoring_ui and add agent project map
- Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
- Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
- Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
- Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
- Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
- Configure CORS handling in employee_api for monitoring dashboard access
──────
### Alternative Short Commit Message
refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep Patterns
|
||||
|
||||
Bicep default-path patterns for AppOnboard scaffold. Used as the primary IaC format. For the alternative Terraform path (existing `.tf` files or user override), scaffold uses `mcp_azure_mcp_azureterraformbestpractices` output patterns.
|
||||
|
||||
> **Source:** Adapted from Azure Bicep best practices. See [Bicep best practices](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/best-practices) for updates.
|
||||
|
||||
## File Structure
|
||||
|
||||
> ⛔ **Always use `targetScope = 'subscription'`.** Subscription-scope Bicep creates the resource group in IaC with all 5 AppOnboard tags (including `created-at`). Resource-group scope requires `az group create` via CLI, which consistently misses `created-at` because CLI-created resource groups don't receive IaC-managed tags. There are zero benefits to resource-group scope for AppOnboard.
|
||||
|
||||
```
|
||||
infra/
|
||||
├── main.bicep # Entry point (subscription scope)
|
||||
├── main.parameters.json # ARM JSON parameter values (NOT .bicepparam)
|
||||
└── modules/
|
||||
├── container-app.bicep
|
||||
├── app-service.bicep
|
||||
├── sql-database.bicep
|
||||
├── key-vault.bicep
|
||||
├── log-analytics.bicep
|
||||
└── ...
|
||||
```
|
||||
|
||||
Each service gets its own module. `main.bicep` orchestrates resource group creation + module calls.
|
||||
|
||||
## main.bicep Skeleton
|
||||
|
||||
```bicep
|
||||
targetScope = 'subscription'
|
||||
|
||||
@minLength(1)
|
||||
@maxLength(64)
|
||||
param environmentName string
|
||||
|
||||
@minLength(1)
|
||||
param location string
|
||||
|
||||
param sessionId string
|
||||
|
||||
param deployedBy string // resolved via: az ad signed-in-user show --query displayName -o tsv
|
||||
|
||||
// ⛔ createdAt: passed in parameters.json, NOT utcNow() default (crashes Portal blade)
|
||||
param createdAt string
|
||||
|
||||
var tags = {
|
||||
'app-onboard-skill': 'true'
|
||||
'app-onboard-session-id': sessionId
|
||||
'created-at': createdAt
|
||||
environment: environmentName
|
||||
'deployed-by': deployedBy
|
||||
}
|
||||
|
||||
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
|
||||
name: 'rg-${environmentName}'
|
||||
location: location
|
||||
tags: tags
|
||||
}
|
||||
|
||||
// ⛔ scope: rg (symbolic) — creates implicit dependsOn. Do NOT use resourceGroup(name) — it races against RG creation.
|
||||
module resources './modules/resources.bicep' = {
|
||||
name: 'resources'
|
||||
scope: rg
|
||||
params: {
|
||||
location: location
|
||||
environmentName: environmentName
|
||||
tags: tags
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## main.parameters.json
|
||||
|
||||
> ⛔ **ARM JSON only.** Do NOT use `.bicepparam` syntax (`using`, `param`, `readEnvironmentVariable()`). AppOnboard deploys via `az deployment sub create` (subscription-scope default) — not `azd` — and `.bicepparam` requires azd or newer tooling. If the user lacks subscription-level permissions, the deploy phase falls back to `az deployment group create` automatically.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"environmentName": { "value": "{project}-{env}" },
|
||||
"location": { "value": "{region}" },
|
||||
"sessionId": { "value": "{context.json.sessionId}" },
|
||||
"deployedBy": { "value": "{context.json.azure.userDisplayName}" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Naming Convention (Bicep)
|
||||
|
||||
The prepare phase generates a logical resource prefix in `prepare-plan.json.naming.resourcePrefix` (e.g., `myapp-dev`). Scaffold MUST add a globally unique suffix using Bicep's `uniqueString()` function to prevent cross-deployment name collisions on globally unique Azure resources (App Service, Storage Account, ACR).
|
||||
|
||||
```bicep
|
||||
// main.bicep — derive unique suffix from resource group
|
||||
var nameSuffix = uniqueString(resourceGroup().id)
|
||||
|
||||
// Pass unique names to modules
|
||||
param appName string = 'app-${resourcePrefix}-${take(nameSuffix, 4)}'
|
||||
param storName string = 'st${replace(resourcePrefix, '-', '')}${take(nameSuffix, 4)}'
|
||||
param acrName string = 'cr${replace(resourcePrefix, '-', '')}${take(nameSuffix, 4)}'
|
||||
```
|
||||
|
||||
> ⛔ **Do NOT use `uniqueString()` for secrets** — it is deterministic and predictable. See [bicep-patterns-security.md](bicep-patterns-security.md) § Secrets for correct secret patterns.
|
||||
|
||||
If `prepare-plan.json.naming.resources[]` provides pre-computed names with suffixes, prefer those — but ALWAYS ensure globally unique resources include a `uniqueString()` or equivalent hash in main.bicep as a safety net.
|
||||
|
||||
## Log Analytics Module Output
|
||||
|
||||
> ⛔ **Output the resource ID (`.id`), NOT `.properties.customerId`.** Container Apps Environment requires `workspaceResourceId` (the full ARM resource ID). `.properties.customerId` is the GUID used for queries — passing it as `workspaceResourceId` causes an ARM deploy failure (`BadRequest`). Separate the two outputs:
|
||||
> ```bicep
|
||||
> output workspaceId string = logAnalyticsWorkspace.id // ARM resource ID — for CAE, App Insights
|
||||
> output workspaceCustomerId string = logAnalyticsWorkspace.properties.customerId // GUID — for Log Analytics queries only
|
||||
> ```
|
||||
|
||||
## Compute-Target Patterns
|
||||
|
||||
Read the file(s) matching the service mapping — load only what's needed:
|
||||
- **Functions on Flex Consumption** (the default Functions floor): [bicep-functions-flex.md](bicep-functions-flex.md) — `functionAppConfig`, MI-based deployment storage, forbidden App Service settings. ⛔ Do NOT use the App Service module for a Flex function app.
|
||||
- **App Service (and Functions on Premium/EP1):** [bicep-app-service.md](bicep-app-service.md) — module template, SCM/FTP auth, native module deploy strategy
|
||||
- **Container Apps:** [bicep-container-apps.md](bicep-container-apps.md) — two-phase ACR wiring, ingress, secretRef, image parameter, multi-container DNS
|
||||
- **Static Web Apps:** [bicep-swa.md](bicep-swa.md) — module template, detached deploy rule
|
||||
|
||||
Load multiple only if the plan includes multiple compute targets.
|
||||
|
||||
> ⛔ **F1/D1 SKU: do NOT generate a Dockerfile.** If `prepare-plan.json` specifies F1 or D1 (free/shared tier), use the platform's built-in runtime stack (e.g., `NODE|20-lts` for Node.js, `PYTHON|3.12` for Python). Dockerfiles are for B1+ or Container Apps only.
|
||||
|
||||
> ⛔ **Native module deploy strategy.** If `prepare-plan.json.deployStrategy` exists, read [bicep-app-service.md § Native Module Deploy Strategy](bicep-app-service.md) and apply the startup command + app settings. `deployStrategy.startupCommand` → `appCommandLine`, `deployStrategy.requiredAppSettings` → `appSettings[]`. When no `deployStrategy` exists, do NOT set `appCommandLine`.
|
||||
|
||||
## Cross-Origin (CORS) Between Frontend and Backend
|
||||
|
||||
> ⛔ **If the frontend is served from a different hostname than the API, the API MUST declare the frontend
|
||||
> origin.** A Static Web App calling a Function App / App Service is *always* cross-origin — different
|
||||
> hostname means different origin, so the browser sends a preflight `OPTIONS` and blocks the response
|
||||
> unless the API returns `Access-Control-Allow-Origin`. A backend deployed with no `cors` block fails
|
||||
> **every** browser call with:
|
||||
> `No 'Access-Control-Allow-Origin' header is present on the requested resource`.
|
||||
> The API works fine from `curl` and from the portal test console, so this surfaces only in the browser.
|
||||
|
||||
> ⛔ **Local success proves nothing here.** `local.settings.json` has `"Host": { "CORS": "*" }`, but that
|
||||
> file configures the **local Functions emulator only** — it is git-ignored and never deployed. F5 working
|
||||
> is not evidence that the deployed API allows the deployed frontend.
|
||||
|
||||
Emit `cors` on the backend's `siteConfig`, alongside the frontend resource in the same module:
|
||||
|
||||
```bicep
|
||||
resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
|
||||
properties: {
|
||||
siteConfig: {
|
||||
cors: {
|
||||
// staticSites → `defaultHostname`; the value has no scheme, so add https://
|
||||
allowedOrigins: [ 'https://${staticWebApp.properties.defaultHostname}' ]
|
||||
supportCredentials: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the frontend and backend are in **different modules**, pass the origin instead of referencing across.
|
||||
Guard with `empty()` so the same module still compiles for an API with no browser frontend:
|
||||
|
||||
```bicep
|
||||
// api module
|
||||
param allowedOrigin string = ''
|
||||
...
|
||||
siteConfig: {
|
||||
cors: {
|
||||
allowedOrigins: empty(allowedOrigin) ? [] : [ allowedOrigin ]
|
||||
supportCredentials: false
|
||||
}
|
||||
}
|
||||
|
||||
// frontend module
|
||||
output defaultHostname string = staticWebApp.properties.defaultHostname
|
||||
|
||||
// main.bicep
|
||||
module api './modules/api.bicep' = {
|
||||
params: { allowedOrigin: 'https://${frontend.outputs.defaultHostname}' }
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ **A symbolic reference only compiles inside the file that declares it.** `staticWebApp.properties...`
|
||||
> fails `bicep build` with **`BCP057: The name "staticWebApp" does not exist in the current context`**
|
||||
> whenever the SWA lives in another module — or when there is no frontend at all. Use the direct reference
|
||||
> only in the file that declares both; otherwise use the `allowedOrigin` param form above. For an API with
|
||||
> no browser frontend, leave `allowedOrigin` empty (or omit `cors` entirely).
|
||||
|
||||
> ⛔ **Property casing differs by resource type.** `Microsoft.Web/staticSites` exposes
|
||||
> `properties.defaultHostname` (lowercase **n**); `Microsoft.Web/sites` exposes `properties.defaultHostName`
|
||||
> (capital **N**). Using the wrong one fails at `bicep build` — verify against the resource you are actually
|
||||
> referencing when the frontend is an App Service rather than a SWA.
|
||||
|
||||
> ⛔ **`allowedOrigins: ['*']` cannot be combined with `supportCredentials: true`** — Azure rejects the pair.
|
||||
> Always emit the explicit origin; it is required anyway the moment cookie or bearer auth is added, and it
|
||||
> avoids re-work. Include any custom domain as an additional entry when one is configured.
|
||||
|
||||
**No dependency cycle.** The frontend receives the API URL at **build** time (`VITE_API_BASE_URL` /
|
||||
`NEXT_PUBLIC_API_URL`, baked into the bundle from `deploy-result.json.endpoints[]` — see
|
||||
[deploy-checklist-template.md](../../deploy/references/deploy-checklist-template.md)), so the frontend
|
||||
resource never references the backend in Bicep. The backend may therefore reference the frontend hostname
|
||||
freely. Do **not** add the API URL to the SWA resource to "pair" them — that is what creates a cycle.
|
||||
|
||||
**Alternative that removes CORS entirely:** linking an existing Function App as an SWA backend makes the
|
||||
frontend call `/api/*` on its own origin, so no preflight occurs. This is allowed only when
|
||||
`context.json.overrides[]` records explicit user approval for `linkFunctionsToStaticWebApp`. The Function
|
||||
App remains a separate resource and uses the Functions deployment channel. Never move or copy its source
|
||||
into an SWA API directory, emit SWA `buildProperties.apiLocation`, or change the backend's runtime or
|
||||
authentication provider to `azureStaticWebApps`.
|
||||
|
||||
**Self-review check:** If the plan has a browser frontend AND a separate HTTP API resource, verify the API's
|
||||
`siteConfig.cors.allowedOrigins` contains the frontend origin. **FLAGGED** if absent — the app will deploy
|
||||
successfully and then fail on first use.
|
||||
|
||||
## Service Tagging
|
||||
|
||||
> ⛔ **You MUST read [iac-generation-rules.md § Session Tags](iac-generation-rules.md).** All resources MUST include the 5 AppOnboard session tags. Pass `tags` object from `main.bicep` into every module.
|
||||
|
||||
## API Version Policy
|
||||
|
||||
Use the latest stable API version for each resource type. Never use preview APIs unless required for a feature with no GA alternative. Validate via `bicep build` — stale API versions produce warnings.
|
||||
|
||||
## App-Internal Secret Syntax (No Key Vault)
|
||||
|
||||
⛔ **No Key Vault is created.** App-internal secrets (e.g. `SECRET_KEY`) are stored on the compute resource from an `@secure()` param at deploy time. App Service → `siteConfig.appSettings`; Container Apps → native `secrets: [{ name, value }]` + `secretRef` (see [bicep-container-apps.md](bicep-container-apps.md)). Never use `@Microsoft.KeyVault(...)` / `keyVaultUrl`. See [bicep-patterns-security.md](bicep-patterns-security.md) § Secrets. For Terraform, see [terraform-patterns.md](terraform-patterns.md).
|
||||
Reference in New Issue
Block a user