@camstack/addon-admin-ui 1.2.40 → 1.2.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/assets/{AddLocationWizard-_UpnD2Rb.js → AddLocationWizard-CQwIKMPj.js} +1 -1
  2. package/dist/assets/{AddonCollectionPage-DsqpERSx.js → AddonCollectionPage-D47hM2eT.js} +1 -1
  3. package/dist/assets/Addons-CnIGTOiE.js +1 -0
  4. package/dist/assets/{AdminTabs-C1b21Umm.js → AdminTabs-CZRSZTPU.js} +1 -1
  5. package/dist/assets/{AiProfiles-CxmKPOGN.js → AiProfiles-BZzDob0L.js} +1 -1
  6. package/dist/assets/{BrokerForm-BxVQ7-k2.js → BrokerForm-BAqaxvfH.js} +1 -1
  7. package/dist/assets/{Build-eERcQhc5.js → Build-ut1CQDDo.js} +4 -4
  8. package/dist/assets/{Capabilities-CkTJoG8P.js → Capabilities-NKE6_qP5.js} +1 -1
  9. package/dist/assets/{CapabilityBadges-Pi0oFNyA.js → CapabilityBadges-BC_kpGZQ.js} +1 -1
  10. package/dist/assets/Cluster-ej3HxB6z.js +1 -0
  11. package/dist/assets/{ClusterRecordingTab-g0pESRbc.js → ClusterRecordingTab-Rj_59RkE.js} +1 -1
  12. package/dist/assets/{Dashboard-BOdpB8Zi.js → Dashboard-C2O6I-_T.js} +1 -1
  13. package/dist/assets/{Data-D_lbeA81.js → Data-BTpXqPiI.js} +1 -1
  14. package/dist/assets/{DetectionIntelligence-Dsj8oMfR.js → DetectionIntelligence-CwuQGH6V.js} +1 -1
  15. package/dist/assets/DeviceAggregateSection-BUZ_WJht.js +1 -0
  16. package/dist/assets/DeviceDetail-DSCcC8RF.js +2 -0
  17. package/dist/assets/{Devices-Ctgv5UD9.js → Devices-DM0iJ9yF.js} +1 -1
  18. package/dist/assets/{EmbedPlayerPage-BH6rYrQY.js → EmbedPlayerPage-DWnlloAu.js} +1 -1
  19. package/dist/assets/Events-7ddWruIT.js +1 -0
  20. package/dist/assets/{FormBuilder-4y2vfIFX.js → FormBuilder-kyBE_B5G.js} +1 -1
  21. package/dist/assets/{Identity-lIu4PKp0.js → Identity-Dt3ylW9N.js} +1 -1
  22. package/dist/assets/IntegrationDetail-w86mAryt.js +1 -0
  23. package/dist/assets/Integrations-Cc34FTD5.js +1 -0
  24. package/dist/assets/{Integrations-CtXQURhT.js → Integrations-DJV-So6R.js} +1 -1
  25. package/dist/assets/{Logs-CjKk3Pid.js → Logs-BndkeK5m.js} +1 -1
  26. package/dist/assets/{MotionZonesSettings-Lydieqfe.js → MotionZonesSettings-BQJ4kVFF.js} +1 -1
  27. package/dist/assets/{MyAccess-W28I5kd-.js → MyAccess-CSmeU_6Z.js} +1 -1
  28. package/dist/assets/Network-Cu_VSmA2.js +1 -0
  29. package/dist/assets/{NodeAddonsSettingsPanel-BI-P31wN.js → NodeAddonsSettingsPanel-SSVryw6D.js} +1 -1
  30. package/dist/assets/{NodeInferenceDevices-BmhhZKpR.js → NodeInferenceDevices-DtEFMSa-.js} +1 -1
  31. package/dist/assets/{Notifications-BGKlEsy-.js → Notifications-DkHNN7u-.js} +1 -1
  32. package/dist/assets/Pipeline-C-9Jkm22.js +1 -0
  33. package/dist/assets/{Plates-CXXsVO_i.js → Plates-CpWDWN0r.js} +1 -1
  34. package/dist/assets/{PrivacyMaskSettings-Cm6_Ayev.js → PrivacyMaskSettings-CkdB1xCI.js} +1 -1
  35. package/dist/assets/{ProviderIcon-NBKDrcT9.js → ProviderIcon-bdbnq4N6.js} +1 -1
  36. package/dist/assets/Recordings-DBw-hB4-.js +1 -0
  37. package/dist/assets/{ScanNetworkModal-BEJE3rpa.js → ScanNetworkModal-_YV1Zq_l.js} +1 -1
  38. package/dist/assets/{Settings-2DUxyOI7.js → Settings-BiLVD3It.js} +1 -1
  39. package/dist/assets/{Showroom-DsDBDxPb.js → Showroom-4VD16iez.js} +1 -1
  40. package/dist/assets/{Terminal-DsDxn595.js → Terminal-BnUlIw8O.js} +2 -2
  41. package/dist/assets/{_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-DuoUM9go.js → _virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-BmDbz_rz.js} +1 -1
  42. package/dist/assets/{boxes-byknuFCy.js → boxes-e-GYxfpL.js} +1 -1
  43. package/dist/assets/{build-cluster-graph-gWLixa2m.js → build-cluster-graph-DxvZ0xhr.js} +2 -2
  44. package/dist/assets/circle-check-big-B-yIR8q2.js +1 -0
  45. package/dist/assets/circle-x-D03G0GdO.js +1 -0
  46. package/dist/assets/clock-BXGwoKYC.js +1 -0
  47. package/dist/assets/{copy-DtilJJvc.js → copy-CqwrIa4N.js} +1 -1
  48. package/dist/assets/{debounced-invalidate-DHj6wyxg.js → debounced-invalidate-DBoL8JpN.js} +1 -1
  49. package/dist/assets/{dist-CL9jy06y.js → dist-BJ0Zt6VD.js} +1 -1
  50. package/dist/assets/dist-DXPrpRpS.js +345 -0
  51. package/dist/assets/{download-Dj8UGwXy.js → download-CmuC-FeE.js} +1 -1
  52. package/dist/assets/{face-bbox-BFrLPrmw.js → face-bbox-CJXXrwZD.js} +1 -1
  53. package/dist/assets/{hostInit-dir45U0P.js → hostInit-Bl7Xplbe.js} +1 -1
  54. package/dist/assets/index-DKn-frpn.js +4 -0
  55. package/dist/assets/{key-round-DmdC7g1C.js → key-round-CM8vUNHb.js} +1 -1
  56. package/dist/assets/{mf-entry-bootstrap-0-7417bcdb.js → mf-entry-bootstrap-0-87bf9ee5.js} +2 -2
  57. package/dist/assets/{pencil-CM0OcKoQ.js → pencil-c6tQI1wA.js} +1 -1
  58. package/dist/assets/play-Q0Jaad1z.js +1 -0
  59. package/dist/assets/{player-overlays-CM9K8NbB.js → player-overlays-CLg7IUhf.js} +1 -1
  60. package/dist/assets/plus-XQw0_Xxm.js +1 -0
  61. package/dist/assets/{radio-C1-CuToc.js → radio-aWUgXHcO.js} +1 -1
  62. package/dist/assets/{refresh-cw-BCQ0uqSo.js → refresh-cw-tMjElbNM.js} +1 -1
  63. package/dist/assets/remoteEntry-BKnWOAq9.js +1 -0
  64. package/dist/assets/rotate-ccw-BrUDF1Jl.js +1 -0
  65. package/dist/assets/rotate-cw-BHhlWumi.js +1 -0
  66. package/dist/assets/search-vkCcifvz.js +1 -0
  67. package/dist/assets/{send-85b3c2pF.js → send-CMFUzdc4.js} +1 -1
  68. package/dist/assets/{server-CqHxFx4j.js → server-B8x_DAB-.js} +1 -1
  69. package/dist/assets/square-BKiKY80C.js +1 -0
  70. package/dist/assets/{square-check-big-BFcA-8wr.js → square-check-big-kCHymcdg.js} +1 -1
  71. package/dist/assets/src-c6oHWYCY.js +345 -0
  72. package/dist/assets/src-vvsO4SIS.js +67 -0
  73. package/dist/assets/{undo-2-DDQAKjyU.js → undo-2-CHnYllAb.js} +1 -1
  74. package/dist/assets/{upload-D0e5f6HZ.js → upload-I6q-wv2N.js} +1 -1
  75. package/dist/assets/{useEventInvalidation-BnSiq41g.js → useEventInvalidation-JO5cEBnp.js} +1 -1
  76. package/dist/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-1o9kxso_.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-C7terUe9.js} +2 -2
  77. package/dist/assets/{wifi-CkMq-H2F.js → wifi-DMRZBskQ.js} +1 -1
  78. package/dist/assets/{zap-C0CZz-JF.js → zap-BFD9xYRb.js} +1 -1
  79. package/dist/index.html +5 -5
  80. package/dist/server/addon.js +369 -6
  81. package/package.json +1 -1
  82. package/dist/assets/Addons-Cbzvs2ne.js +0 -1
  83. package/dist/assets/Cluster-D7MeMUz9.js +0 -1
  84. package/dist/assets/DeviceAggregateSection-UayHxH_7.js +0 -1
  85. package/dist/assets/DeviceDetail-2vx05ko3.js +0 -2
  86. package/dist/assets/Events-GU0DqH5U.js +0 -1
  87. package/dist/assets/IntegrationDetail-C0Zcxbgi.js +0 -1
  88. package/dist/assets/Integrations-BP2jk3NT.js +0 -1
  89. package/dist/assets/Network-Cnq4bp10.js +0 -1
  90. package/dist/assets/Pipeline-6vY94dNS.js +0 -1
  91. package/dist/assets/Recordings-fIC-_Plr.js +0 -1
  92. package/dist/assets/circle-check-big-D8Wjv0ct.js +0 -1
  93. package/dist/assets/clock-ISV-5mJ1.js +0 -1
  94. package/dist/assets/dist-DwLd0mpq.js +0 -345
  95. package/dist/assets/index-BMoYnIQI.js +0 -4
  96. package/dist/assets/play-CBwgjWMW.js +0 -1
  97. package/dist/assets/plus-CByuXPqh.js +0 -1
  98. package/dist/assets/remoteEntry-BtIghGso.js +0 -1
  99. package/dist/assets/rotate-ccw-CiIETsd8.js +0 -1
  100. package/dist/assets/rotate-cw-Bo4UhVCj.js +0 -1
  101. package/dist/assets/search-m5N8S4EI.js +0 -1
  102. package/dist/assets/square-CwYcDkPi.js +0 -1
  103. package/dist/assets/src-BaYQ7lHC.js +0 -345
  104. package/dist/assets/src-s1tm1Ako.js +0 -67
