> */ public static function all(): array { return [ 'ring' => [ 'key' => 'ring', 'name' => 'Ring', 'vendor' => 'Ring', 'icon' => 'doorbell', // Ring has no local API — the established bridge is ring-mqtt (cloud, token auth). 'cloud' => true, 'summary_key' => 'addons.ring.summary', 'body_key' => 'addons.ring.body', // The ring-mqtt sidecar generates a refresh token from an interactive Ring login // (email + password + 2FA). We store that token; the sidecar does the rest. 'backend' => 'ring-mqtt', 'docs' => 'https://github.com/tsightler/ring-mqtt', 'fields' => [ ['name' => 'email', 'type' => 'email', 'label_key' => 'addons.ring.email', 'secret' => false], ['name' => 'password', 'type' => 'password', 'label_key' => 'addons.ring.password', 'secret' => true], ['name' => 'code', 'type' => 'text', 'label_key' => 'addons.ring.code', 'secret' => false, 'optional' => true], ], ], ]; } public static function get(string $key): ?array { return static::all()[$key] ?? null; } public static function has(string $key): bool { return isset(static::all()[$key]); } /** * Merge each catalogue definition with its persisted state, creating rows lazily so the * page can render before anything is installed. * * @return array> */ public static function withState(): array { $rows = Addon::whereIn('key', array_keys(static::all()))->get()->keyBy('key'); return collect(static::all())->map(function (array $def) use ($rows) { $state = $rows->get($def['key']); return $def + [ 'installed' => (bool) ($state?->installed ?? false), 'status' => $state?->status ?? 'disconnected', 'status_detail' => $state?->status_detail, 'connected_at' => $state?->connected_at, 'uuid' => $state?->uuid, ]; })->values()->all(); } }