diff --git a/server/src/ledgrab/static/js/types.ts b/server/src/ledgrab/static/js/types.ts index 1ed23b4..be1938f 100644 --- a/server/src/ledgrab/static/js/types.ts +++ b/server/src/ledgrab/static/js/types.ts @@ -3,45 +3,26 @@ * * These mirror the JSON shapes returned by the REST API. Field names use * snake_case to match the JSON payloads — no camelCase transformation is done. + * + * Bindable primitives have been extracted into ``types/bindable.ts`` and + * are re-exported here so existing ``import { ... } from '../types.ts'`` + * call sites keep working. The intention is for further entity-shape + * groups (devices, sources, integrations, …) to follow the same pattern + * in subsequent passes — see audit finding H6. */ -// ── Bindable Float ─────────────────────────────────────────── -// A scalar that is either a static value (plain number) or bound to a value source (dict). +// ── Bindable Primitives ───────────────────────────────────── +export type { BindableFloat, BindableColor } from './types/bindable.ts'; +export { + bindableValue, + bindableSourceId, + bindableColor, + bindableColorSourceId, +} from './types/bindable.ts'; -export type BindableFloat = number | { value: number; source_id: string }; - -/** Extract the static value from a BindableFloat. */ -export function bindableValue(b: BindableFloat | undefined, fallback: number): number { - if (b === undefined || b === null) return fallback; - if (typeof b === 'number') return b; - return b.value ?? fallback; -} - -/** Extract the source_id from a BindableFloat (empty string = not bound). */ -export function bindableSourceId(b: BindableFloat | undefined): string { - if (b === undefined || b === null) return ''; - if (typeof b === 'number') return ''; - return b.source_id ?? ''; -} - -// ── Bindable Color ────────────────────────────────────────── -// An RGB color that is either static ([R,G,B] array) or bound to a color value source. - -export type BindableColor = number[] | { color: number[]; source_id: string }; - -/** Extract the static [R,G,B] from a BindableColor. */ -export function bindableColor(b: BindableColor | undefined, fallback: number[]): number[] { - if (b === undefined || b === null) return fallback; - if (Array.isArray(b)) return b; - return b.color ?? fallback; -} - -/** Extract the source_id from a BindableColor (empty string = not bound). */ -export function bindableColorSourceId(b: BindableColor | undefined): string { - if (b === undefined || b === null) return ''; - if (Array.isArray(b)) return ''; - return b.source_id ?? ''; -} +// Local aliases used by the entity interfaces below so TypeScript can +// resolve them without an extra import at every reference site. +import type { BindableFloat, BindableColor } from './types/bindable.ts'; // ── Device ──────────────────────────────────────────────────── @@ -186,7 +167,7 @@ export type OutputTarget = LedOutputTarget | HALightOutputTarget | Z2MLightOutpu // ── Color Strip Source ──────────────────────────────────────── export type CSSSourceType = - | 'picture' | 'picture_advanced' | 'static' | 'gradient' + | 'picture' | 'picture_advanced' | 'single_color' | 'gradient' | 'effect' | 'composite' | 'mapped' | 'audio' | 'api_input' | 'notification' | 'daylight' | 'candlelight' | 'processed' | 'weather' | 'key_colors' @@ -379,7 +360,7 @@ export type ValueSourceType = | 'adaptive_time' | 'adaptive_scene' | 'daylight' | 'static_color' | 'animated_color' | 'adaptive_time_color' | 'ha_entity' | 'gradient_map' | 'css_extract' - | 'system_metrics' | 'game_event'; + | 'system_metrics' | 'game_event' | 'http'; export interface SchedulePoint { time: string; @@ -534,6 +515,17 @@ export interface GameEventValueSource extends ValueSourceBase { timeout: number; } +export interface HTTPValueSource extends ValueSourceBase { + source_type: 'http'; + return_type: 'float'; + http_endpoint_id: string; + json_path: string; + interval_s: number; + min_value: number; + max_value: number; + smoothing: number; +} + export type ValueSource = | StaticValueSource | AnimatedValueSource @@ -548,7 +540,8 @@ export type ValueSource = | GradientMapValueSource | CSSExtractValueSource | SystemMetricsValueSource - | GameEventValueSource; + | GameEventValueSource + | HTTPValueSource; // ── Audio Source ─────────────────────────────────────────────── @@ -772,6 +765,68 @@ export interface MQTTStatusResponse { connected_count: number; } +// ── HTTP Endpoint ──────────────────────────────────────────── +// +// A connection definition only (URL + auth + headers + timeout). +// No polling cadence is configured on the endpoint itself — +// HTTPValueSource owns interval_s and references the endpoint. + +export type HTTPMethod = 'GET' | 'HEAD'; + +export interface HTTPEndpoint { + id: string; + name: string; + url: string; + method: HTTPMethod; + /** Server NEVER returns the token; this flag indicates one is stored. */ + auth_token_set: boolean; + headers: Record; + timeout_s: number; + description?: string; + tags: string[]; + icon?: string; + icon_color?: string; + created_at: string; + updated_at: string; +} + +export interface HTTPEndpointListResponse { + endpoints: HTTPEndpoint[]; + count: number; +} + +/** Wire payload for `POST /http/endpoints` / `PUT /http/endpoints/{id}`. + * All fields optional — the route validates required-on-create separately. */ +export interface HTTPEndpointWritePayload { + name?: string; + url?: string; + method?: HTTPMethod; + /** Plaintext token. PUT distinguishes None=keep / ""=clear; omit the field to keep. */ + auth_token?: string; + headers?: Record; + timeout_s?: number; + description?: string; + tags?: string[]; + icon?: string; + icon_color?: string; +} + +export interface HTTPTestRequest { + url: string; + method: HTTPMethod; + auth_token: string; + headers: Record; + timeout_s: number; +} + +export interface HTTPTestResponse { + success: boolean; + status_code?: number; + body_preview?: string; + body_json?: unknown; + error?: string; +} + // ── Asset ──────────────────────────────────────────────────── export interface Asset { @@ -799,7 +854,12 @@ export interface AssetListResponse { export type RuleType = | 'application' | 'time_of_day' | 'system_idle' - | 'display_state' | 'mqtt' | 'webhook' | 'startup'; + | 'display_state' | 'mqtt' | 'webhook' | 'startup' + | 'home_assistant' | 'http_poll'; + +export type HTTPPollOperator = + | 'equals' | 'not_equals' | 'contains' | 'regex' + | 'gt' | 'lt' | 'exists'; export interface AutomationRule { rule_type: RuleType; @@ -814,6 +874,13 @@ export interface AutomationRule { payload?: string; match_mode?: string; token?: string; + /** home_assistant rule */ + ha_source_id?: string; + entity_id?: string; + /** http_poll rule — references an HTTPValueSource. */ + value_source_id?: string; + operator?: HTTPPollOperator; + value?: string; } export interface Automation { diff --git a/server/src/ledgrab/static/js/types/bindable.ts b/server/src/ledgrab/static/js/types/bindable.ts new file mode 100644 index 0000000..796309d --- /dev/null +++ b/server/src/ledgrab/static/js/types/bindable.ts @@ -0,0 +1,47 @@ +/** + * Bindable scalar / colour primitives. + * + * A "bindable" value is either a plain literal or a reference to a value + * source (``{value | color, source_id}``). These types and the four + * accessor helpers are imported widely — over 30 frontend modules read + * them through the ``types.ts`` barrel — so they live in their own file + * to keep the entity-shape files free of helper-function noise. + */ + +// ── Bindable Float ─────────────────────────────────────────── +// A scalar that is either a static value (plain number) or bound to a value source (dict). + +export type BindableFloat = number | { value: number; source_id: string }; + +/** Extract the static value from a BindableFloat. */ +export function bindableValue(b: BindableFloat | undefined, fallback: number): number { + if (b === undefined || b === null) return fallback; + if (typeof b === 'number') return b; + return b.value ?? fallback; +} + +/** Extract the source_id from a BindableFloat (empty string = not bound). */ +export function bindableSourceId(b: BindableFloat | undefined): string { + if (b === undefined || b === null) return ''; + if (typeof b === 'number') return ''; + return b.source_id ?? ''; +} + +// ── Bindable Color ────────────────────────────────────────── +// An RGB color that is either static ([R,G,B] array) or bound to a color value source. + +export type BindableColor = number[] | { color: number[]; source_id: string }; + +/** Extract the static [R,G,B] from a BindableColor. */ +export function bindableColor(b: BindableColor | undefined, fallback: number[]): number[] { + if (b === undefined || b === null) return fallback; + if (Array.isArray(b)) return b; + return b.color ?? fallback; +} + +/** Extract the source_id from a BindableColor (empty string = not bound). */ +export function bindableColorSourceId(b: BindableColor | undefined): string { + if (b === undefined || b === null) return ''; + if (Array.isArray(b)) return ''; + return b.source_id ?? ''; +}