@@ -7094,6 +7094,104 @@ object({
7094
7094
  })
7095
7095
  });
7096
7096
  /**
7097
+ * Adoption job — the background form of `device-adoption.adopt`.
7098
+ *
7099
+ * ## Why this exists
7100
+ *
7101
+ * `adopt({childNativeIds: [...]})` materialises one CamStack device per
7102
+ * candidate PLUS every accessory child, and the whole array shares ONE UDS
7103
+ * request deadline (60s). Measured on the live hub against Home Assistant:
7104
+ * each device the kernel creates costs ~450 ms — `devices.create` pre-seeds
7105
+ * meta with up to eleven SEQUENTIAL round trips (`setName`, `setType`,
7106
+ * `setRole`, … `persistConfig`) before the class is constructed — and an
7107
+ * accessory child costs the same as its parent. So the real unit of work is
7108
+ * the CHILD, not the candidate:
7109
+ *
7110
+ * - 25 candidates averaging 6 children → ~150 devices → **>60s, times out**
7111
+ * - ONE candidate with 217 children → ~217 devices → **>60s, times out**
7112
+ *
7113
+ * That second line is why this is a job and not a smaller batch. No chunking,
7114
+ * no bounded concurrency over candidates and no per-call tuning can fix a
7115
+ * shape where **N=1 already exceeds the deadline** — the count that blows the
7116
+ * budget is the source system's accessory fan-out, which the operator does not
7117
+ * choose and cannot see. A design that only works below some N is the same bug
7118
+ * deferred.
7119
+ *
7120
+ * ## What the timeout did NOT do
7121
+ *
7122
+ * It did not stop the work. The UDS deadline ends the CALLER's wait; the
7123
+ * provider's loop runs to completion. Measured: a 25-candidate adopt that
7124
+ * "failed" at 60s had adopted 17 by 87s and all 25 by ~130s. The operator saw
7125
+ * an error and had no way to learn that. Every field below exists so that
7126
+ * question has an answer.
7127
+ *
7128
+ * ## Idempotency
7129
+ *
7130
+ * Jobs are in-RAM; a restart forgets them. That is safe here because adoption
7131
+ * is keyed by a stable id (`ha:<broker>:dev:<nativeId>` and equivalents), so
7132
+ * re-running a job re-adopts nothing: an already-adopted candidate is SKIPPED
7133
+ * by the engine before any provider call and lands in `alreadyAdopted`. It is
7134
+ * never a duplicate device, and never an error the operator has to interpret.
7135
+ */
7136
+ var AdoptionJobStateSchema = _enum([
7137
+ "running",
7138
+ "done",
7139
+ "failed",
7140
+ "cancelled"
7141
+ ]);
7142
+ /**
7143
+ * Per-candidate result. Every candidate the job was asked to adopt ends in
7144
+ * exactly one of these buckets — there is no silent drop, and the operator can
7145
+ * always answer "which of my 25 landed?".
7146
+ *
7147
+ * - `adopted` — created now by this job.
7148
+ * - `already-adopted` — a device for this candidate existed before the job
7149
+ * reached it (a re-run, or a retry after a timeout). Not an error.
7150
+ * - `failed` — the provider threw; `error` carries the message.
7151
+ * - `cancelled` — the operator cancelled before this candidate was reached.
7152
+ */
7153
+ var AdoptionOutcomeSchema = _enum([
7154
+ "adopted",
7155
+ "already-adopted",
7156
+ "failed",
7157
+ "cancelled"
7158
+ ]);
7159
+ var AdoptionCandidateResultSchema = object({
7160
+ childNativeId: string(),
7161
+ outcome: AdoptionOutcomeSchema,
7162
+ /** The materialised parent device id — null for `failed` / `cancelled`. */
7163
+ parentDeviceId: number().int().nonnegative().nullable(),
7164
+ /** Accessory children created for this candidate. */
7165
+ accessoryCount: number().int().nonnegative(),
7166
+ /** Failure message; null unless `outcome === 'failed'`. */
7167
+ error: string().nullable()
7168
+ });
7169
+ var AdoptionJobSchema = object({
7170
+ jobId: string(),
7171
+ /** The integration provider this job adopts through (the `addonId` pin). */
7172
+ addonId: string(),
7173
+ integrationId: string(),
7174
+ state: AdoptionJobStateSchema,
7175
+ /** Candidates the job was asked to adopt. Known up front, so never null. */
7176
+ total: number().int().nonnegative(),
7177
+ /** Candidates that have reached a terminal bucket. */
7178
+ processed: number().int().nonnegative(),
7179
+ adopted: number().int().nonnegative(),
7180
+ alreadyAdopted: number().int().nonnegative(),
7181
+ failed: number().int().nonnegative(),
7182
+ /** Accessory child devices created across every candidate — the real unit
7183
+ * of work, surfaced so a slow job is legible rather than mysterious. */
7184
+ accessoriesCreated: number().int().nonnegative(),
7185
+ /** The candidate currently being adopted; null when idle or finished. */
7186
+ currentChildNativeId: string().nullable(),
7187
+ /** One entry per candidate, in the order they were processed. */
7188
+ results: array(AdoptionCandidateResultSchema).readonly(),
7189
+ startedAt: number(),
7190
+ finishedAt: number().nullable(),
7191
+ /** Set only when the job itself broke (not a per-candidate failure). */
7192
+ error: string().nullable()
7193
+ });
7194
+ /**
7097
7195
  * Per-camera FUNCTION SWITCHES — the one coherent on/off surface over the
7098
7196
  * pipeline functions an operator thinks in terms of.
7099
7197
  *
@@ -11760,6 +11858,15 @@ method(object({
11760
11858
  }), method(ReleaseInputSchema.extend({ addonId: string() }), _void(), {
11761
11859
  kind: "mutation",
11762
11860
  auth: "admin"
11861
+ }), method(AdoptInputSchema.extend({ addonId: string() }), object({ jobId: string() }), {
11862
+ kind: "mutation",
11863
+ auth: "admin"
11864
+ }), method(object({
11865
+ addonId: string(),
11866
+ integrationId: string().optional()
11867
+ }), array(AdoptionJobSchema).readonly(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
11868
+ kind: "mutation",
11869
+ auth: "admin"
11763
11870
  }), method(ResyncInputSchema, ResyncResultSchema, {
11764
11871
  kind: "mutation",
11765
11872
  auth: "admin"
@@ -14639,6 +14746,76 @@ object({
14639
14746
  * Each provider returns a static descriptor; the core enumerates them
14640
14747
  * to validate the `integration=` query param and resolve the consent
14641
14748
  * label + the scopes baked into the issued token.
14749
+ *
14750
+ * ## Declaring one
14751
+ *
14752
+ * An OAuth client is integration-specific knowledge — who the client is, what
14753
+ * it may ask for, where it may be sent — so it is declared by the ADDON that
14754
+ * owns the integration, never by the kernel and never as a branch inside
14755
+ * `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
14756
+ * steps, no others:
14757
+ *
14758
+ * 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
14759
+ * manifest entry. This is also what tells the hub, at addon-LOAD time, that
14760
+ * a descriptor is owed — see "the boot window" below.
14761
+ * 2. Return a provider from `onInitialize()`:
14762
+ *
14763
+ * ```ts
14764
+ * const provider: IOauthIntegrationProvider = {
14765
+ * getDescriptor: async () => ({
14766
+ * integrationId: 'my-thing', // the `integration=` query param
14767
+ * displayName: 'My Thing',
14768
+ * requestedScopes: [ … ], // see below
14769
+ * allowedRedirectPrefixes: ['https://callback.example/'],
14770
+ * }),
14771
+ * }
14772
+ * return [{ capability: oauthIntegrationCapability, provider }]
14773
+ * ```
14774
+ *
14775
+ * The descriptor must be **static** — it is read on the authorize path, so
14776
+ * never put an await on network or disk behind it, and never register it
14777
+ * behind one either (a provider is registered only once `onInitialize`
14778
+ * RETURNS, so anything awaited before the return delays linking).
14779
+ * 3. Nothing else. There is no allow-list to join, no id to register with the
14780
+ * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
14781
+ * `/api/oauth2/integrations` are built from this collection alone.
14782
+ *
14783
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
14784
+ * NEEDS to function.** Not a blast radius, not a conservative
14785
+ * under-declaration, not a description of some other path the addon happens to
14786
+ * have. Derive it from what the client actually calls **with this token** —
14787
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
14788
+ * addon HTTP route it posts to — and write the call that justifies each entry
14789
+ * next to it. Two integrations once used this field to mean two different
14790
+ * things; the operator ruled there is one meaning, and any third integration
14791
+ * inherits it (2026-08-09).
14792
+ *
14793
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
14794
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
14795
+ * checks an integration token's grant before letting it reach an
14796
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
14797
+ * that stops working** — a missing `addon:` entry means `403 Token scope
14798
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
14799
+ * honestly rather than weakening a check to make a route pass.
14800
+ *
14801
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
14802
+ * genuinely needs a whole family; a category scope grants every future member
14803
+ * of that category too. `category:system [create]` has been rejected once and
14804
+ * should stay rejected: it hands `addons.installPackage` to an integration.
14805
+ *
14806
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
14807
+ * scope-checked, so they are not what this field describes — but reaching the
14808
+ * addon's route in the first place IS, and that is the entry to declare.
14809
+ *
14810
+ * **The boot window.** An addon registers its provider after its runner forks
14811
+ * and initialises, so between hub start and that moment this collection is
14812
+ * incomplete and an `integrationId` can be legitimately absent. The core does
14813
+ * not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
14814
+ * it compares the manifest declarers against the registered providers and
14815
+ * answers `503 temporarily_unavailable` (with `Retry-After` and the pending
14816
+ * addon ids) instead of `400 unknown integration`, and reports
14817
+ * `complete: false` on `GET /api/oauth2/integrations`. A client should retry
14818
+ * while the list is incomplete rather than conclude the hub cannot do OAuth.
14642
14819
  */
