@loadstrike/loadstrike-sdk 1.0.31001 → 1.0.32601
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.md +16 -2
- package/dist/cjs/internal/prometheus-remote-write.js +37 -0
- package/dist/cjs/internal/reporting-sink-http-error.js +17 -0
- package/dist/cjs/internal/vendor-metric-payloads.js +390 -0
- package/dist/cjs/iteration-observations.js +24 -8
- package/dist/cjs/local-report-input.js +21 -0
- package/dist/cjs/local.js +48 -63
- package/dist/cjs/report-history.js +421 -0
- package/dist/cjs/reporting-containment.js +242 -0
- package/dist/cjs/reporting-svg.js +116 -0
- package/dist/cjs/reporting.js +413 -136
- package/dist/cjs/runtime.js +237 -8
- package/dist/cjs/sinks.js +1337 -38
- package/dist/cjs/transports.js +1339 -151
- package/dist/esm/internal/prometheus-remote-write.js +31 -0
- package/dist/esm/internal/reporting-sink-http-error.js +13 -0
- package/dist/esm/internal/vendor-metric-payloads.js +382 -0
- package/dist/esm/iteration-observations.js +24 -8
- package/dist/esm/local-report-input.js +17 -0
- package/dist/esm/local.js +49 -64
- package/dist/esm/report-history.js +413 -0
- package/dist/esm/reporting-containment.js +238 -0
- package/dist/esm/reporting-svg.js +113 -0
- package/dist/esm/reporting.js +413 -136
- package/dist/esm/runtime.js +239 -10
- package/dist/esm/sinks.js +1334 -35
- package/dist/esm/transports.js +1335 -151
- package/dist/types/contracts.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/internal/prometheus-remote-write.d.ts +2 -0
- package/dist/types/internal/reporting-sink-http-error.d.ts +6 -0
- package/dist/types/internal/vendor-metric-payloads.d.ts +48 -0
- package/dist/types/local-report-input.d.ts +6 -0
- package/dist/types/local.d.ts +0 -6
- package/dist/types/report-history.d.ts +124 -0
- package/dist/types/reporting-containment.d.ts +2 -0
- package/dist/types/reporting-svg.d.ts +2 -0
- package/dist/types/reporting.d.ts +6 -3
- package/dist/types/runtime.d.ts +24 -0
- package/dist/types/sinks.d.ts +134 -17
- package/dist/types/transports.d.ts +2 -0
- package/package.json +9 -3
- package/dist/cjs/internal-build.js +0 -4
- package/dist/esm/internal-build.js +0 -1
- package/dist/types/internal-build.d.ts +0 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const UNBOUND_EXPANDED_SECTIONS = [
|
|
2
|
+
["PrometheusRemoteWrite", "PrometheusRemoteWriteReportingSink"],
|
|
3
|
+
["CloudWatch", "CloudWatchReportingSink"],
|
|
4
|
+
["Dynatrace", "DynatraceReportingSink"],
|
|
5
|
+
["NewRelic", "NewRelicReportingSink"],
|
|
6
|
+
["Elasticsearch", "ElasticsearchReportingSink"],
|
|
7
|
+
["OpenSearch", "OpenSearchReportingSink"],
|
|
8
|
+
["Kafka", "KafkaReportingSink"],
|
|
9
|
+
["StatsD", "StatsDReportingSink"],
|
|
10
|
+
["DogStatsD", "DogStatsDReportingSink"],
|
|
11
|
+
["Netdata", "NetdataStatsDReportingSink"],
|
|
12
|
+
["Jsonl", "JsonlFileReportingSink"],
|
|
13
|
+
["Webhook", "GenericWebhookReportingSink"]
|
|
14
|
+
];
|
|
15
|
+
export function assertNoUnsupportedReportingInfraConfig(infraConfig, sinks = []) {
|
|
16
|
+
if (!isRecord(infraConfig)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const selectedSections = selectedExpandedReportingSections(sinks);
|
|
20
|
+
for (const [sectionName, sinkType] of UNBOUND_EXPANDED_SECTIONS) {
|
|
21
|
+
if (selectedSections.has(sectionName)
|
|
22
|
+
&& hasNonEmptyReportingSectionAlias(infraConfig, sectionName)) {
|
|
23
|
+
throw new Error(`Configuration section 'LoadStrike:ReportingSinks:${sectionName}' is not bound automatically by the TypeScript SDK. `
|
|
24
|
+
+ `Construct ${sinkType} explicitly and pass its options directly.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function selectedExpandedReportingSections(sinks) {
|
|
29
|
+
const selected = new Set();
|
|
30
|
+
const visited = new Set();
|
|
31
|
+
const inspect = (sink) => {
|
|
32
|
+
if (!isRecord(sink) || visited.has(sink)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
visited.add(sink);
|
|
36
|
+
const identities = [
|
|
37
|
+
sink.licenseFeature,
|
|
38
|
+
sink.LicenseFeature,
|
|
39
|
+
sink.feature,
|
|
40
|
+
sink.Feature
|
|
41
|
+
];
|
|
42
|
+
if (!hasReportingSinkLifecycle(sink)) {
|
|
43
|
+
identities.push(sink.kind, sink.Kind);
|
|
44
|
+
}
|
|
45
|
+
const normalizedIdentities = identities
|
|
46
|
+
.map(normalizeExpandedSinkIdentity)
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
for (const identity of normalizedIdentities) {
|
|
49
|
+
const sectionName = EXPANDED_SECTION_BY_IDENTITY.get(identity);
|
|
50
|
+
if (sectionName) {
|
|
51
|
+
selected.add(sectionName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const children = Array.isArray(sink.sinks)
|
|
55
|
+
? sink.sinks
|
|
56
|
+
: Array.isArray(sink.Sinks)
|
|
57
|
+
? sink.Sinks
|
|
58
|
+
: [];
|
|
59
|
+
for (const child of children) {
|
|
60
|
+
inspect(child);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
for (const sink of sinks) {
|
|
64
|
+
inspect(sink);
|
|
65
|
+
}
|
|
66
|
+
return selected;
|
|
67
|
+
}
|
|
68
|
+
const EXPANDED_SECTION_BY_IDENTITY = new Map([
|
|
69
|
+
["prometheusremotewrite", "PrometheusRemoteWrite"],
|
|
70
|
+
["cloudwatch", "CloudWatch"],
|
|
71
|
+
["dynatrace", "Dynatrace"],
|
|
72
|
+
["elasticsearch", "Elasticsearch"],
|
|
73
|
+
["opensearch", "OpenSearch"],
|
|
74
|
+
["newrelic", "NewRelic"],
|
|
75
|
+
["webhook", "Webhook"],
|
|
76
|
+
["genericwebhook", "Webhook"],
|
|
77
|
+
["kafka", "Kafka"],
|
|
78
|
+
["statsd", "StatsD"],
|
|
79
|
+
["dogstatsd", "DogStatsD"],
|
|
80
|
+
["netdata", "Netdata"],
|
|
81
|
+
["netdatastatsd", "Netdata"],
|
|
82
|
+
["jsonl", "Jsonl"],
|
|
83
|
+
["jsonlfile", "Jsonl"]
|
|
84
|
+
]);
|
|
85
|
+
function normalizeExpandedSinkIdentity(value) {
|
|
86
|
+
let normalized = normalizeIdentityToken(value);
|
|
87
|
+
const featurePrefix = "extensionsreportingsinks";
|
|
88
|
+
if (normalized.startsWith(featurePrefix)) {
|
|
89
|
+
normalized = normalized.slice(featurePrefix.length);
|
|
90
|
+
}
|
|
91
|
+
const classSuffix = "reportingsink";
|
|
92
|
+
if (normalized.endsWith(classSuffix)) {
|
|
93
|
+
normalized = normalized.slice(0, -classSuffix.length);
|
|
94
|
+
}
|
|
95
|
+
return normalized;
|
|
96
|
+
}
|
|
97
|
+
export function assertNoUnsupportedReportingSinkGraph(sinks) {
|
|
98
|
+
const visited = new Set();
|
|
99
|
+
const active = new Set();
|
|
100
|
+
const inspect = (sink) => {
|
|
101
|
+
if (!isRecord(sink)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (active.has(sink)) {
|
|
105
|
+
throw new Error("Composite reporting sink graph cannot contain a cycle.");
|
|
106
|
+
}
|
|
107
|
+
if (visited.has(sink)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
active.add(sink);
|
|
111
|
+
const identityTokens = [sink.sinkName, sink.SinkName, sink.kind, sink.Kind]
|
|
112
|
+
.map(normalizeIdentityToken)
|
|
113
|
+
.filter(Boolean);
|
|
114
|
+
const featureTokens = [sink.licenseFeature, sink.LicenseFeature, sink.feature, sink.Feature]
|
|
115
|
+
.map((value) => String(value ?? "").trim().toLowerCase())
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
const hasLifecycle = hasReportingSinkLifecycle(sink);
|
|
118
|
+
const displayName = directVendorDisplayName(hasLifecycle ? [] : identityTokens, hasLifecycle ? [] : featureTokens);
|
|
119
|
+
if (displayName) {
|
|
120
|
+
throw unsupportedDirectVendorReportingError(displayName);
|
|
121
|
+
}
|
|
122
|
+
if (identityTokens.includes("composite") || identityTokens.includes("compositereportingsink")) {
|
|
123
|
+
const children = Array.isArray(sink.sinks)
|
|
124
|
+
? sink.sinks
|
|
125
|
+
: Array.isArray(sink.Sinks)
|
|
126
|
+
? sink.Sinks
|
|
127
|
+
: [];
|
|
128
|
+
for (const child of children) {
|
|
129
|
+
inspect(child);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
active.delete(sink);
|
|
133
|
+
visited.add(sink);
|
|
134
|
+
};
|
|
135
|
+
for (const sink of sinks) {
|
|
136
|
+
inspect(sink);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function hasNonEmptyReportingSectionAlias(infraConfig, sectionName) {
|
|
140
|
+
const segments = ["LoadStrike", "ReportingSinks", sectionName];
|
|
141
|
+
const values = [
|
|
142
|
+
...readDirectValuesCaseInsensitive(infraConfig, segments.join(":")),
|
|
143
|
+
...readDirectValuesCaseInsensitive(infraConfig, segments.join(".")),
|
|
144
|
+
...readNestedValuesCaseInsensitive(infraConfig, segments)
|
|
145
|
+
];
|
|
146
|
+
return values.some(isNonEmptySection);
|
|
147
|
+
}
|
|
148
|
+
function readDirectValuesCaseInsensitive(record, key) {
|
|
149
|
+
const expected = key.toLowerCase();
|
|
150
|
+
return Object.entries(record)
|
|
151
|
+
.filter(([candidate]) => candidate.toLowerCase() === expected)
|
|
152
|
+
.map(([, value]) => value);
|
|
153
|
+
}
|
|
154
|
+
function readNestedValuesCaseInsensitive(record, segments) {
|
|
155
|
+
let current = [record];
|
|
156
|
+
for (const segment of segments) {
|
|
157
|
+
const next = [];
|
|
158
|
+
for (const candidate of current) {
|
|
159
|
+
if (isRecord(candidate)) {
|
|
160
|
+
next.push(...readDirectValuesCaseInsensitive(candidate, segment));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (next.length === 0) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
current = next;
|
|
167
|
+
}
|
|
168
|
+
return current;
|
|
169
|
+
}
|
|
170
|
+
function isNonEmptySection(value) {
|
|
171
|
+
if (value == null) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === "string") {
|
|
175
|
+
return value.trim().length > 0;
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
return value.length > 0;
|
|
179
|
+
}
|
|
180
|
+
if (isRecord(value)) {
|
|
181
|
+
return Object.keys(value).length > 0;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
function directVendorDisplayName(identityTokens, featureTokens) {
|
|
186
|
+
if (identityTokens.includes("prometheusremotewrite")
|
|
187
|
+
|| identityTokens.includes("prometheusremotewritereportingsink")
|
|
188
|
+
|| featureTokens.includes("extensions.reporting_sinks.prometheus_remote_write")) {
|
|
189
|
+
return "Prometheus Remote Write";
|
|
190
|
+
}
|
|
191
|
+
if (identityTokens.includes("cloudwatch")
|
|
192
|
+
|| identityTokens.includes("cloudwatchreportingsink")
|
|
193
|
+
|| featureTokens.includes("extensions.reporting_sinks.cloudwatch")) {
|
|
194
|
+
return "CloudWatch";
|
|
195
|
+
}
|
|
196
|
+
if (identityTokens.includes("dynatrace")
|
|
197
|
+
|| identityTokens.includes("dynatracereportingsink")
|
|
198
|
+
|| featureTokens.includes("extensions.reporting_sinks.dynatrace")) {
|
|
199
|
+
return "Dynatrace";
|
|
200
|
+
}
|
|
201
|
+
if (identityTokens.includes("newrelic")
|
|
202
|
+
|| identityTokens.includes("newrelicreportingsink")
|
|
203
|
+
|| featureTokens.includes("extensions.reporting_sinks.new_relic")) {
|
|
204
|
+
return "New Relic";
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function unsupportedDirectVendorReportingError(displayName) {
|
|
209
|
+
return new Error(`Direct ${displayName} reporting is not supported by the TypeScript SDK. `
|
|
210
|
+
+ "Use GenericWebhookReportingSink through a converting gateway or a custom LoadStrikeReportingSink.");
|
|
211
|
+
}
|
|
212
|
+
function normalizeIdentityToken(value) {
|
|
213
|
+
return String(value ?? "")
|
|
214
|
+
.trim()
|
|
215
|
+
.toLowerCase()
|
|
216
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
217
|
+
}
|
|
218
|
+
function hasReportingSinkLifecycle(sink) {
|
|
219
|
+
return [
|
|
220
|
+
"init",
|
|
221
|
+
"Init",
|
|
222
|
+
"start",
|
|
223
|
+
"Start",
|
|
224
|
+
"saveRealtimeStats",
|
|
225
|
+
"SaveRealtimeStats",
|
|
226
|
+
"saveRealtimeMetrics",
|
|
227
|
+
"SaveRealtimeMetrics",
|
|
228
|
+
"saveRunResult",
|
|
229
|
+
"SaveRunResult",
|
|
230
|
+
"saveIterationBatch",
|
|
231
|
+
"SaveIterationBatch",
|
|
232
|
+
"stop",
|
|
233
|
+
"Stop"
|
|
234
|
+
].some((member) => typeof sink[member] === "function");
|
|
235
|
+
}
|
|
236
|
+
function isRecord(value) {
|
|
237
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
238
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const REPORT_SVG_CSS = String.raw `
|
|
2
|
+
.chart-collection{--chart-min:360px}
|
|
3
|
+
.chart-collection[data-grid-size='compact']{--chart-min:260px}
|
|
4
|
+
.chart-collection[data-grid-size='comfortable']{--chart-min:360px}
|
|
5
|
+
.chart-collection[data-grid-size='spacious']{--chart-min:520px}
|
|
6
|
+
.chart-collection .chart-grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--chart-min)),1fr))}
|
|
7
|
+
.chart-collection .correlation-chart-grid{grid-template-columns:repeat(auto-fill,minmax(min(100%,420px),720px));justify-content:center}
|
|
8
|
+
.chart-collection-tools{display:flex;align-items:end;gap:10px;flex-wrap:wrap;margin:0 0 12px}
|
|
9
|
+
.chart-collection-tools label{display:grid;gap:4px;color:var(--muted);font-size:12px}
|
|
10
|
+
.chart-collection-tools input,.chart-collection-tools select{min-height:34px;border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--text);padding:5px 9px}
|
|
11
|
+
.chart-empty{display:none;color:var(--muted);padding:12px;border:1px dashed var(--line);border-radius:8px}
|
|
12
|
+
.chart-empty.visible{display:block}
|
|
13
|
+
.chart-card[hidden]{display:none}
|
|
14
|
+
.chart-actions{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:7px}
|
|
15
|
+
.chart-action,.chart-legend button,.chart-modal-close{border:1px solid #53657c;border-radius:6px;background:#17243a;color:#e6edf3;min-height:30px;padding:4px 8px;cursor:pointer}
|
|
16
|
+
.chart-action:disabled{opacity:.42;cursor:not-allowed}
|
|
17
|
+
.chart-action:focus-visible,.chart-legend button:focus-visible,.chart-modal-close:focus-visible,.chart-canvas:focus-visible{outline:3px solid #60a5fa;outline-offset:2px}
|
|
18
|
+
.chart-host{position:relative;width:100%}
|
|
19
|
+
.chart-canvas{width:100%;height:auto;aspect-ratio:720/320;display:block;color:#dbe6f4}
|
|
20
|
+
.correlation-chart-card .chart-canvas{aspect-ratio:720/320}
|
|
21
|
+
.chart-legend{display:flex;gap:7px;flex-wrap:wrap;margin:7px 0 0}
|
|
22
|
+
.chart-legend button{display:inline-flex;align-items:center;gap:6px;font-size:12px}
|
|
23
|
+
.chart-legend button[aria-pressed='false']{opacity:.55;text-decoration:line-through}
|
|
24
|
+
.chart-legend-swatch{width:10px;height:10px;border-radius:999px;background:var(--series-color)}
|
|
25
|
+
.chart-tooltip{position:absolute;z-index:5;pointer-events:none;max-width:min(340px,85%);padding:7px 9px;border:1px solid #64748b;border-radius:7px;background:#020617;color:#f8fafc;font-size:12px;white-space:pre-line;box-shadow:0 8px 18px rgba(0,0,0,.35);transform:translate(10px,-110%)}
|
|
26
|
+
.chart-tooltip[hidden]{display:none}
|
|
27
|
+
.chart-modal{position:fixed;inset:0;z-index:2000;display:none;align-items:center;justify-content:center;background:rgba(2,6,23,.86);padding:24px}
|
|
28
|
+
.chart-modal.open{display:flex}
|
|
29
|
+
.chart-modal-panel{width:min(1280px,96vw);max-height:94vh;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px}
|
|
30
|
+
.chart-modal-head{display:flex;justify-content:flex-end;margin-bottom:5px}
|
|
31
|
+
.chart-modal .chart-card{max-width:none;margin:0}
|
|
32
|
+
.chart-modal .chart-canvas{width:100%;height:auto;aspect-ratio:720/320}
|
|
33
|
+
body[data-chart-modal-open='true']{overflow:hidden}
|
|
34
|
+
@media print{.tabs-pane,.theme-toggle-report,.chart-actions,.chart-collection-tools,.chart-modal{display:none!important}.tab{display:block!important}.chart-card[hidden]{display:block!important}.report-layout{display:block}}
|
|
35
|
+
@media (max-width:980px){.chart-collection{--chart-min:100%}}
|
|
36
|
+
`;
|
|
37
|
+
export const REPORT_SVG_SCRIPT = String.raw `
|
|
38
|
+
const btns=[...document.querySelectorAll('.tab-btn')];
|
|
39
|
+
const tabSections=[...document.querySelectorAll('.tab')];
|
|
40
|
+
const tabsPane=document.getElementById('tab-pane');
|
|
41
|
+
const reportThemeKey='loadstrike-report-theme';
|
|
42
|
+
const reportThemeToggle=document.querySelector('[data-report-theme-toggle]');
|
|
43
|
+
const reportLogo=document.querySelector('[data-report-logo]');
|
|
44
|
+
const SVG_NS='http://www.w3.org/2000/svg';
|
|
45
|
+
const SVG_ELEMENTS=new Set(['g','line','rect','path','circle','text','title']);
|
|
46
|
+
const SVG_ATTRIBUTES=new Set(['x','y','x1','y1','x2','y2','width','height','rx','ry','d','cx','cy','r','fill','stroke','stroke-width','stroke-dasharray','text-anchor','font-size','font-weight','opacity','transform','class','aria-hidden']);
|
|
47
|
+
const chartStates=new WeakMap();
|
|
48
|
+
const chartTouchSelections=new WeakMap();
|
|
49
|
+
const chartPrintStates=new Map();
|
|
50
|
+
const linePalette=['#38bdf8','#22c55e','#f59e0b','#a855f7','#f43f5e','#14b8a6','#eab308','#818cf8','#06b6d4','#84cc16'];
|
|
51
|
+
const modal=document.querySelector('[data-chart-fullscreen-overlay]');
|
|
52
|
+
const modalPanel=modal&&modal.querySelector('[data-chart-modal-panel]');
|
|
53
|
+
let expandedCard=null;
|
|
54
|
+
let expandedPlaceholder=null;
|
|
55
|
+
let expandedTrigger=null;
|
|
56
|
+
let printExpandedCard=false;
|
|
57
|
+
let printExpandedFocus=null;
|
|
58
|
+
function safeColor(value,fallback){const text=(value||'').toString();return /^#[0-9a-f]{6}$/i.test(text)?text:fallback;}
|
|
59
|
+
function svgNode(name,attributes,text){if(!SVG_ELEMENTS.has(name))throw new Error('Unsupported SVG element');const node=document.createElementNS(SVG_NS,name);Object.entries(attributes||{}).forEach(([key,value])=>{if(!SVG_ATTRIBUTES.has(key))throw new Error('Unsupported SVG attribute');node.setAttribute(key,String(value));});if(text!==undefined&&text!==null)node.textContent=String(text);return node;}
|
|
60
|
+
function finiteValue(value,unit){if(value===null||value===undefined||value==='')return null;const number=Number(value);if(!Number.isFinite(number)||number<0)return null;if(unit==='%'&&number>100)return null;return number;}
|
|
61
|
+
function fullMetric(value){return Number.isFinite(value)?String(value):'n/a';}
|
|
62
|
+
function axisMetric(value){if(!Number.isFinite(value))return '';const absolute=Math.abs(value);if(absolute>=1000000000)return (value/1000000000).toFixed(1)+'B';if(absolute>=1000000)return (value/1000000).toFixed(1)+'M';if(absolute>=1000)return (value/1000).toFixed(1)+'K';return absolute>=100?value.toFixed(0):String(Number(value.toFixed(2)));}
|
|
63
|
+
function maximumFinite(values,fallback){let maximum=0;for(const value of values){if(Number.isFinite(value)&&value>maximum)maximum=value;}return maximum>0?maximum:(Number.isFinite(fallback)&&fallback>0?fallback:1);}
|
|
64
|
+
function applyReportTheme(theme){const normalized=theme==='dark'?'dark':'light';document.body.setAttribute('data-theme',normalized);if(reportLogo){const lightLogo=reportLogo.dataset.logoLight||reportLogo.getAttribute('src');const darkLogo=reportLogo.dataset.logoDark||reportLogo.getAttribute('src');reportLogo.setAttribute('src',normalized==='dark'?darkLogo:lightLogo);}if(reportThemeToggle){const darkActive=normalized==='dark';const nextLabel=darkActive?'light':'dark';reportThemeToggle.textContent=darkActive?'\u2600':'\u263e';reportThemeToggle.setAttribute('aria-pressed',darkActive?'true':'false');reportThemeToggle.setAttribute('aria-label','Switch to '+nextLabel+' theme');reportThemeToggle.setAttribute('title','Switch to '+nextLabel+' theme');}}
|
|
65
|
+
function show(id){btns.forEach(button=>button.classList.toggle('active',button.dataset.tab===id));tabSections.forEach(tab=>tab.classList.toggle('active',tab.id===id));}
|
|
66
|
+
function readJsonNode(selector){const node=document.querySelector(selector);if(!node)return {labels:[],series:[]};try{return JSON.parse(node.textContent||'{}');}catch{return {labels:[],series:[]};}}
|
|
67
|
+
function rawChart(host){const source=host.dataset.chartSource||'';if(source==='grouped-correlation'){const key=host.dataset.groupedKey||'';return readJsonNode('script[type="application/json"][data-grouped-correlation-chart="'+CSS.escape(key)+'"]');}if(source==='ungrouped-correlation'){const key=host.dataset.ungroupedKey||'';return readJsonNode('script[type="application/json"][data-ungrouped-correlation="'+CSS.escape(key)+'"]');}if(source==='historyLatencyCharts'){const index=Number.parseInt(host.dataset.chartIndex||'-1',10);const item=Array.isArray(reportCharts.historyLatencyCharts)?reportCharts.historyLatencyCharts[index]:null;return item&&item.chart;}return reportCharts[source];}
|
|
68
|
+
function normalizeChart(host){const raw=rawChart(host);const kind=host.dataset.chartKind||'line';const unit=host.dataset.chartUnit||'';if(Array.isArray(raw)){if(kind==='donut'){return {labels:['Total'],series:raw.map((point,index)=>({name:String(point&&point.label||'Series '+(index+1)),color:safeColor(point&&point.color,linePalette[index%linePalette.length]),values:[finiteValue(point&&point.value,unit)]}))};}return {labels:raw.map(point=>String(point&&point.label||'')),series:[{name:host.dataset.seriesLabel||'Value',color:safeColor(raw[0]&&raw[0].color,linePalette[0]),values:raw.map(point=>finiteValue(point&&point.value,unit))}]};}const labels=Array.isArray(raw&&raw.labels)?raw.labels.map(value=>String(value)):[];const series=Array.isArray(raw&&raw.series)?raw.series.map((item,index)=>({name:String(item&&item.name||'Series '+(index+1)),color:safeColor(item&&item.color,linePalette[index%linePalette.length]),values:Array.isArray(item&&item.values)?item.values.map(value=>finiteValue(value,unit)):[]})):[];return {labels,series};}
|
|
69
|
+
function stateFor(host,labelCount){let state=chartStates.get(host);if(!state){state={start:0,end:Math.max(0,labelCount-1),hidden:new Set(),active:0};chartStates.set(host,state);}state.end=Math.min(Math.max(state.start,state.end),Math.max(0,labelCount-1));state.start=Math.min(state.start,state.end);state.active=Math.min(Math.max(state.start,state.active),state.end);return state;}
|
|
70
|
+
function chartSvg(host){return host.querySelector('svg.chart-canvas');}
|
|
71
|
+
function tooltipFor(host){return host.querySelector('[data-chart-tooltip]');}
|
|
72
|
+
function hideTooltip(host){const tooltip=tooltipFor(host);if(tooltip)tooltip.hidden=true;const marker=chartSvg(host)&&chartSvg(host).querySelector('.chart-active-marker');if(marker)marker.setAttribute('opacity','0');}
|
|
73
|
+
function pointIndexFor(host,state,ratio){const domain=Math.max(0,state.end-state.start);if(host.dataset.chartKind==='bar'){const count=domain+1;return state.start+Math.min(count-1,Math.floor(ratio*count));}return state.start+Math.round(ratio*domain);}
|
|
74
|
+
function pointerPlotRatio(svg,clientX){const bounds=svg.getBoundingClientRect();const viewX=(clientX-bounds.left)/Math.max(1,bounds.width)*720;return Math.max(0,Math.min(1,(viewX-58)/644));}
|
|
75
|
+
function showTooltip(host,chart,state,index,clientX,clientY){if(index<state.start||index>state.end)return;const visible=chart.series.filter((series,seriesIndex)=>!state.hidden.has(seriesIndex));const rows=visible.map(series=>{const value=series.values[index];return Number.isFinite(value)?series.name+': '+fullMetric(value)+(host.dataset.chartUnit?' '+host.dataset.chartUnit:''):null;}).filter(Boolean);if(rows.length===0){hideTooltip(host);return;}const tooltip=tooltipFor(host);if(!tooltip)return;tooltip.textContent=[chart.labels[index]||'Point '+(index+1),...rows].join('\n');tooltip.hidden=false;const bounds=host.getBoundingClientRect();const localX=Number.isFinite(clientX)?clientX-bounds.left:bounds.width/2;const localY=Number.isFinite(clientY)?clientY-bounds.top:bounds.height/2;tooltip.style.left=Math.max(0,Math.min(bounds.width-20,localX))+'px';tooltip.style.top=Math.max(20,localY)+'px';const svg=chartSvg(host);const marker=svg&&svg.querySelector('.chart-active-marker');if(marker){const first=visible.map(series=>series.values[index]).find(Number.isFinite);if(Number.isFinite(first)){const domain=state.end-state.start;const count=Math.max(1,domain+1);const x=host.dataset.chartKind==='bar'?58+(index-state.start+.5)*644/count:58+(domain<=0?322:(index-state.start)*644/domain);const values=visible.flatMap(series=>series.values.slice(state.start,state.end+1)).filter(Number.isFinite);const max=maximumFinite(values,1);const y=270-(first/max)*242;marker.setAttribute('cx',String(x));marker.setAttribute('cy',String(y));marker.setAttribute('opacity','1');}}state.active=index;}
|
|
76
|
+
function noData(svg,message){svg.append(svgNode('text',{x:360,y:160,'text-anchor':'middle',fill:'#9fb0c3','font-size':14},message));}
|
|
77
|
+
function drawAxes(svg,max,labels,start,end,unit,categorical){const left=58,right=18,top=28,bottom=50,width=720-left-right,height=320-top-bottom;for(let tick=0;tick<=4;tick++){const y=top+height*tick/4;svg.append(svgNode('line',{x1:left,y1:y,x2:720-right,y2:y,stroke:'#334155','stroke-width':1}));const value=max*(1-tick/4);svg.append(svgNode('text',{x:left-7,y:y+4,'text-anchor':'end',fill:'#b5c2d3','font-size':11},axisMetric(value)));}const count=Math.max(1,end-start+1);const thin=Math.max(1,Math.ceil(count/8));for(let index=start;index<=end;index++){if((index-start)%thin!==0&&index!==end)continue;const x=categorical?left+(index-start+.5)*width/count:(count<=1?left+width/2:left+(index-start)*width/(count-1));svg.append(svgNode('text',{x,y:304,'text-anchor':'middle',fill:'#b5c2d3','font-size':10},String(labels[index]||'').slice(0,24)));}if(unit)svg.append(svgNode('text',{x:10,y:18,fill:'#b5c2d3','font-size':11},unit));return {left,top,width,height};}
|
|
78
|
+
function drawLine(svg,host,chart,state,visible){const values=visible.flatMap(item=>item.series.values.slice(state.start,state.end+1)).filter(Number.isFinite);if(values.length===0){noData(svg,'No data');return;}const max=maximumFinite(values,1);const plot=drawAxes(svg,max,chart.labels,state.start,state.end,host.dataset.chartUnit||'',false);const domain=state.end-state.start;visible.forEach(item=>{let path='',started=false,isolatedGlyphPath='';for(let index=state.start;index<=state.end;index++){const value=item.series.values[index];if(!Number.isFinite(value)){started=false;continue;}const x=plot.left+(domain<=0?plot.width/2:(index-state.start)*plot.width/domain);const y=plot.top+plot.height-(value/max)*plot.height;path+=(started?' L ':' M ')+x.toFixed(2)+' '+y.toFixed(2);started=true;const previousFinite=index>state.start&&Number.isFinite(item.series.values[index-1]);const nextFinite=index<state.end&&Number.isFinite(item.series.values[index+1]);if(!previousFinite&&!nextFinite)isolatedGlyphPath+=' M '+(x-3).toFixed(2)+' '+y.toFixed(2)+' L '+x.toFixed(2)+' '+(y-3).toFixed(2)+' L '+(x+3).toFixed(2)+' '+y.toFixed(2)+' L '+x.toFixed(2)+' '+(y+3).toFixed(2)+' Z';}if(path)svg.append(svgNode('path',{d:path.trim(),fill:'none',stroke:item.series.color,'stroke-width':2.4}));if(isolatedGlyphPath)svg.append(svgNode('path',{d:isolatedGlyphPath.trim(),fill:item.series.color,stroke:'#f8fafc','stroke-width':1.5,class:'chart-isolated-points','aria-hidden':'true'}));});svg.append(svgNode('circle',{cx:0,cy:0,r:5,fill:'#020617',stroke:'#f8fafc','stroke-width':2,opacity:0,class:'chart-active-marker','aria-hidden':'true'}));}
|
|
79
|
+
function drawBars(svg,host,chart,state,visible){const values=visible.flatMap(item=>item.series.values.slice(state.start,state.end+1)).filter(Number.isFinite);if(values.length===0){noData(svg,'No data');return;}const max=maximumFinite(values,1);const plot=drawAxes(svg,max,chart.labels,state.start,state.end,host.dataset.chartUnit||'',true);const count=Math.max(1,state.end-state.start+1);const groupWidth=plot.width/count;const barWidth=Math.max(2,Math.min(42,groupWidth*.72/Math.max(1,visible.length)));for(let index=state.start;index<=state.end;index++){visible.forEach((item,visibleIndex)=>{const value=item.series.values[index];if(!Number.isFinite(value))return;const height=value/max*plot.height;const x=plot.left+(index-state.start)*groupWidth+(groupWidth-visible.length*barWidth)/2+visibleIndex*barWidth;svg.append(svgNode('rect',{x:x.toFixed(2),y:(plot.top+plot.height-height).toFixed(2),width:Math.max(1,barWidth-2).toFixed(2),height:height.toFixed(2),rx:2,fill:item.series.color}));});}svg.append(svgNode('circle',{cx:0,cy:0,r:5,fill:'#020617',stroke:'#f8fafc','stroke-width':2,opacity:0,class:'chart-active-marker','aria-hidden':'true'}));}
|
|
80
|
+
function arcPath(cx,cy,outer,inner,start,end){const large=end-start>Math.PI?1:0;const point=(radius,angle)=>[cx+radius*Math.cos(angle),cy+radius*Math.sin(angle)];const a=point(outer,start),b=point(outer,end),c=point(inner,end),d=point(inner,start);return 'M '+a[0]+' '+a[1]+' A '+outer+' '+outer+' 0 '+large+' 1 '+b[0]+' '+b[1]+' L '+c[0]+' '+c[1]+' A '+inner+' '+inner+' 0 '+large+' 0 '+d[0]+' '+d[1]+' Z';}
|
|
81
|
+
function donutLegendSeriesParticipates(series){return Number.isFinite(series.values[0]);}
|
|
82
|
+
function donutSeriesHasArc(series){return donutLegendSeriesParticipates(series)&&series.values[0]>0;}
|
|
83
|
+
function positiveDonutSlices(items){return items.map(item=>({item,value:item.series.values[0]})).filter(slice=>donutSeriesHasArc(slice.item.series));}
|
|
84
|
+
function donutArcSpan(value,total,sliceCount){return sliceCount===1?Math.PI*2-.00001:value/total*Math.PI*2;}
|
|
85
|
+
function donutLegendLabel(name,value,total){const percent=total>0?Number((value/total*100).toFixed(2)):0;return name+': '+fullMetric(value)+' ('+percent+'%)';}
|
|
86
|
+
function drawDonut(svg,host,chart,state,visible){const slices=positiveDonutSlices(visible);const total=slices.reduce((sum,slice)=>sum+slice.value,0);if(total<=0){noData(svg,'No data');return;}let angle=-Math.PI/2;slices.forEach(slice=>{const delta=donutArcSpan(slice.value,total,slices.length);const next=angle+delta;svg.append(svgNode('path',{d:arcPath(260,158,104,58,angle,next),fill:slice.item.series.color}));angle=next;});svg.append(svgNode('text',{x:260,y:157,'text-anchor':'middle',fill:'#e5eefc','font-size':20,'font-weight':700},fullMetric(total)));svg.append(svgNode('text',{x:260,y:178,'text-anchor':'middle',fill:'#9fb0c3','font-size':11},host.dataset.chartUnit||''));}
|
|
87
|
+
function renderLegend(host,chart,state){const legend=host.querySelector('[data-chart-legend]');if(!legend)return;legend.replaceChildren();const donut=host.dataset.chartKind==='donut';const donutTotal=positiveDonutSlices(chart.series.map((series,index)=>({series,index}))).reduce((sum,slice)=>sum+slice.value,0);const participates=series=>donut?donutLegendSeriesParticipates(series):series.values.some(Number.isFinite);const draws=series=>donut?donutSeriesHasArc(series):participates(series);chart.series.forEach((series,index)=>{if(!participates(series))return;const value=series.values[0];const button=document.createElement('button');button.type='button';button.dataset.seriesIndex=String(index);button.setAttribute('aria-pressed',state.hidden.has(index)?'false':'true');button.setAttribute('aria-label',(state.hidden.has(index)?'Show ':'Hide ')+series.name+' series');const swatch=document.createElement('span');swatch.className='chart-legend-swatch';swatch.style.setProperty('--series-color',series.color);const label=document.createElement('span');label.textContent=donut?donutLegendLabel(series.name,value,donutTotal):series.name;button.append(swatch,label);button.addEventListener('click',()=>{if(state.hidden.has(index)){state.hidden.delete(index);}else{const visibleCount=chart.series.filter((candidate,candidateIndex)=>!state.hidden.has(candidateIndex)&&draws(candidate)).length;if(draws(series)&&visibleCount<=1)return;state.hidden.add(index);}renderHost(host);const replacement=host.querySelector('[data-chart-legend] button[data-series-index="'+index+'"]');if(replacement)replacement.focus();});legend.append(button);});}
|
|
88
|
+
function updateActions(host,state,labelCount,kind){const card=host.closest('.chart-card');if(!card)return;const zoomable=kind!=='donut'&&labelCount>1;const full=state.start===0&&state.end===Math.max(0,labelCount-1);card.querySelectorAll('[data-chart-action]').forEach(button=>{const action=button.dataset.chartAction;if(action==='expand')button.disabled=false;else if(!zoomable)button.disabled=true;else if(action==='zoom-in')button.disabled=state.end-state.start<2;else if(action==='zoom-out'||action==='reset')button.disabled=full;else if(action==='pan-left')button.disabled=state.start===0;else if(action==='pan-right')button.disabled=state.end===labelCount-1;});}
|
|
89
|
+
function renderHost(host){const svg=chartSvg(host);if(!svg)return;const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);svg.replaceChildren();svg.append(svgNode('title',{},host.dataset.chartTitle||'LoadStrike chart'));const visible=chart.series.map((series,index)=>({series,index})).filter(item=>!state.hidden.has(item.index)&&item.series.values.some(Number.isFinite));const kind=host.dataset.chartKind||'line';if(chart.labels.length===0||visible.length===0){noData(svg,'No data');}else if(kind==='donut'){drawDonut(svg,host,chart,state,visible);}else if(kind==='bar'){drawBars(svg,host,chart,state,visible);}else{drawLine(svg,host,chart,state,visible);}renderLegend(host,chart,state);updateActions(host,state,chart.labels.length,kind);host.dataset.rendered='true';}
|
|
90
|
+
function renderAllCharts(){document.querySelectorAll('[data-loadstrike-chart-engine="svg-v2"]').forEach(renderHost);}
|
|
91
|
+
function changeViewport(host,action){const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);const length=chart.labels.length;if(length<=1)return;let span=state.end-state.start+1;if(action==='zoom-in'&&span>2){const next=Math.max(2,Math.ceil(span*.7));state.start+=Math.floor((span-next)/2);state.end=state.start+next-1;}else if(action==='zoom-out'){const next=Math.min(length,Math.ceil(span/0.7));state.start=Math.max(0,state.start-Math.floor((next-span)/2));state.end=Math.min(length-1,state.start+next-1);state.start=Math.max(0,state.end-next+1);}else if(action==='pan-left'&&state.start>0){const shift=Math.max(1,Math.floor(span/4));state.start=Math.max(0,state.start-shift);state.end=state.start+span-1;}else if(action==='pan-right'&&state.end<length-1){const shift=Math.max(1,Math.floor(span/4));state.end=Math.min(length-1,state.end+shift);state.start=state.end-span+1;}else if(action==='reset'){state.start=0;state.end=length-1;}state.active=Math.min(Math.max(state.start,state.active),state.end);hideTooltip(host);renderHost(host);}
|
|
92
|
+
function expandCard(card,trigger){if(!modal||!modalPanel||expandedCard)return;expandedCard=card;expandedTrigger=trigger;expandedPlaceholder=document.createComment('loadstrike-chart-placeholder');card.before(expandedPlaceholder);modalPanel.append(card);modal.classList.add('open');modal.setAttribute('aria-hidden','false');document.body.setAttribute('data-chart-modal-open','true');const close=modal.querySelector('[data-chart-modal-close]');if(close)close.focus();requestAnimationFrame(()=>{const host=card.querySelector('[data-loadstrike-chart-engine]');if(host)renderHost(host);});}
|
|
93
|
+
function closeExpanded(){if(!expandedCard||!expandedPlaceholder)return;expandedPlaceholder.replaceWith(expandedCard);modal.classList.remove('open');modal.setAttribute('aria-hidden','true');document.body.removeAttribute('data-chart-modal-open');const host=expandedCard.querySelector('[data-loadstrike-chart-engine]');if(host)requestAnimationFrame(()=>renderHost(host));if(expandedTrigger)expandedTrigger.focus();expandedCard=null;expandedPlaceholder=null;expandedTrigger=null;}
|
|
94
|
+
function prepareExpandedCardForPrint(){if(printExpandedCard||!expandedCard||!expandedPlaceholder||!expandedPlaceholder.parentNode)return;printExpandedFocus=modal&&modal.contains(document.activeElement)?document.activeElement:null;expandedPlaceholder.parentNode.insertBefore(expandedCard,expandedPlaceholder);printExpandedCard=true;}
|
|
95
|
+
function restoreExpandedCardAfterPrint(){if(!printExpandedCard)return null;if(expandedCard&&modalPanel)modalPanel.append(expandedCard);printExpandedCard=false;const focus=printExpandedFocus;printExpandedFocus=null;return focus;}
|
|
96
|
+
document.addEventListener('click',event=>{const actionButton=event.target.closest&&event.target.closest('[data-chart-action]');if(actionButton){const card=actionButton.closest('.chart-card');const host=card&&card.querySelector('[data-loadstrike-chart-engine]');if(!host)return;const action=actionButton.dataset.chartAction;if(action==='expand')expandCard(card,actionButton);else changeViewport(host,action);return;}if(event.target.closest&&event.target.closest('[data-chart-modal-close]'))closeExpanded();});
|
|
97
|
+
document.querySelectorAll('[data-loadstrike-chart-engine="svg-v2"]').forEach(host=>{const svg=chartSvg(host);if(!svg)return;const selectAtPointer=event=>{const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);const index=pointIndexFor(host,state,pointerPlotRatio(svg,event.clientX));showTooltip(host,chart,state,index,event.clientX,event.clientY);return index;};svg.addEventListener('pointermove',event=>{if(event.pointerType==='touch')return;selectAtPointer(event);});svg.addEventListener('pointerup',event=>{if(event.pointerType!=='touch')return;const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);const index=pointIndexFor(host,state,pointerPlotRatio(svg,event.clientX));if(chartTouchSelections.get(host)===index){chartTouchSelections.delete(host);hideTooltip(host);}else{chartTouchSelections.set(host,index);showTooltip(host,chart,state,index,event.clientX,event.clientY);}});svg.addEventListener('pointerleave',event=>{if(event.pointerType!=='touch')hideTooltip(host);});svg.addEventListener('keydown',event=>{const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);if(event.key==='Escape'){hideTooltip(host);return;}if(event.key==='Home')state.active=state.start;else if(event.key==='End')state.active=state.end;else if(event.key==='ArrowLeft')state.active=Math.max(state.start,state.active-1);else if(event.key==='ArrowRight')state.active=Math.min(state.end,state.active+1);else return;event.preventDefault();showTooltip(host,chart,state,state.active);});svg.addEventListener('focus',()=>{const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);showTooltip(host,chart,state,state.active);});});
|
|
98
|
+
document.addEventListener('pointerdown',event=>{if(event.target.closest&&event.target.closest('[data-loadstrike-chart-engine="svg-v2"]'))return;document.querySelectorAll('[data-loadstrike-chart-engine="svg-v2"]').forEach(host=>{chartTouchSelections.delete(host);hideTooltip(host);});});
|
|
99
|
+
document.querySelectorAll('[data-chart-search]').forEach(input=>input.addEventListener('input',()=>{const collection=input.closest('[data-chart-collection]');if(!collection)return;const query=input.value.trim().toLocaleLowerCase();let visible=0;collection.querySelectorAll('.chart-card').forEach(card=>{const match=!query||(card.dataset.chartTitle||'').toLocaleLowerCase().includes(query);card.hidden=!match;if(match)visible++;});const empty=collection.querySelector('[data-chart-empty]');if(empty)empty.classList.toggle('visible',visible===0);}));
|
|
100
|
+
document.querySelectorAll('[data-chart-grid-size]').forEach(select=>select.addEventListener('change',()=>{const collection=select.closest('[data-chart-collection]');if(collection)collection.dataset.gridSize=select.value;}));
|
|
101
|
+
if(modal){modal.addEventListener('pointerdown',event=>{if(event.target===modal)closeExpanded();});modal.addEventListener('keydown',event=>{if(event.key==='Escape'){event.preventDefault();closeExpanded();return;}if(event.key==='Tab'){const focusable=[...modal.querySelectorAll('button:not(:disabled),[tabindex="0"]')];if(focusable.length===0)return;const first=focusable[0],last=focusable[focusable.length-1];if(event.shiftKey&&document.activeElement===first){event.preventDefault();last.focus();}else if(!event.shiftKey&&document.activeElement===last){event.preventDefault();first.focus();}}});}
|
|
102
|
+
const storedReportTheme=(()=>{try{return localStorage.getItem(reportThemeKey);}catch{return null;}})();
|
|
103
|
+
applyReportTheme(storedReportTheme==='dark'?'dark':'light');
|
|
104
|
+
if(reportThemeToggle)reportThemeToggle.addEventListener('click',()=>{const next=document.body.getAttribute('data-theme')==='dark'?'light':'dark';applyReportTheme(next);try{localStorage.setItem(reportThemeKey,next);}catch{}renderAllCharts();});
|
|
105
|
+
btns.forEach(button=>button.addEventListener('click',()=>{show(button.dataset.tab);requestAnimationFrame(renderAllCharts);}));
|
|
106
|
+
if(btns.length>0)show(btns[0].dataset.tab);
|
|
107
|
+
if(typeof ResizeObserver!=='undefined'){const observer=new ResizeObserver(entries=>entries.forEach(entry=>{if(entry.target.dataset.rendered==='true')renderHost(entry.target);}));document.querySelectorAll('[data-loadstrike-chart-engine="svg-v2"]').forEach(host=>observer.observe(host));}
|
|
108
|
+
window.addEventListener('beforeprint',()=>{prepareExpandedCardForPrint();document.querySelectorAll('[data-loadstrike-chart-engine="svg-v2"]').forEach(host=>{const chart=normalizeChart(host);const state=stateFor(host,chart.labels.length);chartPrintStates.set(host,{start:state.start,end:state.end,active:state.active});state.start=0;state.end=Math.max(0,chart.labels.length-1);renderHost(host);});});
|
|
109
|
+
window.addEventListener('afterprint',()=>{const focus=restoreExpandedCardAfterPrint();chartPrintStates.forEach((saved,host)=>{const state=chartStates.get(host);if(state){state.start=saved.start;state.end=saved.end;state.active=saved.active;renderHost(host);}});chartPrintStates.clear();if(focus&&document.contains(focus)&&typeof focus.focus==='function')focus.focus();});
|
|
110
|
+
function initPanePan(){if(!tabsPane)return;let pointer=null,startY=0,startScroll=0;tabsPane.addEventListener('pointerdown',event=>{if(event.button!==0||event.target.closest('button,a,input,textarea,select,label'))return;pointer=event.pointerId;startY=event.clientY;startScroll=tabsPane.scrollTop;tabsPane.classList.add('panning');tabsPane.setPointerCapture(pointer);});tabsPane.addEventListener('pointermove',event=>{if(pointer===event.pointerId)tabsPane.scrollTop=startScroll-(event.clientY-startY);});const stop=event=>{if(pointer!==event.pointerId)return;pointer=null;tabsPane.classList.remove('panning');};tabsPane.addEventListener('pointerup',stop);tabsPane.addEventListener('pointercancel',stop);}
|
|
111
|
+
renderAllCharts();
|
|
112
|
+
initPanePan();
|
|
113
|
+
`;
|