@goodandready/dsh-subscriptions 0.7.34 → 0.7.35

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.
package/lib/client.js CHANGED
@@ -1018,6 +1018,17 @@ window.__ModuleLoader__.load({
1018
1018
  }
1019
1019
  const Chevron = ChevronIcon || FallbackChevron
1020
1020
 
1021
+ function lazyPanel(name) {
1022
+ const Component = React.lazy(() => require.async('./client.panels.js').then((mod) => ({ default: mod[name] })))
1023
+ return function LazyPanel(props) {
1024
+ return h(React.Suspense, { fallback: null }, h(Component, props))
1025
+ }
1026
+ }
1027
+ const WebhookTestButton = lazyPanel('WebhookTestButton')
1028
+ const VaultSnapshotsPanel = lazyPanel('VaultSnapshotsPanel')
1029
+ const ProviderPacksPanel = lazyPanel('ProviderPacksPanel')
1030
+ const SourcesPanel = lazyPanel('SourcesPanel')
1031
+
1021
1032
  // #839 model-list helpers
1022
1033
  function modelListSearchText(model) {
1023
1034
  const parts = [model && model.id, model && model.name, model && model.alias, model && model.provider, model && model.providerName]
@@ -4260,181 +4271,6 @@ h('div', { className: 'dsub-row' },
4260
4271
  )
4261
4272
  }
4262
4273
 
4263
- function WebhookTestButton(props) {
4264
- const t = (props && props.t) || ((k) => k)
4265
- const [state, setState] = React.useState({ busy: false, message: '', error: null })
4266
- const send = () => {
4267
- setState({ busy: true, message: '', error: null })
4268
- fetch('/dsh-subscriptions/webhook/test', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
4269
- .then(async (res) => {
4270
- const data = await res.json().catch(() => ({}))
4271
- if (!res.ok || !data.delivered) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
4272
- setState({ busy: false, message: t('delivered'), error: null })
4273
- })
4274
- .catch((err) => setState({ busy: false, message: '', error: String(err && err.message || err) }))
4275
- }
4276
- return h('div', { className: 'dsub-row' },
4277
- h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: send }, state.busy ? '…' : t('sendTest')),
4278
- state.message ? h('span', { className: 'dsub-ok' }, state.message) : null,
4279
- state.error ? h('span', { className: 'dsub-bad' }, state.error) : null,
4280
- )
4281
- }
4282
-
4283
- // #736: vault snapshot list with create/restore. Payload stays server-side.
4284
- function VaultSnapshotsPanel(props) {
4285
- const t = (props && props.t) || ((k) => k)
4286
- const [state, setState] = React.useState({ snapshots: [], stats: null, message: '', error: null, busy: false })
4287
- const pull = () => {
4288
- fetch('/dsh-subscriptions/vault/snapshots', { cache: 'no-store' })
4289
- .then((res) => res.json())
4290
- .then((data) => setState((st) => Object.assign({}, st, { snapshots: data.snapshots || [], stats: data.stats || null, error: null })))
4291
- .catch((err) => setState((st) => Object.assign({}, st, { error: String(err && err.message || err) })))
4292
- }
4293
- React.useEffect(() => { pull() }, [])
4294
- const call = (url, body) => fetch(url, {
4295
- method: 'POST',
4296
- headers: { 'content-type': 'application/json' },
4297
- body: JSON.stringify(body || {}),
4298
- }).then(async (res) => {
4299
- const data = await res.json().catch(() => ({}))
4300
- if (!res.ok || !data.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
4301
- return data
4302
- })
4303
- const create = () => {
4304
- setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
4305
- call('/dsh-subscriptions/vault/snapshots', { label: 'manual' })
4306
- .then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: t('created') + ' ' + data.snapshot.id })); pull() })
4307
- .catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
4308
- }
4309
- const restore = (id) => {
4310
- setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
4311
- call('/dsh-subscriptions/vault/snapshots/restore', { id })
4312
- .then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: t('restoredSlots') + ' ' + data.slotCount })); pull() })
4313
- .catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
4314
- }
4315
- return h('div', { className: 'dsub-section-card' },
4316
- h('div', { className: 'dsub-section-title' }, t('vaultSnapshots'), h('button', { type: 'button', className: 'dsub-mini', onClick: create, disabled: state.busy }, state.busy ? '…' : t('createSnapshot'))),
4317
- h('div', { className: 'dsub-section-desc' }, t('vaultSnapshotsHint')),
4318
- state.stats ? h('div', { className: 'dsub-dim' }, state.stats.count + ' snapshots · ' + state.stats.bytes + ' bytes · ' + state.stats.path) : null,
4319
- state.snapshots.length ? state.snapshots.map((snap) => h('div', { className: 'dsub-row', key: snap.id },
4320
- h('span', { className: 'dsub-h' }, snap.id),
4321
- h('span', { className: 'dsub-dim' }, (snap.label || '-') + ' · ' + snap.slotCount + ' slots · ' + snap.createdAt),
4322
- h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: () => restore(snap.id) }, t('restoreSnapshot')),
4323
- )) : h('div', { className: 'dsub-dim' }, t('noSnapshots')),
4324
- state.message ? h('div', { className: 'dsub-ok' }, state.message) : null,
4325
- state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
4326
- )
4327
- }
4328
-
4329
- // #724: provider pack manager. Displays ids/models only; headers are masked.
4330
- function ProviderPacksPanel(props) {
4331
- const t = (props && props.t) || ((k) => k)
4332
- const [state, setState] = React.useState({ packs: [], text: '', message: '', error: null, busy: false })
4333
- const publicPack = (pack) => {
4334
- const copy = Object.assign({}, pack)
4335
- if (copy.clientSecret) copy.clientSecret = '••••'
4336
- if (copy.headers && typeof copy.headers === 'object') {
4337
- copy.headers = Object.fromEntries(Object.keys(copy.headers).map((key) => [key, '••••']))
4338
- }
4339
- return copy
4340
- }
4341
- const pull = () => {
4342
- fetch('/dsh-subscriptions/config', { cache: 'no-store' })
4343
- .then((res) => res.json())
4344
- .then((cfg) => setState((st) => Object.assign({}, st, { packs: (cfg.config && cfg.config.providerPacks) || [] })))
4345
- .catch((err) => setState((st) => Object.assign({}, st, { error: String(err && err.message || err) })))
4346
- }
4347
- React.useEffect(() => { pull() }, [])
4348
- const parse = () => {
4349
- const raw = JSON.parse(state.text || '{}')
4350
- return raw && raw.pack ? raw.pack : raw
4351
- }
4352
- const validate = (pack) => fetch('/dsh-subscriptions/provider-packs/validate', {
4353
- method: 'POST',
4354
- headers: { 'content-type': 'application/json' },
4355
- body: JSON.stringify({ pack }),
4356
- }).then(async (res) => {
4357
- const data = await res.json()
4358
- if (!res.ok || !data.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
4359
- return data.pack
4360
- })
4361
- const save = () => {
4362
- setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
4363
- Promise.resolve().then(parse)
4364
- .then(async (pack) => {
4365
- const checked = await validate(pack)
4366
- const cfg = await fetch('/dsh-subscriptions/config', { cache: 'no-store' }).then((res) => res.json())
4367
- const packs = ((cfg.config && cfg.config.providerPacks) || []).filter((p) => p && p.id !== pack.id)
4368
- packs.push(pack)
4369
- const res = await fetch('/dsh-subscriptions/config', {
4370
- method: 'PUT',
4371
- headers: { 'content-type': 'application/json' },
4372
- body: JSON.stringify({ config: Object.assign({}, cfg.config, { providerPacks: packs }), revision: cfg.revision }),
4373
- })
4374
- if (!res.ok) throw new Error('HTTP ' + res.status)
4375
- setState((st) => Object.assign({}, st, { packs, busy: false, message: t('saved') + ' ' + checked.id, text: '' }))
4376
- })
4377
- .catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
4378
- }
4379
- const exportPacks = () => setState((st) => Object.assign({}, st, { text: JSON.stringify(st.packs.map(publicPack), null, 2), message: t('exported') }))
4380
- return h('div', { className: 'dsub-section-card' },
4381
- h('div', { className: 'dsub-section-title' }, t('providerPacks'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, '↻')),
4382
- h('div', { className: 'dsub-section-desc' }, t('providerPacksHint')),
4383
- state.packs.length ? state.packs.map((pack) => h('div', { className: 'dsub-row', key: pack.id },
4384
- h('span', { className: 'dsub-h' }, pack.id),
4385
- h('span', { className: 'dsub-dim' }, (pack.displayName || '') + ' · ' + ((pack.models || []).length) + ' models'),
4386
- )) : h('div', { className: 'dsub-dim' }, t('noPacks')),
4387
- h('textarea', {
4388
- className: 'dsub-inp',
4389
- style: { width: '100%', minHeight: 120, fontFamily: 'ui-monospace,monospace', padding: 8 },
4390
- value: state.text,
4391
- placeholder: '{"id":"acme","displayName":"Acme AI","authUrl":"https://…","tokenUrl":"https://…","baseUrl":"https://…","clientId":"…","models":[{"id":"acme-1"}]}',
4392
- onChange: (e) => setState((st) => Object.assign({}, st, { text: e.target.value, message: '', error: null })),
4393
- }),
4394
- h('div', { className: 'dsub-row' },
4395
- h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: save }, state.busy ? '…' : t('validateAndSave')),
4396
- h('button', { type: 'button', className: 'dsub-mini', onClick: exportPacks }, t('exportPacks')),
4397
- ),
4398
- state.message ? h('div', { className: 'dsub-ok' }, state.message) : null,
4399
- state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
4400
- )
4401
- }
4402
-
4403
- // #713: read-only source chain panel. Never renders key values.
4404
- function SourcesPanel(props) {
4405
- const t = (props && props.t) || ((k) => k)
4406
- const [state, setState] = React.useState({ loading: true, data: null, error: null })
4407
- const pull = () => {
4408
- fetch('/dsh-subscriptions/sources', { cache: 'no-store' })
4409
- .then((res) => res.json())
4410
- .then((data) => setState({ loading: false, data, error: null }))
4411
- .catch((err) => setState({ loading: false, data: null, error: String(err && err.message || err) }))
4412
- }
4413
- React.useEffect(() => {
4414
- pull()
4415
- const id = setInterval(pull, 30 * 1000)
4416
- return () => clearInterval(id)
4417
- }, [])
4418
- const sourceLabel = (source) => source === 'api-key' ? t('apiKeySource') : (source === 'oauth' ? t('oauthSource') : (source === 'ollama' ? t('ollamaSource') : t('sourceNone')))
4419
- return h('div', { className: 'dsub-section-card' },
4420
- h('div', { className: 'dsub-section-title' }, t('sources'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, state.loading ? '…' : '↻')),
4421
- h('div', { className: 'dsub-section-desc' }, t('sourcesHint')),
4422
- state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
4423
- state.data && state.data.keyRotation && !state.data.keyRotation.available
4424
- ? h('div', { className: 'dsub-dim' }, t('keyRotationOffline') + (state.data.keyRotation.reason ? ': ' + state.data.keyRotation.reason : ''))
4425
- : null,
4426
- state.data && state.data.keyRotation && state.data.keyRotation.available
4427
- ? h('div', { className: 'dsub-dim' }, t('keyRotationHealth') + ': ' + state.data.keyRotation.healthyKeys + '/' + state.data.keyRotation.totalKeys + ' healthy keys')
4428
- : null,
4429
- (state.data && state.data.providers ? state.data.providers : []).map((row) => h('div', { className: 'dsub-row', key: row.provider },
4430
- h('span', { className: 'dsub-h' }, row.provider),
4431
- h('span', { className: 'dsub-pillTag' }, sourceLabel(row.selected)),
4432
- row.candidates.map((c, i) => h('span', { key: i, className: 'dsub-dim' },
4433
- sourceLabel(c.source) + (c.available ? ' ✓' : ' ✕'))),
4434
- )),
4435
- )
4436
- }
4437
-
4438
4274
  // #688: floating/docked quota capsule. Draggable unless docked; opens settings.