14643
14820
  var OauthIntegrationDescriptorSchema = object({
14644
14821
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
@@ -14669,7 +14846,30 @@ var OauthIntegrationDescriptorSchema = object({
14669
14846
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
14670
14847
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
14671
14848
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
14672
- hubUrl: string().optional()
14849
+ hubUrl: string().optional(),
14850
+ /**
14851
+ * How long a REFRESH token issued for this integration lives — seconds, or
14852
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
14853
+ * 30-day default, which is what every link used before this field existed.
14854
+ *
14855
+ * Declared here for the same reason `requestedScopes` is: the integration
14856
+ * knows what it needs. Amazon's account linking and a Home Assistant config
14857
+ * entry are both meant to survive indefinitely, and re-linking is a manual
14858
+ * user action, so a 30-day expiry silently unlinks a working integration.
14859
+ *
14860
+ * **The security posture, stated so it is owned deliberately.** A refresh
14861
+ * token that never expires is permanent access if it leaks. What bounds it is
14862
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
14863
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
14864
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
14865
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
14866
+ * revoking a link takes effect there only after its remaining hour. That hour
14867
+ * is why the access TTL is not configurable.
14868
+ *
14869
+ * The value is baked into the authorization code at `/authorize` and travels
14870
+ * on the tokens, so editing this field changes FUTURE links only.
14871
+ */
14872
+ refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
14673
14873
  });
14674
14874
  method(_void(), OauthIntegrationDescriptorSchema);
14675
14875
  /**
@@ -17697,6 +17897,37 @@ DeviceType.Camera, method(object({
17697
17897
  lastCapturedAt: number().nullable(),
17698
17898
  cacheAgeMs: number().nullable(),
17699
17899
  etag: string().nullable()
17900
+ }))), systemMethod(object({
17901
+ /** The tiles a surface is actually rendering. One entry per (device,
17902
+ * width) the caller will paint — the width is snapped to the server's
17903
+ * ladder and becomes part of the link's SIGNED identity. */
17904
+ targets: array(object({
17905
+ deviceId: number(),
17906
+ /** Target width in px. Omit for the frame as captured — correct
17907
+ * for a full-bleed surface, wrong (and expensive) for a grid. */
17908
+ width: number().int().positive().optional()
17909
+ })).min(1).max(200) }), array(object({
17910
+ deviceId: number(),
17911
+ /** Root-relative signed path, or null when the link plane is not
17912
+ * served (no data-plane facility). Present even for a device that has
17913
+ * never captured — the request is what triggers the first one (D94). */
17914
+ url: string().nullable(),
17915
+ /** Epoch ms of the frame this link serves. Null = never captured.
17916
+ * THE honest age: the tRPC path carried none before this. */
17917
+ capturedAt: number().nullable(),
17918
+ /** Age of that frame at the moment the answer was built. */
17919
+ ageMs: number().nullable(),
17920
+ /** Epoch ms after which `url` stops verifying. */
17921
+ expiresAt: number().nullable(),
17922
+ /** Ladder rung the bytes are at; null = the frame as captured. */
17923
+ width: number().nullable(),
17924
+ /** The device has never produced a frame. An empty state, not a
17925
+ * failure — and never a reason to withhold the link (D94). */
17926
+ neverCaptured: boolean(),
17927
+ /** A sleeping battery camera: the frame is deliberately stale and will
17928
+ * NOT refresh in the background. A surface should say so rather than
17929
+ * present it as current. */
17930
+ sleeping: boolean()
17700
17931
  })));
17701
17932
  /**
17702
17933
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
@@ -17742,11 +17973,29 @@ var SsoBridgeClaimsSchema = object({
17742
17973
  codeChallenge: string().optional(),
17743
17974
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
17744
17975
  * tokens so the verify path can check the session is not revoked. */
17745
- sessionId: string().optional()
17976
+ sessionId: string().optional(),
17977
+ /**
17978
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
17979
+ * Baked into the code at `/authorize` from the integration's descriptor and
17980
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
17981
+ * on the token rather than being re-read from the descriptor on purpose:
17982
+ * editing a descriptor must not retroactively extend or shorten a link the
17983
+ * operator already consented to.
17984
+ */
17985
+ refreshTtl: union([number().int().positive(), literal("never")]).optional()
17746
17986
  });
