mirror of
https://github.com/kerberos-io/helm-charts.git
synced 2026-10-19 17:38:27 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774ef07d43 |
@@ -0,0 +1,36 @@
|
||||
name: Workflows queue and identity consistency
|
||||
|
||||
# Fails the build if the analysis producer, the workflows engine and the stage
|
||||
# workers would render onto different WORKFLOWS_QUEUE names — the silent
|
||||
# producer/consumer queue-name drift that leaves runs piling up with no
|
||||
# consumer. Also validates stable workflow identities and API/engine definition
|
||||
# consistency. Pure `helm template` render checks, no cluster required.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'charts/hub/**'
|
||||
- 'scripts/check-workflows-queue-consistency.sh'
|
||||
- 'scripts/check-workflow-configuration.sh'
|
||||
- '.github/workflows/workflows-queue-consistency.yaml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
with:
|
||||
version: v3.16.2
|
||||
|
||||
- name: Check workflow queue and identity consistency
|
||||
run: ./scripts/check-workflows-queue-consistency.sh charts/hub
|
||||
|
||||
- name: Check workflow configuration and classification catalog
|
||||
run: bash scripts/check-workflow-configuration.sh charts/hub
|
||||
@@ -16,7 +16,7 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.163.0
|
||||
version: 0.151.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
|
||||
+52
-501
@@ -76,7 +76,7 @@ The inline examples use the following order, leaving gaps of ten:
|
||||
|
||||
Enable the annotation in each service's existing environment values section.
|
||||
No separate example values file is required. These annotations do not change
|
||||
image tags or enable disabled services such as sprite or dominantColor.
|
||||
image tags or enable disabled services such as sprite.
|
||||
Deploy the compatible analysis bridge before replying workers, and select the
|
||||
fixed counting release; waves do not enforce minimum compatible image versions.
|
||||
The chart's default image tags are not a compatibility-tested rollout bundle.
|
||||
@@ -99,51 +99,6 @@ workflow engine. These deployment-global, ops-managed workflows are read-only
|
||||
through the API; manage them in Helm values. There are no per-project on/off
|
||||
overrides.
|
||||
|
||||
To run a global workflow on specific devices across organisations, select them
|
||||
by system tag. Enable `deviceTags.system` and define the tags in its catalog;
|
||||
owners assign them to devices in Hub (only catalog tags can be assigned):
|
||||
|
||||
```yaml
|
||||
deviceTags:
|
||||
system:
|
||||
enabled: true
|
||||
catalog:
|
||||
- key: anpr-pilot
|
||||
label: ANPR pilot
|
||||
description: Runs licence plate recognition on this camera
|
||||
|
||||
kerberoshub:
|
||||
workflows:
|
||||
definitions:
|
||||
anpr-pilot:
|
||||
id: "6720f3a1c2d4e5f60718293a"
|
||||
enabled: true
|
||||
triggers:
|
||||
- type: automatic
|
||||
systemTags: ["anpr-pilot"]
|
||||
stages:
|
||||
- operation: anpr
|
||||
```
|
||||
|
||||
Every tag a trigger selects must be in the catalog; otherwise rendering fails,
|
||||
so a typo cannot silently match no device. Because owners set system tags, any
|
||||
owner could enroll devices into such a workflow, so `deviceTags.system.enabled`
|
||||
is `false` by default. When it is on, the catalog is rendered into the
|
||||
`device-system-tags` ConfigMap and mounted into Hub API
|
||||
(`DEVICE_SYSTEM_TAGS_FILE`), like the classification catalog. While it is off,
|
||||
Hub API gets no catalog (system tags cannot be set) and rendering fails on any workflow that mentions system
|
||||
tags anywhere (trigger `systemTags`, or a `device.systemTags` condition at any
|
||||
depth, in any key casing). Enable it only on deployments whose owners are
|
||||
trusted administrators (self-hosted), never on multi-tenant SaaS.
|
||||
|
||||
The engine reads keys case-insensitively and ignores unknown ones, so a typo
|
||||
could silently widen a workflow to every device. Workflow definitions may
|
||||
therefore only use the keys `id`, `enabled`, `triggers` and `stages`, and
|
||||
triggers only the trigger fields the engine knows, spelled exactly; anything
|
||||
else fails rendering. System-tag workflows also need a hub-workflows image
|
||||
that understands `systemTags` (uug-ai/hub-workflows#87 or later): an older
|
||||
engine drops the field and would run the workflow on every device.
|
||||
|
||||
Supply an explicit, stable `id` for new definitions:
|
||||
|
||||
```yaml
|
||||
@@ -190,6 +145,12 @@ explicit/name-derived. Validation includes disabled definitions and runs even
|
||||
when the engine is disabled, so identities remain reserved. Omit the `id` key
|
||||
entirely to use the legacy fallback; invalid explicit IDs never fall back.
|
||||
|
||||
Run the render-only queue and identity checks (Helm and Python 3 required):
|
||||
|
||||
```sh
|
||||
./scripts/check-workflows-queue-consistency.sh charts/hub
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Below all configuration options and parameters are listed.
|
||||
@@ -230,7 +191,6 @@ kerberospipeline:
|
||||
| `mongodb.authenticationMechanism` | MongoDB authentication mechanism (for example `SCRAM-SHA-256`). | `"SCRAM-SHA-256"` |
|
||||
| `mongodb.username` | MongoDB user account, we are using in the hub installation `'root'`. | `"yourusername"` |
|
||||
| `mongodb.password` | MongoDB user password, by default `'yourmongodbpassword'` | `"yourpassword"` |
|
||||
| `mongodb.appNamePerService` | When `mongodb.uri` is set, override its `appName` per workload with the service name (for example `hub-api`, `hub-pipeline-monitor`), so the MongoDB Atlas query profiler shows which service issued each operation. | `true` |
|
||||
| `mongodb.retryWrites` | Enable or disable MongoDB retryable writes. | `"true"` |
|
||||
| `mongodb.flavor` | Backend engine flavor: `"mongodb"` (native MongoDB / Atlas) or `"documentdb"` (AWS DocumentDB). The `documentdb` flavor disables features DocumentDB does not support (geospatial queries/indexes, complex `$lookup` pipelines). When set to `documentdb`, also set `mongodb.retryWrites: "false"`. | `"mongodb"` |
|
||||
| `mongodb.tls.enabled` | Enable TLS for MongoDB connections. When `mongodb.uri` is set, the chart appends missing `tls=true` and `tlsCAFile` query parameters. | `false` |
|
||||
@@ -268,8 +228,6 @@ kerberospipeline:
|
||||
| `opentelemetry.enabled` | Enable or disable OpenTelemetry instrumentation. | `false` |
|
||||
| `opentelemetry.routingEnabled` | Enable or disable OpenTelemetry routing/export behavior. | `false` |
|
||||
| `opentelemetry.collector.endpoint` | OpenTelemetry collector endpoint used for trace export. | `"http://otel-collector:4317"` |
|
||||
| `opentelemetry.sampler.type` | Trace sampler, exported as `OTEL_TRACES_SAMPLER` (e.g. `parentbased_traceidratio`). Empty keeps the SDK default (sample everything). | `""` |
|
||||
| `opentelemetry.sampler.arg` | Sampler argument, exported as `OTEL_TRACES_SAMPLER_ARG` (e.g. `"0.1"` for ~10% of traces). | `""` |
|
||||
| `openai.enabled` | Enable or disable OpenAI-backed semantic features. | `false` |
|
||||
| `openai.apikey` | OpenAI API key used when OpenAI integration is enabled. | `"xxx"` |
|
||||
| `kerberosvault.uri` | The default Kerberos Vault uri (you can add multiple within the app), by default `'https://api.storage.yourdomain.com'` | `"https://api.vault.yourdomain.com"` |
|
||||
@@ -314,7 +272,6 @@ kerberospipeline:
|
||||
| `kerberoshub.api.jwtSecret` | A secret that is for generating JWT tokens. | `"this-is-a-secret-please-change-to-random-string"` |
|
||||
| `kerberoshub.api.schema` | The protocol to serve the Kerberos Hub API, `'http'` or `'https'`. | `"https"` |
|
||||
| `kerberoshub.api.url` | The Kerberos Hub API ingress to access the API. | `"api.yourdomain.com"` |
|
||||
| `kerberoshub.api.corsAllowedOrigins` | Additional complete HTTP(S) origins allowed to call the Hub API. Configured frontend origins remain allowed automatically. | `[]` |
|
||||
| `kerberoshub.api.disableAgentHeartbeat` | Temporarily stop serving the polled `GET /devices` list (hub-api returns `204` without database reads). Devices still load once through `/session/bootstrap`; online/offline status stops refreshing. | `false` |
|
||||
| `kerberoshub.api.mediaProxy.enabled` | Serve media through hub-api (`/media/proxy`) instead of handing out Vault/storage URLs, hiding the storage endpoint from clients. | `false` |
|
||||
| `kerberoshub.api.mediaProxy.publicUrl` | Public hub-api base URL used in proxied media URLs. Defaults to `{schema}://{url}`. | `""` |
|
||||
@@ -377,7 +334,6 @@ kerberospipeline:
|
||||
| `kerberoshub.frontend.sentry.url` | No longer used. | `"https://xxx@sentry.io/xxx"` |
|
||||
| `kerberoshub.frontend.stripe.publicKey` | Public key for `kerberoshub.frontend.stripe`. | `""` |
|
||||
| `kerberoshub.frontend.googlemaps.apikey` | Within Kerberos Hub frontend a couple of maps are being used, the google maps is leveraged for that. | `"xxxx"` |
|
||||
| `kerberoshub.frontend.googlemaps.placesApi` | Google Places API used for address autocomplete with the same `apikey`: `"legacy"` (Places API) or `"new"` (Places API (New), required for keys that cannot enable the legacy Places API). | `"legacy"` |
|
||||
| `kerberoshub.frontend.zendesk.url` | No longer used. | `"yourdomain.zendesk.com"` |
|
||||
| `kerberoshub.frontend.posthog.key` | The API key retrieved from the Posthog instance. | `"xxx"` |
|
||||
| `kerberoshub.frontend.posthog.url` | Posthog's endpoint (http/https). | `"https://posthog.domain.com"` |
|
||||
@@ -404,7 +360,6 @@ kerberospipeline:
|
||||
| `kerberoshub.frontend.features.i18n.defaultLanguage` | Default language code used by the front-end (e.g. `en`, `nl`, `pl`, `tr`, `fr`, `sv`, `de`). | `"en"` |
|
||||
| `kerberoshub.frontend.features.workflows.enabled` | Enable or disable the workflows feature in the frontend. | `"false"` |
|
||||
| `kerberoshub.frontend.features.audit.enabled` | Enable or disable the owner/admin Audit events page and navigation link. | `"true"` |
|
||||
| `kerberoshub.frontend.features.dayOverview.enabled` | Enable or disable the Day overview page and navigation menu. Role page permissions still apply. | `"true"` |
|
||||
| `kerberoshub.frontend.features.organisations.enabled` | Enable or disable the organisation feature family, including projects. When empty, child groups apply independently. | `""` |
|
||||
| `kerberoshub.frontend.features.organisations.switcherEnabled` | Enable or disable the organisation dropdown and switching. The current organisation remains visible when disabled. | `"false"` |
|
||||
| `kerberoshub.frontend.features.organisations.creationEnabled` | Enable or disable organisation creation. Requires organisation switching to be enabled. | `"false"` |
|
||||
@@ -506,7 +461,6 @@ kerberospipeline:
|
||||
| `kerberoshub.cleanup.globalPassDeleteBudget` | Max documents deleted during a global pass. | `"0"` |
|
||||
| `kerberoshub.cleanup.defaultTaskRetentionDays` | Default retention (in days) applied to tasks without an explicit `retention_days`. Tasks older than this (anchored on `creation_date`) are deleted with their `case_media` rows. Set to `"0"` or a negative value to keep tasks forever. Must match `kerberoshub.api.defaultTaskRetentionDays`. | `"0"` |
|
||||
| `kerberoshub.cleanup.auditEventRetentionDays` | Deployment-wide audit-event retention in days, independent of recording plans, inactive-account cleanup, and the optional global pass. Set to `"0"` or a negative value to keep audit events indefinitely. | `"400"` |
|
||||
| `kerberoshub.cleanup.workflowRunRetentionDays` | Deployment-wide workflow run retention in days, anchored on run execution time (`start`). Applied on top of media-linked retention (the stricter cutoff wins) and independent of the optional global pass. Set to `"0"` or a negative value to let runs expire only with their media. | `"0"` |
|
||||
| `kerberoshub.cleanup.resources.requests.memory` | Memory request for `kerberoshub.cleanup`. | `"10Mi"` |
|
||||
| `kerberoshub.cleanup.resources.requests.cpu` | CPU request for `kerberoshub.cleanup`. | `"10m"` |
|
||||
| `kerberoshub.audit.repository` | Hub audit service container image repository. | `"ghcr.io/uug-ai/hub-audit"` |
|
||||
@@ -557,6 +511,7 @@ kerberospipeline:
|
||||
| `kerberospipeline.monitor.tag` | The Docker image tag/version. | `"v1.3.9"` |
|
||||
| `kerberospipeline.monitor.replicas` | Number of replicas for `kerberospipeline.monitor`. | `1` |
|
||||
| `kerberospipeline.monitor.logLevel` | Monitor log level. Set to `debug` for per-event processing checkpoints. | `"info"` |
|
||||
| `kerberospipeline.monitor.latestMediaUpdateIntervalSeconds` | Minimum recording-time gap, in seconds, between writes of a device's `latestMedia` snapshot. Reduces writes to the devices collection; the device's latest recording can lag by up to this interval. `0` writes on every recording. | `300` |
|
||||
| `kerberospipeline.monitor.resources.requests.memory` | Memory request for `kerberospipeline.monitor`. | `"10Mi"` |
|
||||
| `kerberospipeline.monitor.resources.requests.cpu` | CPU request for `kerberospipeline.monitor`. | `"10m"` |
|
||||
| `kerberospipeline.sequence.repository` | The [sequencer microservice](https://doc.kerberos.io/hub/pipeline/#sequencer), grouping recordings in chunks/groups. | `"ghcr.io/uug-ai/hub-pipeline-sequence"` |
|
||||
@@ -592,12 +547,8 @@ kerberospipeline:
|
||||
| `kerberospipeline.analysis.tag` | The Docker image tag/version. | `"v1.7.8"` |
|
||||
| `kerberospipeline.analysis.replicas` | Number of replicas for `kerberospipeline.analysis`. | `1` |
|
||||
| `kerberospipeline.analysis.logLevel` | Log verbosity level for `kerberospipeline.analysis`. | `"info"` |
|
||||
| `kerberospipeline.analysis.thumbnailRegeneration.enabled` | Regenerate thumbnails from the first frame of a classified moving object (`THUMBNAIL_REGENERATION_ENABLED`). Adds one thumbnail job per classified recording. Requires an analysis image that reads the flag. | `false` |
|
||||
| `kerberospipeline.analysis.dbCache.enabled` | Cache hot-path MongoDB lookups (site vault per device) in memory. Site changes apply after at most one TTL. | `false` |
|
||||
| `kerberospipeline.analysis.dbCache.ttl` | Lifetime of a cached lookup, as a Go duration (e.g. `"30s"`, `"5m"`). | `"5m"` |
|
||||
| `kerberospipeline.analysis.resources.requests.memory` | Memory request for `kerberospipeline.analysis`. | `"10Mi"` |
|
||||
| `kerberospipeline.analysis.resources.requests.cpu` | CPU request for `kerberospipeline.analysis`. | `"10m"` |
|
||||
| `kerberospipeline.dominantColor.enabled` | Deploy `pipe-dominantcolor` and let the analysis stage schedule it (`DOMINANTCOLOR_ENABLED`). Requires an analysis image that reads the flag. | `false` |
|
||||
| `kerberospipeline.dominantColor.repository` | The dominant color microservices is computing a top 3 color histogram. | `"ghcr.io/uug-ai/hub-pipeline-dominantcolors"` |
|
||||
| `kerberospipeline.dominantColor.pullPolicy` | The Docker registry pull policy. | `"IfNotPresent"` |
|
||||
| `kerberospipeline.dominantColor.tag` | The Docker image tag/version. | `"v2.0.2"` |
|
||||
@@ -611,8 +562,6 @@ kerberospipeline:
|
||||
| `kerberospipeline.thumbnail.pullPolicy` | The Docker registry pull policy. | `"IfNotPresent"` |
|
||||
| `kerberospipeline.thumbnail.tag` | The Docker image tag/version. | `"v1.3.4"` |
|
||||
| `kerberospipeline.thumbnail.replicas` | Number of replicas for `kerberospipeline.thumbnail`. | `2` |
|
||||
| `kerberospipeline.thumbnail.prefetchCount` | Max unacknowledged RabbitMQ messages per pod (`RABBITMQ_PREFETCH_COUNT`); `0` keeps the service default of 5. Requires hub-pipeline-thumbnail ≥ v1.3.15. | `0` |
|
||||
| `kerberospipeline.sprite.prefetchCount` | Max unacknowledged RabbitMQ messages per pod (`RABBITMQ_PREFETCH_COUNT`); `0` keeps the service default of 5. Requires hub-pipeline-sprite ≥ v1.1.21. | `0` |
|
||||
| `kerberospipeline.thumbnail.logLevel` | Log verbosity level for `kerberospipeline.thumbnail`. | `"info"` |
|
||||
| `kerberospipeline.thumbnail.quality` | Configuration value for `kerberospipeline.thumbnail.quality`. | `"1"` |
|
||||
| `kerberospipeline.thumbnail.width` | Configuration value for `kerberospipeline.thumbnail.width`. | `"600"` |
|
||||
@@ -718,354 +667,66 @@ As mentioned during the Post installation step, you'll import some `.nosql` file
|
||||
Within the Kerberos Hub front-end you'll see the option to filter through classifications. This filtered is stored in the `settings` collection. By changing the entries of the `classifications` object, you can add, edit or remove items from the filters.
|
||||
|
||||
The default shared classification list lives in
|
||||
[`templates/configMaps/catalogs/classifications.yaml`](templates/configMaps/catalogs/classifications.yaml),
|
||||
independently of workflow contracts. The chart uses this bundled definition
|
||||
unless a deployer defines the catalog inline or provides a ConfigMap override. Each entry contains the stable classifier output
|
||||
[`configuration/catalogs/classifications.json`](configuration/catalogs/classifications.json),
|
||||
independently of workflow contracts. Set `classificationCatalog` to a list to
|
||||
replace those defaults; unset or `null` uses the bundled file. Each entry contains the stable classifier output
|
||||
`key`, the user-facing `label`, and an `icon` key. Hub API exposes this catalog
|
||||
to alerts and filters. When the mounted catalog is unavailable, compatible Hub
|
||||
API versions fall back to the legacy `settings` document and then the built-in
|
||||
classification list. Set `classificationCatalogEnabled: false` to omit the
|
||||
ConfigMap and make Hub API use that fallback directly.
|
||||
|
||||
The simplest way to customize the catalog is to define the complete list inline
|
||||
in your values (restored in chart **0.162.0** after its removal in 0.155.0):
|
||||
|
||||
```yaml
|
||||
classificationCatalog:
|
||||
- key: car
|
||||
label: Car
|
||||
icon: car
|
||||
- key: forklift
|
||||
label: Forklift
|
||||
```
|
||||
|
||||
The list replaces the bundled defaults entirely (entries are not merged) and is
|
||||
rendered into the chart's `classification-catalog` ConfigMap, so Hub API and the
|
||||
bundled `catalogs/classifications.json` of workflow packaging use the same list
|
||||
and a change rolls the API pod. Each entry needs a `key` and `label`, `icon` is
|
||||
optional, and keys must be unique. Omitting the key (or `null`) keeps the bundled
|
||||
defaults; `[]` is rejected, so use `classificationCatalogEnabled: false` for the
|
||||
API fallback. An inline list cannot be combined with
|
||||
`classificationCatalogEnabled: false`, `classificationCatalogExistingConfigMap` or
|
||||
a per-file catalog replacement.
|
||||
|
||||
Alternatively, create a ConfigMap in the release namespace containing the complete
|
||||
JSON array, then use the per-file replacement interface:
|
||||
|
||||
```yaml
|
||||
classificationCatalogEnabled: true
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
includeDefaults: true
|
||||
configMaps:
|
||||
- name: hub-classification-catalog
|
||||
items:
|
||||
- key: classifications.json
|
||||
path: catalogs/classifications.json
|
||||
replace: true
|
||||
classificationCatalog:
|
||||
- key: forklift
|
||||
label: Forklift
|
||||
icon: vehicle
|
||||
```
|
||||
|
||||
Deploy that ConfigMap before the chart upgrade. The bundled list remains the
|
||||
default when no override is configured.
|
||||
### Workflow condition contracts (preparatory)
|
||||
|
||||
The existing `classificationCatalogExistingConfigMap: hub-classification-catalog`
|
||||
option remains supported, including for deployments without workflow packaging.
|
||||
It requires the key `classifications.json`. Do not combine it with a per-file
|
||||
catalog replacement. Helm does not create or checksum either kind of external
|
||||
catalog. With bundled workflow packaging, the API mount and Start metadata
|
||||
reference the same effective ConfigMap. With `includeDefaults: false`, map any
|
||||
required catalog explicitly through `configMaps`; the API's catalog selection
|
||||
remains independent.
|
||||
Disabling `classificationCatalogEnabled` disables the API catalog mount regardless
|
||||
of this setting. The external ConfigMap and key must exist before the API pod can
|
||||
start. Manage updates and any required rollout/reload through deployment tooling;
|
||||
Helm cannot inspect or validate its contents. The catalog schema remains available
|
||||
for authoring validation.
|
||||
|
||||
### Workflow editor contracts
|
||||
|
||||
This chart defines the v1 contract format and opt-in file packaging.
|
||||
**Consuming contracts requires compatible Hub API and frontend versions.**
|
||||
Enabling packaging only mounts files; it neither enables an editor feature nor
|
||||
registers workers or changes execution. Compatible APIs use validated contracts
|
||||
to gate custom-editor node availability, together with worker routing.
|
||||
|
||||
Deployers describe what a node exposes; Hub code owns what that means:
|
||||
|
||||
| Contract controls | Code owns |
|
||||
| --- | --- |
|
||||
| `presentation`: name, description, approved icon, colour token | Widgets, layout and theme colours |
|
||||
| `params`: settings authors enter on a worker stage | Validation, secret masking and storage |
|
||||
| `fields`: Start selectors and typed stage outputs, labels, order, choices, bounds | Operator semantics, authorized resource lookups, Start's bindings |
|
||||
| Start `controls` and `triggerModes` offered for new authoring | Schedule, condition and trigger execution |
|
||||
|
||||
Worker images, queues, resources and credentials stay in `kerberoshub.services`.
|
||||
No new loader environment variable is set.
|
||||
|
||||
Conditions are not free-form: a new or changed predicate on a connection may
|
||||
only test a declared field of Start or of a stage upstream of that connection
|
||||
(stage outputs are addressed as `results.<stage>.<path>`), with one of its
|
||||
declared operators and a value of its type, within its bounds and, for equality
|
||||
on enumerated choices, one of its options. Predicates already stored keep
|
||||
running and can be removed, but not edited.
|
||||
This chart defines the initial v1 metadata format and opt-in file packaging.
|
||||
**Hub API and the frontend do not yet consume these contracts.** Enabling the
|
||||
packaging only mounts files; it neither enables an editor feature nor registers
|
||||
workers or changes execution. No new loader environment variable is set.
|
||||
|
||||
The earlier device-bound workflow configurations were development fixtures, not
|
||||
a recommended deployment model. Charts describe capabilities, not selected
|
||||
devices, project IDs, concrete workflow edges, or user-authored conditions.
|
||||
Device filtering is optional. Saved selections belong in individual workflows.
|
||||
|
||||
ConfigMap templates live together under `templates/configMaps/`, with bundled
|
||||
catalogs and workflow contracts in their own subdirectories:
|
||||
The configuration is split into independent shared catalogs and their consumers:
|
||||
|
||||
```text
|
||||
templates/configMaps/
|
||||
configmap-mongodb.yaml
|
||||
configmap-hub-audit.yaml
|
||||
configuration/
|
||||
catalogs/
|
||||
classifications.yaml
|
||||
classifications.json
|
||||
workflow-contracts/
|
||||
start.yaml
|
||||
anpr.yaml
|
||||
forwarder.yaml
|
||||
schemas/
|
||||
catalog.schema.json
|
||||
workflow-contract.schema.json
|
||||
```
|
||||
|
||||
- [Start contract ConfigMap](templates/configMaps/workflow-contracts/start.yaml): current device,
|
||||
site, group and initial-classification presets, schedule/condition controls and
|
||||
both trigger modes.
|
||||
- [ANPR contract ConfigMap](templates/configMaps/workflow-contracts/anpr.yaml):
|
||||
presentation plus plate/read-status and OCR metadata from the ANPR worker; no
|
||||
catalog required. It declares no `params`, so any settings come from
|
||||
`kerberoshub.workflows.editorStages`.
|
||||
- [Forwarder contract ConfigMap](templates/configMaps/workflow-contracts/forwarder.yaml):
|
||||
presentation and required per-node delivery and RabbitMQ destination settings
|
||||
for custom workflows. Deployment-global workflows keep their deployment
|
||||
bindings. No output fields are declared.
|
||||
- [Start contract](configuration/workflow-contracts/start.yaml): current device,
|
||||
site, group and initial-classification presets, plus schedule/condition controls.
|
||||
- [Custom pose example](examples/workflow-contracts/pose.yaml): hypothetical
|
||||
worker, demonstrating presentation, settings (`params`), a file catalog, inline
|
||||
choices and a numeric field.
|
||||
worker output, demonstrating a file catalog, inline choices and a numeric field.
|
||||
This example does not deploy a worker or claim a universal pose-model format.
|
||||
- [Contract schema](schemas/workflow-contract.schema.json): format, source-name
|
||||
documentation and YAML editor completion for standalone contracts via the
|
||||
`$schema` comment.
|
||||
documentation and YAML editor completion via the `$schema` comment.
|
||||
- [Catalog schema](schemas/catalog.schema.json): shared JSON lists of
|
||||
`{key, label, icon?}`, compatible with the current classification file format.
|
||||
|
||||
The bundled contract and catalog templates define their payloads as named Helm
|
||||
templates and emit ConfigMaps when enabled. These are Kubernetes templates, not standalone contract
|
||||
files; Helm cannot access files under `templates/` through `.Files.Get`.
|
||||
Validate their rendered data against the schemas. The standalone custom examples
|
||||
remain plain YAML/JSON files.
|
||||
|
||||
Do not associate the contract schema with an entire Helm template or ConfigMap:
|
||||
it describes only the contract document inside `data`. The schemas are included
|
||||
in the chart package, but are not mounted into pods. Helm validates
|
||||
`values.schema.json`, not these payload schemas. Unique field IDs/catalog keys
|
||||
and resolvable catalog references also require semantic checks; a JSON Schema
|
||||
pass alone does not establish runtime compatibility.
|
||||
|
||||
#### Current bundled defaults
|
||||
|
||||
Start, ANPR, the forwarder and the shared classification catalog are bundled. The pose files
|
||||
under `examples/` are illustrative, not installed presets.
|
||||
|
||||
| Start field | Context path | Operators | Choices |
|
||||
| --- | --- | --- | --- |
|
||||
| `device` | `device.deviceKey` | `eq`, `in` | `project.devices` |
|
||||
| `sites` | `device.siteIds.*` | `in` | `project.sites` |
|
||||
| `groups` | `device.groupIds.*` | `in` | `project.groups` |
|
||||
| `classifications` | `inputs.classify.details.*.classified` | `in` | `../catalogs/classifications.json` |
|
||||
|
||||
These are editor condition presets, not worker deployment settings. Only
|
||||
classifications are marked optional: initial classification data may be absent.
|
||||
The bundled Start contract also declares two condition-only context fields,
|
||||
`deviceName` (`device.deviceName`) and `objectClass` (the classification path
|
||||
with `eq`/`ne`/`in`/`exists`). Other Start field IDs are not selectors; they must
|
||||
use paths under `device.` or `inputs.` and only extend what advanced conditions
|
||||
may test.
|
||||
|
||||
Site/group arrays can be empty. A contract may omit any of these fields, the
|
||||
`weeklySchedule`/`conditions` controls (omitted offers none) or a trigger mode
|
||||
(`triggerModes` omitted offers both). Omissions affect new authoring only:
|
||||
values already saved in workflows are kept, keep running and can be cleared.
|
||||
|
||||
The catalog preserves the API/frontend compatibility defaults: `animal`,
|
||||
`pedestrian`, `cyclist`, `motorbike`, `lorry`, `car`, `handbag`, `suitcase` and
|
||||
`cell phone`. It is not an exhaustive vocabulary for every detector. Keys are
|
||||
literal output/comparison values, not aliases: `motorbike` does not match
|
||||
`motorcycle`. Deployments using a different detector vocabulary must supply the
|
||||
appropriate complete catalog rather than relabeling keys silently.
|
||||
|
||||
##### ANPR: the stage result
|
||||
|
||||
The ANPR contract describes `results.anpr` as `models.WorkflowAnprResult`
|
||||
defines it: the engine groups hub-anpr's ingest blocks into one detection run
|
||||
(`detections`) with one track per vehicle, and one marker per vehicle
|
||||
(`markers`). Paths are relative to that result.
|
||||
|
||||
| ANPR field | Relative result path | Input |
|
||||
| --- | --- | --- |
|
||||
| `plate` | `detections.*.tracks.*.meta.plate` | Uppercase letters and digits without separators (`AB123CD`); empty when unread |
|
||||
| `unread` | `detections.*.tracks.*.meta.unread` | Boolean |
|
||||
| `vehicleClass` | `detections.*.tracks.*.meta.classifiedAs` | Classification label copied from classify (only `ANPR_PLATE_CLASSES`, by default `car`) |
|
||||
| `ocrConfidence` | `markers.*.metadata.confidence` | Number; only for read plates; about 0–1 for `fast`, 0.5 for `tesseract`, unclamped for `http` |
|
||||
| `ocrEngine` | `markers.*.metadata.engine` | `fast`, `tesseract`, `tesseract+opencv` or `http`; only for read plates |
|
||||
| `secondsVisible` | `markers.*.duration` | Whole seconds the vehicle was visible |
|
||||
|
||||
The panel offers "Plate contains" and "Plate could not be read", plus a
|
||||
collapsed Recognition section (minimum OCR confidence, vehicle class, seconds
|
||||
visible). `exists` is not offered: plate, unread and class are present for every
|
||||
vehicle, so it would always match.
|
||||
|
||||
Important output semantics:
|
||||
|
||||
- Wildcards fan out independently. Plate, unread and class are on the same
|
||||
track and can be tied to one vehicle with "Must match the same item"; OCR
|
||||
confidence and engine are on the markers and cannot be tied to a plate.
|
||||
- Vehicles removed by the movement and jump gates produce neither a track nor a
|
||||
marker, so the lists can be empty.
|
||||
- Track confidence is a constant 0.9 and is deliberately not offered.
|
||||
- Node settings (`params`) override the worker's `ANPR_*` environment variables
|
||||
per run: vehicle classes to read (`plateClasses`, a multiselect from the
|
||||
classification catalog), minimum movement, maximum jump, frames to read,
|
||||
agreeing reads required and minimum OCR confidence. Their defaults equal the
|
||||
worker's built-in defaults and are stored on nodes when a workflow is saved,
|
||||
so a deployment that changes those environment variables should change the
|
||||
contract defaults as well. Requires hub-anpr with node settings and an engine
|
||||
that dispatches settings to every stage.
|
||||
|
||||
ANPR metadata is bundled whenever default workflow packaging is enabled, even
|
||||
if its worker is disabled. It does not deploy the worker. Because a valid
|
||||
contract plus worker routing offers a stage, ANPR becomes authorable as soon as
|
||||
its worker queue is configured; replace or omit the bundled contract to prevent
|
||||
that.
|
||||
|
||||
The forwarder contract follows the same rule: the forwarder becomes authorable
|
||||
wherever its worker queue is configured. Replace or omit the bundled contract to
|
||||
prevent that.
|
||||
|
||||
Its nine required custom-workflow settings are `broker`,
|
||||
`mode`, `host`, `port` (whole number, 1-65535), `queue`, `vhost`, `tls`,
|
||||
`username` and `password`. The contract defaults `broker` to `rabbitmq`, `mode`
|
||||
to `delivered` and `tls` to `true`; those values are stored on save. Destination
|
||||
and credentials must be supplied before enabling a custom workflow. The password is a secret:
|
||||
the API masks it on reads and the engine includes it only in worker dispatches.
|
||||
Changing other node settings requires re-entering saved secrets.
|
||||
|
||||
Custom workflows never inherit deployment connection settings or credentials.
|
||||
They use their own RabbitMQ connection, verified TLS with system trust roots
|
||||
when enabled, and their own `<queue>.forwarder-dead-letter` queue. Incomplete
|
||||
custom settings fail closed even if a replacement contract makes them optional.
|
||||
Shared destinations and custom HTTPS webhooks are not supported in this phase.
|
||||
Deployment-global workflows continue using their configured RabbitMQ or HTTPS
|
||||
bindings without node overrides. `mode: callback` requires a
|
||||
configured callback base URL (`FORWARDER_CALLBACK_BASE_URL` for the
|
||||
environment-backed worker); the external service supplies its own Hub token.
|
||||
Custom invocations carry the run's inputs and results, not deployment-specific
|
||||
field mappings. TLS certificate and queue declaration policies are not node
|
||||
settings.
|
||||
|
||||
Deploy the engine that stamps the top-level `forwarderDestinationMode`
|
||||
(`deployment` or `custom`) before the enforcing worker. Missing or invalid mode
|
||||
is rejected; drain old unstamped dispatches before the worker upgrade. Update
|
||||
existing custom workflows with complete settings. The mode is engine-owned,
|
||||
not contract-defined, and the forwarder queue must accept trusted engine
|
||||
publishers only.
|
||||
|
||||
This chart source layout does not change the mounted layout:
|
||||
|
||||
```text
|
||||
/etc/kerberos/configuration/
|
||||
catalogs/
|
||||
classifications.json
|
||||
workflow-contracts/
|
||||
start.yaml
|
||||
anpr.yaml
|
||||
forwarder.yaml
|
||||
```
|
||||
|
||||
Each contract defines a `stage` type, a `schemaVersion`, a `contractVersion`,
|
||||
optional `presentation` and (worker stages only) `params`, and its own `fields`.
|
||||
`controls` and `triggerModes` are Start-only. `fields: []` is valid for a node without exposed condition
|
||||
fields; omitting the `fields` property is invalid. A field declares `id`, `label`, `path`, scalar `type` and
|
||||
Each contract defines a `stage` type, a `schemaVersion`, a `contractVersion` and
|
||||
its own `fields`. A field declares `id`, `label`, `path`, scalar `type` and
|
||||
supported `operators`; `optional`, numeric bounds and `choices` are optional.
|
||||
The `type` describes each wildcard-selected candidate, not the containing array.
|
||||
IDs must be unique within a contract. Increment `contractVersion` when changing
|
||||
its meaning/output shape; the future runtime must bind workflows to compatible
|
||||
versions rather than silently reinterpreting saved conditions.
|
||||
|
||||
#### Field paths and connection panels
|
||||
|
||||
Every contract path addresses the workflow condition root
|
||||
(`models.WorkflowConditionRootSchema`): Start fields use absolute paths, stage
|
||||
fields are relative to `results.<stage>`. A compatible API rejects Start paths
|
||||
that are not in that schema (for example a typo such as `device.devicename`)
|
||||
and lists the valid alternatives; worker-defined `inputs.<operation>` data and
|
||||
stage outputs are accepted as declared.
|
||||
|
||||
`panel` lays out the inputs on connections leaving a node. Sections place
|
||||
declared fields with an editor widget (`multiselect`, `select`, `text`,
|
||||
`number`, `toggle`), Start's weekly schedule (`controls: [weeklySchedule]`) or
|
||||
the Extra conditions catch-all (`picker: true`), which offers every declared
|
||||
field of Start and the stages before the connection. Connections also show the
|
||||
panels of earlier steps, collapsed. Contracts without a panel get a default one,
|
||||
so existing contracts keep working; on Start a panel replaces the top-level
|
||||
`controls` list. See the pose example and the contract schema for the full
|
||||
format.
|
||||
|
||||
#### Contract-gated custom editor
|
||||
|
||||
With a compatible API/frontend, adding or changing a custom-workflow stage node
|
||||
requires both of the following:
|
||||
|
||||
- Its operation has configured worker routing (normally from
|
||||
`kerberoshub.services.<operation>.queue`).
|
||||
- A valid supported contract for that operation is present under
|
||||
`workflow-contracts/`, and any referenced catalogs are valid.
|
||||
|
||||
The contract is the allow-list. `kerberoshub.workflows.editorStages` is optional
|
||||
metadata: a stage's name, description and params are used only where its
|
||||
contract omits `presentation` or `params`.
|
||||
|
||||
This is a configuration check, not a live worker-health check. The chart's queue
|
||||
catalog does not prove that a worker pod is running; deployers still manage the
|
||||
worker's enabled state and deployment.
|
||||
|
||||
Start is structural rather than a worker operation and always exists: every
|
||||
workflow has exactly one Start node and it needs no worker queue. Without a
|
||||
`workflow-contracts/start.yaml`, a compatible API applies a built-in default
|
||||
identical to the bundled one; a valid file replaces it entirely. An invalid or
|
||||
untrusted Start contract does not remove Start: it falls back to a restricted
|
||||
Start without selectors or controls and reports diagnostics in the editor, so a
|
||||
mistake never re-exposes fields the deployer meant to hide.
|
||||
|
||||
Enable `workflowConfiguration.enabled` and provide stage contracts before using
|
||||
contract-gated stages. The chart currently bundles Start, ANPR and the forwarder; other
|
||||
editor operations need deployer-supplied contracts. Leaving configuration
|
||||
disabled does not stop existing workflows: Start remains authorable with its
|
||||
built-in default, but no new stage nodes can be added unless their contracts
|
||||
are supplied through another configuration mount.
|
||||
|
||||
The contract-gating API currently supports schema and contract version 1. It
|
||||
reads configuration on demand, accepting direct YAML/JSON contract files and
|
||||
JSON catalogs inside the configured root, including Kubernetes projected-volume
|
||||
symlinks. Files are limited to 64 KiB, contracts to 128 fields and 64 params,
|
||||
inline choices to 256 values, and catalogs to 1024 entries. Unsupported versions,
|
||||
duplicate declarations, unreadable files and invalid references produce
|
||||
availability diagnostics, not implicit built-in replacements.
|
||||
|
||||
Previously saved nodes whose contracts become unavailable remain visible and
|
||||
keep their stored settings. They can be removed or repositioned, but their
|
||||
settings and connections cannot be changed while unavailable. Other compatible
|
||||
edits to the workflow remain possible. Removing a node also removes its incident
|
||||
connections. This authoring rule does not disable existing workflows, change
|
||||
manual/automatic execution, or change deployment-global workflow definitions.
|
||||
|
||||
The schemas describe data formats, not executable plugins. Contract availability
|
||||
does not yet provide a generic output-field editor for every stage.
|
||||
|
||||
#### Choice origins
|
||||
|
||||
Use exactly one of these, or omit `choices` for typed user input:
|
||||
@@ -1094,7 +755,8 @@ The supported system-resource vocabulary for v1 is:
|
||||
| `project.groups` | Authorized group names | Stable group IDs |
|
||||
|
||||
These are direct built-in API resolver identifiers, not database paths or
|
||||
deployer-selected authorization scopes. Consumers must reuse existing project/resource authorization and
|
||||
deployer-selected authorization scopes. They still require implementation in the
|
||||
future loader. Resolvers must reuse existing project/resource authorization and
|
||||
legacy-owner compatibility; metadata cannot grant access.
|
||||
|
||||
File references resolve relative to the mounted contract, within the shared
|
||||
@@ -1107,159 +769,48 @@ not owned by workflows. A library may ship a catalog without implementing an API
|
||||
#### Bundled and external packaging
|
||||
|
||||
`workflowConfiguration.enabled` defaults to `false`. With it enabled and
|
||||
`includeDefaults` omitted or `true`, the chart mounts the bundled Start/ANPR contracts and the
|
||||
`existingConfigMap` empty, the chart mounts the bundled Start contract and the
|
||||
**effective** classification list under `/etc/kerberos/configuration`, using
|
||||
the directory layout above. The projected volume reuses the existing
|
||||
classification ConfigMap, including `classificationCatalogExistingConfigMap`
|
||||
overrides, an inline `classificationCatalog` or explicit per-file replacements, rather
|
||||
classification ConfigMap, including `classificationCatalog` overrides, rather
|
||||
than storing another copy in the workflow ConfigMap.
|
||||
|
||||
The default classification ConfigMap name, mount path, `CLASSIFICATION_CATALOG_FILE`
|
||||
and API endpoint remain unchanged. An inline `classificationCatalog` list is
|
||||
rendered into that ConfigMap; `[]` is rejected. The current API
|
||||
still falls back to settings/built-in values for an empty/invalid mounted catalog. The preparatory
|
||||
The existing classification ConfigMap, mount path, `CLASSIFICATION_CATALOG_FILE`
|
||||
and API endpoint remain unchanged. `classificationCatalog: []` remains an
|
||||
explicit empty list, not a request for chart defaults; the current API falls
|
||||
back to settings/built-in values for an empty/invalid catalog. The preparatory
|
||||
bundle does not reproduce that runtime fallback and must not be treated as a
|
||||
resolved API catalog. The Start-specific loader shares the existing classification
|
||||
resolver; other consumers must preserve that consistency.
|
||||
Bundled packaging requires `classificationCatalogEnabled: true`; an
|
||||
resolved API catalog. The future loader must share the existing classification
|
||||
loader. Bundled packaging requires `classificationCatalogEnabled: true`; an
|
||||
external bundle can be packaged independently.
|
||||
|
||||
Custom deployers can maintain files in their own repository, create ConfigMaps
|
||||
in the release namespace, and use the unchanged chart. `configMaps` adds
|
||||
their files alongside the bundled Start/ANPR contracts and effective classification
|
||||
catalog. Catalogs can be supplied separately from contracts and mounted into
|
||||
other consumers without copying their contents. For example, from this
|
||||
Custom deployers can maintain files in their own repository, create a ConfigMap
|
||||
in the release namespace, and use the unchanged chart. For example, from this
|
||||
chart directory (replace the namespace):
|
||||
|
||||
```sh
|
||||
kubectl -n YOUR_NAMESPACE create configmap custom-workflow-contracts \
|
||||
--from-file=pose.yaml=examples/workflow-contracts/pose.yaml
|
||||
kubectl -n YOUR_NAMESPACE create configmap shared-pose-catalog \
|
||||
kubectl -n YOUR_NAMESPACE create configmap custom-workflow-configuration \
|
||||
--from-file=pose.yaml=examples/workflow-contracts/pose.yaml \
|
||||
--from-file=pose-keypoints.json=examples/catalogs/pose-keypoints.json
|
||||
```
|
||||
|
||||
Then use [the example values overlay](examples/workflow-configuration-values.yaml).
|
||||
Each `configMaps` entry has a `name` and `items` mapping ConfigMap keys to
|
||||
files in `workflow-contracts/` and `catalogs/`. A single read-only projected
|
||||
volume combines these with the built-in sources, without `subPath`. Paths must
|
||||
be unique in the final projection. Unmarked collisions fail rendering instead
|
||||
of silently overriding data. ConfigMap size limits apply.
|
||||
|
||||
##### Replacing individual bundled files
|
||||
|
||||
Keep `includeDefaults: true` and set `replace: true` on a `configMaps` item to
|
||||
replace that entire bundled file while retaining all other defaults:
|
||||
|
||||
```yaml
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
includeDefaults: true
|
||||
configMaps:
|
||||
- name: deployment-start-contract
|
||||
items:
|
||||
- key: start.yaml
|
||||
path: workflow-contracts/start.yaml
|
||||
replace: true
|
||||
- name: custom-workflow-contracts
|
||||
items:
|
||||
- key: pose.yaml
|
||||
path: workflow-contracts/pose.yaml
|
||||
- name: shared-pose-catalog
|
||||
items:
|
||||
- key: pose-keypoints.json
|
||||
path: catalogs/pose-keypoints.json
|
||||
```
|
||||
|
||||
Here the chart still provides the effective classification catalog, but the
|
||||
Start contract comes from the deployment. Non-replaced defaults follow the
|
||||
pinned chart version; replaced files are maintained by the deployer and are
|
||||
not automatically merged with later chart versions.
|
||||
|
||||
Replacement targets must already be bundled paths: currently
|
||||
`workflow-contracts/start.yaml`, `workflow-contracts/anpr.yaml`,
|
||||
`workflow-contracts/forwarder.yaml` and `catalogs/classifications.json`. Unknown
|
||||
targets, repeated destinations, and replacements with `includeDefaults: false`
|
||||
or the legacy interface fail rendering. For a new custom path, omit `replace`.
|
||||
The flag is consumed by Helm and is not emitted into Kubernetes volume items.
|
||||
|
||||
ANPR can be replaced independently, without adding any catalog:
|
||||
|
||||
```yaml
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
includeDefaults: true
|
||||
configMaps:
|
||||
- name: deployment-anpr-contract
|
||||
items:
|
||||
- key: anpr.yaml
|
||||
path: workflow-contracts/anpr.yaml
|
||||
replace: true
|
||||
```
|
||||
|
||||
This suppresses the bundled ANPR ConfigMap and checksum while retaining the
|
||||
default Start contract and classification catalog.
|
||||
|
||||
A replacement for `catalogs/classifications.json` also supplies the standalone
|
||||
classification API. Both mounts use that ConfigMap and key, even when its key
|
||||
is not named `classifications.json`. The chart does not generate or checksum the
|
||||
replaced ConfigMap. To avoid two competing sources, do not combine this
|
||||
replacement with a nonempty `classificationCatalogExistingConfigMap` or an inline
|
||||
`classificationCatalog`. Those options remain supported when no per-file catalog
|
||||
replacement is configured.
|
||||
`classificationCatalogEnabled` must remain true when including defaults.
|
||||
|
||||
This is whole-file replacement, not field-level YAML merging or catalog-entry
|
||||
extension. See [the replacement example](examples/workflow-configuration-overrides-values.yaml)
|
||||
for replacing both built-in files while adding a custom contract and catalog.
|
||||
|
||||
To manage the complete bundle yourself, set `includeDefaults: false` and supply
|
||||
a nonempty `configMaps` list. No Start ConfigMap is generated and no default files
|
||||
are projected. The standalone classification API remains independently configured:
|
||||
|
||||
```yaml
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
includeDefaults: false
|
||||
configMaps:
|
||||
- name: hub-start-contract
|
||||
items:
|
||||
- key: start.yaml
|
||||
path: workflow-contracts/start.yaml
|
||||
- name: hub-classification-catalog
|
||||
items:
|
||||
- key: classifications.json
|
||||
path: catalogs/classifications.json
|
||||
```
|
||||
|
||||
The older `existingConfigMap`, top-level `items`, and `extraConfigMaps` options
|
||||
retain their behavior: a nonempty `existingConfigMap` replaces the defaults,
|
||||
while `extraConfigMaps` extends either that replacement or the defaults.
|
||||
New deployments should use `includeDefaults` and `configMaps`. Do not mix the
|
||||
interfaces, even with empty legacy fields. Remove legacy keys from all values
|
||||
overlays when migrating. The chart omits both interfaces from its defaults so
|
||||
Helm's value merging does not create a mixed configuration.
|
||||
|
||||
Migration: replace legacy extras with `configMaps` and keep `includeDefaults: true`
|
||||
for additive mode. For legacy replacement mode, set `includeDefaults: false`,
|
||||
move `existingConfigMap` and its `items` into the first `configMaps` entry, and
|
||||
append any legacy extras. This produces the same mounts without a special base
|
||||
ConfigMap. Duplicate destinations remain errors, never order-dependent overrides.
|
||||
`existingConfigMap` replaces the entire bundled configuration, not individual
|
||||
files. Include Start and its referenced catalog too if the custom bundle needs
|
||||
them. `items` explicitly maps ConfigMap keys to files in `workflow-contracts/`
|
||||
and `catalogs/`; files are mounted read-only, without `subPath`. ConfigMap size
|
||||
limits apply. Mount the same shared catalog ConfigMap into other consumers as
|
||||
needed; do not maintain divergent copies of its contents.
|
||||
|
||||
The chart checks item paths and required options but cannot inspect an existing
|
||||
ConfigMap at render time. Validate its files against the supplied schemas.
|
||||
Bundled changes update the pod checksum. For externally managed ConfigMaps,
|
||||
manage rollout/reload in your deployment tooling; the chart cannot checksum
|
||||
their contents. Missing referenced ConfigMaps or keys prevent Kubernetes from
|
||||
mounting the volume: create them before syncing Hub (for example using an earlier
|
||||
Argo CD sync wave). This is distinct from invalid contract/catalog contents,
|
||||
which the contract-gating API reports as availability diagnostics without stopping
|
||||
Hub. Content diagnostics should be advisory in CI, while rendering/mount
|
||||
configuration errors and failing tests remain blocking. Unavailable sections
|
||||
must carry explicit errors, never become unrestricted choices or conditions.
|
||||
their contents.
|
||||
|
||||
Helm validates `values.schema.json`, **not** arbitrary bundled YAML/JSON files.
|
||||
The standalone schemas support authoring validation. The contract-gating API
|
||||
also rejects duplicate field/catalog keys, unknown sources, missing/invalid
|
||||
The standalone schemas support authoring validation. The future loader must
|
||||
also reject duplicate field/catalog keys, unknown sources, missing/invalid
|
||||
files, incompatible bounds, unsupported contract versions and unsafe paths.
|
||||
Validation failures must be surfaced, not silently replaced with empty choices.
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{"key": "animal", "label": "Animal", "icon": "animal"},
|
||||
{"key": "pedestrian", "label": "Pedestrian", "icon": "pedestrian"},
|
||||
{"key": "cyclist", "label": "Cyclist", "icon": "cyclist"},
|
||||
{"key": "motorbike", "label": "Motorbike", "icon": "motorbike"},
|
||||
{"key": "lorry", "label": "Lorry", "icon": "lorry"},
|
||||
{"key": "car", "label": "Car", "icon": "car"},
|
||||
{"key": "handbag", "label": "Handbag", "icon": "handbag"},
|
||||
{"key": "suitcase", "label": "Suitcase", "icon": "suitcase"},
|
||||
{"key": "cell phone", "label": "Cell phone", "icon": "cell phone"}
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# yaml-language-server: $schema=../../schemas/workflow-contract.schema.json
|
||||
# Metadata only; no current API loader consumes this contract.
|
||||
schemaVersion: 1
|
||||
stage: start
|
||||
contractVersion: 1
|
||||
|
||||
# Paths refer to the safe initial context, not the full event or credentials.
|
||||
fields:
|
||||
- id: device
|
||||
label: Device
|
||||
path: device.deviceKey
|
||||
type: string
|
||||
operators: [eq, in]
|
||||
choices:
|
||||
source: project.devices
|
||||
- id: sites
|
||||
label: Sites
|
||||
path: device.siteIds.*
|
||||
type: string
|
||||
operators: [in]
|
||||
choices:
|
||||
source: project.sites
|
||||
- id: groups
|
||||
label: Groups
|
||||
path: device.groupIds.*
|
||||
type: string
|
||||
operators: [in]
|
||||
choices:
|
||||
source: project.groups
|
||||
- id: classifications
|
||||
label: Initial classifications
|
||||
path: inputs.classify.details.*.classified
|
||||
type: string
|
||||
operators: [in]
|
||||
optional: true
|
||||
choices:
|
||||
file: ../catalogs/classifications.json
|
||||
|
||||
# Existing structured controls, not fields produced by Start.
|
||||
controls:
|
||||
- id: weeklySchedule
|
||||
label: Weekly schedule
|
||||
- id: conditions
|
||||
label: Advanced conditions
|
||||
@@ -44,8 +44,7 @@
|
||||
"incomplete": "Enter all 4 characters to continue.",
|
||||
"unlock": "Unlock encrypted data",
|
||||
"switchAccount": "or switch account",
|
||||
"ariaCharacter": "Passphrase character",
|
||||
"rememberCode": "Remember code for 24 hours on this device"
|
||||
"ariaCharacter": "Passphrase character"
|
||||
},
|
||||
"darkMode": {
|
||||
"switchToLight": "Switch to light mode",
|
||||
@@ -125,7 +124,6 @@
|
||||
"recording": "Recording",
|
||||
"dayOverview": "Day overview",
|
||||
"analytics": "Analytics",
|
||||
"markers": "Markers",
|
||||
"watchlist": "Watchlist",
|
||||
"cases": "Cases",
|
||||
"alerts": "Alerts",
|
||||
@@ -136,257 +134,7 @@
|
||||
"allOf": "All {{name}}s",
|
||||
"accounts": "Accounts",
|
||||
"roles": "Roles",
|
||||
"audit": "Audit events",
|
||||
"settings": "Settings",
|
||||
"context": {
|
||||
"label": "Workspace",
|
||||
"title": "Switch workspace",
|
||||
"open": "Choose organisation and project",
|
||||
"resize": "Resize organisation column"
|
||||
},
|
||||
"projects": {
|
||||
"label": "Project",
|
||||
"open": "Show projects",
|
||||
"search": "Search projects",
|
||||
"noResults": "No matching projects",
|
||||
"fallback": "Project 1",
|
||||
"empty": "No projects",
|
||||
"unavailable": "Projects unavailable",
|
||||
"loading": "Loading projects..."
|
||||
},
|
||||
"organisations": {
|
||||
"label": "Organisation",
|
||||
"search": "Search organisations",
|
||||
"noResults": "No matching organisations",
|
||||
"current": "Current organisation",
|
||||
"none": "No organisation",
|
||||
"select": "Select organisation",
|
||||
"unavailable": "Organisation unavailable",
|
||||
"loading": "Loading organisations...",
|
||||
"empty": "No organisations yet",
|
||||
"create": "Create organisation",
|
||||
"name": "Organisation name",
|
||||
"modal": {
|
||||
"title": "Create organisation",
|
||||
"subtitle": "Add the essentials now. Company and contact details can be completed later.",
|
||||
"create": "Create organisation",
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"company": "Company",
|
||||
"contact": "Contact & location"
|
||||
},
|
||||
"fields": {
|
||||
"name": "Organisation name *",
|
||||
"description": "Description",
|
||||
"domain": "Email domain",
|
||||
"legalName": "Legal name",
|
||||
"tradingName": "Trading name",
|
||||
"industry": "Industry",
|
||||
"website": "Website",
|
||||
"email": "Contact email",
|
||||
"phone": "Contact phone",
|
||||
"street": "Street address",
|
||||
"street2": "Address line 2",
|
||||
"city": "City",
|
||||
"region": "State / region",
|
||||
"postalCode": "Postal code",
|
||||
"country": "Country"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "For example, Acme Security",
|
||||
"description": "What this organisation is used for",
|
||||
"domain": "example.com",
|
||||
"website": "https://example.com",
|
||||
"email": "contact@example.com"
|
||||
},
|
||||
"errors": {
|
||||
"form": "Please complete all the missing fields.",
|
||||
"nameRequired": "Enter an organisation name.",
|
||||
"nameLength": "Use at least 2 characters.",
|
||||
"email": "Enter a valid email address."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Organisation settings",
|
||||
"open": "Open organisation settings",
|
||||
"loading": "Loading organisation...",
|
||||
"loadFailed": "The organisation could not be loaded.",
|
||||
"save": "Save changes",
|
||||
"saving": "Saving...",
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"company": "Company",
|
||||
"address": "Billing address",
|
||||
"contacts": "Contacts",
|
||||
"regional": "Regional",
|
||||
"users": "Users",
|
||||
"subscription": "Subscription"
|
||||
},
|
||||
"cards": {
|
||||
"userTitle": "{{count}} user",
|
||||
"usersTitle": "{{count}} users",
|
||||
"usersSubtitle": "People with access to this organisation",
|
||||
"usersFooter": "Organisation users",
|
||||
"viewUsers": "View users"
|
||||
},
|
||||
"subscription": {
|
||||
"currentPlan": "Current plan",
|
||||
"noPlan": "No active plan",
|
||||
"description": "Manage the plan, billing method, limits, and integrations for this organisation.",
|
||||
"manage": "Manage subscription",
|
||||
"status": {
|
||||
"no-plan": "No subscription is currently active.",
|
||||
"active": "The subscription is active.",
|
||||
"cancelled": "The subscription has been cancelled.",
|
||||
"reached-limit": "The subscription limit has been reached."
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "Organisation users",
|
||||
"descriptionSingle": "{{count}} person currently has access to this organisation.",
|
||||
"description": "{{count}} people currently have access to this organisation.",
|
||||
"manage": "Manage users",
|
||||
"inactive": "Inactive",
|
||||
"empty": "No users found",
|
||||
"emptyDescription": "Add users to give them access to this organisation."
|
||||
},
|
||||
"contacts": {
|
||||
"primary": "Primary contact",
|
||||
"financial": "Financial contact",
|
||||
"technical": "Technical contact"
|
||||
},
|
||||
"fields": {
|
||||
"name": "Organisation name *",
|
||||
"description": "Description",
|
||||
"domain": "Email domain",
|
||||
"legalName": "Legal name",
|
||||
"tradingName": "Trading name",
|
||||
"registrationNumber": "Registration number",
|
||||
"vatNumber": "VAT number",
|
||||
"industry": "Industry",
|
||||
"website": "Website",
|
||||
"email": "Contact email",
|
||||
"phone": "Contact phone",
|
||||
"street": "Street address",
|
||||
"street2": "Address line 2",
|
||||
"city": "City",
|
||||
"region": "State / region",
|
||||
"postalCode": "Postal code",
|
||||
"country": "Country",
|
||||
"timezone": "Timezone",
|
||||
"locale": "Locale",
|
||||
"currency": "Currency",
|
||||
"contactName": "Name",
|
||||
"contactEmail": "Email",
|
||||
"contactPhone": "Phone"
|
||||
},
|
||||
"placeholders": {
|
||||
"domain": "example.com",
|
||||
"website": "https://example.com",
|
||||
"email": "contact@example.com",
|
||||
"timezone": "Europe/Brussels",
|
||||
"locale": "en-US",
|
||||
"currency": "EUR"
|
||||
},
|
||||
"errors": {
|
||||
"form": "Please correct the highlighted fields.",
|
||||
"nameRequired": "Enter an organisation name.",
|
||||
"nameLength": "Use at least 2 characters.",
|
||||
"email": "Enter a valid email address."
|
||||
}
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"label": "Project",
|
||||
"current": "Current project",
|
||||
"none": "No project",
|
||||
"select": "Select project",
|
||||
"unavailable": "Project unavailable",
|
||||
"loading": "Loading projects...",
|
||||
"empty": "No projects yet",
|
||||
"search": "Search projects",
|
||||
"create": "Create project",
|
||||
"name": "Project name",
|
||||
"settings": {
|
||||
"title": "Project settings",
|
||||
"details": "Project details",
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"credentials": "Credentials"
|
||||
},
|
||||
"encryption": "Encryption keys",
|
||||
"publicKey": "RSA public key",
|
||||
"privateKey": "RSA private key",
|
||||
"legacyPublicKey": "Agent public key",
|
||||
"legacyPrivateKey": "Agent private key",
|
||||
"encryptionKey": "Encryption key",
|
||||
"secretKeys": "Secret keys",
|
||||
"generate": "Generate credentials",
|
||||
"regenerate": "Regenerate credentials",
|
||||
"generating": "Generating keys...",
|
||||
"generateHint": "Generate keys for this project.",
|
||||
"oneTimeHint": "Copy these keys now. They will not be shown again.",
|
||||
"copyPublicKey": "Copy RSA public key",
|
||||
"copyPrivateKey": "Copy RSA private key",
|
||||
"copyLegacyPublicKey": "Copy Agent public key",
|
||||
"copyLegacyPrivateKey": "Copy Agent private key",
|
||||
"copyEncryptionKey": "Copy encryption key",
|
||||
"pincode": {
|
||||
"generateTitle": "Generate project credentials?",
|
||||
"generateMessage": "Generate new keys for this project.",
|
||||
"label": "PIN code (optional)",
|
||||
"hint": "Enter exactly 4 digits to encrypt the stored encryption key. The PIN is never saved.",
|
||||
"error": "Enter exactly 4 digits or leave the PIN empty.",
|
||||
"statusLabel": "PIN protection",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"import": {
|
||||
"open": "Bring your own keys",
|
||||
"title": "Bring your own keys",
|
||||
"description": "Import an RSA-4096 public key generated outside the platform and provide your encryption key.",
|
||||
"replacementWarning": "This replaces the current credentials. Cameras using them may disconnect.",
|
||||
"formatHint": "The public key may be OpenSSL PEM or single-line Base64 DER. The encryption key must contain exactly 32 hexadecimal characters.",
|
||||
"encryptionKeyError": "Enter exactly 32 hexadecimal characters.",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Import credentials",
|
||||
"importing": "Importing..."
|
||||
},
|
||||
"regenerateWarning": {
|
||||
"title": "Regenerate project credentials?",
|
||||
"message": "This immediately replaces the current public, private and encryption keys. This action cannot be undone.",
|
||||
"cameraImpact": "Cameras using the current credentials may disconnect and must be configured with the new credentials.",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Regenerate credentials"
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"title": "Create project",
|
||||
"subtitle": "A project keeps its devices, sites and recordings separate from the rest of the organisation.",
|
||||
"create": "Create project",
|
||||
"fields": {
|
||||
"name": "Project name *",
|
||||
"slug": "Handle *",
|
||||
"description": "Description"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "For example, Warehouse North",
|
||||
"slug": "warehouse-north",
|
||||
"description": "What this project is used for"
|
||||
},
|
||||
"hints": {
|
||||
"slug": "Lowercase letters, numbers and single hyphens. Used to reference the project later."
|
||||
},
|
||||
"errors": {
|
||||
"form": "Please complete all the missing fields.",
|
||||
"nameRequired": "Enter a project name.",
|
||||
"nameLength": "Use at least 2 characters.",
|
||||
"slugRequired": "Enter a handle.",
|
||||
"slugFormat": "Use lowercase letters, numbers and single hyphens only.",
|
||||
"slugLength": "Use at most 63 characters."
|
||||
}
|
||||
}
|
||||
},
|
||||
"plansIntegrations": "Plans & integrations",
|
||||
"dataUploaded": "Data uploaded",
|
||||
"dailyLimit": "{{percent}}% – {{usage}}/{{max}} GB daily limit",
|
||||
@@ -460,57 +208,6 @@
|
||||
"Vault": "Vault"
|
||||
},
|
||||
"pages": {
|
||||
"audit": {
|
||||
"title": "Audit events",
|
||||
"refresh": "Refresh",
|
||||
"loadError": "Audit events could not be loaded. Try again.",
|
||||
"metadata": "Metadata",
|
||||
"noMetadata": "No metadata was recorded for this event.",
|
||||
"changes": "Changes",
|
||||
"filters": {
|
||||
"actor": "Actor",
|
||||
"actorPlaceholder": "Name or ID",
|
||||
"action": "Action",
|
||||
"target": "Target",
|
||||
"targetPlaceholder": "Name or ID",
|
||||
"operation": "Operation",
|
||||
"permission": "Permission",
|
||||
"category": "Category",
|
||||
"outcome": "Outcome",
|
||||
"search": "Search all fields",
|
||||
"searchPlaceholder": "Actor, action, target, device...",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"any": "Any",
|
||||
"clear": "Clear filters"
|
||||
},
|
||||
"table": {
|
||||
"timestamp": "Timestamp",
|
||||
"actor": "Actor",
|
||||
"action": "Action",
|
||||
"target": "Target",
|
||||
"outcome": "Outcome",
|
||||
"details": "Event details",
|
||||
"showDetails": "Show metadata",
|
||||
"hideDetails": "Hide metadata"
|
||||
},
|
||||
"authorization": "Authorization",
|
||||
"request": "Request",
|
||||
"policy": "Policy",
|
||||
"granted": "Granted",
|
||||
"denied": "Denied",
|
||||
"noResults": {
|
||||
"title": "No audit events found",
|
||||
"description": "Events will appear here as activity is recorded."
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "Showing {{start}}–{{end}} of {{total}}",
|
||||
"rows": "Rows",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"page": "Page {{page}}"
|
||||
}
|
||||
},
|
||||
"floorplans": {
|
||||
"noneUploaded": "No floor plans uploaded yet",
|
||||
"uploadFirstHint": "Upload your first floor plan to visualize your devices on this site",
|
||||
@@ -563,13 +260,6 @@
|
||||
"completeAllFields": "Please complete all the missing fields",
|
||||
"nameExists": "This name already exists",
|
||||
"badgeColor": "Badge color (HEX)",
|
||||
"fineTuneLocation": "Fine-tune map position",
|
||||
"fineTuneInstructions": "Move the map so the pin marks the exact site location.",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"invalidCoordinates": "Enter valid coordinates.",
|
||||
"selectAddressBeforeFineTune": "Select an address before fine-tuning its map position.",
|
||||
"resetLocation": "Reset position",
|
||||
"vaultSaveToUse": "Save changes to use this Vault",
|
||||
"vaultCheckCredentials": "Check credentials and try again"
|
||||
},
|
||||
@@ -614,85 +304,6 @@
|
||||
"analytics": {
|
||||
"manageAlerts": "Manage alerts",
|
||||
"subscribePrompt": "Subscribe to a plan to generate notifications",
|
||||
"markers": {
|
||||
"title": "Markers",
|
||||
"overview": "Overview",
|
||||
"searchPlaceholder": "Search marker names, e.g. a license plate",
|
||||
"clearFilters": "Clear filters",
|
||||
"loading": "Loading markers",
|
||||
"loadError": "Markers could not be loaded. Please try again.",
|
||||
"invalidDateRange": "The start date must be before the end date.",
|
||||
"openDetails": "Open marker details",
|
||||
"openMarkerDetails": "Open details for marker {{name}}",
|
||||
"openMedia": "Open recording",
|
||||
"noMedia": "No linked recording",
|
||||
"mediaErrorTitle": "Recording unavailable",
|
||||
"mediaErrorMessage": "The recording linked to this marker could not be opened.",
|
||||
"detail": {
|
||||
"title": "Marker details",
|
||||
"recording": "Marker recording",
|
||||
"loadingRecording": "Loading linked recording...",
|
||||
"videoUnavailable": "The linked recording has no playable video.",
|
||||
"markerPosition": "Marker starts {{offset}} into this recording",
|
||||
"openFullRecording": "Open full recording",
|
||||
"overview": "Overview",
|
||||
"start": "Start",
|
||||
"end": "End",
|
||||
"description": "Description",
|
||||
"classification": "Classification",
|
||||
"metadata": "Metadata",
|
||||
"source": "Source",
|
||||
"engine": "Engine",
|
||||
"modelVersion": "Model version",
|
||||
"boundingBox": "Bounding box",
|
||||
"rawMetadata": "Raw metadata",
|
||||
"comments": "Comments",
|
||||
"identifiers": "Identifiers",
|
||||
"markerId": "Marker ID",
|
||||
"deviceId": "Device ID",
|
||||
"siteId": "Site ID",
|
||||
"groupId": "Group ID",
|
||||
"organisationId": "Organisation ID",
|
||||
"mediaKey": "Media key",
|
||||
"audit": "Audit information"
|
||||
},
|
||||
"filters": {
|
||||
"categories": "Categories",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"sort": "Sort markers"
|
||||
},
|
||||
"sort": {
|
||||
"latest": "Latest first",
|
||||
"oldest": "Oldest first",
|
||||
"nameAsc": "Name A-Z",
|
||||
"nameDesc": "Name Z-A"
|
||||
},
|
||||
"table": {
|
||||
"timestamp": "Timestamp",
|
||||
"marker": "Marker",
|
||||
"categories": "Categories",
|
||||
"device": "Device",
|
||||
"duration": "Duration",
|
||||
"source": "Source",
|
||||
"signals": "Events & tags",
|
||||
"events": "Events",
|
||||
"tags": "Tags",
|
||||
"confidence": "Confidence",
|
||||
"media": "Recording"
|
||||
},
|
||||
"noResults": {
|
||||
"title": "No markers found",
|
||||
"description": "Adjust the filters or search for another marker name."
|
||||
},
|
||||
"pagination": {
|
||||
"showing": "Showing {{start}}-{{end}}",
|
||||
"rows": "Rows",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"page": "Page {{page}}"
|
||||
}
|
||||
},
|
||||
"noAlerts": {
|
||||
"description": "Create an alert or enable existing ones to start receiving analytics.",
|
||||
"button": "Alerts"
|
||||
@@ -729,19 +340,6 @@
|
||||
"placeholderDescription": "To create your first case go to the media section, and select a media item. Create a new case and assign it to the relevant person.",
|
||||
"goToMedia": "Go to Media",
|
||||
"myCases": "My cases",
|
||||
"loadError": "Something went wrong while loading the case. Please try again.",
|
||||
"autoplayNextOn": "Autoplay next: On",
|
||||
"autoplayNextOff": "Autoplay next: Off",
|
||||
"mediaProcessing": "Processing",
|
||||
"mediaFailed": "Failed",
|
||||
"a11y": {
|
||||
"toggleCase": "Toggle case details",
|
||||
"statusOpen": "Case status: open",
|
||||
"statusApproved": "Case status: approved",
|
||||
"statusRejected": "Case status: rejected",
|
||||
"loadingCases": "Loading cases",
|
||||
"caseThumbnail": "Case thumbnail"
|
||||
},
|
||||
"actions": {
|
||||
"reopenCase": "Reopen case",
|
||||
"approveCase": "Approve case",
|
||||
@@ -965,8 +563,6 @@
|
||||
"filter": {
|
||||
"date": "Date",
|
||||
"region": "Region",
|
||||
"regionExcluded": "Not in results",
|
||||
"regionExcludedHint": "No region drawn on this camera, so it is left out of the results. Draw a region to include it again.",
|
||||
"categories": "Categories",
|
||||
"markers": "Markers",
|
||||
"events": "Events",
|
||||
@@ -974,15 +570,7 @@
|
||||
"newest": "Newest",
|
||||
"oldest": "Oldest",
|
||||
"showAllRecordings": "Show all recordings",
|
||||
"showStarredRecordings": "Show starred recordings only",
|
||||
"synchronisedPlayer": "Synchronised player",
|
||||
"synchronisedPlayerHint": "Play all recordings of the selected region, one player per camera",
|
||||
"synchronisedPlayerRequirement": "Select a time region of maximum 60 minutes on the timeline to enable the synchronised player",
|
||||
"synchronisedPlayerPlay": "Play",
|
||||
"synchronisedPlayerPause": "Pause",
|
||||
"synchronisedPlayerNoFootage": "No recording at this moment",
|
||||
"synchronisedPlayerEmpty": "No playable recordings found in the selected time region.",
|
||||
"timelinePreviewPlayVideo": "Play video"
|
||||
"showStarredRecordings": "Show starred recordings only"
|
||||
},
|
||||
"emptyState": {
|
||||
"selectDayAndDevice": "Select day and device",
|
||||
@@ -998,12 +586,8 @@
|
||||
"timeline": {
|
||||
"title": "Timeline",
|
||||
"clearSelectedTime": "Clear selected time",
|
||||
"selectionStart": "Start of the selected time range",
|
||||
"selectionEnd": "End of the selected time range",
|
||||
"fetching": "Fetching timeline rows for selected devices.",
|
||||
"noRecordings": "No recordings are available for the selected date and devices.",
|
||||
"resizeHint": "Drag to resize the timeline",
|
||||
"rowHeight": "Drag to change the height of the recording bars"
|
||||
"noRecordings": "No recordings are available for the selected date and devices."
|
||||
},
|
||||
"chart": {
|
||||
"title": "Chart",
|
||||
@@ -1024,13 +608,8 @@
|
||||
"copyVideoUrl": "Copy Video URL",
|
||||
"verifyVideo": "Verify video",
|
||||
"shareRecording": "Share Recording",
|
||||
"runWorkflow": "Run workflow",
|
||||
"runWorkflowSubtitle": "Choose a workflow to run on this recording.",
|
||||
"noMediaWorkflows": "No workflows are available for recordings.",
|
||||
"newCase": "New case",
|
||||
"addToCase": "Add to case",
|
||||
"assignToCase": "Assign to case",
|
||||
"caseActions": "Case actions",
|
||||
"share": "Share",
|
||||
"save": "Save",
|
||||
"loading": "Loading...",
|
||||
@@ -1547,7 +1126,7 @@
|
||||
"k1": "Add a Dataset",
|
||||
"k2": "Are you sure you want to remove device",
|
||||
"k3": "Search dataset..",
|
||||
"k4": "Set up your first Agent",
|
||||
"k4": "Set up your first Kerberos Agent",
|
||||
"k5": "No devices are available",
|
||||
"k6": "Subscribe to a plan",
|
||||
"k7": "Dataset",
|
||||
@@ -1595,12 +1174,12 @@
|
||||
},
|
||||
"new": {
|
||||
"new": {
|
||||
"k1": "Before you will see your recordings shown up on Kerberos Hub, you first need to install and configure an Agent. An Agent is a piece of software that processes a device, and will make recordings when a portion of the video stream has changed. These recordings will then be send to, and processed by the Kerberos Hub application.",
|
||||
"k2": "After you've properly installed one or more Agents, you can link it to the Kerberos Hub application using your",
|
||||
"k1": "Before you will see your recordings shown up on Kerberos Hub, you first need to install and configure a Kerberos Agent. A Kerberos Agent is a piece of software that processes a device, and will make recordings when a portion of the video stream has changed. These recordings will then be send to, and processed by the Kerberos Hub application.",
|
||||
"k2": "After you've properly installed one or more Kerberos Agents, you can link it to the Kerberos Hub application using your",
|
||||
"k3": "cloud credentials",
|
||||
"k4": ". When linked successfully, your device will show up on the devices page.",
|
||||
"k5": "Get started",
|
||||
"k6": "We have different ways to get your Agent up and running. You can install the Agent using a prebuilt Docker container, inside your Kubernetes Cluster, on your Raspberry Pi (or any other compatible board) or on a simple VM. Please select the most appropriate solution to get started. Visit",
|
||||
"k6": "We have different ways to get your Kerberos Agent up and running. You can install the Kerberos Agent using a prebuilt Docker container, inside your Kubernetes Cluster, on your Raspberry Pi (or any other compatible board) or on a simple VM. Please select the most appropriate solution to get started. Visit",
|
||||
"k7": "our documentation website to learn more",
|
||||
"k8": "A Docker image (amd64, armv7, arm64) is",
|
||||
"k9": "available on the Docker Hub",
|
||||
@@ -1610,8 +1189,8 @@
|
||||
"k13": "Go to the plan page",
|
||||
"k14": "on your cloud account and copy your cloud credentials.",
|
||||
"k15": "When created successfully, you can navigate to the Kerberos Factory interface, and enter your cloud credentials in your deployment of global configuration.",
|
||||
"k16": "Your new Agent will show up on the devices page.",
|
||||
"k17": "If you want to manage Agents at scale with Kubernetes, then",
|
||||
"k16": "Your new Kerberos Agent will show up on the devices page.",
|
||||
"k17": "If you want to manage Kerberos Agents at scale with Kubernetes, then",
|
||||
"k18": "Kerberos Factory is what you are looking for",
|
||||
"k19": "Setup a Kubernetes cluster or use your existing Kubernetes cluster.",
|
||||
"k20": "Install the different components of",
|
||||
@@ -1620,7 +1199,7 @@
|
||||
"k23": "Go to the plans & integrations page",
|
||||
"k24": "on your Kerberos Hub account and copy your credentials.",
|
||||
"k25": "When configured Kerberos Factory successfully, you can modify the global settings, and enter your credentials.",
|
||||
"k26": "All Agent you will now add, will show up on your devices page.",
|
||||
"k26": "All Kerberos Agent you will now add, will show up on your devices page.",
|
||||
"k27": "KiOS is a custom linux OS, created for the Raspberry Pi, which runs Kerberos.io out-of-the-box. This is the most simple and basic installation procedure.",
|
||||
"k28": "Download",
|
||||
"k29": "the KiOS installer",
|
||||
@@ -2047,7 +1626,7 @@
|
||||
"k11": "Delete attachment",
|
||||
"k12": "Are you sure you want to delete this attachment? This action cannot be undone.",
|
||||
"k13": "Yes, delete",
|
||||
"k14": "Redact"
|
||||
"k14": "Redact faces"
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
@@ -2107,7 +1686,7 @@
|
||||
"blocks": {
|
||||
"face_redaction": {
|
||||
"face_redaction": {
|
||||
"k1": "Redact video",
|
||||
"k1": "Edit video",
|
||||
"k2": "Edit video tools",
|
||||
"k3": "This editing tool is coming soon.",
|
||||
"k4": "No detections available",
|
||||
@@ -2123,7 +1702,9 @@
|
||||
"k14": "fps",
|
||||
"k15": "Detection run",
|
||||
"k16": "Select a detection run",
|
||||
"k17": "No detections"
|
||||
"k17": "No detections",
|
||||
"k18": "Automatic tracks unavailable",
|
||||
"k19": "Automatic face tracks are disabled. You can still draw redaction boxes manually on the player and submit."
|
||||
}
|
||||
},
|
||||
"motionmap": {
|
||||
@@ -2349,15 +1930,13 @@
|
||||
"k62": "Expiry date",
|
||||
"k63": "Open update access token",
|
||||
"k64": "No access tokens found",
|
||||
"k65": "time",
|
||||
"k65": "date, time",
|
||||
"k66": "ip",
|
||||
"k67": "device",
|
||||
"k68": "status",
|
||||
"k67": "browser",
|
||||
"k68": "os",
|
||||
"k69": "No login history found.",
|
||||
"k70": "My account",
|
||||
"k71": "Manage your profile, billing methods and invoices",
|
||||
"k72": "Success",
|
||||
"k73": "Failed"
|
||||
"k71": "Manage your profile, billing methods and invoices"
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
@@ -2566,9 +2145,9 @@
|
||||
"k20": "Kerberos Hub credentials",
|
||||
"k21": "Cloud credentials",
|
||||
"k22": "Media storage region",
|
||||
"k23": "Your media can be stored in one of the following regions. Different Agents can store in different regions.",
|
||||
"k23": "Your media can be stored in one of the following regions. Different Kerberos agents can store in different regions.",
|
||||
"k24": "Copy",
|
||||
"k25": "Use the below credentials to connect your Agents to your Kerberos Hub.",
|
||||
"k25": "Use the below credentials to connect your Kerberos Agents to your Kerberos Hub.",
|
||||
"k26": "Use the below credentials to connect your devices to our cloud.",
|
||||
"k27": "How to connect Agents to Kerberos Hub",
|
||||
"k28": "Public key",
|
||||
@@ -3007,7 +2586,7 @@
|
||||
"k2": "draft",
|
||||
"k3": "Device detail",
|
||||
"k4": "Kerberos Factory",
|
||||
"k5": "Agent",
|
||||
"k5": "Kerberos Agent",
|
||||
"k6": "n/a",
|
||||
"k7": "Remove device"
|
||||
}
|
||||
@@ -3243,7 +2822,7 @@
|
||||
"k2": "PREVIEW",
|
||||
"k3": "Switch to live [Shift + L]",
|
||||
"k4": "LIVE",
|
||||
"k5": "Make sure you are using the latest Agent and a Gold subscription",
|
||||
"k5": "Make sure you are using the latest Kerberos agent and a Gold subscription",
|
||||
"k6": "You are not allowed to watch a live stream.",
|
||||
"k7": "Turn sound off",
|
||||
"k8": "Turn sound on",
|
||||
@@ -3255,22 +2834,7 @@
|
||||
"k14": "Device Idle",
|
||||
"k15": "The agent is online, but the device stream seems to be offline",
|
||||
"k16": "No permissions",
|
||||
"k17": "You aren't allowed to view this livestream.",
|
||||
"k18": "Record",
|
||||
"k19": "Recording",
|
||||
"k20": "REC",
|
||||
"k21": "Stop recording",
|
||||
"k22": "Camera is set to continuous recording",
|
||||
"k23": "Show motion detection",
|
||||
"k24": "Hide motion detection",
|
||||
"k25": "Recent recordings",
|
||||
"k26": "No recordings available",
|
||||
"k27": "Back 5 seconds",
|
||||
"k28": "Play",
|
||||
"k29": "Pause",
|
||||
"k30": "Forward 5 seconds",
|
||||
"k31": "Previous time window",
|
||||
"k32": "Next time window"
|
||||
"k17": "You aren't allowed to view this livestream."
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
@@ -3330,13 +2894,6 @@
|
||||
"k3": "End time"
|
||||
}
|
||||
},
|
||||
"time_spinner": {
|
||||
"time_spinner": {
|
||||
"hours": "Hours",
|
||||
"minutes": "Minutes",
|
||||
"seconds": "Seconds"
|
||||
}
|
||||
},
|
||||
"video_js": {
|
||||
"video_js": {
|
||||
"k1": "To view this video please enable JavaScript, and consider upgrading to a web browser that",
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Create these ConfigMaps in the release namespace before deploying the chart.
|
||||
# Keys contain complete contracts/catalogs; they are not merged with chart data.
|
||||
# Do not also set classificationCatalog or classificationCatalogExistingConfigMap.
|
||||
classificationCatalogEnabled: true
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
includeDefaults: true
|
||||
configMaps:
|
||||
- name: deployment-workflow-overrides
|
||||
items:
|
||||
- key: start.yaml
|
||||
path: workflow-contracts/start.yaml
|
||||
replace: true
|
||||
- key: deployment-classifications.json
|
||||
path: catalogs/classifications.json
|
||||
replace: true
|
||||
- name: custom-workflow-contracts
|
||||
items:
|
||||
- key: pose.yaml
|
||||
path: workflow-contracts/pose.yaml
|
||||
- name: shared-pose-catalog
|
||||
items:
|
||||
- key: pose-keypoints.json
|
||||
path: catalogs/pose-keypoints.json
|
||||
@@ -1,13 +1,9 @@
|
||||
# Packaging only; does not activate contract loading or deploy a pose worker.
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
includeDefaults: true
|
||||
configMaps:
|
||||
- name: custom-workflow-contracts
|
||||
items:
|
||||
- key: pose.yaml
|
||||
path: workflow-contracts/pose.yaml
|
||||
- name: shared-pose-catalog
|
||||
items:
|
||||
- key: pose-keypoints.json
|
||||
path: catalogs/pose-keypoints.json
|
||||
existingConfigMap: custom-workflow-configuration
|
||||
items:
|
||||
- key: pose.yaml
|
||||
path: workflow-contracts/pose.yaml
|
||||
- key: pose-keypoints.json
|
||||
path: catalogs/pose-keypoints.json
|
||||
|
||||
@@ -3,23 +3,6 @@
|
||||
schemaVersion: 1
|
||||
stage: pose
|
||||
contractVersion: 1
|
||||
presentation:
|
||||
name: Detect poses
|
||||
description: Estimate body keypoints and posture for people in the recording.
|
||||
icon: person
|
||||
color: teal
|
||||
# Settings authors enter per node; the worker receives them as node data.
|
||||
params:
|
||||
- name: minConfidence
|
||||
label: Minimum keypoint confidence
|
||||
type: number
|
||||
default: 0.5
|
||||
- name: model
|
||||
label: Model
|
||||
type: select
|
||||
options: [fast, accurate]
|
||||
optionLabels: {fast: Fast, accurate: Accurate}
|
||||
default: fast
|
||||
fields:
|
||||
- id: keypoint
|
||||
label: Keypoint
|
||||
@@ -42,12 +25,3 @@ fields:
|
||||
operators: [gt, gte, lt, lte]
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
# Connections leaving this stage: one section with fixed inputs, then the
|
||||
# Extra conditions catch-all (appended collapsed when not placed explicitly).
|
||||
panel:
|
||||
sections:
|
||||
- id: posture
|
||||
label: Posture
|
||||
fields:
|
||||
- {field: posture, widget: multiselect}
|
||||
- {field: confidence, widget: number, operator: gte, label: Minimum confidence}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Hub workflow editor contract v1",
|
||||
"title": "Hub workflow condition contract v1 (preparatory)",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schemaVersion", "stage", "contractVersion", "fields"],
|
||||
@@ -11,32 +11,11 @@
|
||||
"pattern": "^[a-z][a-z0-9-]*$",
|
||||
"description": "Stage type, not a node instance. start describes initial context."
|
||||
},
|
||||
"contractVersion": {"const": 1},
|
||||
"presentation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "How the editor shows this node. Icons and colours come from approved sets.",
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1, "maxLength": 80},
|
||||
"description": {"type": "string", "minLength": 1, "maxLength": 500},
|
||||
"icon": {"enum": ["play", "camera", "person", "eye-crossed", "car", "clock", "mlModel", "share"]},
|
||||
"color": {
|
||||
"enum": ["blue", "teal", "rose", "amber", "indigo", "violet", "green", "grey"],
|
||||
"description": "Theme colour token."
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"type": "array",
|
||||
"maxItems": 64,
|
||||
"description": "Worker-stage settings authors enter, stored per node. An explicit list (even empty) replaces kerberoshub.workflows.editorStages params. Not supported on start.",
|
||||
"items": {"$ref": "#/definitions/param"}
|
||||
},
|
||||
"contractVersion": {"type": "integer", "minimum": 1},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"description": "Typed values conditions may test. Start: the selectors device, sites, groups and classifications plus condition-only fields whose path exists in the condition root schema (models.WorkflowConditionRootSchema), e.g. device.deviceName or inputs.classify.details.*.isStatic; other inputs.<operation> paths are worker-defined. Stage: outputs relative to results.<stage>. New conditions may only use declared fields. An empty array is valid.",
|
||||
"items": {"$ref": "#/definitions/field"},
|
||||
"maxItems": 128
|
||||
"minItems": 1,
|
||||
"items": {"$ref": "#/definitions/field"}
|
||||
},
|
||||
"controls": {
|
||||
"type": "array",
|
||||
@@ -48,18 +27,8 @@
|
||||
"id": {"enum": ["weeklySchedule", "conditions"]},
|
||||
"label": {"type": "string", "minLength": 1}
|
||||
}
|
||||
},
|
||||
"description": "Start only. Controls offered for new authoring; omitted offers none. conditions applies to edges leaving Start.",
|
||||
"maxItems": 16
|
||||
},
|
||||
"triggerModes": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {"enum": ["automatic", "manual"]},
|
||||
"description": "Start only. Trigger modes offered for new authoring; omitted offers both."
|
||||
},
|
||||
"panel": {"$ref": "#/definitions/panel"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"choices": {
|
||||
@@ -70,15 +39,9 @@
|
||||
"type": "string",
|
||||
"description": "Built-in authorized project-resource resolver; not a database path.",
|
||||
"oneOf": [
|
||||
{
|
||||
"const": "project.devices",
|
||||
"description": "Device keys as values, device names as labels."
|
||||
},
|
||||
{"const": "project.devices", "description": "Device keys as values, device names as labels."},
|
||||
{"const": "project.sites", "description": "Site IDs as values, site names as labels."},
|
||||
{
|
||||
"const": "project.groups",
|
||||
"description": "Group IDs as values, group names as labels."
|
||||
}
|
||||
{"const": "project.groups", "description": "Group IDs as values, group names as labels."}
|
||||
]
|
||||
},
|
||||
"file": {
|
||||
@@ -93,7 +56,11 @@
|
||||
"items": {"type": ["string", "number", "boolean"]}
|
||||
}
|
||||
},
|
||||
"oneOf": [{"required": ["source"]}, {"required": ["file"]}, {"required": ["values"]}]
|
||||
"oneOf": [
|
||||
{"required": ["source"]},
|
||||
{"required": ["file"]},
|
||||
{"required": ["values"]}
|
||||
]
|
||||
},
|
||||
"field": {
|
||||
"type": "object",
|
||||
@@ -164,158 +131,12 @@
|
||||
{
|
||||
"if": {"properties": {"type": {"enum": ["integer", "number", "boolean"]}}},
|
||||
"then": {
|
||||
"properties": {"choices": {"not": {"anyOf": [{"required": ["file"]}, {"required": ["source"]}]}}}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"param": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "type"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z][A-Za-z0-9_]*$",
|
||||
"maxLength": 64,
|
||||
"description": "Key in node data; unique within the contract."
|
||||
},
|
||||
"label": {"type": "string", "minLength": 1, "maxLength": 160},
|
||||
"description": {"type": "string", "minLength": 1, "maxLength": 500},
|
||||
"group": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 80,
|
||||
"description": "Editor section; params sharing a group appear together."
|
||||
},
|
||||
"placeholder": {"type": "string", "minLength": 1, "maxLength": 160},
|
||||
"type": {"enum": ["string", "number", "boolean", "select", "multiselect", "secret"]},
|
||||
"required": {"type": "boolean"},
|
||||
"default": {
|
||||
"description": "Value the editor pre-fills and the API stores when a node leaves the setting unset; never for secrets. Typically the worker's own default."
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 256,
|
||||
"uniqueItems": true,
|
||||
"items": {"type": "string", "minLength": 1, "maxLength": 160}
|
||||
},
|
||||
"optionLabels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string", "minLength": 1, "maxLength": 160}
|
||||
},
|
||||
"minimum": {"type": "number", "description": "Inclusive lower bound for a number setting."},
|
||||
"maximum": {"type": "number", "description": "Inclusive upper bound for a number setting."},
|
||||
"choices": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["file"],
|
||||
"properties": {
|
||||
"file": {"type": "string", "pattern": "^\\.\\./catalogs/[A-Za-z0-9_-][A-Za-z0-9._-]*\\.json$"}
|
||||
},
|
||||
"description": "Options for a select or multiselect setting from a shared catalog file (keys become options, labels become optionLabels). Use instead of options."
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {"properties": {"type": {"enum": ["select", "multiselect"]}}},
|
||||
"then": {"oneOf": [{"required": ["options"]}, {"required": ["choices"]}]},
|
||||
"else": {
|
||||
"not": {
|
||||
"anyOf": [{"required": ["options"]}, {"required": ["optionLabels"]}, {"required": ["choices"]}]
|
||||
"properties": {
|
||||
"choices": {"not": {"anyOf": [{"required": ["file"]}, {"required": ["source"]}]}}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {"properties": {"type": {"const": "secret"}}},
|
||||
"then": {"not": {"required": ["default"]}}
|
||||
},
|
||||
{
|
||||
"if": {"properties": {"type": {"enum": ["string", "select"]}}},
|
||||
"then": {"properties": {"default": {"type": "string"}}}
|
||||
},
|
||||
{
|
||||
"if": {"properties": {"type": {"const": "multiselect"}}},
|
||||
"then": {
|
||||
"properties": {"default": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {"properties": {"type": {"const": "number"}}},
|
||||
"then": {"properties": {"default": {"type": "number"}}},
|
||||
"else": {"not": {"anyOf": [{"required": ["minimum"]}, {"required": ["maximum"]}]}}
|
||||
},
|
||||
{
|
||||
"if": {"properties": {"type": {"const": "boolean"}}},
|
||||
"then": {"properties": {"default": {"type": "boolean"}}}
|
||||
}
|
||||
]
|
||||
},
|
||||
"panel": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["sections"],
|
||||
"description": "Layout of connections leaving this node. Without it, a default panel is used. A section with picker: true positions the Extra conditions catch-all; without one it is appended collapsed. On start, a panel replaces the top-level controls list.",
|
||||
"properties": {
|
||||
"sections": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 16,
|
||||
"items": {"$ref": "#/definitions/panelSection"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panelSection": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "label"],
|
||||
"properties": {
|
||||
"id": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_-]*$", "maxLength": 80},
|
||||
"label": {"type": "string", "minLength": 1, "maxLength": 160},
|
||||
"collapsed": {"type": "boolean"},
|
||||
"picker": {
|
||||
"type": "boolean",
|
||||
"description": "This section is the Extra conditions catch-all over every declared field of Start and upstream stages; it holds no fields or controls."
|
||||
},
|
||||
"controls": {
|
||||
"type": "array",
|
||||
"items": {"const": "weeklySchedule"},
|
||||
"maxItems": 1,
|
||||
"description": "Start only."
|
||||
},
|
||||
"fields": {"type": "array", "maxItems": 32, "items": {"$ref": "#/definitions/panelField"}}
|
||||
},
|
||||
"if": {"properties": {"picker": {"const": true}}, "required": ["picker"]},
|
||||
"then": {"not": {"anyOf": [{"required": ["fields"]}, {"required": ["controls"]}]}}
|
||||
},
|
||||
"panelField": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["field", "widget"],
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"description": "ID of a field declared in this contract; each field is placed at most once."
|
||||
},
|
||||
"label": {"type": "string", "minLength": 1, "maxLength": 160},
|
||||
"widget": {
|
||||
"enum": ["multiselect", "select", "text", "number", "toggle"],
|
||||
"description": "multiselect: string with choices (in); select: choices (eq, ne); text: string (eq, contains, matches, ne, in); number: integer or number (gte, gt, lte, lt, eq, ne); toggle: boolean (eq, ne)."
|
||||
},
|
||||
"operator": {
|
||||
"enum": ["eq", "ne", "in", "contains", "matches", "gt", "gte", "lt", "lte"],
|
||||
"description": "Fixed operator; must be declared by the field and supported by the widget. Defaults to the first widget operator the field declares."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {"properties": {"stage": {"const": "start"}}, "required": ["stage"]},
|
||||
"then": {"not": {"required": ["params"]}},
|
||||
"else": {"not": {"anyOf": [{"required": ["controls"]}, {"required": ["triggerModes"]}]}}
|
||||
},
|
||||
{"if": {"required": ["panel"]}, "then": {"not": {"required": ["controls"]}}}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +1,27 @@
|
||||
{{- define "hub.workflowConfiguration.bundledPaths" -}}
|
||||
{{- list "workflow-contracts/start.yaml" "workflow-contracts/anpr.yaml" "workflow-contracts/forwarder.yaml" "catalogs/classifications.json" | toYaml -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "hub.workflowConfiguration.anpr.configMapName" -}}
|
||||
{{- printf "%s-workflow-contract-anpr" (.Release.Name | trunc 40 | trimSuffix "-") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "hub.workflowConfiguration.forwarder.configMapName" -}}
|
||||
{{- printf "%s-workflow-contract-forwarder" (.Release.Name | trunc 40 | trimSuffix "-") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "hub.classificationCatalog.configMapName" -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- $replacement := index $config.replacements "catalogs/classifications.json" | default dict -}}
|
||||
{{- if $replacement -}}
|
||||
{{- $replacement.name -}}
|
||||
{{/* Keep the legacy endpoint and the preparatory bundle on the same chart data. */}}
|
||||
{{- define "hub.classificationCatalog" -}}
|
||||
{{- if kindIs "invalid" .Values.classificationCatalog -}}
|
||||
{{- .Files.Get "configuration/catalogs/classifications.json" | mustFromJson | toPrettyJson -}}
|
||||
{{- else if kindIs "slice" .Values.classificationCatalog -}}
|
||||
{{- .Values.classificationCatalog | toPrettyJson -}}
|
||||
{{- else -}}
|
||||
{{- default "classification-catalog" .Values.classificationCatalogExistingConfigMap -}}
|
||||
{{- fail "classificationCatalog must be null (bundled defaults) or a list" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Normalize both interfaces without changing the caller's values. */}}
|
||||
{{- define "hub.workflowConfiguration.settings" -}}
|
||||
{{- $config := .Values.workflowConfiguration | default dict -}}
|
||||
{{- $unified := or (hasKey $config "includeDefaults") (hasKey $config "configMaps") -}}
|
||||
{{- $legacy := or (hasKey $config "existingConfigMap") (hasKey $config "items") (hasKey $config "extraConfigMaps") -}}
|
||||
{{- if and $unified $legacy -}}
|
||||
{{- fail "workflowConfiguration cannot mix includeDefaults/configMaps with existingConfigMap/items/extraConfigMaps" -}}
|
||||
{{- end -}}
|
||||
{{- $includeDefaults := true -}}
|
||||
{{- $sources := $config.extraConfigMaps | default list -}}
|
||||
{{- if $unified -}}
|
||||
{{- $sources = $config.configMaps | default list -}}
|
||||
{{- if hasKey $config "includeDefaults" -}}
|
||||
{{- $includeDefaults = $config.includeDefaults -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if $config.existingConfigMap -}}
|
||||
{{- if and $config.enabled (not $config.items) -}}
|
||||
{{- fail "workflowConfiguration.items is required with existingConfigMap" -}}
|
||||
{{- end -}}
|
||||
{{- $includeDefaults = false -}}
|
||||
{{- $sources = prepend $sources (dict "name" $config.existingConfigMap "items" ($config.items | default list)) -}}
|
||||
{{- else if and $config.enabled $config.items -}}
|
||||
{{- fail "workflowConfiguration.items requires existingConfigMap" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $enabled := $config.enabled | default false -}}
|
||||
{{- $replacements := dict -}}
|
||||
{{- $normalizedSources := list -}}
|
||||
{{- $bundledPaths := include "hub.workflowConfiguration.bundledPaths" . | fromYamlArray -}}
|
||||
{{- range $sources -}}
|
||||
{{- $name := .name -}}
|
||||
{{- $items := list -}}
|
||||
{{- range .items -}}
|
||||
{{- if and $enabled .replace -}}
|
||||
{{- if or (not $unified) (not $includeDefaults) -}}
|
||||
{{- fail "workflowConfiguration item replace: true requires configMaps with includeDefaults: true" -}}
|
||||
{{- end -}}
|
||||
{{- if not (has .path $bundledPaths) -}}
|
||||
{{- fail (printf "workflowConfiguration item replace: true targets unknown bundled path %q" .path) -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey $replacements .path -}}
|
||||
{{- fail (printf "duplicate workflowConfiguration replacement path %q" .path) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $replacements .path (dict "name" $name "key" .key) -}}
|
||||
{{- end -}}
|
||||
{{- $items = append $items (omit . "replace") -}}
|
||||
{{- end -}}
|
||||
{{- $normalizedSources = append $normalizedSources (dict "name" $name "items" $items) -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey $replacements "catalogs/classifications.json" -}}
|
||||
{{- if .Values.classificationCatalogExistingConfigMap -}}
|
||||
{{- fail "a classifications.json replacement cannot be combined with classificationCatalogExistingConfigMap; choose one override mechanism" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "invalid" .Values.classificationCatalog) -}}
|
||||
{{- if not .Values.classificationCatalogEnabled -}}
|
||||
{{- fail "classificationCatalog requires classificationCatalogEnabled; remove the inline list to use the API fallback" -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.classificationCatalogExistingConfigMap -}}
|
||||
{{- fail "classificationCatalog cannot be combined with classificationCatalogExistingConfigMap; choose one override mechanism" -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey $replacements "catalogs/classifications.json" -}}
|
||||
{{- fail "classificationCatalog cannot be combined with a classifications.json replacement; choose one override mechanism" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- dict "enabled" $enabled "includeDefaults" $includeDefaults "configMaps" $normalizedSources "replacements" $replacements | toYaml -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "hub.workflowConfiguration.validate" -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- $config := .Values.workflowConfiguration | default dict -}}
|
||||
{{- if $config.enabled -}}
|
||||
{{- if $config.existingConfigMap -}}
|
||||
{{- if not $config.items -}}
|
||||
{{- fail "workflowConfiguration.items is required with existingConfigMap" -}}
|
||||
{{- end -}}
|
||||
{{- $paths := dict -}}
|
||||
{{- if $config.includeDefaults -}}
|
||||
{{- if not .Values.classificationCatalogEnabled -}}
|
||||
{{- fail "bundled workflowConfiguration requires classificationCatalogEnabled; disable includeDefaults and supply configMaps for a custom bundle" -}}
|
||||
{{- end -}}
|
||||
{{- range $path := (include "hub.workflowConfiguration.bundledPaths" . | fromYamlArray) -}}
|
||||
{{- if not (hasKey $config.replacements $path) -}}
|
||||
{{- $_ := set $paths $path true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if not $config.configMaps -}}
|
||||
{{- fail "workflowConfiguration.configMaps must be nonempty when enabled without defaults" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- range $config.configMaps -}}
|
||||
{{- if not .items -}}
|
||||
{{- fail (printf "workflowConfiguration ConfigMap %q requires nonempty items" .name) -}}
|
||||
{{- end -}}
|
||||
{{- range .items -}}
|
||||
{{- range $config.items -}}
|
||||
{{- if not (regexMatch "^[A-Za-z0-9._-]+$" .key) -}}
|
||||
{{- fail "workflowConfiguration.items keys must be valid ConfigMap keys" -}}
|
||||
{{- end -}}
|
||||
{{- if or (not (regexMatch "^(workflow-contracts|catalogs)/[A-Za-z0-9_-][A-Za-z0-9._-]*\\.(yaml|json)$" .path)) (ne (clean .path) .path) -}}
|
||||
{{- if or (not (regexMatch "^(workflow-contracts|catalogs)/[A-Za-z0-9._/-]+$" .path)) (ne (clean .path) .path) -}}
|
||||
{{- fail "workflowConfiguration.items paths must be normalized relative files under workflow-contracts/ or catalogs/" -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey $paths .path -}}
|
||||
@@ -123,6 +29,13 @@
|
||||
{{- end -}}
|
||||
{{- $_ := set $paths .path true -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if $config.items -}}
|
||||
{{- fail "workflowConfiguration.items requires existingConfigMap" -}}
|
||||
{{- end -}}
|
||||
{{- if not .Values.classificationCatalogEnabled -}}
|
||||
{{- fail "bundled workflowConfiguration requires classificationCatalogEnabled; use an existingConfigMap for a custom bundle" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
{{/*
|
||||
The system tag catalog (deviceTags.system.catalog) uses the Hub shared choice
|
||||
catalog format: {key, label, description?}. values.schema.json validates each
|
||||
entry (key in stored device tag form, models.NormalizeDeviceTags: 1-64
|
||||
characters of a-z, 0-9, ".", "_", ":" and "-", starting and ending with a
|
||||
letter or digit) and that enabled is a boolean; keys must also be unique.
|
||||
*/}}
|
||||
{{- define "kerberoshub.deviceTags.system.validate" -}}
|
||||
{{- $seen := dict -}}
|
||||
{{- range $tag := (include "kerberoshub.deviceTags.system.catalog" . | fromJsonArray) -}}
|
||||
{{- $key := toString $tag.key -}}
|
||||
{{- if hasKey $seen $key -}}
|
||||
{{- fail (printf "deviceTags.system.catalog: duplicate key %q" $key) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $seen $key true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The configured system tag catalog as a JSON array, whether or not system tags
|
||||
are enabled (missing values read as empty).
|
||||
*/}}
|
||||
{{- define "kerberoshub.deviceTags.system.catalog" -}}
|
||||
{{- $deviceTags := .Values.deviceTags | default dict -}}
|
||||
{{- $system := $deviceTags.system | default dict -}}
|
||||
{{- $system.catalog | default list | toJson -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Whether system tags are enabled (deviceTags.system.enabled): "true" or "".
|
||||
*/}}
|
||||
{{- define "kerberoshub.deviceTags.system.enabled" -}}
|
||||
{{- $deviceTags := .Values.deviceTags | default dict -}}
|
||||
{{- $system := $deviceTags.system | default dict -}}
|
||||
{{- if and (kindIs "bool" $system.enabled) $system.enabled -}}true{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The system tag catalog file hub-api reads (DEVICE_SYSTEM_TAGS_FILE), rendered
|
||||
from deviceTags.system.catalog into the device-system-tags ConfigMap.
|
||||
*/}}
|
||||
{{- define "kerberoshub.deviceTags.system.json" -}}
|
||||
{{- include "kerberoshub.deviceTags.system.validate" . -}}
|
||||
{{- $tags := list -}}
|
||||
{{- range $tag := (include "kerberoshub.deviceTags.system.catalog" . | fromJsonArray) -}}
|
||||
{{- $entry := dict "key" $tag.key "label" $tag.label -}}
|
||||
{{- with $tag.description }}{{- $_ := set $entry "description" . -}}{{- end }}
|
||||
{{- $tags = append $tags $entry -}}
|
||||
{{- end -}}
|
||||
{{- $tags | toJson -}}
|
||||
{{- end -}}
|
||||
@@ -33,42 +33,6 @@
|
||||
{{- $uri -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
MongoDB URI with appName set to one workload, so the Atlas query profiler and
|
||||
currentOp attribute each operation to the service that issued it. Renders
|
||||
nothing when no URI is configured or mongodb.appNamePerService is false.
|
||||
Usage: include "hub.mongodb.appUri" (dict "root" $ "app" "hub-api")
|
||||
*/}}
|
||||
{{- define "hub.mongodb.appUri" -}}
|
||||
{{- $uri := include "hub.mongodb.uri" .root -}}
|
||||
{{- if and $uri .root.Values.mongodb.appNamePerService -}}
|
||||
{{- if regexMatch "(?i)[?&]appName=" $uri -}}
|
||||
{{- $uri = regexReplaceAll "(?i)([?&])appName=[^&]*" $uri (printf "${1}appName=%s" .app) -}}
|
||||
{{- else -}}
|
||||
{{- $separator := "&" -}}
|
||||
{{- if not (contains "?" $uri) -}}
|
||||
{{- $separator = "?" -}}
|
||||
{{- else if or (hasSuffix "?" $uri) (hasSuffix "&" $uri) -}}
|
||||
{{- $separator = "" -}}
|
||||
{{- end -}}
|
||||
{{- $uri = printf "%s%sappName=%s" $uri $separator .app -}}
|
||||
{{- end -}}
|
||||
{{- $uri -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Container env entry that overrides MONGODB_URI from mongodb-config with the
|
||||
workload's own appName. An explicit env entry takes precedence over envFrom.
|
||||
Usage: {{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-api") | nindent 12 }}
|
||||
*/}}
|
||||
{{- define "hub.mongodb.appUriEnv" -}}
|
||||
{{- with include "hub.mongodb.appUri" . -}}
|
||||
- name: MONGODB_URI
|
||||
value: {{ . | quote }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Render the shared MongoDB CA Secret volume. */}}
|
||||
{{- define "hub.mongodb.tlsVolume" -}}
|
||||
- name: mongodb-tls
|
||||
@@ -81,20 +45,4 @@ Usage: {{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-api") | nin
|
||||
- name: mongodb-tls
|
||||
mountPath: {{ .Values.mongodb.tls.mountPath }}
|
||||
readOnly: true
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
OpenTelemetry sampler env entries, rendered only when configured. The OTel SDKs
|
||||
read these natively; unset keeps the SDK default (parentbased_always_on).
|
||||
Usage: {{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
*/}}
|
||||
{{- define "hub.opentelemetry.samplerEnv" -}}
|
||||
{{- with .Values.opentelemetry.sampler.type }}
|
||||
- name: OTEL_TRACES_SAMPLER
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.opentelemetry.sampler.arg }}
|
||||
- name: OTEL_TRACES_SAMPLER_ARG
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -1,46 +0,0 @@
|
||||
{{- define "hub.classificationCatalog.defaults" -}}
|
||||
[
|
||||
{"key": "animal", "label": "Animal", "icon": "animal"},
|
||||
{"key": "pedestrian", "label": "Pedestrian", "icon": "pedestrian"},
|
||||
{"key": "cyclist", "label": "Cyclist", "icon": "cyclist"},
|
||||
{"key": "motorbike", "label": "Motorbike", "icon": "motorbike"},
|
||||
{"key": "lorry", "label": "Lorry", "icon": "lorry"},
|
||||
{"key": "car", "label": "Car", "icon": "car"},
|
||||
{"key": "handbag", "label": "Handbag", "icon": "handbag"},
|
||||
{"key": "suitcase", "label": "Suitcase", "icon": "suitcase"},
|
||||
{"key": "cell phone", "label": "Cell phone", "icon": "cell phone"}
|
||||
]
|
||||
{{- end -}}
|
||||
{{/*
|
||||
The effective chart-rendered catalog: the inline classificationCatalog values
|
||||
list when set, otherwise the bundled defaults above. values.schema.json
|
||||
validates each entry ({key, label, icon?}); keys must also be unique.
|
||||
*/}}
|
||||
{{- define "hub.classificationCatalog.json" -}}
|
||||
{{- if kindIs "invalid" .Values.classificationCatalog -}}
|
||||
{{- include "hub.classificationCatalog.defaults" . | mustFromJson | toJson -}}
|
||||
{{- else if and (kindIs "slice" .Values.classificationCatalog) .Values.classificationCatalog -}}
|
||||
{{- $seen := dict -}}
|
||||
{{- range $entry := .Values.classificationCatalog -}}
|
||||
{{- $key := toString $entry.key -}}
|
||||
{{- if hasKey $seen $key -}}
|
||||
{{- fail (printf "classificationCatalog: duplicate key %q" $key) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $seen $key true -}}
|
||||
{{- end -}}
|
||||
{{- .Values.classificationCatalog | toJson -}}
|
||||
{{- else -}}
|
||||
{{- fail "classificationCatalog must be omitted (bundled defaults) or a nonempty list; to use the API fallback set classificationCatalogEnabled: false" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- if and .Values.classificationCatalogEnabled (not .Values.classificationCatalogExistingConfigMap) (not (hasKey $config.replacements "catalogs/classifications.json")) }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: classification-catalog
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
classifications.json: |-
|
||||
{{- include "hub.classificationCatalog.json" . | mustFromJson | toPrettyJson | nindent 4 }}
|
||||
{{- end -}}
|
||||
@@ -1,12 +0,0 @@
|
||||
{{- if include "kerberoshub.deviceTags.system.enabled" . }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: device-system-tags
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
# Rendered from deviceTags.system.catalog; hub-api reads it through
|
||||
# DEVICE_SYSTEM_TAGS_FILE.
|
||||
system-tags.json: |-
|
||||
{{- include "kerberoshub.deviceTags.system.json" . | mustFromJson | toPrettyJson | nindent 4 }}
|
||||
{{- end -}}
|
||||
@@ -1,135 +0,0 @@
|
||||
{{/* Validate the rendered anpr.yaml payload against schemas/workflow-contract.schema.json in the chart root. */}}
|
||||
{{- define "hub.workflowConfiguration.anpr" -}}
|
||||
schemaVersion: 1
|
||||
stage: anpr
|
||||
contractVersion: 1
|
||||
presentation:
|
||||
name: Number plate recognition
|
||||
description: Read the number plates of vehicles in the recording.
|
||||
icon: car
|
||||
color: amber
|
||||
|
||||
# Node settings hub-anpr reads per run (see its README, "Node settings"). Each
|
||||
# overrides the matching ANPR_* environment variable; the defaults below equal
|
||||
# the worker's built-in defaults. A deployment that changes those variables
|
||||
# should change these defaults too, because new nodes store them on save.
|
||||
params:
|
||||
- name: plateClasses
|
||||
label: Read plates for
|
||||
description: Vehicle classes whose plates are read (ANPR_PLATE_CLASSES).
|
||||
type: multiselect
|
||||
choices: {file: ../catalogs/classifications.json}
|
||||
default: [car]
|
||||
group: Which vehicles
|
||||
- name: minMovement
|
||||
label: Minimum movement
|
||||
description: Skip vehicles that barely moved; 0 reads every vehicle (ANPR_READ_MOVEMENT_MIN).
|
||||
type: number
|
||||
minimum: 0
|
||||
default: 0
|
||||
group: Which vehicles
|
||||
- name: maxJump
|
||||
label: Maximum jump between frames
|
||||
description: Drop unstable tracks; 0 disables the check (ANPR_READ_MAX_JUMP).
|
||||
type: number
|
||||
minimum: 0
|
||||
default: 0
|
||||
group: Which vehicles
|
||||
- name: readFrames
|
||||
label: Frames to read per vehicle
|
||||
description: Frames OCR'd per vehicle; 0 disables OCR (ANPR_READ_FRAMES).
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 10
|
||||
default: 1
|
||||
group: Recognition
|
||||
- name: readQuorum
|
||||
label: Agreeing reads required
|
||||
description: Reads that must agree before a plate is accepted; 0 accepts any read (ANPR_READ_QUORUM).
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 10
|
||||
default: 0
|
||||
group: Recognition
|
||||
- name: minConfidence
|
||||
label: Minimum OCR confidence
|
||||
description: Ignore reads below this confidence (ANPR_READ_CONF_MIN).
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
default: 0
|
||||
group: Recognition
|
||||
|
||||
# Paths are relative to results.anpr (models.WorkflowAnprResult): one track and
|
||||
# one marker per vehicle. Plate, unread and class can be tied to the same
|
||||
# vehicle with "Must match the same item"; OCR confidence and engine live on
|
||||
# the markers and cannot be tied to a plate.
|
||||
fields:
|
||||
- id: plate
|
||||
label: Number plate
|
||||
path: detections.*.tracks.*.meta.plate
|
||||
type: string
|
||||
operators: [eq, in, contains, matches] # stored uppercase, letters and digits only, e.g. AB123CD
|
||||
optional: true
|
||||
- id: unread
|
||||
label: Plate could not be read
|
||||
path: detections.*.tracks.*.meta.unread
|
||||
type: boolean
|
||||
operators: [eq]
|
||||
optional: true
|
||||
- id: vehicleClass
|
||||
label: Vehicle class (from classification)
|
||||
path: detections.*.tracks.*.meta.classifiedAs
|
||||
type: string
|
||||
operators: [eq, in]
|
||||
optional: true
|
||||
choices: {file: ../catalogs/classifications.json}
|
||||
- id: ocrConfidence
|
||||
label: OCR confidence
|
||||
path: markers.*.metadata.confidence
|
||||
type: number
|
||||
operators: [gte, gt, lte, lt] # read plates only; about 0-1 for fast, 0.5 for tesseract
|
||||
minimum: 0
|
||||
optional: true
|
||||
- id: ocrEngine
|
||||
label: OCR engine
|
||||
path: markers.*.metadata.engine
|
||||
type: string
|
||||
operators: [eq, in] # read plates only
|
||||
optional: true
|
||||
choices: {values: [fast, tesseract, tesseract+opencv, http]}
|
||||
- id: secondsVisible
|
||||
label: Seconds visible
|
||||
path: markers.*.duration
|
||||
type: integer
|
||||
operators: [gte, lte]
|
||||
minimum: 0
|
||||
optional: true
|
||||
|
||||
# Connections leaving this stage; Extra conditions are appended collapsed.
|
||||
panel:
|
||||
sections:
|
||||
- id: plate
|
||||
label: Plate
|
||||
fields:
|
||||
- {field: plate, widget: text, operator: contains, label: Plate contains (e.g. AB123)}
|
||||
- {field: unread, widget: toggle, label: Plate could not be read}
|
||||
- id: recognition
|
||||
label: Recognition
|
||||
collapsed: true
|
||||
fields:
|
||||
- {field: ocrConfidence, widget: number, operator: gte, label: Minimum OCR confidence}
|
||||
- {field: vehicleClass, widget: multiselect}
|
||||
- {field: secondsVisible, widget: number, operator: gte, label: Visible for at least (seconds)}
|
||||
{{ end -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- if and $config.enabled $config.includeDefaults (not (hasKey $config.replacements "workflow-contracts/anpr.yaml")) (or (eq .Values.mode "all") (eq .Values.mode "ui")) }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "hub.workflowConfiguration.anpr.configMapName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
anpr.yaml: |-
|
||||
{{- include "hub.workflowConfiguration.anpr" . | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -1,95 +0,0 @@
|
||||
{{/* Validate the rendered forwarder.yaml payload against schemas/workflow-contract.schema.json in the chart root. */}}
|
||||
{{- define "hub.workflowConfiguration.forwarder" -}}
|
||||
schemaVersion: 1
|
||||
stage: forwarder
|
||||
contractVersion: 1
|
||||
presentation:
|
||||
name: Forward to my system
|
||||
description: Send the run's data to your own RabbitMQ destination.
|
||||
icon: share
|
||||
color: green
|
||||
|
||||
# Custom workflows supply their own connection; deployment credentials are never
|
||||
# inherited. These non-secret defaults are stored on save, not read from the worker.
|
||||
params:
|
||||
- name: broker
|
||||
label: Provider
|
||||
description: Custom workflows use their own RabbitMQ destination. Shared deployment integrations are reserved for global workflows.
|
||||
type: select
|
||||
required: true
|
||||
options: [rabbitmq]
|
||||
optionLabels: {rabbitmq: RabbitMQ}
|
||||
default: rabbitmq
|
||||
group: Delivery
|
||||
- name: mode
|
||||
label: Completion mode
|
||||
description: Complete after delivery, or wait for the external result. Callback requires a deployment-configured Hub callback URL.
|
||||
type: select
|
||||
required: true
|
||||
options: [delivered, callback]
|
||||
optionLabels: {delivered: Complete after delivery, callback: Wait for result}
|
||||
default: delivered
|
||||
group: Delivery
|
||||
- name: host
|
||||
label: Host
|
||||
description: Your RabbitMQ destination host. Required before enabling the workflow.
|
||||
type: string
|
||||
required: true
|
||||
placeholder: rabbitmq.example.com
|
||||
group: RabbitMQ destination
|
||||
- name: port
|
||||
label: Port
|
||||
description: Your RabbitMQ destination port, a whole number (usually 5671 with TLS or 5672 without TLS).
|
||||
type: number
|
||||
required: true
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
group: RabbitMQ destination
|
||||
- name: queue
|
||||
label: Queue
|
||||
description: Your destination queue. Its dead-letter queue is this name followed by .forwarder-dead-letter.
|
||||
type: string
|
||||
required: true
|
||||
group: RabbitMQ destination
|
||||
- name: vhost
|
||||
label: Virtual host
|
||||
description: Your RabbitMQ virtual host. Use / for the broker's default virtual host.
|
||||
type: string
|
||||
required: true
|
||||
placeholder: /
|
||||
group: RabbitMQ destination
|
||||
- name: tls
|
||||
label: Use TLS
|
||||
description: Encrypt the connection and verify the broker certificate using system trust roots. Deployment TLS overrides are never inherited.
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
group: RabbitMQ destination
|
||||
- name: username
|
||||
label: Username
|
||||
description: Your RabbitMQ destination username. Deployment credentials are never used.
|
||||
type: string
|
||||
required: true
|
||||
group: RabbitMQ credentials
|
||||
- name: password
|
||||
label: Password
|
||||
description: Your RabbitMQ destination password. Required before enabling the workflow; masked after saving.
|
||||
type: secret
|
||||
required: true
|
||||
group: RabbitMQ credentials
|
||||
|
||||
# It contributes no output fields: connections after it can still use Start and
|
||||
# earlier stages in Extra conditions.
|
||||
fields: []
|
||||
{{ end -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- if and $config.enabled $config.includeDefaults (not (hasKey $config.replacements "workflow-contracts/forwarder.yaml")) (or (eq .Values.mode "all") (eq .Values.mode "ui")) }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "hub.workflowConfiguration.forwarder.configMapName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
forwarder.yaml: |-
|
||||
{{- include "hub.workflowConfiguration.forwarder" . | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -1,75 +0,0 @@
|
||||
{{/* Validate the rendered start.yaml payload against schemas/workflow-contract.schema.json in the chart root. */}}
|
||||
{{- define "hub.workflowConfiguration.start" -}}
|
||||
schemaVersion: 1
|
||||
stage: start
|
||||
contractVersion: 1
|
||||
|
||||
# Paths refer to the safe initial context, not the full event or credentials.
|
||||
fields:
|
||||
- id: device
|
||||
label: Device
|
||||
path: device.deviceKey
|
||||
type: string
|
||||
operators: [eq, in]
|
||||
choices:
|
||||
source: project.devices
|
||||
- id: sites
|
||||
label: Sites
|
||||
path: device.siteIds.*
|
||||
type: string
|
||||
operators: [in]
|
||||
choices:
|
||||
source: project.sites
|
||||
- id: groups
|
||||
label: Groups
|
||||
path: device.groupIds.*
|
||||
type: string
|
||||
operators: [in]
|
||||
choices:
|
||||
source: project.groups
|
||||
- id: classifications
|
||||
label: Initial classifications
|
||||
path: inputs.classify.details.*.classified
|
||||
type: string
|
||||
operators: [in]
|
||||
optional: true
|
||||
choices:
|
||||
file: ../catalogs/classifications.json
|
||||
# Condition-only context fields (not selectors): advanced conditions on
|
||||
# Start connections may test only declared fields, under device. or inputs.
|
||||
- id: deviceName
|
||||
label: Camera name
|
||||
path: device.deviceName
|
||||
type: string
|
||||
operators: [eq, in, contains, matches]
|
||||
- id: objectClass
|
||||
label: Detected object
|
||||
path: inputs.classify.details.*.classified
|
||||
type: string
|
||||
operators: [eq, ne, in, exists]
|
||||
optional: true
|
||||
choices:
|
||||
file: ../catalogs/classifications.json
|
||||
|
||||
# Controls offered for new authoring; an omitted control is not offered.
|
||||
controls:
|
||||
- id: weeklySchedule
|
||||
label: Weekly schedule
|
||||
- id: conditions
|
||||
label: Advanced conditions
|
||||
|
||||
# Trigger modes offered for new authoring; omitted offers both.
|
||||
triggerModes: [automatic, manual]
|
||||
{{ end -}}
|
||||
{{- include "hub.workflowConfiguration.validate" . -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- if and $config.enabled $config.includeDefaults (not (hasKey $config.replacements "workflow-contracts/start.yaml")) (or (eq .Values.mode "all") (eq .Values.mode "ui")) }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ printf "%s-workflow-configuration" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
start.yaml: |-
|
||||
{{- include "hub.workflowConfiguration.start" . | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{- if .Values.classificationCatalogEnabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: classification-catalog
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
classifications.json: |-
|
||||
{{- include "hub.classificationCatalog" . | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- include "hub.workflowConfiguration.validate" . -}}
|
||||
{{- $config := .Values.workflowConfiguration | default dict -}}
|
||||
{{- if and $config.enabled (not $config.existingConfigMap) (or (eq .Values.mode "all") (eq .Values.mode "ui")) }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ printf "%s-workflow-configuration" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
start.yaml: |-
|
||||
{{- .Files.Get "configuration/workflow-contracts/start.yaml" | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -1,6 +1,5 @@
|
||||
{{- if or (eq .Values.mode "all") (eq .Values.mode "ui") -}}
|
||||
{{- $workflowConfiguration := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- $catalogReplacement := index $workflowConfiguration.replacements "catalogs/classifications.json" | default dict -}}
|
||||
{{- $workflowConfiguration := .Values.workflowConfiguration | default dict -}}
|
||||
{{- if .Values.kerberoshub.api.serviceEnabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -121,21 +120,12 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config-mongodb: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
{{- if and .Values.classificationCatalogEnabled (not .Values.classificationCatalogExistingConfigMap) (not $catalogReplacement) }}
|
||||
checksum/classification-catalog: {{ include (print $.Template.BasePath "/configMaps/catalogs/classifications.yaml") . | sha256sum }}
|
||||
checksum/config-mongodb: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
{{- if .Values.classificationCatalogEnabled }}
|
||||
checksum/classification-catalog: {{ include (print $.Template.BasePath "/configmap-classification-catalog.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if include "kerberoshub.deviceTags.system.enabled" . }}
|
||||
checksum/device-system-tags: {{ include (print $.Template.BasePath "/configMaps/catalogs/device-system-tags.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if and $workflowConfiguration.enabled $workflowConfiguration.includeDefaults (not (hasKey $workflowConfiguration.replacements "workflow-contracts/start.yaml")) }}
|
||||
checksum/workflow-configuration: {{ include (print $.Template.BasePath "/configMaps/workflow-contracts/start.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if and $workflowConfiguration.enabled $workflowConfiguration.includeDefaults (not (hasKey $workflowConfiguration.replacements "workflow-contracts/anpr.yaml")) }}
|
||||
checksum/workflow-contract-anpr: {{ include (print $.Template.BasePath "/configMaps/workflow-contracts/anpr.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if and $workflowConfiguration.enabled $workflowConfiguration.includeDefaults (not (hasKey $workflowConfiguration.replacements "workflow-contracts/forwarder.yaml")) }}
|
||||
checksum/workflow-contract-forwarder: {{ include (print $.Template.BasePath "/configMaps/workflow-contracts/forwarder.yaml") . | sha256sum }}
|
||||
{{- if and $workflowConfiguration.enabled (not $workflowConfiguration.existingConfigMap) }}
|
||||
checksum/workflow-configuration: {{ include (print $.Template.BasePath "/configmap-workflow-configuration.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
labels:
|
||||
app: hub-api
|
||||
@@ -150,64 +140,34 @@ spec:
|
||||
{{- $serverTLS := .Values.kerberoshub.api.serverTLS }}
|
||||
{{- $mongodbTLS := .Values.mongodb.tls }}
|
||||
{{- $classificationCatalogEnabled := .Values.classificationCatalogEnabled }}
|
||||
{{- $deviceSystemTagsEnabled := include "kerberoshub.deviceTags.system.enabled" . }}
|
||||
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled $deviceSystemTagsEnabled .Values.kerberoshub.api.volumes (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
|
||||
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled .Values.kerberoshub.api.volumes (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
|
||||
volumes:
|
||||
{{- if $workflowConfiguration.enabled }}
|
||||
- name: workflow-configuration
|
||||
{{- if $workflowConfiguration.existingConfigMap }}
|
||||
configMap:
|
||||
name: {{ $workflowConfiguration.existingConfigMap | quote }}
|
||||
items:
|
||||
{{- toYaml $workflowConfiguration.items | nindent 14 }}
|
||||
{{- else }}
|
||||
projected:
|
||||
sources:
|
||||
{{- if $workflowConfiguration.includeDefaults }}
|
||||
{{- if not (hasKey $workflowConfiguration.replacements "workflow-contracts/start.yaml") }}
|
||||
- configMap:
|
||||
name: {{ printf "%s-workflow-configuration" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
items:
|
||||
- key: start.yaml
|
||||
path: workflow-contracts/start.yaml
|
||||
{{- end }}
|
||||
{{- if not (hasKey $workflowConfiguration.replacements "workflow-contracts/anpr.yaml") }}
|
||||
- configMap:
|
||||
name: {{ include "hub.workflowConfiguration.anpr.configMapName" . }}
|
||||
items:
|
||||
- key: anpr.yaml
|
||||
path: workflow-contracts/anpr.yaml
|
||||
{{- end }}
|
||||
{{- if not (hasKey $workflowConfiguration.replacements "workflow-contracts/forwarder.yaml") }}
|
||||
- configMap:
|
||||
name: {{ include "hub.workflowConfiguration.forwarder.configMapName" . }}
|
||||
items:
|
||||
- key: forwarder.yaml
|
||||
path: workflow-contracts/forwarder.yaml
|
||||
{{- end }}
|
||||
{{- if not $catalogReplacement }}
|
||||
- configMap:
|
||||
name: {{ include "hub.classificationCatalog.configMapName" . }}
|
||||
name: classification-catalog
|
||||
items:
|
||||
- key: classifications.json
|
||||
path: catalogs/classifications.json
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $workflowConfiguration.configMaps }}
|
||||
- configMap:
|
||||
name: {{ .name | quote }}
|
||||
items:
|
||||
{{- toYaml .items | nindent 20 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $classificationCatalogEnabled }}
|
||||
- name: classification-catalog
|
||||
configMap:
|
||||
name: {{ include "hub.classificationCatalog.configMapName" . }}
|
||||
{{- if or .Values.classificationCatalogExistingConfigMap $catalogReplacement }}
|
||||
items:
|
||||
- key: {{ default "classifications.json" $catalogReplacement.key | quote }}
|
||||
path: classifications.json
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $deviceSystemTagsEnabled }}
|
||||
- name: device-system-tags
|
||||
configMap:
|
||||
name: device-system-tags
|
||||
name: classification-catalog
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.api.volumes }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
@@ -236,7 +196,7 @@ spec:
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled $deviceSystemTagsEnabled .Values.kerberoshub.api.volumeMounts (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
|
||||
{{- if or $workflowConfiguration.enabled $classificationCatalogEnabled .Values.kerberoshub.api.volumeMounts (and $serverTLS.enabled $serverTLS.secretName) (and $mongodbTLS.enabled $mongodbTLS.existingSecret) }}
|
||||
volumeMounts:
|
||||
{{- if $workflowConfiguration.enabled }}
|
||||
- name: workflow-configuration
|
||||
@@ -248,11 +208,6 @@ spec:
|
||||
mountPath: /etc/kerberos/classifications
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- if $deviceSystemTagsEnabled }}
|
||||
- name: device-system-tags
|
||||
mountPath: /etc/kerberos/device-tags
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.api.volumeMounts}}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
@@ -270,17 +225,10 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-api") | nindent 12 }}
|
||||
{{- if $classificationCatalogEnabled }}
|
||||
- name: CLASSIFICATION_CATALOG_FILE
|
||||
value: /etc/kerberos/classifications/classifications.json
|
||||
{{- end }}
|
||||
{{- if $deviceSystemTagsEnabled }}
|
||||
# System tag catalog (deviceTags.system.catalog): the only system
|
||||
# tags owners can assign. Unset (disabled) disables system tags.
|
||||
- name: DEVICE_SYSTEM_TAGS_FILE
|
||||
value: /etc/kerberos/device-tags/system-tags.json
|
||||
{{- end }}
|
||||
- name: ENVIRONMENT
|
||||
value: "{{ .Values.environment }}"
|
||||
- name: READ_ONLY
|
||||
@@ -310,9 +258,6 @@ spec:
|
||||
{{- if and .Values.kerberoshub.frontend.demoEnabled .Values.kerberoshub.frontend.demoUrl }}
|
||||
{{- $corsOrigins = append $corsOrigins (printf "%s://%s" .Values.kerberoshub.frontend.schema .Values.kerberoshub.frontend.demoUrl) }}
|
||||
{{- end }}
|
||||
{{- range .Values.kerberoshub.api.corsAllowedOrigins }}
|
||||
{{- $corsOrigins = append $corsOrigins . }}
|
||||
{{- end }}
|
||||
- name: CORS_ALLOWED_ORIGINS
|
||||
value: {{ join "," $corsOrigins | quote }}
|
||||
{{ if .Values.isPrivate }}
|
||||
@@ -597,6 +542,5 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config-mongodb: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config-mongodb: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: hub-audit
|
||||
spec:
|
||||
@@ -114,7 +114,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-audit") | nindent 12 }}
|
||||
- name: RABBITMQ_HOST
|
||||
value: {{ .Values.rabbitmq.host | quote }}
|
||||
- name: RABBITMQ_EXCHANGE
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: hub-cleanup
|
||||
spec:
|
||||
@@ -66,7 +66,6 @@ spec:
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-cleanup") | nindent 12 }}
|
||||
- name: MODE
|
||||
value: "{{ .Values.kerberoshub.cleanup.mode }}"
|
||||
- name: LOG_LEVEL
|
||||
@@ -111,8 +110,6 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.cleanup.defaultTaskRetentionDays }}"
|
||||
- name: AUDIT_EVENT_RETENTION_DAYS
|
||||
value: "{{ .Values.kerberoshub.cleanup.auditEventRetentionDays }}"
|
||||
- name: WORKFLOW_RUN_RETENTION_DAYS
|
||||
value: "{{ .Values.kerberoshub.cleanup.workflowRunRetentionDays | default "0" }}"
|
||||
{{- if .Values.kerberoshub.extraEnv }}
|
||||
{{- toYaml .Values.kerberoshub.extraEnv | nindent 12 }}
|
||||
{{- end }}
|
||||
|
||||
@@ -192,8 +192,6 @@ spec:
|
||||
# Google maps for using and displaying cameras, sites on a map
|
||||
- name: GOOGLEMAPS_KEY
|
||||
value: "{{ .Values.kerberoshub.frontend.googlemaps.apikey }}"
|
||||
- name: GOOGLEMAPS_PLACES_API
|
||||
value: "{{ .Values.kerberoshub.frontend.googlemaps.placesApi }}"
|
||||
|
||||
# features > chart
|
||||
- name: CHART_COLORS
|
||||
@@ -237,8 +235,6 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.frontend.features.workflows.enabled }}"
|
||||
- name: FEATURE_AUDIT_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.audit.enabled }}"
|
||||
- name: FEATURE_DAY_OVERVIEW_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.dayOverview.enabled }}"
|
||||
- name: FEATURE_DEVICE_ANALYTICS_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.devices.analyticsEnabled }}"
|
||||
- name: FEATURE_DASHBOARD_SITES_FULLSCREEN_ENABLED
|
||||
|
||||
@@ -275,8 +275,6 @@ spec:
|
||||
# Google maps for using and displaying cameras, sites on a map
|
||||
- name: GOOGLEMAPS_KEY
|
||||
value: "{{ .Values.kerberoshub.frontend.googlemaps.apikey }}"
|
||||
- name: GOOGLEMAPS_PLACES_API
|
||||
value: "{{ .Values.kerberoshub.frontend.googlemaps.placesApi }}"
|
||||
|
||||
# Zendesk for support
|
||||
- name: ZENDESK_URL
|
||||
@@ -316,8 +314,6 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.frontend.features.workflows.enabled }}"
|
||||
- name: FEATURE_AUDIT_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.audit.enabled }}"
|
||||
- name: FEATURE_DAY_OVERVIEW_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.dayOverview.enabled }}"
|
||||
- name: FEATURE_DEVICE_ANALYTICS_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.devices.analyticsEnabled }}"
|
||||
- name: FEATURE_DASHBOARD_SITES_FULLSCREEN_ENABLED
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: hub-monitor-device
|
||||
spec:
|
||||
@@ -64,7 +64,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-monitor-device") | nindent 12 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberoshub.monitordevice.logLevel }}"
|
||||
- name: READ_ONLY
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: hub-reactivate-subscription
|
||||
spec:
|
||||
@@ -57,7 +57,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-reactivatesubscriptions") | nindent 12 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberoshub.reactivate.logLevel }}"
|
||||
- name: READ_ONLY
|
||||
|
||||
@@ -21,64 +21,6 @@ workflow later cannot silently alias another workflow's runs or references.
|
||||
{{- end -}}
|
||||
{{- $_ := set $identities $id $name -}}
|
||||
{{- end -}}
|
||||
{{- include "kerberoshub.workflows.validateSystemTags" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Guard the definitions that can select devices by system tag. The engine reads
|
||||
JSON keys case-insensitively and ignores unknown ones, so a misspelled or
|
||||
re-cased key silently changes what a definition selects (a dropped
|
||||
systemTags scope makes a global workflow run on every device). Therefore:
|
||||
- definitions and triggers may only use the keys the engine knows, spelled
|
||||
exactly (definition: id, enabled, triggers, stages; trigger: the
|
||||
models.WorkflowTrigger fields);
|
||||
- while deviceTags.system.enabled is false, no definition may mention system
|
||||
tags anywhere (trigger systemTags, or a device.systemTags condition at any
|
||||
depth, in triggers, shared conditions or stage needs), matched
|
||||
case-insensitively on the whole definition;
|
||||
- every system tag a trigger selects must be in deviceTags.system.catalog,
|
||||
so a typo fails rendering instead of never matching a device.
|
||||
Disabled definitions are checked too, so enabling one cannot fail later.
|
||||
*/}}
|
||||
{{- define "kerberoshub.workflows.validateSystemTags" -}}
|
||||
{{- include "kerberoshub.deviceTags.system.validate" . -}}
|
||||
{{- $catalog := dict -}}
|
||||
{{- range $tag := (include "kerberoshub.deviceTags.system.catalog" . | fromJsonArray) -}}
|
||||
{{- $_ := set $catalog (toString $tag.key) true -}}
|
||||
{{- end -}}
|
||||
{{- $enabled := include "kerberoshub.deviceTags.system.enabled" . -}}
|
||||
{{- $definitionKeys := list "id" "enabled" "triggers" "stages" -}}
|
||||
{{- $triggerKeys := list "type" "edgeId" "sharedConditions" "devices" "siteIds" "groupIds" "systemTags" "classifications" "conditionMode" "conditions" "weeklySchedule" "surfaces" -}}
|
||||
{{- range $name, $wf := .Values.kerberoshub.workflows.definitions -}}
|
||||
{{- if $wf -}}
|
||||
{{- if not (kindIs "map" $wf) -}}
|
||||
{{- fail (printf "kerberoshub.workflows.definitions[%q] must be an object" $name) -}}
|
||||
{{- end -}}
|
||||
{{- if and (not $enabled) (contains "systemtags" (lower (toJson $wf))) -}}
|
||||
{{- fail (printf "kerberoshub.workflows.definitions[%q] selects devices by system tag (systemTags or a device.systemTags condition), but deviceTags.system.enabled is false" $name) -}}
|
||||
{{- end -}}
|
||||
{{- range $key, $_ := $wf -}}
|
||||
{{- if not (has $key $definitionKeys) -}}
|
||||
{{- fail (printf "kerberoshub.workflows.definitions[%q]: unknown key %q (allowed: %s). Keys are case-sensitive; a misspelled key would be ignored" $name $key (join ", " $definitionKeys)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- range $i, $trigger := ($wf.triggers | default list) -}}
|
||||
{{- if not (kindIs "map" $trigger) -}}
|
||||
{{- fail (printf "kerberoshub.workflows.definitions[%q].triggers[%d] must be an object" $name $i) -}}
|
||||
{{- end -}}
|
||||
{{- range $key, $_ := $trigger -}}
|
||||
{{- if not (has $key $triggerKeys) -}}
|
||||
{{- fail (printf "kerberoshub.workflows.definitions[%q].triggers[%d]: unknown key %q (allowed: %s). Keys are case-sensitive; a misspelled key would be ignored and widen the trigger" $name $i $key (join ", " $triggerKeys)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- range $tag := ($trigger.systemTags | default list) -}}
|
||||
{{- if not (hasKey $catalog (toString $tag)) -}}
|
||||
{{- fail (printf "kerberoshub.workflows.definitions[%q].triggers[%d].systemTags: %q is not in deviceTags.system.catalog; add it to the system tag catalog" $name $i (toString $tag)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: hub-workflows
|
||||
spec:
|
||||
@@ -65,7 +65,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-workflows") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberoshub.services.workflows.logLevel }}"
|
||||
- name: QUEUE_SYSTEM
|
||||
@@ -115,7 +114,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-analysis
|
||||
spec:
|
||||
@@ -68,7 +68,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-analysis") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.analysis.logLevel }}"
|
||||
- name: CLOUD_PROVIDER
|
||||
@@ -121,20 +120,6 @@ spec:
|
||||
# Sprites
|
||||
- name: SPRITE_ENABLED
|
||||
value: "{{ .Values.kerberospipeline.sprite.enabled }}"
|
||||
|
||||
# Dominant colors
|
||||
- name: DOMINANTCOLOR_ENABLED
|
||||
value: "{{ .Values.kerberospipeline.dominantColor.enabled }}"
|
||||
|
||||
# Thumbnail regeneration from classified frames (opt-in)
|
||||
- name: THUMBNAIL_REGENERATION_ENABLED
|
||||
value: {{ .Values.kerberospipeline.analysis.thumbnailRegeneration.enabled | default false | quote }}
|
||||
|
||||
# In-memory cache for hot-path MongoDB lookups
|
||||
- name: DB_CACHE_ENABLED
|
||||
value: {{ .Values.kerberospipeline.analysis.dbCache.enabled | default false | quote }}
|
||||
- name: DB_CACHE_TTL
|
||||
value: {{ .Values.kerberospipeline.analysis.dbCache.ttl | default "5m" | quote }}
|
||||
|
||||
{{- if .Values.opentelemetry.enabled }}
|
||||
# Open Telemetry tracing
|
||||
@@ -144,7 +129,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -89,7 +89,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{- if and (or (eq .Values.mode "all") (eq .Values.mode "pipeline")) (eq .Values.kerberospipeline.dominantColor.enabled true) -}}
|
||||
{{- if or (eq .Values.mode "all") (eq .Values.mode "pipeline") -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -89,7 +89,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-event
|
||||
spec:
|
||||
@@ -70,7 +70,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-event") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.event.logLevel }}"
|
||||
- name: READ_ONLY
|
||||
@@ -110,7 +109,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-export
|
||||
spec:
|
||||
@@ -64,7 +64,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-export") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.export.logLevel }}"
|
||||
{{- if .Values.kerberospipeline.export.playerAssetsPath }}
|
||||
@@ -142,7 +141,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-monitor
|
||||
spec:
|
||||
@@ -68,10 +68,11 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-monitor") | nindent 8 }}
|
||||
# Application
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.monitor.logLevel }}"
|
||||
- name: LATEST_MEDIA_UPDATE_INTERVAL_SECONDS
|
||||
value: {{ .Values.kerberospipeline.monitor.latestMediaUpdateIntervalSeconds | quote }}
|
||||
|
||||
# Queue
|
||||
- name: QUEUE_SYSTEM
|
||||
@@ -131,7 +132,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-notify-test
|
||||
spec:
|
||||
@@ -64,7 +64,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-notification-test") | nindent 8 }}
|
||||
- name: QUEUE_SYSTEM
|
||||
value: "{{ .Values.queueProvider }}"
|
||||
|
||||
@@ -130,7 +129,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-notify
|
||||
spec:
|
||||
@@ -68,7 +68,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-notification") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.notify.logLevel }}"
|
||||
- name: NOTIFICATION_EXPIRY_MINUTES
|
||||
@@ -176,7 +175,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-sequence
|
||||
spec:
|
||||
@@ -68,7 +68,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-sequence") | nindent 8 }}
|
||||
- name: CLOUD_PROVIDER
|
||||
value: "{{ .Values.cloudProvider }}"
|
||||
- name: QUEUE_SYSTEM
|
||||
@@ -104,7 +103,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -87,10 +87,6 @@ spec:
|
||||
value: "{{ .Values.rabbitmq.username }}"
|
||||
- name: RABBITMQ_PASSWORD
|
||||
value: "{{ .Values.rabbitmq.password }}"
|
||||
{{- with .Values.kerberospipeline.sprite.prefetchCount }}
|
||||
- name: RABBITMQ_PREFETCH_COUNT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
|
||||
# There is an option to store the sprites in a storage provider in Kerberos Vault.
|
||||
- name: VAULT_SPRITE_ENABLED
|
||||
@@ -116,7 +112,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -22,7 +22,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: pipe-throttler
|
||||
spec:
|
||||
@@ -68,7 +68,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-throttler") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.throttler.logLevel }}"
|
||||
- name: CLOUD_PROVIDER
|
||||
@@ -106,7 +105,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -87,10 +87,6 @@ spec:
|
||||
value: "{{ .Values.rabbitmq.username }}"
|
||||
- name: RABBITMQ_PASSWORD
|
||||
value: "{{ .Values.rabbitmq.password }}"
|
||||
{{- with .Values.kerberospipeline.thumbnail.prefetchCount }}
|
||||
- name: RABBITMQ_PREFETCH_COUNT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
|
||||
# Thumbnail generation
|
||||
- name: THUMBNAIL_QUALITY
|
||||
@@ -119,7 +115,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
---
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configMaps/configmap-mongodb.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap-mongodb.yaml") . | sha256sum }}
|
||||
labels:
|
||||
app: vault-forwarder
|
||||
spec:
|
||||
@@ -62,7 +62,6 @@ spec:
|
||||
- configMapRef:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "vault-forwarder") | nindent 10 }}
|
||||
- name: ITERATION_SPEED
|
||||
value: "5"
|
||||
- name: BUFFER_TIME
|
||||
|
||||
+11
-125
@@ -2,103 +2,13 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"classificationCatalogExistingConfigMap": {
|
||||
"type": "string",
|
||||
"maxLength": 253,
|
||||
"pattern": "^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
|
||||
},
|
||||
"classificationCatalog": {
|
||||
"type": ["array", "null"],
|
||||
"minItems": 1,
|
||||
"description": "Optional inline classification catalog in the Hub shared choice catalog format; replaces the bundled defaults in the chart's classification-catalog ConfigMap. Omit (or null) for the bundled defaults. Keys must be unique (checked when rendering).",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "label"],
|
||||
"properties": {
|
||||
"key": {"type": "string", "minLength": 1},
|
||||
"label": {"type": "string", "minLength": 1},
|
||||
"icon": {"type": "string", "minLength": 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deviceTags": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"system": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Owners may assign system tags and global workflows may select devices by them. Only for deployments whose owners are trusted (self-hosted)."
|
||||
},
|
||||
"catalog": {
|
||||
"type": "array",
|
||||
"description": "The system tags owners can assign, in the Hub shared choice catalog format (key, label) plus an optional description. Keys must be unique (checked when rendering).",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "label"],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"maxLength": 64,
|
||||
"pattern": "^[a-z0-9]([a-z0-9._:-]*[a-z0-9])?$"
|
||||
},
|
||||
"label": {"type": "string", "minLength": 1},
|
||||
"description": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kerberoshub": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"corsAllowedOrigins": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^https?://[^/?#]+$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"classificationCatalog": {"type": ["array", "null"]},
|
||||
"workflowConfiguration": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["enabled"],
|
||||
"not": {
|
||||
"allOf": [
|
||||
{"anyOf": [{"required": ["includeDefaults"]}, {"required": ["configMaps"]}]},
|
||||
{"anyOf": [{"required": ["existingConfigMap"]}, {"required": ["items"]}, {"required": ["extraConfigMaps"]}]}
|
||||
]
|
||||
},
|
||||
"required": ["enabled", "existingConfigMap", "items"],
|
||||
"properties": {
|
||||
"enabled": {"type": "boolean"},
|
||||
"includeDefaults": {
|
||||
"type": "boolean",
|
||||
"description": "Include bundled files not explicitly replaced by configMaps items; true when omitted."
|
||||
},
|
||||
"configMaps": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/configMapSource"},
|
||||
"description": "Add files or explicitly replace a bundled file using replace: true on an item."
|
||||
},
|
||||
"extraConfigMaps": {
|
||||
"type": "array",
|
||||
"description": "Legacy additive sources; use configMaps for new deployments.",
|
||||
"items": {"$ref": "#/definitions/configMapSource"}
|
||||
},
|
||||
"existingConfigMap": {
|
||||
"type": "string",
|
||||
"maxLength": 253,
|
||||
@@ -106,39 +16,15 @@
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/configMapItem"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"configMapSource": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "items"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 253,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {"$ref": "#/definitions/configMapItem"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"configMapItem": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "path"],
|
||||
"properties": {
|
||||
"key": {"type": "string", "pattern": "^[A-Za-z0-9._-]+$"},
|
||||
"path": {"type": "string", "pattern": "^(workflow-contracts|catalogs)/[A-Za-z0-9_-][A-Za-z0-9._-]*\\.(yaml|json)$"},
|
||||
"replace": {
|
||||
"type": "boolean",
|
||||
"description": "Replace this whole bundled file, not individual fields/entries. Requires configMaps and includeDefaults: true; the path must identify a bundled file."
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "path"],
|
||||
"properties": {
|
||||
"key": {"type": "string", "pattern": "^[A-Za-z0-9._-]+$"},
|
||||
"path": {"type": "string", "pattern": "^(workflow-contracts|catalogs)/[A-Za-z0-9_-][A-Za-z0-9._-]*\\.(yaml|json)$"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-104
@@ -46,10 +46,6 @@ mongodb:
|
||||
# MongoDB URI (for example for a SaaS service like MongoDB Atlas)
|
||||
# If uri is set, the below properties are not used (host, adminDatabase, username, password)
|
||||
#uri: "mongodb+srv://xx:xx@kerberos-hub.xxx.mongodb.net/?retryWrites=true&w=majority&appName=xxx"
|
||||
# When uri is set, give each workload its own appName (hub-api,
|
||||
# hub-pipeline-monitor, ...) instead of the one in uri, so the Atlas query
|
||||
# profiler and currentOp show which service issued an operation.
|
||||
appNamePerService: true
|
||||
|
||||
# Self-hosted mongodb, you could also use the uri property above.
|
||||
host: mongodb.mongodb
|
||||
@@ -78,60 +74,20 @@ mongodb:
|
||||
# downstream processing. Classifier output keys must match these values.
|
||||
# Disable this to use the legacy classification catalog stored in MongoDB.
|
||||
classificationCatalogEnabled: true
|
||||
# Optional existing ConfigMap in the release namespace with classifications.json.
|
||||
# When set, do not create a catalog ConfigMap; this replaces bundled defaults.
|
||||
classificationCatalogExistingConfigMap: ""
|
||||
# Defaults live in templates/configMaps/catalogs/classifications.yaml.
|
||||
# Optionally define the complete catalog inline instead; it replaces the
|
||||
# defaults in the chart's classification-catalog ConfigMap (and so the bundled
|
||||
# catalogs/classifications.json). Entries are {key, label, icon?} with unique
|
||||
# keys. Do not combine it with classificationCatalogExistingConfigMap or a
|
||||
# catalogs/classifications.json replacement in workflowConfiguration.
|
||||
# classificationCatalog:
|
||||
# - key: car
|
||||
# label: Car
|
||||
# icon: car
|
||||
# classificationCatalog is optional and omitted by default, which uses
|
||||
# configuration/catalogs/classifications.json. Set it to a list to override.
|
||||
# Explicit null also uses defaults; [] retains the API fallback behavior.
|
||||
|
||||
# Device tags, grouped by type. System tags are defined here and assigned by
|
||||
# owners (a multi-select on the device page); global workflows select devices
|
||||
# across organisations by system tag (`systemTags` in a trigger). Owners set
|
||||
# them, so any owner could enroll devices into such a workflow: enable only
|
||||
# where owners are the deployment's administrators (self-hosted), never on
|
||||
# multi-tenant SaaS. While disabled, hub-api gets an empty catalog (system tags
|
||||
# cannot be set) and rendering fails on workflows that select devices by
|
||||
# system tag.
|
||||
deviceTags:
|
||||
system:
|
||||
enabled: false # a boolean (true/false), not a string
|
||||
# The only system tags owners can assign, in the Hub shared choice catalog
|
||||
# format (as catalogs/classifications.json) plus an optional description.
|
||||
# Rendered into the device-system-tags ConfigMap for hub-api.
|
||||
# key stored tag: 1-64 characters of a-z, 0-9, ".", "_", ":" or
|
||||
# "-", starting and ending with a letter or digit; unique.
|
||||
# label display name.
|
||||
# description optional help text shown with the tag.
|
||||
# Workflow trigger systemTags must exist here; otherwise rendering fails.
|
||||
catalog: []
|
||||
# - key: anpr-pilot
|
||||
# label: ANPR pilot
|
||||
# description: Runs licence plate recognition on this camera
|
||||
|
||||
# Configuration packaging only: consuming contracts requires a compatible
|
||||
# API/frontend. Enabling this mounts files but does not register stage workers.
|
||||
# Preparatory configuration packaging only: the API does not yet load workflow
|
||||
# contracts. Enabling this mounts files but does not activate a new editor/runtime.
|
||||
workflowConfiguration:
|
||||
enabled: false
|
||||
# Omitted includeDefaults means true; false requires a nonempty configMaps list.
|
||||
# includeDefaults: true
|
||||
# configMaps: []
|
||||
# Each ConfigMap supplies name and items [{key, path, replace?}] in the release namespace.
|
||||
# An item with replace: true replaces one bundled file while retaining other defaults.
|
||||
# Replacement requires includeDefaults: true and a known bundled destination path.
|
||||
# Unmarked collisions and repeated custom destinations are rejected.
|
||||
# Replacing catalogs/classifications.json also supplies the classification API:
|
||||
# do not combine it with classificationCatalogExistingConfigMap or classificationCatalog.
|
||||
# Legacy existingConfigMap/items/extraConfigMaps remain supported, but cannot
|
||||
# be mixed with includeDefaults/configMaps. Both interfaces are omitted here
|
||||
# so Helm's default merging does not introduce a false mixed configuration.
|
||||
# Empty uses the bundled Start contract and effective classification catalog.
|
||||
# Otherwise mount this existing ConfigMap instead; it must be in this namespace.
|
||||
existingConfigMap: ""
|
||||
# Required only with existingConfigMap: map ConfigMap keys to relative files
|
||||
# under /etc/kerberos/configuration (workflow-contracts/... or catalogs/...).
|
||||
items: []
|
||||
###################################################
|
||||
# MQTT configuration (bi-directional communication)
|
||||
###################################################
|
||||
@@ -209,17 +165,6 @@ opentelemetry:
|
||||
# the collector terminates TLS. A scheme-less value defaults to TLS and
|
||||
# fails against a plaintext collector ("server gave HTTP response to HTTPS client").
|
||||
endpoint: "http://otel-collector:4318"
|
||||
# Trace sampling, passed to the services as OTEL_TRACES_SAMPLER and
|
||||
# OTEL_TRACES_SAMPLER_ARG. Leave empty to keep the SDK default, which records
|
||||
# every trace. Example: keep ~10% of traces, and follow the parent's decision
|
||||
# for spans that continue an upstream trace:
|
||||
# type: "parentbased_traceidratio"
|
||||
# arg: "0.1"
|
||||
# Supported types: always_on, always_off, traceidratio, parentbased_always_on,
|
||||
# parentbased_always_off, parentbased_traceidratio.
|
||||
sampler:
|
||||
type: ""
|
||||
arg: ""
|
||||
############################################
|
||||
# OpenAI configuration (semantic search)
|
||||
#
|
||||
@@ -336,9 +281,6 @@ kerberoshub:
|
||||
jwtSecret: "this-is-a-secret-please-change-to-random-string" # change to a random value, this is for generating JWT tokens.
|
||||
schema: "https"
|
||||
url: "api.yourdomain.com"
|
||||
# Additional complete HTTP(S) origins allowed to call the Hub API.
|
||||
# The configured frontend origins remain allowed automatically.
|
||||
corsAllowedOrigins: []
|
||||
# Temporarily stop serving the polled GET /devices list: hub-api answers 204
|
||||
# without querying MongoDB. The frontend still loads devices once through
|
||||
# /session/bootstrap, but device online/offline status stops refreshing.
|
||||
@@ -582,9 +524,6 @@ kerberoshub:
|
||||
# Audit events are visible to owner/admin users when enabled.
|
||||
audit:
|
||||
enabled: "true" # Enable or disable the Audit events page and navigation link 'true' or 'false'
|
||||
# Day overview page and its navigation menu; role page permissions still apply.
|
||||
dayOverview:
|
||||
enabled: "true" # Enable or disable the Day overview page and navigation menu 'true' or 'false'
|
||||
# Organisation controls remain visible as a read-only current organisation when switching is disabled.
|
||||
organisations:
|
||||
enabled: "" # Enable or disable the organisation feature family, including projects; when empty, child groups apply independently
|
||||
@@ -709,7 +648,6 @@ kerberoshub:
|
||||
publicKey: ""
|
||||
googlemaps: # Google maps is used inside the application to visualise cameras and sites.
|
||||
apikey: "xxxx"
|
||||
placesApi: "legacy" # Places API for address autocomplete: "legacy" (default) or "new" for keys with Places API (New) enabled. Uses the same apikey.
|
||||
zendesk: # We can use different support tools, for now we use Zendesk but others can be integrated
|
||||
url: "yourdomain.zendesk.com"
|
||||
posthog: # Posthog is used for auditing and user interaction logging
|
||||
@@ -776,10 +714,6 @@ kerberoshub:
|
||||
# recording plans, inactive-account cleanup, and the optional global pass.
|
||||
# Set to "0" or a negative value to retain audit events indefinitely.
|
||||
auditEventRetentionDays: "400"
|
||||
# Deployment-wide cap for workflow runs, anchored on run execution time.
|
||||
# Runs always expire with their media; this only shortens their lifetime.
|
||||
# Set to "0" or a negative value to let runs follow media retention only.
|
||||
workflowRunRetentionDays: "0"
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
@@ -871,9 +805,7 @@ kerberoshub:
|
||||
# triggers how a run OPENS. Omit for a single bare automatic trigger
|
||||
# (opens for every recording); the per-stage `needs` then decide
|
||||
# which stages FIRE. Narrow with device/schedule triggers, e.g.
|
||||
# `- {type: automatic, devices: [{key: <device-key>}]}`, or by
|
||||
# device label across organisations, e.g.
|
||||
# `- {type: automatic, systemTags: [anpr-pilot]}` (see deviceTags).
|
||||
# `- {type: automatic, devices: [{key: <device-key>}]}`.
|
||||
# stages the executable stages, each {operation, dispatch?, needs?,
|
||||
# needsMode?}. dispatch is "always" (default) or "conditional";
|
||||
# a conditional stage's `needs` are upstream dependencies, each
|
||||
@@ -916,10 +848,9 @@ kerberoshub:
|
||||
# stages:
|
||||
# - operation: vlm
|
||||
# dispatch: always
|
||||
# Optional editor metadata (name, description, params) per operation. With
|
||||
# workflowConfiguration contracts, a stage is offered when it has a valid
|
||||
# contract and a deployed worker (services.<operation>.queue); contract
|
||||
# presentation and params take precedence over these entries.
|
||||
# Stages tenants may place on the workflow editor canvas next to the device
|
||||
# node. Each entry needs a deployed worker (services.<operation>.queue);
|
||||
# entries without one are hidden. Empty means the editor offers no stages.
|
||||
editorStages: []
|
||||
# - operation: vlm
|
||||
# name: Describe recording
|
||||
@@ -1172,6 +1103,10 @@ kerberospipeline:
|
||||
# - name: extra
|
||||
# mountPath: /data
|
||||
logLevel: "info" # possible values: debug, info, warn, error
|
||||
# Minimum recording-time gap (seconds) between two writes of a device's
|
||||
# latestMedia snapshot; 0 writes on every recording. Requires hub-pipeline-monitor
|
||||
# with latest-media throttling (older images ignore it).
|
||||
latestMediaUpdateIntervalSeconds: 300
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
@@ -1266,7 +1201,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "40"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.8.22"
|
||||
tag: "v1.8.5"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1277,26 +1212,11 @@ kerberospipeline:
|
||||
# - name: extra
|
||||
# mountPath: /data
|
||||
logLevel: "info" # possible values: trace, debug, info, warn, error
|
||||
# Opt-in: after classification, re-enqueue the recording on the thumbnail
|
||||
# queue to regenerate its thumbnail from the first frame of a tracked moving
|
||||
# object (THUMBNAIL_REGENERATION_ENABLED). Adds one thumbnail job per
|
||||
# classified recording; size pipe-thumbnail accordingly before enabling.
|
||||
thumbnailRegeneration:
|
||||
enabled: false
|
||||
# In-memory cache for hot-path MongoDB lookups (site vault per device).
|
||||
# Site changes apply after at most one TTL; each replica caches separately.
|
||||
dbCache:
|
||||
enabled: false
|
||||
ttl: "5m" # Go duration, e.g. "30s", "5m"
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
dominantColor:
|
||||
# Opt-in: deploys pipe-dominantcolor and tells the analysis stage to schedule
|
||||
# it (DOMINANTCOLOR_ENABLED). Requires an analysis image that reads the flag;
|
||||
# older images always publish to the dominantcolor queue.
|
||||
enabled: false # Enable or disable dominant color extraction 'true' or 'false'
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-dominantcolors
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
@@ -1326,9 +1246,8 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "50"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.3.15"
|
||||
tag: "v1.3.10"
|
||||
replicas: 2 # Number of pods for the service.
|
||||
prefetchCount: 0 # Max unacknowledged RabbitMQ messages per pod; 0 = service default (5).
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
#volumes:
|
||||
@@ -1378,9 +1297,8 @@ kerberospipeline:
|
||||
# argocd.argoproj.io/sync-wave: "50"
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-sprite
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.1.21"
|
||||
tag: "v1.1.16"
|
||||
replicas: 5 # Number of pods for the service.
|
||||
prefetchCount: 0 # Max unacknowledged RabbitMQ messages per pod; 0 = service default (5).
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
#volumes:
|
||||
|
||||
@@ -51,21 +51,21 @@ expected="$(printf '%s\n' \
|
||||
'pipe-counting 50' 'pipe-sequence 60' 'pipe-monitor 70' | sort)"
|
||||
|
||||
for mode in all pipeline; do
|
||||
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true --set kerberospipeline.dominantColor.enabled=true)"
|
||||
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true)"
|
||||
assert_waves "$default_render" ""
|
||||
if printf '%s\n' "$default_render" | grep -q '^ annotations: null'; then
|
||||
echo "Empty annotations must be omitted" >&2
|
||||
exit 1
|
||||
fi
|
||||
configured_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true --set kerberospipeline.dominantColor.enabled=true)"
|
||||
configured_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set "mode=$mode" --set kerberospipeline.sprite.enabled=true)"
|
||||
assert_waves "$configured_render" "$expected"
|
||||
echo "OK ($mode): defaults opt out; per-service values assign all ten Deployment waves"
|
||||
done
|
||||
|
||||
disabled_optional="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=pipeline)"
|
||||
assert_waves "$disabled_optional" "$(printf '%s\n' "$expected" | grep -v -e '^pipe-sprite ' -e '^pipe-dominantcolor ')"
|
||||
disabled_sprite="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=pipeline)"
|
||||
assert_waves "$disabled_sprite" "$(printf '%s\n' "$expected" | grep -v '^pipe-sprite ')"
|
||||
|
||||
ui_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=ui --set kerberospipeline.sprite.enabled=true --set kerberospipeline.dominantColor.enabled=true)"
|
||||
ui_render="$(helm template hub "$CHART_DIR" "${WAVE_FLAGS[@]}" --set mode=ui --set kerberospipeline.sprite.enabled=true)"
|
||||
assert_waves "$ui_render" ""
|
||||
|
||||
# Exercise zero/negative strings and an unrelated annotation on the same map.
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
# Chart-only checks; no cluster or application loader required.
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
|
||||
assert_contains() {
|
||||
if ! grep -Fq -- "$2" <<<"$1"; then
|
||||
printf 'FAIL: expected rendered output to contain %s\n' "$2" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_absent() {
|
||||
if grep -Fq -- "$2" <<<"$1"; then
|
||||
printf 'FAIL: unexpected rendered output contains %s\n' "$2" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_failure() {
|
||||
local expected="$1"
|
||||
shift
|
||||
local output
|
||||
if output="$(helm template hub "$CHART_DIR" "$@" 2>&1)"; then
|
||||
printf 'FAIL: render unexpectedly succeeded: %s\n' "$*" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_contains "$output" "$expected"
|
||||
}
|
||||
|
||||
if grep -q '^classificationCatalog:' "$CHART_DIR/values.yaml"; then
|
||||
echo "FAIL: classificationCatalog should be omitted from default values" >&2
|
||||
exit 1
|
||||
fi
|
||||
default_out="$(helm template hub "$CHART_DIR")"
|
||||
assert_absent "$default_out" "name: workflow-configuration"
|
||||
assert_absent "$default_out" "checksum/workflow-configuration"
|
||||
assert_contains "$default_out" "value: /etc/kerberos/classifications/classifications.json"
|
||||
legacy_out="$(helm template hub "$CHART_DIR" --set workflowConfiguration=null)"
|
||||
if [ "$default_out" != "$legacy_out" ]; then
|
||||
echo "FAIL: absent workflowConfiguration changed the default render" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
null_out="$(helm template hub "$CHART_DIR" --set classificationCatalog=null)"
|
||||
if [ "$default_out" != "$null_out" ]; then
|
||||
echo "FAIL: omitted and null classificationCatalog render differently" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A file-backed default must render identically to the previous inline-list form.
|
||||
explicit_out="$(helm template hub "$CHART_DIR" \
|
||||
--set-json "classificationCatalog=$(tr -d '\n' <"$CHART_DIR/configuration/catalogs/classifications.json")")"
|
||||
if [ "$default_out" != "$explicit_out" ]; then
|
||||
echo "FAIL: bundled defaults and explicit classification list render differently" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
override_flags=(--set-json 'classificationCatalog=[{"key":"forklift","label":"Forklift","icon":"vehicle"}]')
|
||||
override_out="$(helm template hub "$CHART_DIR" "${override_flags[@]}")"
|
||||
assert_contains "$override_out" '"key": "forklift"'
|
||||
assert_absent "$override_out" '"key": "pedestrian"'
|
||||
|
||||
empty_out="$(helm template hub "$CHART_DIR" --set-json 'classificationCatalog=[]')"
|
||||
assert_contains "$empty_out" " []"
|
||||
assert_absent "$empty_out" '"key": "pedestrian"'
|
||||
disabled_out="$(helm template hub "$CHART_DIR" --set classificationCatalogEnabled=false)"
|
||||
assert_absent "$disabled_out" "name: classification-catalog"
|
||||
assert_absent "$disabled_out" "CLASSIFICATION_CATALOG_FILE"
|
||||
|
||||
bundle_out="$(helm template hub "$CHART_DIR" --set workflowConfiguration.enabled=true)"
|
||||
assert_contains "$bundle_out" "checksum/workflow-configuration:"
|
||||
assert_contains "$bundle_out" "mountPath: /etc/kerberos/configuration"
|
||||
assert_contains "$bundle_out" "path: workflow-contracts/start.yaml"
|
||||
assert_contains "$bundle_out" "path: catalogs/classifications.json"
|
||||
assert_contains "$bundle_out" "source: project.devices"
|
||||
assert_contains "$bundle_out" "file: ../catalogs/classifications.json"
|
||||
assert_absent "$bundle_out" "WORKFLOW_CONTRACTS"
|
||||
bundle_config="$(helm template hub "$CHART_DIR" --set workflowConfiguration.enabled=true \
|
||||
--show-only templates/configmap-workflow-configuration.yaml)"
|
||||
assert_absent "$bundle_config" "classifications.json: |-"
|
||||
override_bundle="$(helm template hub "$CHART_DIR" --set workflowConfiguration.enabled=true "${override_flags[@]}")"
|
||||
assert_contains "$override_bundle" '"key": "forklift"'
|
||||
assert_absent "$override_bundle" '"key": "pedestrian"'
|
||||
|
||||
external_flags=(-f "$CHART_DIR/examples/workflow-configuration-values.yaml")
|
||||
external_out="$(helm template hub "$CHART_DIR" "${external_flags[@]}" --set classificationCatalogEnabled=false)"
|
||||
assert_contains "$external_out" 'name: "custom-workflow-configuration"'
|
||||
assert_contains "$external_out" "path: workflow-contracts/pose.yaml"
|
||||
assert_contains "$external_out" "path: catalogs/pose-keypoints.json"
|
||||
assert_absent "$external_out" "name: hub-workflow-configuration"
|
||||
assert_absent "$external_out" "checksum/workflow-configuration"
|
||||
|
||||
ui_out="$(helm template hub "$CHART_DIR" --set mode=ui --set workflowConfiguration.enabled=true)"
|
||||
assert_contains "$ui_out" "name: hub-workflow-configuration"
|
||||
pipeline_out="$(helm template hub "$CHART_DIR" --set mode=pipeline --set workflowConfiguration.enabled=true)"
|
||||
assert_absent "$pipeline_out" "name: hub-workflow-configuration"
|
||||
assert_absent "$pipeline_out" "name: workflow-configuration"
|
||||
|
||||
expect_failure "requires classificationCatalogEnabled" \
|
||||
--set workflowConfiguration.enabled=true --set classificationCatalogEnabled=false
|
||||
expect_failure "items is required" \
|
||||
--set workflowConfiguration.enabled=true --set workflowConfiguration.existingConfigMap=custom
|
||||
expect_failure "items requires existingConfigMap" "${external_flags[@]}" --set workflowConfiguration.existingConfigMap=
|
||||
expect_failure "duplicate workflowConfiguration.items path" "${external_flags[@]}" \
|
||||
--set 'workflowConfiguration.items[1].path=workflow-contracts/pose.yaml'
|
||||
expect_failure "workflowConfiguration" "${external_flags[@]}" \
|
||||
--set 'workflowConfiguration.items[0].path=../../secret.yaml'
|
||||
expect_failure "workflowConfiguration" "${external_flags[@]}" \
|
||||
--set 'workflowConfiguration.items[0].path=/etc/secret.yaml'
|
||||
expect_failure "classificationCatalog" --set classificationCatalog=not-a-list
|
||||
|
||||
echo "All workflow configuration and classification catalog checks passed."
|
||||
Executable
+233
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Render the hub chart and assert that every deployment which carries the
|
||||
# workflows hand-off queue (the WORKFLOWS_QUEUE env var) resolves to the SAME,
|
||||
# non-empty value.
|
||||
#
|
||||
# Why: the analysis pipeline (pipe-analysis) publishes opened workflow runs to
|
||||
# WORKFLOWS_QUEUE, the workflows engine (hub-workflows) consumes it, and every
|
||||
# stage worker (hub-stage) routes its result back to it. All three templates
|
||||
# read the single key `kerberoshub.services.workflows.queue`. If a future edit
|
||||
# hardcodes a value, reads the wrong key, or drops the env on one of them, the
|
||||
# producer and consumer silently drift onto different queue names and messages
|
||||
# pile up with no consumer. This check fails the build before that can ship.
|
||||
#
|
||||
# Also checks workflow identity validation and identical WORKFLOW_DEFINITIONS
|
||||
# in the API and engine. Requires Helm and Python 3 (standard library only).
|
||||
#
|
||||
# Usage: scripts/check-workflows-queue-consistency.sh [chart-dir]
|
||||
# (chart-dir defaults to charts/hub, relative to the repo root)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
PROBE="drift-probe-queue-name"
|
||||
|
||||
# Flags that force all three deployment kinds (analysis, engine and one stage
|
||||
# worker) to render, so the check actually has something to compare. The chart
|
||||
# ships NO enabled stage worker by default (custom stages are values-only and
|
||||
# opt-in), so we synthesise a throwaway stage purely to exercise the generic
|
||||
# hub-stage path. The name is a neutral fixture ("queuecheck") on purpose: any
|
||||
# arbitrary stage key must render the same way, so the check must not depend on
|
||||
# a specific bundled worker.
|
||||
STAGE="queuecheck"
|
||||
RENDER_FLAGS=(
|
||||
--set mode=all
|
||||
--set kerberoshub.workflows.enabled=true
|
||||
--set "kerberoshub.workflows.stages.${STAGE}.enabled=true"
|
||||
--set "kerberoshub.services.${STAGE}.enabled=true"
|
||||
--set "kerberoshub.services.${STAGE}.repository=example.invalid/queuecheck"
|
||||
--set "kerberoshub.services.${STAGE}.tag=test"
|
||||
--set "kerberoshub.services.${STAGE}.queue=queuecheck-fixture-queue"
|
||||
)
|
||||
|
||||
# Read `helm template` output on stdin and print one WORKFLOWS_QUEUE value per
|
||||
# line. Matches the `- name: WORKFLOWS_QUEUE` env entry and captures the value
|
||||
# from the following `value:` line, skipping blank/comment lines in between.
|
||||
extract_workflows_queue() {
|
||||
awk '
|
||||
/^[[:space:]]*-[[:space:]]*name:[[:space:]]*WORKFLOWS_QUEUE[[:space:]]*$/ { want=1; next }
|
||||
want==1 {
|
||||
if ($0 ~ /^[[:space:]]*#/ || $0 ~ /^[[:space:]]*$/) next
|
||||
v=$0
|
||||
sub(/^[[:space:]]*value:[[:space:]]*/, "", v)
|
||||
sub(/^"/, "", v); sub(/"[[:space:]]*$/, "", v)
|
||||
sub(/[[:space:]]+$/, "", v)
|
||||
print v
|
||||
want=0
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
assert_all_equal() {
|
||||
local expected="$1"; shift
|
||||
local label="$1"; shift
|
||||
local -a vals=("$@")
|
||||
|
||||
if [ "${#vals[@]}" -lt 2 ]; then
|
||||
echo "FAIL (${label}): expected at least 2 WORKFLOWS_QUEUE values (analysis + engine), found ${#vals[@]}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local v
|
||||
for v in "${vals[@]}"; do
|
||||
if [ -z "${v}" ]; then
|
||||
echo "FAIL (${label}): a deployment rendered an empty WORKFLOWS_QUEUE value" >&2
|
||||
return 1
|
||||
fi
|
||||
if [ "${v}" != "${expected}" ]; then
|
||||
echo "FAIL (${label}): WORKFLOWS_QUEUE drift detected — expected '${expected}' but a deployment rendered '${v}'" >&2
|
||||
printf ' rendered values: %s\n' "${vals[*]}" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "OK (${label}): ${#vals[@]} deployments all use WORKFLOWS_QUEUE='${expected}'"
|
||||
}
|
||||
|
||||
echo "== Rendering ${CHART_DIR} with the chart's default workflows queue =="
|
||||
default_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}")"
|
||||
mapfile -t default_vals < <(printf '%s\n' "${default_out}" | extract_workflows_queue)
|
||||
default_queue="${default_vals[0]:-}"
|
||||
assert_all_equal "${default_queue}" "default values" "${default_vals[@]}" || exit 1
|
||||
|
||||
echo "== Rendering ${CHART_DIR} with an overridden workflows queue (-> ${PROBE}) =="
|
||||
probe_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}" \
|
||||
--set kerberoshub.services.workflows.queue="${PROBE}")"
|
||||
mapfile -t probe_vals < <(printf '%s\n' "${probe_out}" | extract_workflows_queue)
|
||||
assert_all_equal "${PROBE}" "override probe" "${probe_vals[@]}" || exit 1
|
||||
|
||||
assert_definitions() {
|
||||
local rendered="$1" expected="$2" label="$3"
|
||||
printf '%s\n' "$rendered" | python3 -c '
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
expected = json.loads(sys.argv[1])
|
||||
label = sys.argv[2]
|
||||
actual = {}
|
||||
for document in sys.stdin.read().split("\n---"):
|
||||
match = re.search(r"(?m)^[ \t]*- name: WORKFLOW_DEFINITIONS[ \t]*\n[ \t]*value: (\".*\")$", document)
|
||||
if not match:
|
||||
continue
|
||||
name = re.search(r"(?m)^metadata:\n name: (.+)$", document).group(1)
|
||||
assert name not in actual, f"duplicate WORKFLOW_DEFINITIONS in {name}"
|
||||
actual[name] = json.loads(json.loads(match.group(1)))
|
||||
assert set(actual) == {"hub-api", "hub-workflows"}, f"unexpected deployments: {set(actual)}"
|
||||
for name, definitions in actual.items():
|
||||
assert definitions == expected, f"{label}: {name}: {definitions!r} != {expected!r}"
|
||||
print(f"OK ({label}): API and engine receive identical expected workflow definitions")
|
||||
' "$expected" "$label"
|
||||
}
|
||||
|
||||
assert_render_failure() {
|
||||
local label="$1" expected="$2"; shift 2
|
||||
local output
|
||||
if output="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}" "$@" 2>&1)"; then
|
||||
echo "FAIL (${label}): Helm accepted invalid workflow identities" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$output" != *"$expected"* ]]; then
|
||||
printf 'FAIL (%s): expected diagnostic %s, got:\n%s\n' "$label" "$expected" "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "OK (${label}): Helm rejected invalid workflow identities"
|
||||
}
|
||||
|
||||
assert_definitions "$default_out" '[]' "empty defaults"
|
||||
|
||||
echo "== Checking explicit and legacy workflow definitions =="
|
||||
identity_fixture='{
|
||||
" Legacy Workflow ": {
|
||||
"enabled": true,
|
||||
"stages": [
|
||||
{"operation": "queuecheck"},
|
||||
{"operation": "external", "dispatch": "conditional", "needs": [{"operation": "queuecheck"}], "needsMode": "all"}
|
||||
]
|
||||
},
|
||||
"explicit": {
|
||||
"id": "ABCDEF0123456789ABCDEF01",
|
||||
"enabled": true,
|
||||
"triggers": [{"type": "automatic", "devices": [{"key": "fixture-device"}]}],
|
||||
"stages": []
|
||||
},
|
||||
"disabled": {"id": "abcdef0123456789abcdef02", "enabled": false}
|
||||
}'
|
||||
expected_definitions='[
|
||||
{
|
||||
"name": " Legacy Workflow ", "enabled": true, "source": "config",
|
||||
"triggers": [{"type": "automatic"}],
|
||||
"stages": [
|
||||
{"operation": "queuecheck", "dispatch": "always", "queue": "queuecheck-fixture-queue"},
|
||||
{"operation": "external", "dispatch": "conditional", "needs": [{"operation": "queuecheck"}], "needsMode": "all"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "explicit", "id": "abcdef0123456789abcdef01", "enabled": true, "source": "config",
|
||||
"triggers": [{"type": "automatic", "devices": [{"key": "fixture-device"}]}],
|
||||
"stages": []
|
||||
}
|
||||
]'
|
||||
identity_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}" \
|
||||
--set-json "kerberoshub.workflows.definitions=$identity_fixture")"
|
||||
assert_definitions "$identity_out" "$expected_definitions" "explicit ID lowercase; legacy omission; disabled filtering; triggers and stages unchanged"
|
||||
|
||||
renamed_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}" \
|
||||
--set-json "kerberoshub.workflows.definitions=${identity_fixture/\"explicit\"/\"renamed\"}")"
|
||||
assert_definitions "$renamed_out" "${expected_definitions/\"explicit\"/\"renamed\"}" "explicit identity survives rename"
|
||||
|
||||
legacy_only_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}" \
|
||||
--set-json 'kerberoshub.workflows.definitions={"legacy":{"enabled":true}}')"
|
||||
assert_definitions "$legacy_only_out" \
|
||||
'[{"name":"legacy","enabled":true,"source":"config","triggers":[{"type":"automatic"}],"stages":[]}]' \
|
||||
"legacy-only values unchanged"
|
||||
|
||||
echo "== Checking bundled example identity pins =="
|
||||
python3 - "$CHART_DIR/values.yaml" <<'PY'
|
||||
import hashlib
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
values = pathlib.Path(sys.argv[1]).read_text()
|
||||
for name in ("tracking-workflow", "vlm-workflow"):
|
||||
expected = hashlib.sha256(name.encode()).hexdigest()[:24]
|
||||
match = re.search(r"(?m)^ # " + re.escape(name) + r':\n # id: "([^"]+)"$', values)
|
||||
assert match and match.group(1) == expected, f"{name} must preserve its name-derived ID {expected}"
|
||||
print(f"OK ({name}): example pins existing identity {expected}")
|
||||
PY
|
||||
|
||||
echo "== Rejecting invalid explicit IDs, including disabled definitions =="
|
||||
invalid_ids=('""' 'null' '"000000000000000000000000"' '0' '123' 'true' 'false' '[]' '{}'
|
||||
'"abc"' '"zzzzzzzzzzzzzzzzzzzzzzzz"' '"abcdef0123456789abcdef01 "'
|
||||
'"abcdef0123456789abcdef0"' '"abcdef0123456789abcdef012"')
|
||||
invalid_diagnostic='kerberoshub.workflows.definitions["invalid"].id must be a non-zero 24-character hexadecimal string'
|
||||
for enabled in true false; do
|
||||
for id in "${invalid_ids[@]}"; do
|
||||
assert_render_failure "id=$id, enabled=$enabled" "$invalid_diagnostic" \
|
||||
--set-json "kerberoshub.workflows.definitions={\"invalid\":{\"enabled\":$enabled,\"id\":$id}}"
|
||||
done
|
||||
done
|
||||
|
||||
# Validation must not depend on either consumer being rendered.
|
||||
for mode in all ui pipeline; do
|
||||
assert_render_failure "engine disabled, mode=$mode" "$invalid_diagnostic" \
|
||||
--set "mode=$mode" --set kerberoshub.workflows.enabled=false \
|
||||
--set-json 'kerberoshub.workflows.definitions={"invalid":{"enabled":false,"id":null}}'
|
||||
done
|
||||
|
||||
echo "== Rejecting duplicate effective IDs =="
|
||||
legacy_id="$(python3 -c 'import hashlib; print(hashlib.sha256(b" Legacy Workflow ").hexdigest()[:24])')"
|
||||
for first_enabled in true false; do
|
||||
for second_enabled in true false; do
|
||||
assert_render_failure "explicit collision, enabled=$first_enabled/$second_enabled" \
|
||||
'duplicate effective workflow id "abcdef0123456789abcdef01" for "first" and "second"' \
|
||||
--set-json "kerberoshub.workflows.definitions={\"first\":{\"enabled\":$first_enabled,\"id\":\"ABCDEF0123456789ABCDEF01\"},\"second\":{\"enabled\":$second_enabled,\"id\":\"abcdef0123456789abcdef01\"}}"
|
||||
assert_render_failure "explicit/derived collision, enabled=$first_enabled/$second_enabled" \
|
||||
"duplicate effective workflow id \"$legacy_id\" for \" Legacy Workflow \" and \"explicit\"" \
|
||||
--set-json "kerberoshub.workflows.definitions={\" Legacy Workflow \":{\"enabled\":$first_enabled},\"explicit\":{\"enabled\":$second_enabled,\"id\":\"${legacy_id^^}\"}}"
|
||||
done
|
||||
done
|
||||
|
||||
echo "All workflow queue and identity checks passed."
|
||||
Reference in New Issue
Block a user