@linxin666/dsh-usage 0.3.23 → 0.4.0
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/README.i18n.yaml +2 -2
- package/README.md +7 -9
- package/README.zh.md +7 -9
- package/icon.svg +3 -0
- package/lib/client.js +621 -65
- package/lib/client.js.map +1 -1
- package/lib/index.js +258 -271
- package/lib/types/client/UsageFootCard.d.ts +55 -0
- package/lib/types/client/UsageSectionCard.d.ts +3 -4
- package/lib/types/client/body-mutations.d.ts +45 -0
- package/lib/types/client/foot-card-mount.d.ts +34 -0
- package/lib/types/client/index.d.ts +21 -8
- package/lib/types/client/locales.d.ts +6 -4
- package/lib/types/core/provider-routes.d.ts +26 -0
- package/lib/types/core/types.d.ts +14 -1
- package/lib/types/host/access.d.ts +17 -0
- package/lib/types/host/http.d.ts +18 -1
- package/lib/types/host/pair-access.d.ts +34 -0
- package/lib/types/host/routes.d.ts +16 -7
- package/lib/types/host/usage-service.d.ts +32 -76
- package/lib/types/index.d.ts +14 -2
- package/package.json +20 -17
- package/src/client/UsageFootCard.tsx +300 -0
- package/src/client/UsageSectionCard.tsx +41 -23
- package/src/client/body-mutations.ts +133 -0
- package/src/client/foot-card-mount.tsx +112 -0
- package/src/client/index.ts +57 -16
- package/src/client/locales.ts +12 -8
- package/src/client/usage.module.css +188 -0
- package/src/core/adapters.ts +40 -6
- package/src/core/provider-routes.ts +37 -0
- package/src/core/types.ts +14 -1
- package/src/host/access.ts +21 -0
- package/src/host/http.ts +23 -1
- package/src/host/pair-access.ts +49 -0
- package/src/host/routes.ts +19 -10
- package/src/host/usage-service.ts +74 -218
- package/src/index.ts +59 -60
package/src/index.ts
CHANGED
|
@@ -8,14 +8,18 @@ import { makeUsageOverviewRoute, makeUsageRefreshRoute } from './host/routes.ts'
|
|
|
8
8
|
|
|
9
9
|
export const name = 'dsh-usage'
|
|
10
10
|
export const inject = ['webServer']
|
|
11
|
+
/**
|
|
12
|
+
* Family settings namespace this row serves. Since 0.1.7 the settings surface
|
|
13
|
+
* addresses one form per profile entry id and the Host generates this row's
|
|
14
|
+
* page from the Config schema below, so the namespace is only the alias the
|
|
15
|
+
* family settings bridge (dsh-web-settings) resolves onto this row's entry id.
|
|
16
|
+
*/
|
|
11
17
|
export const USAGE_SETTINGS_NAMESPACE = 'dsh-usage' as SettingsNamespace
|
|
12
18
|
|
|
13
19
|
export interface Config {
|
|
14
20
|
enabled?: boolean
|
|
15
21
|
/** Provider probe cycle in seconds; 30-3600. */
|
|
16
22
|
pollIntervalSec?: number
|
|
17
|
-
/** Pet bubble mode: always (refreshes each poll), change (only on value change), off. */
|
|
18
|
-
bubbleMode?: string
|
|
19
23
|
/** Ledger retention in local days. */
|
|
20
24
|
retainDays?: number
|
|
21
25
|
}
|
|
@@ -23,7 +27,6 @@ export interface Config {
|
|
|
23
27
|
export const Config: z<Config> = z.object({
|
|
24
28
|
enabled: z.boolean().default(true),
|
|
25
29
|
pollIntervalSec: z.number().min(30).max(3600).default(60),
|
|
26
|
-
bubbleMode: z.string().default('always'),
|
|
27
30
|
retainDays: z.number().min(7).max(730).default(180),
|
|
28
31
|
})
|
|
29
32
|
|
|
@@ -32,83 +35,79 @@ export interface ResolvedConfig extends UsageServiceOptions {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function resolveConfig(config?: Config): ResolvedConfig {
|
|
35
|
-
const bubbleMode = config?.bubbleMode === 'change' || config?.bubbleMode === 'off' ? config.bubbleMode : 'always'
|
|
36
38
|
return {
|
|
37
39
|
enabled: config?.enabled ?? true,
|
|
38
40
|
pollIntervalSec: typeof config?.pollIntervalSec === 'number' ? config.pollIntervalSec : 60,
|
|
39
|
-
bubbleMode,
|
|
40
41
|
retainDays: typeof config?.retainDays === 'number' ? config.retainDays : 180,
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Tail of the process-wide flush chain; undefined while no predecessor's final
|
|
47
|
+
* ledger flush is still in flight. The Host reloads this profile row when its
|
|
48
|
+
* configuration changes, so a successor activation is a dispose + apply pair in
|
|
49
|
+
* the same process: its first ledger load must serialize behind its
|
|
50
|
+
* predecessor's final write (and behind the reloads before that one).
|
|
51
|
+
*/
|
|
52
|
+
let pendingStop: Promise<unknown> | undefined
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Host body. The plugin's own Config schema above is the settings document the
|
|
56
|
+
* 0.1.7 Host serves for this row; the effective config arrives here as the
|
|
57
|
+
* `config` argument the Loader resolved and the Host reloads the row when the
|
|
58
|
+
* user saves a change, so nothing in this body observes or writes settings.
|
|
59
|
+
* @param ctx - host root context.
|
|
60
|
+
* @param config - the row's resolved configuration.
|
|
61
|
+
*/
|
|
44
62
|
export const apply = mountOnce('@linxin666/dsh-usage', (ctx: Context, config?: Config): void => {
|
|
45
|
-
|
|
63
|
+
const value = resolveConfig(config)
|
|
64
|
+
// A disabled row mounts nothing: no probe cycle, no ledger, no routes. The
|
|
65
|
+
// settings page owns the enable switch, and re-enabling reloads this row.
|
|
66
|
+
if (!value.enabled) return
|
|
67
|
+
|
|
68
|
+
let live = true
|
|
46
69
|
let service: UsageService | undefined
|
|
47
|
-
let pendingStop: Promise<void> | undefined
|
|
48
70
|
let disposeRoutes: (() => void) | undefined
|
|
49
71
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (service !== next) return
|
|
66
|
-
next.start()
|
|
67
|
-
const disposers = [makeUsageOverviewRoute(next), makeUsageRefreshRoute(next)]
|
|
68
|
-
.map((route) => ctx.webServer.register(route))
|
|
69
|
-
disposeRoutes = () => {
|
|
70
|
-
for (const dispose of disposers) {
|
|
71
|
-
try {
|
|
72
|
-
dispose()
|
|
73
|
-
} catch {
|
|
74
|
-
// Route fiber already gone during shutdown.
|
|
75
|
-
}
|
|
76
|
-
}
|
|
72
|
+
const begin = (): void => {
|
|
73
|
+
// The fiber may have gone down while the predecessor's final flush was
|
|
74
|
+
// still pending; only start while this activation is the live one.
|
|
75
|
+
if (!live) return
|
|
76
|
+
const next = new UsageService(ctx, value)
|
|
77
|
+
service = next
|
|
78
|
+
next.start()
|
|
79
|
+
const disposers = [makeUsageOverviewRoute(ctx, next), makeUsageRefreshRoute(ctx, next)]
|
|
80
|
+
.map((route) => ctx.webServer.register(route))
|
|
81
|
+
disposeRoutes = () => {
|
|
82
|
+
for (const dispose of disposers) {
|
|
83
|
+
try {
|
|
84
|
+
dispose()
|
|
85
|
+
} catch {
|
|
86
|
+
// Route fiber already gone during shutdown.
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
|
-
// Serialize the start behind a just-stopped predecessor: its final
|
|
80
|
-
// ledger flush must land on disk before this instance loads the file.
|
|
81
|
-
if (pendingStop !== undefined) pendingStop.then(begin, begin)
|
|
82
|
-
else begin()
|
|
83
|
-
} else {
|
|
84
|
-
service.applyOptions(value)
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
ctx.inject(['settings'], (settingsCtx) => {
|
|
89
|
-
try {
|
|
90
|
-
if (typeof settingsCtx.settings?.installSection === 'function') {
|
|
91
|
-
settingsCtx.settings.installSection(ctx, USAGE_SETTINGS_NAMESPACE, Config, config ?? {}, {
|
|
92
|
-
setSource: (next) => { source = next; rearm() },
|
|
93
|
-
onChange: rearm,
|
|
94
|
-
})
|
|
95
|
-
} else if (typeof settingsCtx.settings?.register === 'function') {
|
|
96
|
-
const scope = settingsCtx.settings.register(USAGE_SETTINGS_NAMESPACE, Config, { base: config ?? {} })
|
|
97
|
-
source = () => scope?.get?.() ?? (config ?? {})
|
|
98
|
-
scope?.watch?.(() => { rearm() })
|
|
99
|
-
rearm()
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
// Defensive fallback against settings registration differences
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
92
|
ctx.effect(() => {
|
|
107
|
-
|
|
93
|
+
if (pendingStop === undefined) {
|
|
94
|
+
// No predecessor's flush is in flight: start now.
|
|
95
|
+
begin()
|
|
96
|
+
} else {
|
|
97
|
+
// Serialize the start behind every predecessor: their final writes must
|
|
98
|
+
// land on disk before this instance loads the file.
|
|
99
|
+
pendingStop = pendingStop.then(begin, begin).then(() => undefined)
|
|
100
|
+
}
|
|
108
101
|
return () => {
|
|
102
|
+
live = false
|
|
109
103
|
disposeRoutes?.()
|
|
110
|
-
|
|
104
|
+
disposeRoutes = undefined
|
|
105
|
+
const stopping = service?.stop()
|
|
111
106
|
service = undefined
|
|
107
|
+
// A successor waits for this instance's final flush, which itself waited
|
|
108
|
+
// for the predecessor's start: a row disposed before it ever started
|
|
109
|
+
// keeps the earlier link instead of dropping it.
|
|
110
|
+
if (stopping !== undefined) pendingStop = (pendingStop ?? Promise.resolve()).then(() => stopping)
|
|
112
111
|
}
|
|
113
112
|
}, 'dsh-usage: runtime')
|
|
114
113
|
})
|