@dsh-workbench/shared 0.1.18 → 0.2.1

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 (46) hide show
  1. package/dist/api/unit.d.ts +1 -0
  2. package/dist/api/unit.js +3 -1
  3. package/dist/api/workbench-contract.d.ts +1 -1
  4. package/dist/components/CardBoundary.d.ts +1 -0
  5. package/dist/components/CardBoundary.js +3 -1
  6. package/dist/components/board-module.d.ts +1 -1
  7. package/dist/components/icons.d.ts +2 -0
  8. package/dist/components/icons.js +14 -0
  9. package/dist/config/constants.d.ts +3 -0
  10. package/dist/config/constants.js +3 -0
  11. package/dist/host/http.d.ts +2 -0
  12. package/dist/host/http.js +31 -1
  13. package/dist/host/provision.d.ts +1 -1
  14. package/dist/host/provision.js +5 -0
  15. package/dist/host/tool-approval.d.ts +5 -0
  16. package/dist/host/tool-approval.js +47 -9
  17. package/dist/host/unit-face.d.ts +1 -0
  18. package/dist/host.d.ts +1 -0
  19. package/dist/host.js +1 -0
  20. package/dist/i18n/en.js +30 -13
  21. package/dist/i18n/index.d.ts +31 -14
  22. package/dist/i18n/zh.d.ts +30 -13
  23. package/dist/i18n/zh.js +30 -13
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/lib/panel-state.d.ts +60 -0
  27. package/dist/lib/panel-state.js +146 -0
  28. package/package.json +1 -1
  29. package/src/api/__tests__/unit.test.ts +38 -0
  30. package/src/api/unit.ts +4 -0
  31. package/src/api/workbench-contract.ts +2 -1
  32. package/src/components/CardBoundary.tsx +4 -1
  33. package/src/components/board-module.ts +1 -1
  34. package/src/components/icons.tsx +38 -0
  35. package/src/config/constants.ts +6 -0
  36. package/src/host/__tests__/provision.test.ts +13 -0
  37. package/src/host/http.ts +30 -1
  38. package/src/host/provision.ts +3 -0
  39. package/src/host/tool-approval.ts +54 -8
  40. package/src/host/unit-face.ts +1 -0
  41. package/src/host.ts +1 -0
  42. package/src/i18n/en.ts +31 -13
  43. package/src/i18n/zh.ts +31 -13
  44. package/src/index.ts +1 -0
  45. package/src/lib/panel-state.test.ts +123 -0
  46. package/src/lib/panel-state.ts +209 -0