17747
17987
  method(object({
17748
17988
  claims: SsoBridgeClaimsSchema,
17749
- ttlSec: number().int().positive().optional()
17989
+ /**
17990
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
17991
+ *
17992
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
17993
+ * this field already means "the 5-minute SSO hand-off default", and
17994
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
17995
+ * went through the numeric path would fail at mint time and break
17996
+ * linking rather than produce an eternal token.
17997
+ */
17998
+ ttlSec: union([number().int().positive(), literal("never")]).optional()
17750
17999
  }), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
17751
18000
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
17752
18001
  providerId: string().min(1),
@@ -19447,6 +19696,37 @@ DeviceType.Light, method(object({
19447
19696
  mireds: number().int().optional(),
19448
19697
  lastChangedAt: number()
19449
19698
  });
19699
+ var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
19700
+ object({
19701
+ outcome: literal("validated"),
19702
+ /** Round-trip of the sign-in, when the provider measured it. */
19703
+ latencyMs: number().nonnegative().optional(),
19704
+ /** Optional human detail worth showing next to the tick
19705
+ * ("3 devices visible on this account"). */
19706
+ detail: string().optional()
19707
+ }).strict(),
19708
+ object({
19709
+ outcome: literal("rejected"),
19710
+ error: string()
19711
+ }).strict(),
19712
+ object({
19713
+ outcome: literal("inconclusive"),
19714
+ error: string()
19715
+ }).strict()
19716
+ ]);
19717
+ var ConnectionTestInputSchema = object({
19718
+ /** Candidate integration settings, exactly as the create form collected them. */
19719
+ settings: record(string(), unknown()) });
19720
+ /**
19721
+ * What the provider's test actually DOES, so the UI can say it in words before
19722
+ * the operator presses the button ("Signs in to the Dreo cloud"). Purely
19723
+ * descriptive — it never changes routing.
19724
+ */
19725
+ var ConnectionTestDescriptorSchema = object({ label: string() });
19726
+ method(ConnectionTestInputSchema, ConnectionTestOutcomeSchema, {
19727
+ kind: "mutation",
19728
+ auth: "admin"
19729
+ }), method(_void(), ConnectionTestDescriptorSchema, { auth: "admin" });
19450
19730
  object({
19451
19731
  /** True when the upstream system considers the entity connected. */
19452
19732
  connected: boolean(),
@@ -20348,15 +20628,57 @@ var AvailableIntegrationTypeSchema = object({
20348
20628
  * flow can import (e.g. HA areas). Drives the adopt modal's "import
20349
20629
  * locations" checkbox. Provider-declared in the addon manifest. */
20350
20630
  supportsLocationImport: boolean(),
20631
+ /**
20632
+ * True when this integration DECLARES a pre-creation test (the
20633
+ * `connection-test` cap, or a broker whose settings it stores). Drives the
20634
+ * Test button: an integration that cannot be tested must say so up front
20635
+ * rather than offering a button that always answers the same nonsense.
20636
+ */
20637
+ canTest: boolean(),
20351
20638
  existingInstances: array(object({
20352
20639
  id: string(),
20353
20640
  name: string()
20354
20641
  })),
20355
20642
  canAdd: boolean()
20356
20643
  });
20644
+ /**
20645
+ * Why a test could not be answered as a plain boolean.
20646
+ *
20647
+ * `success` alone collapsed four different situations into one red box, and the
20648
+ * one that mattered most — "nobody ever asked the remote anything" — looked
20649
+ * exactly like "the remote said no". The status is the discriminator:
20650
+ *
20651
+ * - `validated` — a provider-declared test ran and the remote ACCEPTED.
20652
+ * - `rejected` — a provider-declared test ran and the remote REFUSED.
20653
+ * The only status that blocks `integrations.create`.
20654
+ * - `inconclusive` — a test IS declared but could not complete (timeout,
20655
+ * DNS, 5xx). Nothing was observed; not a failure.
20656
+ * - `unsupported` — this integration declares NO test. Nothing was
20657
+ * observed either; not a failure, and not a pass.
20658
+ *
20659
+ * `unsupported` and `inconclusive` both carry `success: false` so an older
20660
+ * client can never read them as a green tick, and both carry an `error` string
20661
+ * that SAYS the test did not run rather than inventing a failure.
20662
+ */
20663
+ var TestConnectionStatusEnum = _enum([
20664
+ "validated",
20665
+ "rejected",
20666
+ "inconclusive",
20667
+ "unsupported"
20668
+ ]);
20357
20669
  var TestConnectionResultSchema$1 = object({
20670
+ /** True ONLY for `validated`. Never true for a test that did not run. */
20358
20671
  success: boolean(),
20359
- error: string().optional()
20672
+ error: string().optional(),
20673
+ /** Optional for wire back-compat with clients built before the tri-state;
20674
+ * the server always sets it. */
20675
+ status: TestConnectionStatusEnum.optional(),
20676
+ /** Addon id whose declared test answered — `null` when none did. Lets the UI
20677
+ * attribute a result instead of blaming "the integration". */
20678
+ testedBy: string().nullable().optional(),
20679
+ latencyMs: number().nonnegative().optional(),
20680
+ /** Human detail from a `validated` result ("3 devices on this account"). */
20681
+ detail: string().optional()
20360
20682
  });
20361
20683
  var CreateIntegrationInputSchema = object({
20362
20684
  addonId: string(),
@@ -23409,7 +23731,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
23409
23731
  hubUrl: string(),
23410
23732
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
23411
23733
  * that carries one can ONLY be exchanged with the matching verifier. */
23412
- codeChallenge: string().optional()
23734
+ codeChallenge: string().optional(),
23735
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
23736
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
23737
+ * code so the link carries its own lifetime; omit for the 30-day
23738
+ * default. */
23739
+ refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
23413
23740
  }), object({ code: string() }), {
23414
23741
  kind: "mutation",
23415
23742
  access: "create"
@@ -24602,6 +24929,18 @@ Object.freeze({
24602
24929
  addonId: null,
24603
24930
  access: "create"
24604
24931
  },
24932
+ "connectionTest.describeTest": {
24933
+ capName: "connection-test",
24934
+ capScope: "system",
24935
+ addonId: null,
24936
+ access: "view"
24937
+ },
24938
+ "connectionTest.testSettings": {
24939
+ capName: "connection-test",
24940
+ capScope: "system",
24941
+ addonId: null,
24942
+ access: "create"
24943
+ },
24605
24944
  "consumables.reset": {
24606
24945
  capName: "consumables",
24607
24946
  capScope: "device",
@@ -24992,6 +25331,12 @@ Object.freeze({
24992
25331
  addonId: null,
24993
25332
  access: "create"
24994
25333
  },
25334
+ "deviceManager.adoptionCancelJob": {
25335
+ capName: "device-manager",
25336
+ capScope: "system",
25337
+ addonId: null,
25338
+ access: "create"
25339
+ },
24995
25340
  "deviceManager.adoptionListCandidateFilters": {
24996
25341
  capName: "device-manager",
24997
25342
  capScope: "system",
@@ -25004,6 +25349,12 @@ Object.freeze({
25004
25349
  addonId: null,
25005
25350
  access: "view"
25006
25351
  },
25352
+ "deviceManager.adoptionListJobs": {
25353
+ capName: "device-manager",
25354
+ capScope: "system",
25355
+ addonId: null,
25356
+ access: "view"
25357
+ },
25007
25358
  "deviceManager.adoptionRefresh": {
25008
25359
  capName: "device-manager",
25009
25360
  capScope: "system",
@@ -25022,6 +25373,12 @@ Object.freeze({
25022
25373
  addonId: null,
25023
25374
  access: "create"
25024
25375
  },
25376
+ "deviceManager.adoptionStartJob": {
25377
+ capName: "device-manager",
25378
+ capScope: "system",
25379
+ addonId: null,
25380
+ access: "create"
25381
+ },
25025
25382
  "deviceManager.allocateDeviceId": {
25026
25383
  capName: "device-manager",
25027
25384
  capScope: "system",
@@ -28160,6 +28517,12 @@ Object.freeze({
28160
28517
  addonId: null,
28161
28518
  access: "view"
28162
28519
  },
28520
+ "snapshot.getSnapshotLinks": {
28521
+ capName: "snapshot",
28522
+ capScope: "device",
28523
+ addonId: null,
28524
+ access: "view"
28525
+ },
28163
28526
  "snapshot.getSnapshotOverview": {
28164
28527
  capName: "snapshot",
28165
28528
  capScope: "device",
@@ -29453,7 +29816,7 @@ var AdminUIAddon = class extends BaseAddon {
29453
29816
  capability: adminUiCapability,
29454
29817
  provider: {
29455
29818
  getStaticDir: async () => ({ staticDir: resolveAdminUiDistDir() }),
29456
- getVersion: async () => ({ version: "1.2.40" })
29819
+ getVersion: async () => ({ version: "1.2.42" })
29457
29820
  }
29458
29821
  }];
29459
29822
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-admin-ui",
3
- "version": "1.2.40",
3
+ "version": "1.2.42",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Admin UI — Vite frontend build and server-side addon",
@@ -1 +0,0 @@
1
- import{Ct as e,Dt as t,Hr as n,Ir as r,Mt as i,Nt as a,Ot as o,St as s,Tt as c,_t as l,bt as u,g as ee,gt as d,ht as f,io as te,jt as p,kt as m,m as h,mt as g,pt as _,so as v,uo as ne,v as re,vt as ie,wt as y,yt as ae}from"./src-s1tm1Ako.js";import{I as b,K as x,Q as S,R as oe,S as C,W as w,b as T,o as se,q as E,x as D,z as O}from"./_virtual_mf___mfe_internal__admin_ui_host__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-DTufy7Ue.js";import{$ as ce}from"./src-BaYQ7lHC.js";import{p as le}from"./player-overlays-CM9K8NbB.js";import{t as k}from"./clock-ISV-5mJ1.js";import{t as A}from"./download-Dj8UGwXy.js";import{t as ue}from"./plus-CByuXPqh.js";import{t as j}from"./refresh-cw-BCQ0uqSo.js";import{t as de}from"./search-m5N8S4EI.js";import{t as M}from"./upload-D0e5f6HZ.js";import{G as N,L as P,N as fe,R as F,S as I,Y as L,g as pe,i as R,m as z,p as me,q as B,s as V}from"./index-BMoYnIQI.js";import{t as H}from"./AdminPage-Dq33XE9j.js";import{t as he}from"./NodeAddonsSettingsPanel-BI-P31wN.js";var ge=N(`chevron-up`,[[`path`,{d:`m18 15-6-6-6 6`,key:`153udz`}]]),U=N(`octagon-alert`,[[`path`,{d:`M12 16h.01`,key:`1drbdi`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z`,key:`1fd625`}]]),W=N(`package`,[[`path`,{d:`M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z`,key:`1a0edw`}],[`path`,{d:`M12 22V12`,key:`d0xqtd`}],[`polyline`,{points:`3.29 7 12 12 20.71 7`,key:`ousv84`}],[`path`,{d:`m7.5 4.27 9 5.15`,key:`1c824w`}]]),G=N(`skip-forward`,[[`polygon`,{points:`5 4 15 12 5 20 5 4`,key:`16p6eg`}],[`line`,{x1:`19`,x2:`19`,y1:`5`,y2:`19`,key:`futhcm`}]]);S();var K=new Set([`completed`,`failed`,`partially-failed`,`cancelled`]),q=new Set([`done`,`failed`,`skipped`]);function J(e){return!q.has(e.phase)&&!K.has(e.jobState)}function _e(e,t){let n=new Map,r=[...e].sort((e,t)=>e.createdAtMs-t.createdAtMs);for(let e of r){let r=K.has(e.state);for(let i of e.tasks)r&&i.finishedAtMs!==null&&t-i.finishedAtMs>6e4||n.set(i.packageName,{jobId:e.jobId,packageName:i.packageName,phase:i.phase,fromVersion:i.fromVersion,toVersion:i.toVersion,error:i.error,jobState:e.state,finishedAtMs:i.finishedAtMs})}return n}function ve(){let{data:e}=s({activeOnly:!0},{refetchOnWindowFocus:!1,refetchInterval:e=>(e.state.data?.length??0)>0?3e3:!1}),{map:t}=ne(ce.AddonsJobProgress,e=>e.job.jobId);return w(()=>{let n=new Map;for(let t of e??[])n.set(t.jobId,t);for(let[e,r]of t)n.set(e,r.job);return _e([...n.values()],Date.now())},[e,t])}var Y={queued:{icon:k,cls:`bg-muted text-foreground-subtle`,label:`queued`},fetching:{icon:I,cls:`bg-primary/10 text-primary`,label:`fetching`,spin:!0},staged:{icon:I,cls:`bg-primary/10 text-primary`,label:`staged`,spin:!0},validating:{icon:I,cls:`bg-primary/10 text-primary`,label:`validating`,spin:!0},applying:{icon:I,cls:`bg-primary/10 text-primary`,label:`applying`,spin:!0},restarting:{icon:I,cls:`bg-amber-500/10 text-amber-600 dark:text-amber-400`,label:`restarting`,spin:!0},applied:{icon:I,cls:`bg-primary/10 text-primary`,label:`finalizing`,spin:!0},done:{icon:F,cls:`bg-success/10 text-success`,label:`updated`},failed:{icon:U,cls:`bg-danger/10 text-danger`,label:`failed`},skipped:{icon:G,cls:`bg-muted text-foreground-subtle`,label:`skipped`}};function ye({progress:e}){let t=Y[e.phase],n=t.icon,r=e.error??`${e.packageName}: ${e.fromVersion??`?`} → ${e.toVersion}`;return C(`span`,{"data-testid":`addon-job-progress`,className:`inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] rounded shrink-0 ${t.cls}`,title:r,children:[D(n,{className:`w-2.5 h-2.5 ${t.spin?`animate-spin`:``}`}),t.label,C(`span`,{className:`font-mono`,children:[`→ `,e.toVersion]})]})}function be({packageName:e,currentVersion:t,installSource:n,onInstall:r,onClose:i,onSelect:a,selectedVersion:o}){let s=n===`upload`||n===`local`,{data:c,isLoading:l}=ie({name:e});return C(`div`,{className:`fixed z-50 w-72 rounded-lg border border-border bg-surface shadow-xl overflow-hidden`,style:{top:`auto`,bottom:`auto`},ref:e=>{if(!e)return;let t=e.parentElement?.getBoundingClientRect();if(!t)return;let n=window.innerHeight-t.bottom,r=t.top;n<280&&r>n?(e.style.bottom=`${window.innerHeight-t.top+4}px`,e.style.top=`auto`):(e.style.top=`${t.bottom+4}px`,e.style.bottom=`auto`),e.style.right=`${window.innerWidth-t.right}px`},onClick:e=>e.stopPropagation(),children:[C(`div`,{className:`flex items-center justify-between px-3 py-2 border-b border-border`,children:[D(`span`,{className:`text-[10px] uppercase tracking-wide text-foreground-subtle font-medium`,children:`Versions`}),D(`button`,{type:`button`,onClick:i,className:`text-foreground-subtle hover:text-foreground`,children:D(R,{className:`h-3 w-3`})})]}),s&&C(`div`,{className:`flex items-start gap-2 px-3 py-2 border-b border-border bg-cyan-500/10 text-[10px]`,children:[D(M,{className:`h-3 w-3 mt-0.5 text-cyan-500 shrink-0`}),C(`div`,{className:`text-cyan-700 dark:text-cyan-200`,children:[C(`span`,{className:`font-semibold`,children:[n===`upload`?`Uploaded copy`:`Local copy`,` in place.`]}),` `,`Re-install `,D(`span`,{className:`font-mono`,children:t}),` from npm to track upstream releases again.`]})]}),l&&C(`div`,{className:`flex items-center justify-center gap-2 py-6 text-[10px] text-foreground-subtle`,children:[D(I,{className:`h-3.5 w-3.5 animate-spin`}),`Loading versions...`]}),c&&D(`div`,{className:`max-h-64 overflow-auto divide-y divide-border`,children:c.map(e=>{let n=e.version===t,i=a!==void 0&&e.version===o,c=/-(alpha|beta|rc|dev|canary|next)/i.test(e.version),l=C(T,{children:[D(`span`,{className:`font-mono flex-1 text-left ${n?`text-primary font-semibold`:c?`text-amber-400`:`text-foreground`}`,children:e.version}),n&&D(`span`,{className:`text-[9px] rounded-full px-1.5 py-0.5 font-medium bg-primary/10 text-primary`,children:`current`}),e.distTags.map(e=>D(`span`,{className:`text-[9px] rounded-full px-1.5 py-0.5 font-medium ${e===`latest`?`bg-emerald-500/15 text-emerald-400`:e===`beta`?`bg-amber-500/15 text-amber-400`:`bg-gray-500/15 text-gray-400`}`,children:e},e)),e.deprecated&&D(`span`,{className:`text-[9px] text-red-400 line-through`,children:`deprecated`}),D(`span`,{className:`text-[9px] text-foreground-subtle shrink-0`,children:new Date(e.publishedAt).toLocaleDateString()})]});return a?C(`button`,{type:`button`,"aria-label":`Select ${e.version}`,onClick:()=>a(e.version),className:`w-full flex items-center gap-2 px-3 py-2 text-[11px] ${i?`bg-primary/5`:`hover:bg-surface-hover`} transition-colors`,children:[l,i?D(F,{className:`h-3 w-3 text-primary shrink-0`}):D(`span`,{className:`w-3 shrink-0`})]},e.version):C(`div`,{className:`flex items-center gap-2 px-3 py-2 text-[11px] ${n?`bg-primary/5`:`hover:bg-surface-hover`} transition-colors`,children:[l,n&&!s?D(F,{className:`h-3 w-3 text-primary shrink-0`}):D(`button`,{type:`button`,onClick:()=>r(e.version),className:`shrink-0 rounded p-0.5 hover:bg-primary/20 text-foreground-subtle hover:text-primary transition-colors`,title:n?`Re-install ${e.version} from npm`:`Install ${e.version}`,children:D(A,{className:`h-3 w-3`})})]},e.version)})}),c&&c.length===0&&D(`div`,{className:`py-4 text-center text-[10px] text-foreground-subtle`,children:`No versions found`})]})}var xe={queued:{icon:k,bg:`bg-muted text-foreground-subtle`,label:`queued`},updating:{icon:I,bg:`bg-primary/10 text-primary`,label:`updating`,spin:!0},"done-pending-restart":{icon:j,bg:`bg-amber-500/10 text-amber-700`,label:`pending restart`},done:{icon:F,bg:`bg-success/10 text-success`,label:`done`},failed:{icon:U,bg:`bg-danger/10 text-danger`,label:`failed`}};function Se({item:e}){let t=xe[e.status],n=t.icon;return C(`span`,{className:`inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] rounded ${t.bg}`,title:e.error,children:[D(n,{className:`w-2.5 h-2.5 ${t.spin?`animate-spin`:``}`}),t.label]})}S();var X=`device-export`;function Ce(e){return e.capabilities.some(e=>(typeof e==`string`?e:e.name)===X)}function we({isBundle:e,installSource:t,addonId:n,iconPath:r,color:i}){let[a,o]=E(!1),s=i?{backgroundColor:`${i.startsWith(`#`)?i:`#${i}`}26`}:void 0,c=e?`bg-indigo-500/20 text-indigo-700 dark:text-indigo-300`:`bg-purple-500/20 text-purple-700 dark:text-purple-300`,l=n&&r?`/api/addon-assets/${n}/${r}`:null,u=!!l&&!a;return C(`div`,{className:`relative flex-shrink-0`,children:[D(`div`,{className:`h-9 w-9 rounded-lg flex items-center justify-center ${s?``:c}`,style:s,children:u?D(`img`,{src:l,alt:n,className:`h-5 w-5`,onError:()=>o(!0)}):D(e?W:pe,{className:`h-4 w-4`})}),t===`local`&&D(`span`,{className:`absolute -top-1.5 -left-1.5 text-[7px] font-bold leading-none bg-orange-500 text-white px-1 py-0.5 rounded -rotate-12 shadow-sm`,children:`DEV`}),t===`npm`&&D(`span`,{className:`absolute -top-1 -left-1 w-4 h-4 rounded bg-[#cb3837] flex items-center justify-center -rotate-12 shadow-sm`,children:D(`svg`,{viewBox:`0 0 780 250`,className:`w-[11px] h-[11px]`,children:D(`path`,{fill:`#fff`,d:`M240 250h100V50h100v200h340V0H0v250z`})})}),t===`upload`&&D(`span`,{className:`absolute -top-1.5 -left-1.5 flex items-center justify-center h-4 w-4 rounded bg-cyan-500 -rotate-12 shadow-sm`,children:D(M,{className:`h-2.5 w-2.5 text-white`})})]})}function Te({version:e,availableUpdate:t,selectedVersion:n,isUpdating:r,installSource:i,disabled:a,onUpdate:o,onOpenPicker:s,children:c}){if(r)return C(`span`,{className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium border border-blue-500/30 bg-blue-500/10 text-blue-400 shrink-0`,children:[D(I,{className:`h-3 w-3 animate-spin`}),`Updating…`]});if(t){let r=n??t;return C(`div`,{className:`relative inline-flex items-center gap-1 shrink-0`,children:[o&&C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),o()},disabled:a,className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-semibold border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed transition-colors`,title:`Update to ${r}`,children:[D(A,{className:`h-3 w-3`}),`Update`]}),C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),s()},disabled:a,"aria-label":`Select version to install`,title:`Installed ${e} — update available. Selected ${r}; tap to choose another version.`,className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-bold font-mono border border-amber-500/40 bg-amber-500/15 text-amber-500 hover:bg-amber-500/25 animate-pulse disabled:opacity-50 disabled:cursor-not-allowed transition-colors`,children:[D(`span`,{className:`font-normal opacity-70`,children:e}),D(`span`,{className:`font-normal opacity-70`,children:`→`}),r,D(P,{className:`h-3 w-3`})]}),c]})}return C(`div`,{className:`relative inline-flex items-center gap-1 shrink-0`,children:[C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),s()},disabled:a,className:[`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-bold font-mono border transition-colors disabled:opacity-50 disabled:cursor-not-allowed`,i===`local`?`border-orange-500/30 bg-orange-500/10 text-orange-400 hover:bg-orange-500/20`:`border-emerald-500/30 bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20`].join(` `),title:`Version ${e} — tap to view all versions`,children:[e,D(P,{className:`h-3 w-3 opacity-50`})]}),c]})}function Ee({addonId:e}){let t=se(),{data:n=`inherit`}=f({addonId:e}),r=m({onSuccess:()=>{t.invalidateQueries({queryKey:[[`addons`,`getAddonAutoUpdate`]]})}});return D(`div`,{className:`flex rounded-lg border border-border overflow-hidden text-[10px] font-medium`,children:[`inherit`,`off`,`latest`,`beta`].map(t=>D(`button`,{type:`button`,onClick:n=>{n.stopPropagation(),r.mutate({addonId:e,channel:t})},disabled:r.isPending,className:`px-2.5 py-1 transition-colors capitalize ${n===t?t===`inherit`?`bg-gray-700/50 text-gray-700 dark:text-gray-300`:t===`off`?`bg-gray-700 text-gray-700 dark:text-gray-300`:t===`latest`?`bg-blue-500/20 text-blue-400`:`bg-amber-500/20 text-amber-400`:`bg-transparent text-foreground-subtle hover:bg-surface-hover`} ${t===`inherit`?``:`border-l border-border`}`,children:t},t))})}function De({addon:e,agents:n=[],clusterStatus:r,hideVersion:a,availableUpdate:o,onUpdate:s,isUpdating:l,jobProgress:u,bundle:d,bulkItem:f,disabledByBulk:p,isSystem:m}){let[g,_]=E(!1),[v,ne]=E(!1),[ie,y]=E(!1),[b,x]=E(null);O(()=>{x(null)},[o]);let S=b??o,[oe,w]=E(!1),[ce,k]=E(!1),[A,de]=E(`logs`),M=se(),N=le(),fe=B(),F=te(),{manifest:I}=e,L=e.installedOn??[],pe=!d&&!I.protected&&I.removable!==!1,H=!!d,G=d?.displayName??I.name,K=d?.packageName??I.packageName,q=d?.version??I.packageVersion??I.version,J=d?.installSource??e.installSource,_e=d?d.bundle?.description??d.addons[0]?.manifest.description:I.description,ve=i({onSuccess:()=>{M.invalidateQueries({queryKey:[[`addons`]]}),M.invalidateQueries({queryKey:[[`addonPages`]]}),M.invalidateQueries({queryKey:[[`capabilities`]]})}}),Y=c({onSuccess:()=>{M.invalidateQueries({queryKey:[[`addons`]]})}}),xe=ae({onSuccess:()=>{M.invalidateQueries({queryKey:[[`addons`]]}),M.invalidateQueries({queryKey:[[`addonPages`]]}),M.invalidateQueries({queryKey:[[`capabilities`]]}),M.invalidateQueries({queryKey:[[`integrations`]]})}}),X=t({onSuccess:()=>{M.invalidateQueries({queryKey:[[`addons`]]}),M.invalidateQueries({queryKey:[[`alerts`]]})}}),Z=e.health,Q=Z?.phase===`failed`&&!Z.inGracePeriod,Oe=Q&&Z?.nextRetryAt&&!Z.retrying;return C(`div`,{className:`rounded-lg border bg-surface overflow-hidden ${Q?`border-red-500/50`:`border-border`}`,children:[Q&&Z&&C(`div`,{className:`bg-danger/10 border-b border-danger/30 px-4 py-2.5 flex items-start gap-2.5`,children:[D(U,{className:`h-4 w-4 text-danger shrink-0 mt-0.5`}),C(`div`,{className:`flex-1 min-w-0`,children:[C(`div`,{className:`text-xs font-semibold text-danger`,children:[`Failed to load (attempt #`,Z.retryCount,`)`]}),D(`div`,{className:`text-[11px] text-foreground mt-0.5 break-words font-mono`,children:Z.lastError?.message??`Unknown error`}),Oe&&Z.nextRetryAt&&C(`div`,{className:`text-[10px] text-foreground-subtle mt-1`,children:[`Auto-retry at `,new Date(Z.nextRetryAt).toLocaleTimeString()]})]}),C(`button`,{type:`button`,disabled:X.isPending||Z.retrying,onClick:e=>{e.stopPropagation(),X.mutate({packageName:K})},className:`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[11px] font-semibold border border-red-400/40 bg-red-400/10 text-red-700 dark:text-red-300 hover:bg-red-400/20 disabled:opacity-50 disabled:cursor-not-allowed transition-colors shrink-0`,title:`Reset retry counter and force immediate reload`,children:[D(j,{className:`h-3 w-3 ${X.isPending||Z.retrying?`animate-spin`:``}`}),`Retry now`]})]}),C(`div`,{className:`flex items-start gap-3 px-4 py-3`,children:[D(we,{isBundle:H||!!I.components&&I.components.length>1,installSource:J,addonId:H?d.addons[0]?.manifest.id:I.id,iconPath:H?d.bundle?.icon??d.addons[0]?.manifest.icon:I.icon,color:H?d.addons[0]?.manifest.color:I.color}),C(`div`,{className:`flex-1 min-w-0`,children:[C(`div`,{className:`flex items-start justify-between gap-2`,children:[D(`span`,{className:`text-sm font-semibold text-foreground truncate min-w-0`,children:G}),!a&&D(Te,{version:q,availableUpdate:o,...S===void 0?{}:{selectedVersion:S},isUpdating:l,installSource:J,disabled:p,...s&&S!==void 0?{onUpdate:()=>s(S)}:{},onOpenPicker:()=>y(e=>!e),children:ie&&D(be,{packageName:K,currentVersion:q,...J?{installSource:J}:{},...o!==void 0&&S!==void 0?{selectedVersion:S,onSelect:e=>{x(e),y(!1)}}:{},onInstall:e=>{y(!1),xe.mutate({packageName:K,version:e})},onClose:()=>y(!1)})})]}),C(`div`,{className:`flex flex-wrap items-center gap-x-1.5 gap-y-1 mt-0.5`,children:[K!==G&&D(`span`,{className:`text-[10px] text-foreground-subtle font-mono truncate max-w-full`,children:K}),H&&C(`span`,{className:`text-[10px] rounded-full bg-primary/10 text-primary px-2 py-0.5 font-medium shrink-0 inline-flex items-center gap-1`,children:[D(W,{className:`h-3 w-3`}),d.addons.length]}),m&&C(`span`,{className:`text-[10px] rounded-full bg-amber-500/15 text-amber-700 dark:text-amber-300 px-2 py-0.5 font-medium shrink-0 inline-flex items-center gap-1`,title:`System package — updating restarts the hub`,children:[D(j,{className:`h-3 w-3`}),`System`]}),f!=null&&D(Se,{item:f}),u!=null&&D(ye,{progress:u})]}),_e&&D(`p`,{className:`text-[10px] text-foreground-subtle mt-1 line-clamp-1`,children:_e}),!H&&e.process&&C(`div`,{className:`flex flex-wrap items-center gap-1.5 mt-1`,children:[D(`span`,{className:`text-[9px] rounded-full px-1.5 py-0.5 font-medium ${e.process.mode===`forked`?`bg-blue-500/15 text-blue-400`:`bg-gray-500/15 text-gray-400`}`,children:e.process.mode===`forked`?`forked`:`in-process`}),e.process.pid!=null&&C(`span`,{className:`text-[9px] font-mono text-foreground-subtle`,children:[`PID `,e.process.pid]}),D(`span`,{className:`text-[9px] rounded-full px-1.5 py-0.5 font-medium ${e.process.state===`running`?`bg-emerald-500/15 text-emerald-400`:e.process.state===`crashed`||e.process.state===`failed`?`bg-red-500/15 text-red-400`:`bg-gray-500/15 text-gray-400`}`,children:e.process.state})]})]})]}),!H&&C(`div`,{className:`flex items-center gap-1 px-4 py-1.5 border-t border-border/50 bg-surface-hover/20`,children:[C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),Y.mutate({addonId:I.id})},disabled:Y.isPending||p,className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium border border-border text-foreground-subtle hover:text-blue-400 hover:border-blue-500/30 hover:bg-blue-500/10 disabled:opacity-50 transition-colors`,children:[D(j,{className:`h-3 w-3 ${Y.isPending?`animate-spin`:``}`}),`Restart`]}),C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),w(e=>!e)},className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium border transition-colors ${oe?`border-primary/30 bg-primary/10 text-primary`:`border-border text-foreground-subtle hover:text-foreground hover:border-primary/30 hover:bg-primary/10`}`,children:[D(z,{className:`h-3 w-3`}),`Logs`]}),!H&&C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),k(!0)},className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium border transition-colors ${ce?`border-primary/30 bg-primary/10 text-primary`:`border-border text-foreground-subtle hover:text-foreground hover:border-primary/30 hover:bg-primary/10`}`,children:[D(me,{className:`h-3 w-3`}),`Settings`]}),D(`div`,{className:`flex-1`}),pe&&C(`button`,{type:`button`,onClick:async e=>{e.stopPropagation(),await F({title:`Uninstall ${I.name}?`,message:`This will remove the addon package and all its data. This action cannot be undone.`,confirmLabel:`Uninstall`,variant:`danger`})&&ve.mutate({packageName:I.packageName})},disabled:ve.isPending||p,className:`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium border border-border text-foreground-subtle hover:text-red-400 hover:border-red-500/30 hover:bg-red-500/10 disabled:opacity-50 transition-colors`,children:[D(V,{className:`h-3 w-3`}),ve.isPending?`Removing...`:`Uninstall`]})]}),r&&r.nodes.length>1&&!H&&C(`div`,{className:`border-t border-border px-4 py-2 bg-muted/10`,children:[D(`div`,{className:`text-[10px] font-medium text-foreground-subtle mb-1.5`,children:`Cluster Deployment`}),D(`div`,{className:`space-y-1`,children:r.nodes.map(e=>C(`div`,{className:`flex items-center gap-2 text-[10px]`,children:[D(`div`,{className:`h-1.5 w-1.5 rounded-full shrink-0 ${e.status===`offline`?`bg-muted-foreground/30`:e.synced?`bg-success`:`bg-amber-500`}`}),D(`span`,{className:`font-medium text-foreground min-w-[60px]`,children:e.name}),D(`span`,{className:`font-mono text-foreground-subtle`,children:e.version}),!e.synced&&e.status!==`offline`&&D(`span`,{className:`text-[9px] px-1 py-0.5 rounded bg-amber-500/10 text-amber-400 border border-amber-500/20`,children:`outdated`}),e.status===`offline`&&D(`span`,{className:`text-[9px] px-1 py-0.5 rounded bg-muted text-muted-foreground`,children:`offline`}),e.synced&&e.status!==`offline`&&D(`span`,{className:`text-[9px] text-success`,children:`synced`})]},e.nodeId))})]}),oe&&!H&&C(`div`,{className:`border-t border-border`,children:[C(`div`,{className:`flex items-center px-4 py-1 bg-surface-hover/20 border-b border-border/50`,children:[C(`div`,{className:`flex rounded border border-border overflow-hidden text-[9px]`,children:[D(`button`,{type:`button`,onClick:()=>de(`logs`),className:`px-2 py-0.5 transition-colors ${A===`logs`?`bg-primary/10 text-primary`:`text-foreground-subtle hover:bg-surface-hover`}`,children:`Logs`}),D(`button`,{type:`button`,onClick:()=>de(`events`),className:`px-2 py-0.5 border-l border-border transition-colors ${A===`events`?`bg-primary/10 text-primary`:`text-foreground-subtle hover:bg-surface-hover`}`,children:`Events`})]}),D(`div`,{className:`flex-1`}),D(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),w(!1)},className:`text-foreground-subtle hover:text-foreground`,children:D(R,{className:`h-3.5 w-3.5`})})]}),A===`logs`&&D(ee,{addonId:I.id,maxHeight:`max-h-64`,showFilters:!1}),A===`events`&&D(h,{addonId:I.id,category:`addon.*`,maxHeight:`max-h-64`})]}),H&&C(T,{children:[D(`button`,{type:`button`,onClick:()=>_(e=>!e),className:`w-full flex items-center justify-center border-t border-border py-1 hover:bg-surface-hover/50 transition-colors`,children:D(g?ge:P,{className:`h-3.5 w-3.5 text-foreground-subtle`})}),g&&D(`div`,{className:`border-t border-border px-3 py-2 space-y-2`,children:d.addons.map(e=>D(De,{addon:e,agents:n,hideVersion:!0},e.manifest.id))})]}),ce&&!H&&D(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4`,onClick:()=>k(!1),children:C(`div`,{className:`w-full max-w-lg max-h-[80vh] rounded-xl border border-border bg-surface shadow-2xl flex flex-col overflow-hidden`,onClick:e=>e.stopPropagation(),children:[C(`div`,{className:`flex items-center justify-between px-5 py-3 border-b border-border flex-shrink-0`,children:[C(`div`,{className:`flex items-center gap-2`,children:[D(me,{className:`h-4 w-4 text-primary`}),C(`h2`,{className:`text-sm font-semibold text-foreground`,children:[G,` Settings`]})]}),D(`button`,{onClick:()=>k(!1),className:`text-foreground-subtle hover:text-foreground transition-colors p-1 rounded`,children:D(R,{className:`h-4 w-4`})})]}),C(`div`,{className:`flex-1 overflow-y-auto px-5 py-4 space-y-4`,children:[D(he,{nodeId:`hub`,addonIds:[I.id],level:`global`}),Ce(I)&&C(`div`,{children:[D(`div`,{className:`text-[10px] font-medium text-foreground-subtle uppercase tracking-wide mb-2`,children:`Device export`}),D(re,{addonId:I.id,trpc:N.trpcClient,onOpenDevice:e=>fe(`/devices/${e}`)})]}),J===`npm`&&C(`div`,{children:[D(`div`,{className:`text-[10px] font-medium text-foreground-subtle uppercase tracking-wide mb-2`,children:`Auto-Update`}),D(Ee,{addonId:I.id})]}),I.components&&I.components.length>0&&C(`div`,{children:[C(`div`,{className:`text-[10px] font-medium text-foreground-subtle uppercase tracking-wide mb-2`,children:[`Components (`,I.components.length,`)`]}),D(`div`,{className:`flex flex-wrap gap-1`,children:I.components.map(e=>D(`span`,{className:`text-[10px] rounded bg-background border border-border px-1.5 py-0.5 text-foreground-subtle font-mono`,children:e},e))})]}),n.length>=2&&C(`div`,{children:[D(`div`,{className:`text-[10px] font-medium text-foreground-subtle uppercase tracking-wide mb-2`,children:`Installed on`}),C(`div`,{className:`flex items-center gap-1.5 flex-wrap`,children:[L.length===0&&D(`span`,{className:`text-[10px] text-foreground-subtle`,children:`Hub (local)`}),L.map(e=>D(`span`,{className:`rounded-full bg-surface-hover border border-border px-2 py-0.5 text-[10px] text-foreground-subtle`,children:e},e)),C(`div`,{className:`relative`,children:[C(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),ne(e=>!e)},className:`inline-flex items-center gap-1 rounded-full border border-dashed border-border px-2 py-0.5 text-[10px] text-foreground-subtle hover:text-foreground hover:border-primary transition-colors`,title:`Install on more agents`,children:[D(ue,{className:`h-3 w-3`}),`Add agent`]}),v&&C(`div`,{className:`absolute left-0 top-full mt-1 z-10 min-w-[160px] rounded-md border border-border bg-surface shadow-lg overflow-hidden`,children:[n.filter(e=>!e.isHub&&!L.includes(e.name)).map(e=>D(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),ne(!1)},className:`w-full text-left px-3 py-1.5 text-[10px] text-foreground hover:bg-surface-hover transition-colors`,children:e.name},e.id)),n.filter(e=>!e.isHub&&!L.includes(e.name)).length===0&&D(`div`,{className:`px-3 py-1.5 text-[10px] text-foreground-subtle`,children:`No available agents`})]})]})]})]})]}),D(`div`,{className:`flex items-center justify-end px-5 py-3 border-t border-border flex-shrink-0`,children:D(`button`,{type:`button`,onClick:()=>k(!1),className:`rounded-lg border border-border px-3 py-1.5 text-xs font-medium text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors`,children:`Close`})})]})})]})}S();var Z=`.tgz,.tar.gz,.zip`;function Q({onUploadSuccess:e}){let[t,n]=E(!1),r=x(null),i=b(async t=>{n(!0);let r=new FormData;r.append(`file`,t);let i=localStorage.getItem(`camstack_admin_token`)??``;try{(await(await fetch(`/api/addons/upload`,{method:`POST`,headers:{Authorization:`Bearer ${i}`},body:r})).json()).success&&e()}finally{n(!1)}},[e]);return C(T,{children:[D(`input`,{ref:r,type:`file`,accept:Z,onChange:b(e=>{let t=e.target.files?.[0];t&&i(t),e.target.value=``},[i]),className:`hidden`}),C(`button`,{type:`button`,onClick:()=>r.current?.click(),disabled:t,className:`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-surface hover:bg-surface-hover border border-border disabled:opacity-50 transition-colors`,children:[t?D(I,{className:`w-3.5 h-3.5 animate-spin`}):D(M,{className:`w-3.5 h-3.5`}),t?`Uploading...`:`Upload`]})]})}S();var Oe=new Set([`@camstack/system`,`@camstack/types`,`@camstack/sdk`,`@camstack/shm-ring`,`@camstack/ui-library`]);function ke(e){return e!==void 0&&Oe.has(e)}function Ae(e,t){let n=e??`hub-only`;return n===`any-node`?!0:n===`hub-only`?t:!t}function je(e){let t=e;if(!t?.manifest)return null;let n={...t.manifest,name:t.manifest.name??t.manifest.id,version:t.manifest.version??`0.0.0`,capabilities:t.manifest.capabilities??[]},r=t.process,i=r?{pid:r.pid,mode:r.mode===`forked`?`forked`:`in-process`,state:r.state??`unknown`}:void 0,a=t.health,o=t.declaration?.execution,s=t.manifest.execution,c=o?.placement??s?.placement;return{manifest:n,enabled:!0,source:t.source,installSource:t.installSource,installedOn:[],...i?{process:i}:{},hasBackup:t.hasBackup,...a?{health:a}:{},...c?{placement:c}:{}}}function Me(e){let t=new Map,n=[];for(let r of e){let e=r.manifest.packageName;if(!e){n.push(r);continue}let i=t.get(e);if(i)i.addons.push(r);else{let n=r.manifest.bundle,i={packageName:e,displayName:n?.displayName??r.manifest.packageDisplayName??r.manifest.name,version:r.manifest.packageVersion,addons:[r],...r.installSource===void 0?{}:{installSource:r.installSource},...n===void 0?{}:{bundle:n}};t.set(e,i)}}let r=[];for(let e of t.values())e.bundle!==void 0||e.addons.length>=2?r.push({bundle:e,representative:e.addons[0]}):r.push(e.addons[0]);return r.push(...n),r}function $(e){return`bundle`in e}function Ne({addons:t,clusterStatus:n,selectedNode:r,onRefresh:i,isRefreshing:s}){let c=se(),[u,ee]=E(``),f=oe(u),[m,h]=E(!1),g=x(null);O(()=>{if(!m)return;let e=e=>{g.current&&!g.current.contains(e.target)&&h(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[m]);let{data:v}=e({nodeId:r.id}),ne=w(()=>{let e=new Map;for(let t of v??[])e.set(t.name,t.latestVersion);return e},[v]),re=w(()=>(v??[]).filter(e=>!e.isSystem).length,[v]),[ie,y]=E(()=>new Set),[S,T]=E(()=>new Map),ce=b(e=>{y(t=>new Set(t).add(e))},[]),le=b(e=>{y(t=>{if(!t.has(e))return t;let n=new Set(t);return n.delete(e),n})},[]),k=b((e,t)=>{T(n=>new Map(n).set(e,t))},[]),ue=b(e=>{T(t=>{if(!t.has(e))return t;let n=new Map(t);return n.delete(e),n})},[]),M=a({onMutate:e=>{ce(e.name),ue(e.name)},onSuccess:(e,t)=>{le(t.name);let n=e;if(n&&n.success===!1){k(t.name,n.error??`Update failed (no error message returned by server)`);return}c.invalidateQueries({queryKey:[[`addons`]]}),c.invalidateQueries({queryKey:[[`addons`,`listUpdates`]]})},onError:(e,t)=>{le(t.name),k(t.name,e instanceof Error?e.message:String(e))}}),N=ve(),P=x(new Set);O(()=>{let e=new Set;for(let[t,n]of N)J(n)&&e.add(t);let t=[...P.current].some(t=>!e.has(t));P.current=e,t&&(c.invalidateQueries({queryKey:[[`addons`]]}),c.invalidateQueries({queryKey:[[`addons`,`listUpdates`]]}))},[N,c]);let{data:pe}=d(void 0),R=_({onSuccess:()=>{c.invalidateQueries({queryKey:[[`addons`,`getAutoUpdateSettings`]]})}}),{data:z,isFetching:me}=o({query:f||void 0},{enabled:m}),B=p(),{data:V}=l(void 0),H=V?.requestedAt,[he,ge]=E(null),[U,W]=E(!1);O(()=>{if(V?.kind===`framework-bulk-update`&&V.requestedAt!==he){ge(V.requestedAt),W(!0);let e=window.setTimeout(()=>W(!1),6e3);return()=>window.clearTimeout(e)}},[H,he,V]),te();let G=b(async()=>{let e=(v??[]).filter(e=>!e.isSystem);e.length!==0&&await B.mutateAsync({kind:`update`,targets:e.map(e=>({name:e.name,version:e.latestVersion})),nodeIds:[r.id]})},[v,r.id,B]),K=ae({onSuccess:()=>{c.invalidateQueries({queryKey:[[`addons`]]}),c.invalidateQueries({queryKey:[[`addonPages`]]}),c.invalidateQueries({queryKey:[[`capabilities`]]}),c.invalidateQueries({queryKey:[[`integrations`]]})}}),[q,_e]=E(`type`),[Y,ye]=E(()=>typeof window>`u`?`grouped`:window.localStorage.getItem(`camstack.addons.viewMode`)===`flat`?`flat`:`grouped`),be=e=>{ye(e),typeof window<`u`&&window.localStorage.setItem(`camstack.addons.viewMode`,e)},[xe,Se]=L(),X=xe.get(`capability`),Ce=w(()=>{let e=t.filter(e=>!ke(e.manifest.packageName));return r.isHub||(e=e.filter(e=>Ae(e.placement,r.isHub))),X&&(e=e.filter(e=>(e.manifest.capabilities??[]).some(e=>(typeof e==`string`?e:e.name)===X))),e},[t,r.isHub,X]),we=()=>{let e=new URLSearchParams(xe);e.delete(`capability`),Se(e,{replace:!0})},Te=Y===`flat`?Ce:Me(Ce),Ee=w(()=>{let e=[...Te];switch(q){case`type`:e.sort((e,t)=>{let n=+!$(e),r=+!$(t);if(n!==r)return n-r;let i=$(e)?e.bundle.displayName:e.manifest.name,a=$(t)?t.bundle.displayName:t.manifest.name;return i.localeCompare(a)});break;case`name`:e.sort((e,t)=>{let n=$(e)?e.bundle.displayName:e.manifest.name,r=$(t)?t.bundle.displayName:t.manifest.name;return n.localeCompare(r)});break;case`source`:e.sort((e,t)=>{let n=$(e)?e.bundle.installSource??``:e.installSource??``,r=$(t)?t.bundle.installSource??``:t.installSource??``;return n.localeCompare(r)});break}return e},[Te,q]);return C(`div`,{className:`space-y-4`,children:[[...S.entries()].map(([e,t])=>D(`div`,{className:`rounded-md border border-danger/30 bg-danger/5 px-3 py-2 text-[11px]`,children:C(`div`,{className:`flex items-start justify-between gap-3`,children:[C(`div`,{className:`space-y-0.5`,children:[C(`div`,{className:`font-medium text-danger`,children:[`Failed to update `,e]}),D(`div`,{className:`text-foreground-subtle font-mono`,children:t})]}),D(`button`,{type:`button`,onClick:()=>ue(e),className:`text-foreground-subtle hover:text-foreground text-[10px]`,children:`dismiss`})]})},e)),C(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[C(`div`,{className:`flex items-center gap-1.5`,children:[D(`span`,{className:`text-[10px] text-foreground-subtle`,children:`Sort:`}),C(`select`,{value:q,onChange:e=>_e(e.target.value),className:`rounded-md border border-border bg-surface px-2 py-1.5 text-[11px] text-foreground focus:outline-none focus:border-primary/50`,children:[D(`option`,{value:`type`,children:`Bundles first`}),D(`option`,{value:`name`,children:`Name`}),D(`option`,{value:`source`,children:`Install source`})]})]}),C(`div`,{className:`flex items-center gap-1.5`,children:[D(`span`,{className:`text-[10px] text-foreground-subtle`,children:`View:`}),D(`div`,{className:`flex rounded-md border border-border overflow-hidden text-[10px] font-medium`,children:[`grouped`,`flat`].map(e=>D(`button`,{type:`button`,onClick:()=>be(e),className:`px-2.5 py-1.5 transition-colors capitalize ${Y===e?`bg-primary/10 text-primary`:`bg-transparent text-foreground-subtle hover:bg-surface-hover`}`,title:e===`grouped`?`Group multi-addon packages into a bundle card`:`Show every addon as its own card`,children:e},e))})]}),r.isHub&&C(`div`,{className:`flex items-center gap-1.5`,children:[D(j,{className:`h-3 w-3 text-foreground-subtle`}),D(`span`,{className:`text-[10px] text-foreground-subtle`,children:`Auto-Update:`}),D(`div`,{className:`flex rounded-md border border-border overflow-hidden text-[10px] font-medium`,children:[`off`,`latest`,`beta`].map(e=>D(`button`,{type:`button`,onClick:()=>R.mutate({channel:e}),disabled:R.isPending,className:`px-2.5 py-1.5 transition-colors capitalize ${pe?.channel===e?e===`off`?`bg-gray-700 text-gray-200`:e===`latest`?`bg-blue-500/20 text-blue-400`:`bg-amber-500/20 text-amber-400`:`bg-transparent text-foreground-subtle hover:bg-surface-hover`} ${e===`off`?``:`border-l border-border`}`,children:e},e))})]}),re>0&&C(`button`,{type:`button`,onClick:()=>void G(),disabled:B.isPending,className:`flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] rounded-md bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 border border-blue-500/30 disabled:opacity-50 transition-colors font-medium`,children:[B.isPending?D(I,{className:`w-3 h-3 animate-spin`}):D(A,{className:`w-3 h-3`}),B.isPending?`Updating…`:`Update All (${re})`]}),C(`button`,{type:`button`,onClick:i,disabled:s||B.isPending,className:`flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] rounded-md bg-surface hover:bg-surface-hover border border-border disabled:opacity-50 transition-colors`,children:[D(j,{className:`w-3 h-3 ${s?`animate-spin`:``}`}),`Refresh`]})]}),C(`div`,{className:`flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3`,children:[C(`div`,{className:`relative flex-1 min-w-0 sm:min-w-[200px]`,ref:g,children:[D(de,{className:`absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-foreground-subtle`}),D(`input`,{type:`text`,value:u,onChange:e=>{ee(e.target.value),h(!0)},onFocus:()=>h(!0),placeholder:`Search addons on npm...`,disabled:B.isPending,className:`w-full rounded-lg border border-border bg-background pl-9 pr-3 py-2 text-xs text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed`}),me&&D(I,{className:`absolute right-2.5 top-1/2 -translate-y-1/2 h-3 w-3 text-foreground-subtle animate-spin`}),m&&z&&z.length>0&&C(`div`,{className:`absolute left-0 right-0 top-full mt-1 z-40 rounded-lg border border-border bg-surface shadow-xl overflow-hidden`,children:[z.length>0&&C(`div`,{className:`px-3 py-1.5 text-[10px] uppercase tracking-wide text-foreground-subtle border-b border-border`,children:[`npm results (`,z.length,`)`]}),z&&z.length>0&&D(`div`,{className:`max-h-72 overflow-auto divide-y divide-border`,children:z.map(e=>C(`div`,{className:`flex items-center gap-3 px-3 py-2 hover:bg-surface-hover transition-colors`,children:[D(`div`,{className:`w-6 h-6 rounded-md bg-primary/10 flex items-center justify-center text-primary text-[9px] font-bold shrink-0`,children:e.name.replace(`@camstack/addon-`,``).charAt(0).toUpperCase()}),C(`div`,{className:`flex-1 min-w-0`,children:[C(`div`,{className:`flex items-baseline gap-2 min-w-0`,children:[D(`span`,{className:`text-[11px] font-semibold break-words`,children:e.name.replace(/^@camstack\/(addon-)?/,``)}),C(`span`,{className:`text-[10px] text-foreground-subtle shrink-0`,children:[`v`,e.version]})]}),D(`div`,{className:`font-mono text-[9px] text-foreground-subtle/80 truncate`,children:e.name}),e.description&&D(`div`,{className:`text-[10px] text-foreground-subtle truncate`,children:e.description})]}),e.installed?C(`span`,{className:`inline-flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded-md border border-emerald-500/30 bg-emerald-500/10 text-emerald-400 shrink-0`,children:[D(F,{className:`h-3 w-3`}),e.installedVersion??`Installed`]}):C(`button`,{type:`button`,onClick:()=>K.mutate({packageName:e.name}),disabled:K.isPending,className:`inline-flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors shrink-0`,children:[K.isPending?D(I,{className:`h-3 w-3 animate-spin`}):D(A,{className:`h-3 w-3`}),`Install`]})]},e.name))})]})]}),D(Q,{onUploadSuccess:()=>{c.invalidateQueries({queryKey:[[`addons`]]}),i()}})]}),X&&C(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-lg bg-primary/10 border border-primary/30 text-sm`,children:[D(`span`,{className:`text-foreground-subtle`,children:`Filtering by capability:`}),D(`span`,{className:`font-mono text-primary font-semibold`,children:X}),D(`button`,{type:`button`,onClick:we,className:`ml-auto text-xs text-foreground-subtle hover:text-foreground underline`,children:`Clear`})]}),U&&C(`div`,{className:`fixed top-[calc(env(safe-area-inset-top)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)] z-50 flex items-start gap-2 px-3 py-2.5 rounded-md border border-green-500/30 bg-green-500/10 shadow-lg backdrop-blur-sm max-w-sm pointer-events-auto`,children:[D(fe,{className:`w-4 h-4 shrink-0 mt-0.5 text-green-400`}),C(`div`,{children:[D(`div`,{className:`text-[11px] font-semibold text-foreground`,children:`Bulk update completed`}),D(`div`,{className:`text-[10px] text-foreground-subtle mt-0.5`,children:`System packages updated — hub restarted successfully.`})]})]}),Ce.length===0&&D(`div`,{className:`text-xs text-foreground-subtle`,children:t.length===0?`No addons installed`:X?`No installed addons declare the "${X}" capability. Browse the npm catalog above to find a compatible addon.`:`No addons compatible with ${r.name} (placement filter active)`}),D(`div`,{className:`space-y-2`,children:Ee.map(e=>{let t=$(e)?e.bundle.packageName:e.manifest.packageName,i=ne.get(t),a=$(e)?e.representative:e,o=i?e=>M.mutate({name:t,version:e,nodeId:r.id}):void 0,s=N.get(t)??null,c=s!==null&&J(s);return D(De,{addon:a,clusterStatus:n?.[a.manifest.id],bundle:$(e)?e.bundle:void 0,availableUpdate:i,isUpdating:ie.has(t)||c,jobProgress:s,onUpdate:o,disabledByBulk:B.isPending},$(e)?t:e.manifest.id)})})]})}function Pe(){let e=se(),{data:t,isLoading:i,isError:a}=u(),{data:o}=r(void 0),{data:s}=n(void 0,{refetchInterval:1e4}),c=w(()=>{let e=new Map;e.set(`hub`,{id:`hub`,name:`Hub`,isHub:!0});for(let t of s??[]){let n=String(t.id??``);n&&e.set(n,{id:n,name:String(t.name??n),isHub:!!t.isHub})}return[...e.values()]},[s]),[l,ee]=E(`hub`),d=c.find(e=>e.id===l)??c[0],f=g(),te=y(),p=f.isPending||te.isPending,m=async()=>{await f.mutateAsync({nodeId:l}),await te.mutateAsync(),e.invalidateQueries({queryKey:[[`addons`]]})},h=x(f);h.current=f,O(()=>{h.current.mutate({nodeId:`hub`})},[]);let _=(t??[]).map(je).filter(e=>e!==null);return C(H,{children:[C(`div`,{className:`flex items-center justify-between gap-3 flex-wrap`,children:[D(v,{selectedNodeId:l,onSelect:ee}),C(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[!i&&!a&&C(`span`,{className:`text-[10px] rounded-full bg-primary/10 text-primary px-2 py-0.5 font-medium`,children:[_.length,` installed`]}),C(`button`,{type:`button`,onClick:()=>void m(),disabled:p,className:`inline-flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] rounded-md bg-surface hover:bg-surface-hover border border-border disabled:opacity-50 transition-colors`,title:`Re-fetch addons + check npm for new versions`,children:[D(j,{className:`w-3 h-3 ${p?`animate-spin`:``}`}),`Check for updates`]})]})]}),!d.isHub&&C(`div`,{className:`rounded-md border border-warning/30 bg-warning/5 px-3 py-2 text-[11px] text-foreground-subtle`,children:[`Showing only addons compatible with`,` `,D(`span`,{className:`font-medium text-foreground`,children:d.name}),` — agents only run`,` `,D(`code`,{className:`font-mono`,children:`agent-only`}),` and`,` `,D(`code`,{className:`font-mono`,children:`any-node`}),` addons. Install / uninstall is hub-driven; agents auto-sync from the hub.`]}),i&&D(`div`,{className:`text-xs text-foreground-subtle animate-pulse`,children:`Loading…`}),a&&D(`div`,{className:`text-xs text-danger`,children:`Failed to load addons`}),!i&&!a&&D(Ne,{addons:_,clusterStatus:o,selectedNode:d,onRefresh:()=>void m(),isRefreshing:p})]})}export{Pe as AddonsPage};