4439
4275
  function QuotaCapsule(props) {
4440
4276
  const t = (props && props.t) || ((k) => k)
@@ -0,0 +1,187 @@
1
+ /* global window */
2
+ window.__ModuleLoader__.load({
3
+ id: '@goodandready/dsh-subscriptions',
4
+ chunk: 'client.panels.js',
5
+ factory: (require) => {
6
+ const React = require('react')
7
+ const h = React.createElement
8
+
9
+ function WebhookTestButton(props) {
10
+ const t = (props && props.t) || ((k) => k)
11
+ const [state, setState] = React.useState({ busy: false, message: '', error: null })
12
+ const send = () => {
13
+ setState({ busy: true, message: '', error: null })
14
+ fetch('/dsh-subscriptions/webhook/test', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
15
+ .then(async (res) => {
16
+ const data = await res.json().catch(() => ({}))
17
+ if (!res.ok || !data.delivered) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
18
+ setState({ busy: false, message: t('delivered'), error: null })
19
+ })
20
+ .catch((err) => setState({ busy: false, message: '', error: String(err && err.message || err) }))
21
+ }
22
+ return h('div', { className: 'dsub-row' },
23
+ h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: send }, state.busy ? '…' : t('sendTest')),
24
+ state.message ? h('span', { className: 'dsub-ok' }, state.message) : null,
25
+ state.error ? h('span', { className: 'dsub-bad' }, state.error) : null,
26
+ )
27
+ }
28
+
29
+ // #736: vault snapshot list with create/restore. Payload stays server-side.
30
+ function VaultSnapshotsPanel(props) {
31
+ const t = (props && props.t) || ((k) => k)
32
+ const [state, setState] = React.useState({ snapshots: [], stats: null, message: '', error: null, busy: false })
33
+ const pull = () => {
34
+ fetch('/dsh-subscriptions/vault/snapshots', { cache: 'no-store' })
35
+ .then((res) => res.json())
36
+ .then((data) => setState((st) => Object.assign({}, st, { snapshots: data.snapshots || [], stats: data.stats || null, error: null })))
37
+ .catch((err) => setState((st) => Object.assign({}, st, { error: String(err && err.message || err) })))
38
+ }
39
+ React.useEffect(() => { pull() }, [])
40
+ const call = (url, body) => fetch(url, {
41
+ method: 'POST',
42
+ headers: { 'content-type': 'application/json' },
43
+ body: JSON.stringify(body || {}),
44
+ }).then(async (res) => {
45
+ const data = await res.json().catch(() => ({}))
46
+ if (!res.ok || !data.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
47
+ return data
48
+ })
49
+ const create = () => {
50
+ setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
51
+ call('/dsh-subscriptions/vault/snapshots', { label: 'manual' })
52
+ .then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: t('created') + ' ' + data.snapshot.id })); pull() })
53
+ .catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
54
+ }
55
+ const restore = (id) => {
56
+ setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
57
+ call('/dsh-subscriptions/vault/snapshots/restore', { id })
58
+ .then((data) => { setState((st) => Object.assign({}, st, { busy: false, message: t('restoredSlots') + ' ' + data.slotCount })); pull() })
59
+ .catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
60
+ }
61
+ return h('div', { className: 'dsub-section-card' },
62
+ h('div', { className: 'dsub-section-title' }, t('vaultSnapshots'), h('button', { type: 'button', className: 'dsub-mini', onClick: create, disabled: state.busy }, state.busy ? '…' : t('createSnapshot'))),
63
+ h('div', { className: 'dsub-section-desc' }, t('vaultSnapshotsHint')),
64
+ state.stats ? h('div', { className: 'dsub-dim' }, state.stats.count + ' snapshots · ' + state.stats.bytes + ' bytes · ' + state.stats.path) : null,
65
+ state.snapshots.length ? state.snapshots.map((snap) => h('div', { className: 'dsub-row', key: snap.id },
66
+ h('span', { className: 'dsub-h' }, snap.id),
67
+ h('span', { className: 'dsub-dim' }, (snap.label || '-') + ' · ' + snap.slotCount + ' slots · ' + snap.createdAt),
68
+ h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: () => restore(snap.id) }, t('restoreSnapshot')),
69
+ )) : h('div', { className: 'dsub-dim' }, t('noSnapshots')),
70
+ state.message ? h('div', { className: 'dsub-ok' }, state.message) : null,
71
+ state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
72
+ )
73
+ }
74
+
75
+ // #724: provider pack manager. Displays ids/models only; headers are masked.
76
+ function ProviderPacksPanel(props) {
77
+ const t = (props && props.t) || ((k) => k)
78
+ const [state, setState] = React.useState({ packs: [], text: '', message: '', error: null, busy: false })
79
+ const publicPack = (pack) => {
80
+ const copy = Object.assign({}, pack)
81
+ if (copy.clientSecret) copy.clientSecret = '••••'
82
+ if (copy.headers && typeof copy.headers === 'object') {
83
+ copy.headers = Object.fromEntries(Object.keys(copy.headers).map((key) => [key, '••••']))
84
+ }
85
+ return copy
86
+ }
87
+ const pull = () => {
88
+ fetch('/dsh-subscriptions/config', { cache: 'no-store' })
89
+ .then((res) => res.json())
90
+ .then((cfg) => setState((st) => Object.assign({}, st, { packs: (cfg.config && cfg.config.providerPacks) || [] })))
91
+ .catch((err) => setState((st) => Object.assign({}, st, { error: String(err && err.message || err) })))
92
+ }
93
+ React.useEffect(() => { pull() }, [])
94
+ const parse = () => {
95
+ const raw = JSON.parse(state.text || '{}')
96
+ return raw && raw.pack ? raw.pack : raw
97
+ }
98
+ const validate = (pack) => fetch('/dsh-subscriptions/provider-packs/validate', {
99
+ method: 'POST',
100
+ headers: { 'content-type': 'application/json' },
101
+ body: JSON.stringify({ pack }),
102
+ }).then(async (res) => {
103
+ const data = await res.json()
104
+ if (!res.ok || !data.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
105
+ return data.pack
106
+ })
107
+ const save = () => {
108
+ setState((st) => Object.assign({}, st, { busy: true, message: '', error: null }))
109
+ Promise.resolve().then(parse)
110
+ .then(async (pack) => {
111
+ const checked = await validate(pack)
112
+ const cfg = await fetch('/dsh-subscriptions/config', { cache: 'no-store' }).then((res) => res.json())
113
+ const packs = ((cfg.config && cfg.config.providerPacks) || []).filter((p) => p && p.id !== pack.id)
114
+ packs.push(pack)
115
+ const res = await fetch('/dsh-subscriptions/config', {
116
+ method: 'PUT',
117
+ headers: { 'content-type': 'application/json' },
118
+ body: JSON.stringify({ config: Object.assign({}, cfg.config, { providerPacks: packs }), revision: cfg.revision }),
119
+ })
120
+ if (!res.ok) throw new Error('HTTP ' + res.status)
121
+ setState((st) => Object.assign({}, st, { packs, busy: false, message: t('saved') + ' ' + checked.id, text: '' }))
122
+ })
123
+ .catch((err) => setState((st) => Object.assign({}, st, { busy: false, error: String(err && err.message || err) })))
124
+ }
125
+ const exportPacks = () => setState((st) => Object.assign({}, st, { text: JSON.stringify(st.packs.map(publicPack), null, 2), message: t('exported') }))
126
+ return h('div', { className: 'dsub-section-card' },
127
+ h('div', { className: 'dsub-section-title' }, t('providerPacks'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, '↻')),
128
+ h('div', { className: 'dsub-section-desc' }, t('providerPacksHint')),
129
+ state.packs.length ? state.packs.map((pack) => h('div', { className: 'dsub-row', key: pack.id },
130
+ h('span', { className: 'dsub-h' }, pack.id),
131
+ h('span', { className: 'dsub-dim' }, (pack.displayName || '') + ' · ' + ((pack.models || []).length) + ' models'),
132
+ )) : h('div', { className: 'dsub-dim' }, t('noPacks')),
133
+ h('textarea', {
134
+ className: 'dsub-inp',
135
+ style: { width: '100%', minHeight: 120, fontFamily: 'ui-monospace,monospace', padding: 8 },
136
+ value: state.text,
137
+ placeholder: '{"id":"acme","displayName":"Acme AI","authUrl":"https://…","tokenUrl":"https://…","baseUrl":"https://…","clientId":"…","models":[{"id":"acme-1"}]}',
138
+ onChange: (e) => setState((st) => Object.assign({}, st, { text: e.target.value, message: '', error: null })),
139
+ }),
140
+ h('div', { className: 'dsub-row' },
141
+ h('button', { type: 'button', className: 'dsub-mini', disabled: state.busy, onClick: save }, state.busy ? '…' : t('validateAndSave')),
142
+ h('button', { type: 'button', className: 'dsub-mini', onClick: exportPacks }, t('exportPacks')),
143
+ ),
144
+ state.message ? h('div', { className: 'dsub-ok' }, state.message) : null,
145
+ state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
146
+ )
147
+ }
148
+
149
+ // #713: read-only source chain panel. Never renders key values.
150
+ function SourcesPanel(props) {
151
+ const t = (props && props.t) || ((k) => k)
152
+ const [state, setState] = React.useState({ loading: true, data: null, error: null })
153
+ const pull = () => {
154
+ fetch('/dsh-subscriptions/sources', { cache: 'no-store' })
155
+ .then((res) => res.json())
156
+ .then((data) => setState({ loading: false, data, error: null }))
157
+ .catch((err) => setState({ loading: false, data: null, error: String(err && err.message || err) }))
158
+ }
159
+ React.useEffect(() => {
160
+ pull()
161
+ const id = setInterval(pull, 30 * 1000)
162
+ return () => clearInterval(id)
163
+ }, [])
164
+ const sourceLabel = (source) => source === 'api-key' ? t('apiKeySource') : (source === 'oauth' ? t('oauthSource') : (source === 'ollama' ? t('ollamaSource') : t('sourceNone')))
165
+ return h('div', { className: 'dsub-section-card' },
166
+ h('div', { className: 'dsub-section-title' }, t('sources'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, state.loading ? '…' : '↻')),
167
+ h('div', { className: 'dsub-section-desc' }, t('sourcesHint')),
168
+ state.error ? h('div', { className: 'dsub-bad' }, state.error) : null,
169
+ state.data && state.data.keyRotation && !state.data.keyRotation.available
170
+ ? h('div', { className: 'dsub-dim' }, t('keyRotationOffline') + (state.data.keyRotation.reason ? ': ' + state.data.keyRotation.reason : ''))
171
+ : null,
172
+ state.data && state.data.keyRotation && state.data.keyRotation.available
173
+ ? h('div', { className: 'dsub-dim' }, t('keyRotationHealth') + ': ' + state.data.keyRotation.healthyKeys + '/' + state.data.keyRotation.totalKeys + ' healthy keys')
174
+ : null,
175
+ (state.data && state.data.providers ? state.data.providers : []).map((row) => h('div', { className: 'dsub-row', key: row.provider },
176
+ h('span', { className: 'dsub-h' }, row.provider),
177
+ h('span', { className: 'dsub-pillTag' }, sourceLabel(row.selected)),
178
+ row.candidates.map((c, i) => h('span', { key: i, className: 'dsub-dim' },
179
+ sourceLabel(c.source) + (c.available ? ' ✓' : ' ✕'))),
180
+ )),
181
+ )
182
+ }
183
+
184
+
185
+ return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel }
186
+ }
187
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.7.34",
3
+ "version": "0.7.35",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",