@@ -0,0 +1,209 @@
1
+ export type PanelCardPhase = 'idle' | 'loading' | 'ready' | 'error';
2
+
3
+ export interface PanelCardError {
4
+ readonly code: string;
5
+ readonly message: string;
6
+ }
7
+
8
+ export interface PanelCardRead {
9
+ readonly unit: string;
10
+ readonly route: string;
11
+ readonly phase: PanelCardPhase;
12
+ readonly at: number;
13
+ readonly error?: PanelCardError;
14
+ }
15
+
16
+ export interface PanelCardCrash {
17
+ readonly unit: string;
18
+ readonly reason: string;
19
+ readonly at: number;
20
+ readonly count: number;
21
+ }
22
+
23
+ export interface PanelUnitReport {
24
+ readonly unit: string;
25
+ readonly at: number;
26
+ readonly okAt: number | null;
27
+ readonly cards: readonly PanelCardRead[];
28
+ readonly crash?: PanelCardCrash;
29
+ }
30
+
31
+ export interface PanelWorkspaceReport {
32
+ readonly workspace: string | null;
33
+ readonly at: number;
34
+ readonly units: readonly PanelUnitReport[];
35
+ }
36
+
37
+ export interface PanelCardsReport {
38
+ readonly workspace: string;
39
+ readonly cards: readonly PanelCardRead[];
40
+ }
41
+
42
+ export interface PanelCrashReport {
43
+ readonly workspace: string;
44
+ readonly unit: string;
45
+ readonly reason: string;
46
+ readonly at: number;
47
+ }
48
+
49
+ export interface PanelStateStore {
50
+ cards(report: PanelCardsReport): void;
51
+ crash(report: PanelCrashReport): void;
52
+ snapshot(workspace: string | null): PanelWorkspaceReport;
53
+ }
54
+
55
+ export const PANEL_STATE_MAX_UNITS = 64;
56
+ export const PANEL_STATE_MAX_CARDS = 12;
57
+ export const PANEL_STATE_MAX_REPORTS = 32;
58
+ export const PANEL_STATE_CRASH_WINDOW_MS = 600000;
59
+ export const PANEL_STATE_MAX_TEXT = 2000;
60
+
61
+ const PHASES: readonly PanelCardPhase[] = ['idle', 'loading', 'ready', 'error'];
62
+
63
+ interface UnitEntry {
64
+ at: number;
65
+ cards: Map<string, PanelCardRead>;
66
+ crash?: PanelCardCrash;
67
+ }
68
+
69
+ interface WorkspaceEntry {
70
+ at: number;
71
+ units: Map<string, UnitEntry>;
72
+ }
73
+
74
+ export type PanelParse<T> =
75
+ | { readonly ok: true; readonly value: T }
76
+ | { readonly ok: false; readonly message: string };
77
+
78
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
79
+ return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : undefined;
80
+ }
81
+
82
+ function textOf(value: unknown, max: number): string {
83
+ if (typeof value !== 'string') return '';
84
+ const trimmed = value.trim();
85
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
86
+ }
87
+
88
+ function atOf(value: unknown): number | null {
89
+ if (typeof value !== 'number' || !Number.isFinite(value)) return null;
90
+ if (value < 0) return null;
91
+ return Math.trunc(value);
92
+ }
93
+
94
+ function errorOf(raw: unknown): PanelCardError | undefined {
95
+ const record = asRecord(raw);
96
+ if (record === undefined) return undefined;
97
+ const code = textOf(record['code'], 120);
98
+ const message = textOf(record['message'], PANEL_STATE_MAX_TEXT);
99
+ if (code === '' && message === '') return undefined;
100
+ return { code: code === '' ? 'unknown' : code, message };
101
+ }
102
+
103
+ export function parseCardsReport(payload: unknown): PanelParse<PanelCardsReport> {
104
+ const record = asRecord(payload);
105
+ if (record === undefined) return { ok: false, message: '请求体要是一个对象' };
106
+ const workspace = textOf(record['workspace'], PANEL_STATE_MAX_TEXT);
107
+ if (workspace === '') return { ok: false, message: 'workspace 不能是空的 —— 面板按工作区存' };
108
+ const raw = record['cards'];
109
+ if (!Array.isArray(raw)) return { ok: false, message: 'cards 要是一个数组' };
110
+ const cards: PanelCardRead[] = [];
111
+ for (const item of raw.slice(0, PANEL_STATE_MAX_REPORTS)) {
112
+ const card = asRecord(item);
113
+ if (card === undefined) continue;
114
+ const unit = textOf(card['unit'], 200);
115
+ const route = textOf(card['route'], 400);
116
+ const phase = textOf(card['phase'], 20) as PanelCardPhase;
117
+ const at = atOf(card['at']);
118
+ if (unit === '' || route === '' || at === null || !PHASES.includes(phase)) continue;
119
+ const error = errorOf(card['error']);
120
+ cards.push({ unit, route, phase, at, ...(error === undefined ? {} : { error }) });
121
+ }
122
+ if (cards.length === 0) return { ok: false, message: 'cards 里没有一条能认的(unit / route / phase / at 都要有)' };
123
+ return { ok: true, value: { workspace, cards } };
124
+ }
125
+
126
+ export function parseCrashReport(payload: unknown): PanelParse<PanelCrashReport> {
127
+ const record = asRecord(payload);
128
+ if (record === undefined) return { ok: false, message: '请求体要是一个对象' };
129
+ const workspace = textOf(record['workspace'], PANEL_STATE_MAX_TEXT);
130
+ const unit = textOf(record['unit'], 200);
131
+ const reason = textOf(record['reason'], PANEL_STATE_MAX_TEXT);
132
+ if (workspace === '') return { ok: false, message: 'workspace 不能是空的' };
133
+ if (unit === '') return { ok: false, message: 'unit 不能是空的' };
134
+ if (reason === '') return { ok: false, message: 'reason 不能是空的(没有原因的崩溃没法查)' };
135
+ return { ok: true, value: { workspace, unit, reason, at: atOf(record['at']) ?? Date.now() } };
136
+ }
137
+
138
+ export function createPanelStateStore(): PanelStateStore {
139
+ const workspaces = new Map<string, WorkspaceEntry>();
140
+
141
+ function trim(entry: Map<string, unknown>, max: number): void {
142
+ while (entry.size > max) {
143
+ const oldest = entry.keys().next();
144
+ if (oldest.done === true) return;
145
+ entry.delete(oldest.value);
146
+ }
147
+ }
148
+
149
+ function workspaceEntryOf(workspace: string): WorkspaceEntry {
150
+ const existing = workspaces.get(workspace);
151
+ if (existing !== undefined) return existing;
152
+ const created: WorkspaceEntry = { at: 0, units: new Map() };
153
+ workspaces.set(workspace, created);
154
+ trim(workspaces, PANEL_STATE_MAX_UNITS);
155
+ return created;
156
+ }
157
+
158
+ function unitEntryOf(entry: WorkspaceEntry, unit: string): UnitEntry {
159
+ const existing = entry.units.get(unit);
160
+ if (existing !== undefined) return existing;
161
+ const created: UnitEntry = { at: 0, cards: new Map() };
162
+ entry.units.set(unit, created);
163
+ return created;
164
+ }
165
+
166
+ return {
167
+ cards(report) {
168
+ const entry = workspaceEntryOf(report.workspace);
169
+ for (const card of report.cards) {
170
+ const unit = unitEntryOf(entry, card.unit);
171
+ unit.cards.delete(card.route);
172
+ unit.cards.set(card.route, card);
173
+ trim(unit.cards, PANEL_STATE_MAX_CARDS);
174
+ if (card.at > unit.at) unit.at = card.at;
175
+ if (card.at > entry.at) entry.at = card.at;
176
+ }
177
+ },
178
+
179
+ crash(report) {
180
+ const entry = workspaceEntryOf(report.workspace);
181
+ const unit = unitEntryOf(entry, report.unit);
182
+ const previous = unit.crash;
183
+ const counted =
184
+ previous !== undefined &&
185
+ previous.reason === report.reason &&
186
+ report.at >= previous.at &&
187
+ report.at - previous.at <= PANEL_STATE_CRASH_WINDOW_MS;
188
+ unit.crash = { unit: report.unit, reason: report.reason, at: report.at, count: counted ? previous.count + 1 : 1 };
189
+ if (report.at > unit.at) unit.at = report.at;
190
+ if (report.at > entry.at) entry.at = report.at;
191
+ },
192
+
193
+ snapshot(workspace) {
194
+ if (workspace === null) return { workspace: null, at: 0, units: [] };
195
+ const entry = workspaces.get(workspace);
196
+ if (entry === undefined) return { workspace, at: 0, units: [] };
197
+ const units = [...entry.units.entries()].map(([unit, one]): PanelUnitReport => {
198
+ const cards = [...one.cards.values()].sort((a, b) => a.route.localeCompare(b.route));
199
+ const okAt = cards.reduce<number | null>(
200
+ (best, card) => (card.phase === 'ready' && (best === null || card.at > best) ? card.at : best),
201
+ null,
202
+ );
203
+ return { unit, at: one.at, okAt, cards, ...(one.crash === undefined ? {} : { crash: one.crash }) };
204
+ });
205
+ units.sort((a, b) => b.at - a.at || a.unit.localeCompare(b.unit));
206
+ return { workspace, at: entry.at, units };
207
+ },
208
+ };
209
+ }