@devicechain/dashboards 0.26.0-rc.1 → 0.27.0-rc.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.
- package/dist/candidates.d.ts +3 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +233 -144
- package/dist/index.js.map +4 -4
- package/dist/internal/alarm-doc.d.ts +6 -0
- package/dist/queries.d.ts +19 -16
- package/dist/recording.d.ts +5 -0
- package/dist/slots.d.ts +2 -1
- package/dist/types.d.ts +5 -0
- package/dist/viewer-bindings.d.ts +4 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -845,12 +845,21 @@ function parseSlots(raw) {
|
|
|
845
845
|
if (binding) slot.defaultBinding = binding;
|
|
846
846
|
const scope = parseScope(spec.scope);
|
|
847
847
|
if (scope) slot.scope = scope;
|
|
848
|
+
const candidates = parseCandidates(spec.candidates);
|
|
849
|
+
if (candidates) slot.candidates = candidates;
|
|
848
850
|
slots[name] = slot;
|
|
849
851
|
}
|
|
850
852
|
if (Object.keys(slots).length === 0) return void 0;
|
|
851
853
|
validateScopes(slots);
|
|
852
854
|
return slots;
|
|
853
855
|
}
|
|
856
|
+
function parseCandidates(raw) {
|
|
857
|
+
if (!isRecord(raw)) return void 0;
|
|
858
|
+
const token = (v) => typeof v === "string" && v ? v : void 0;
|
|
859
|
+
const areaType = token(raw.areaType);
|
|
860
|
+
const assetType = token(raw.assetType);
|
|
861
|
+
return areaType || assetType ? { areaType, assetType } : void 0;
|
|
862
|
+
}
|
|
854
863
|
function parseScope(raw) {
|
|
855
864
|
if (!isRecord(raw)) return void 0;
|
|
856
865
|
const parent = typeof raw.parent === "string" ? raw.parent : "";
|
|
@@ -1371,10 +1380,78 @@ function resolveWidgetDatasource(datasource, bindings) {
|
|
|
1371
1380
|
return { kind: "unbound", ...series };
|
|
1372
1381
|
}
|
|
1373
1382
|
|
|
1374
|
-
// src/
|
|
1383
|
+
// src/candidates.ts
|
|
1375
1384
|
function ownGet(map, key) {
|
|
1376
1385
|
return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : void 0;
|
|
1377
1386
|
}
|
|
1387
|
+
function sameBinding(a, b) {
|
|
1388
|
+
if (!a) return false;
|
|
1389
|
+
if (a.kind === "device" && b.kind === "device") return a.deviceToken === b.deviceToken;
|
|
1390
|
+
if (a.kind === "anchor" && b.kind === "anchor") return a.anchor.targetToken === b.anchor.targetToken;
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
var listEntities = (lister, kind, typeToken) => lister(kind, typeToken);
|
|
1394
|
+
function slotTypeFilter(def, targetType) {
|
|
1395
|
+
return targetType === "customer" ? void 0 : def.candidates?.[targetType === "area" ? "areaType" : "assetType"];
|
|
1396
|
+
}
|
|
1397
|
+
async function resolveSlotCandidates(definition, slot, bindings, resolver, lister) {
|
|
1398
|
+
const slots = definition.slots ?? {};
|
|
1399
|
+
const def = ownGet(slots, slot);
|
|
1400
|
+
if (!def) return [];
|
|
1401
|
+
const current = ownGet(bindings, slot);
|
|
1402
|
+
const mark = (binding, label) => ({
|
|
1403
|
+
binding,
|
|
1404
|
+
label,
|
|
1405
|
+
selected: sameBinding(current, binding)
|
|
1406
|
+
});
|
|
1407
|
+
if (def.scope) {
|
|
1408
|
+
if (def.scope.strategy === "first") return [];
|
|
1409
|
+
const parentBinding = ownGet(bindings, def.scope.parent);
|
|
1410
|
+
if (!parentBinding || parentBinding.kind !== "anchor") return [];
|
|
1411
|
+
let members;
|
|
1412
|
+
try {
|
|
1413
|
+
members = [...await resolver.devicesForAnchor(parentBinding.anchor)].sort();
|
|
1414
|
+
} catch {
|
|
1415
|
+
return [];
|
|
1416
|
+
}
|
|
1417
|
+
return members.map((token) => mark({ kind: "device", deviceToken: token }, token));
|
|
1418
|
+
}
|
|
1419
|
+
if (def.type === "device") {
|
|
1420
|
+
let rows2;
|
|
1421
|
+
try {
|
|
1422
|
+
rows2 = await lister("device");
|
|
1423
|
+
} catch {
|
|
1424
|
+
return [];
|
|
1425
|
+
}
|
|
1426
|
+
return rows2.map((r) => mark({ kind: "device", deviceToken: r.token }, r.name || r.token));
|
|
1427
|
+
}
|
|
1428
|
+
const template = current?.kind === "anchor" ? current.anchor : def.defaultBinding?.kind === "anchor" ? def.defaultBinding.anchor : void 0;
|
|
1429
|
+
if (!template) return [];
|
|
1430
|
+
let rows;
|
|
1431
|
+
try {
|
|
1432
|
+
rows = await listEntities(lister, template.targetType, slotTypeFilter(def, template.targetType));
|
|
1433
|
+
} catch {
|
|
1434
|
+
return [];
|
|
1435
|
+
}
|
|
1436
|
+
return rows.map(
|
|
1437
|
+
(r) => mark(
|
|
1438
|
+
{
|
|
1439
|
+
kind: "anchor",
|
|
1440
|
+
anchor: {
|
|
1441
|
+
relationship: template.relationship,
|
|
1442
|
+
targetType: template.targetType,
|
|
1443
|
+
targetToken: r.token
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
r.name || r.token
|
|
1447
|
+
)
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/context.ts
|
|
1452
|
+
function ownGet2(map, key) {
|
|
1453
|
+
return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : void 0;
|
|
1454
|
+
}
|
|
1378
1455
|
function setBinding(map, key, value) {
|
|
1379
1456
|
if (key === "__proto__") return;
|
|
1380
1457
|
map[key] = value;
|
|
@@ -1390,7 +1467,7 @@ function bindingsWithoutScopedSlots(definition, map) {
|
|
|
1390
1467
|
if (!slots) return map;
|
|
1391
1468
|
const out = {};
|
|
1392
1469
|
for (const name of Object.keys(map)) {
|
|
1393
|
-
if (!
|
|
1470
|
+
if (!ownGet2(slots, name)?.scope) setBinding(out, name, map[name]);
|
|
1394
1471
|
}
|
|
1395
1472
|
return out;
|
|
1396
1473
|
}
|
|
@@ -1402,7 +1479,7 @@ function topoOrder(slots) {
|
|
|
1402
1479
|
const done = /* @__PURE__ */ new Set();
|
|
1403
1480
|
const visit = (name, stack) => {
|
|
1404
1481
|
if (done.has(name) || stack.has(name)) return;
|
|
1405
|
-
const slot =
|
|
1482
|
+
const slot = ownGet2(slots, name);
|
|
1406
1483
|
if (!slot) return;
|
|
1407
1484
|
stack.add(name);
|
|
1408
1485
|
const parent = slot.scope?.parent;
|
|
@@ -1418,16 +1495,16 @@ async function resolveContextBindings(definition, base, overlay, resolver) {
|
|
|
1418
1495
|
const slots = definition.slots ?? {};
|
|
1419
1496
|
const out = {};
|
|
1420
1497
|
for (const name of topoOrder(slots)) {
|
|
1421
|
-
const slot =
|
|
1498
|
+
const slot = ownGet2(slots, name);
|
|
1422
1499
|
const scope = slot?.scope;
|
|
1423
1500
|
if (!scope) {
|
|
1424
|
-
const sel =
|
|
1425
|
-
const b = sel && (!slot || bindingMatchesType(sel, slot.type)) ? sel :
|
|
1501
|
+
const sel = ownGet2(overlay, name);
|
|
1502
|
+
const b = sel && (!slot || bindingMatchesType(sel, slot.type)) ? sel : ownGet2(base, name);
|
|
1426
1503
|
if (b) setBinding(out, name, b);
|
|
1427
1504
|
continue;
|
|
1428
1505
|
}
|
|
1429
1506
|
if (slot && slot.type !== "device") continue;
|
|
1430
|
-
const parentBinding =
|
|
1507
|
+
const parentBinding = ownGet2(out, scope.parent);
|
|
1431
1508
|
if (!parentBinding || parentBinding.kind !== "anchor") continue;
|
|
1432
1509
|
let members;
|
|
1433
1510
|
try {
|
|
@@ -1438,7 +1515,7 @@ async function resolveContextBindings(definition, base, overlay, resolver) {
|
|
|
1438
1515
|
if (scope.strategy === "first") {
|
|
1439
1516
|
if (members.length > 0) setBinding(out, name, { kind: "device", deviceToken: members[0] });
|
|
1440
1517
|
} else {
|
|
1441
|
-
const pick =
|
|
1518
|
+
const pick = ownGet2(overlay, name) ?? ownGet2(base, name);
|
|
1442
1519
|
if (pick && pick.kind === "device" && members.includes(pick.deviceToken)) setBinding(out, name, pick);
|
|
1443
1520
|
}
|
|
1444
1521
|
}
|
|
@@ -1446,7 +1523,7 @@ async function resolveContextBindings(definition, base, overlay, resolver) {
|
|
|
1446
1523
|
if (Object.prototype.hasOwnProperty.call(slots, name) || Object.prototype.hasOwnProperty.call(out, name)) {
|
|
1447
1524
|
continue;
|
|
1448
1525
|
}
|
|
1449
|
-
const b =
|
|
1526
|
+
const b = ownGet2(overlay, name) ?? ownGet2(base, name);
|
|
1450
1527
|
if (b) setBinding(out, name, b);
|
|
1451
1528
|
}
|
|
1452
1529
|
return out;
|
|
@@ -1455,6 +1532,36 @@ function bindingMatchesType(binding, type) {
|
|
|
1455
1532
|
return binding.kind === type;
|
|
1456
1533
|
}
|
|
1457
1534
|
|
|
1535
|
+
// src/viewer-bindings.ts
|
|
1536
|
+
var ownGet3 = (map, key) => Object.prototype.hasOwnProperty.call(map, key) ? map[key] : void 0;
|
|
1537
|
+
function hasRootAnchorSlots(definition) {
|
|
1538
|
+
return Object.values(definition.slots ?? {}).some((s) => s.type === "anchor" && !s.scope);
|
|
1539
|
+
}
|
|
1540
|
+
async function resolveViewerBindings(definition, base, overlay, resolver, lister) {
|
|
1541
|
+
const slots = definition.slots ?? {};
|
|
1542
|
+
let corrected = { ...overlay };
|
|
1543
|
+
if (lister) {
|
|
1544
|
+
for (const name of Object.keys(slots)) {
|
|
1545
|
+
const slot = slots[name];
|
|
1546
|
+
if (slot.type !== "anchor" || slot.scope) continue;
|
|
1547
|
+
const picked = ownGet3(overlay, name);
|
|
1548
|
+
const binding = picked?.kind === "anchor" ? picked : ownGet3(base, name);
|
|
1549
|
+
if (binding?.kind !== "anchor") continue;
|
|
1550
|
+
const { targetType, targetToken } = binding.anchor;
|
|
1551
|
+
let rows;
|
|
1552
|
+
try {
|
|
1553
|
+
rows = await listEntities(lister, targetType, slotTypeFilter(slot, targetType));
|
|
1554
|
+
} catch {
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
if (rows.length > 0 && !rows.some((r) => r.token === targetToken)) {
|
|
1558
|
+
corrected = { ...corrected, [name]: { kind: "anchor", anchor: { ...binding.anchor, targetToken: rows[0].token } } };
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return resolveContextBindings(definition, base, corrected, resolver);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1458
1565
|
// src/deep-link.ts
|
|
1459
1566
|
import { isValidToken } from "@devicechain/client";
|
|
1460
1567
|
var BIND_PARAM_PREFIX = "bind.";
|
|
@@ -1534,7 +1641,7 @@ function bindParamsOf(search) {
|
|
|
1534
1641
|
function slotSelector(slot, measurements, location) {
|
|
1535
1642
|
return location ? { kind: "slot", slot, measurements, location } : { kind: "slot", slot, measurements };
|
|
1536
1643
|
}
|
|
1537
|
-
function
|
|
1644
|
+
function sameBinding2(a, b) {
|
|
1538
1645
|
if (!a || !b) return a === b;
|
|
1539
1646
|
if (a.kind === "device" && b.kind === "device") return a.deviceToken === b.deviceToken;
|
|
1540
1647
|
if (a.kind === "anchor" && b.kind === "anchor") {
|
|
@@ -1558,7 +1665,7 @@ function bindingOfSelector(ds) {
|
|
|
1558
1665
|
return void 0;
|
|
1559
1666
|
}
|
|
1560
1667
|
function findOrAddSlot(slots, binding) {
|
|
1561
|
-
const existing = Object.keys(slots).find((k) => !slots[k].scope &&
|
|
1668
|
+
const existing = Object.keys(slots).find((k) => !slots[k].scope && sameBinding2(slots[k].defaultBinding, binding));
|
|
1562
1669
|
if (existing) return existing;
|
|
1563
1670
|
const name = nextSlotName(slots);
|
|
1564
1671
|
slots[name] = { type: binding.kind, label: bindingLabel(binding), defaultBinding: binding };
|
|
@@ -1584,7 +1691,7 @@ function bindWidgetSlot(def, widgetId, binding, measurements, location) {
|
|
|
1584
1691
|
const current = def.widgets.find((w) => w.id === widgetId)?.datasource;
|
|
1585
1692
|
const currentSlot = current?.kind === "slot" ? current.slot : void 0;
|
|
1586
1693
|
const currentDef = currentSlot ? slots[currentSlot] : void 0;
|
|
1587
|
-
const slot = currentSlot && currentDef && (currentDef.scope ||
|
|
1694
|
+
const slot = currentSlot && currentDef && (currentDef.scope || sameBinding2(currentDef.defaultBinding, binding)) ? currentSlot : findOrAddSlot(slots, binding);
|
|
1588
1695
|
const widgets = def.widgets.map(
|
|
1589
1696
|
(w) => (
|
|
1590
1697
|
// A placeholder for an unknown widget type binds nothing this build can see, so it is
|
|
@@ -1660,6 +1767,17 @@ function setSlotScope(def, slotName, scope) {
|
|
|
1660
1767
|
slots[slotName] = { ...current, scope: { parent: scope.parent, strategy: scope.strategy } };
|
|
1661
1768
|
return { ...def, slots };
|
|
1662
1769
|
}
|
|
1770
|
+
function setSlotCandidates(def, slotName, candidates) {
|
|
1771
|
+
if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;
|
|
1772
|
+
const current = def.slots[slotName];
|
|
1773
|
+
if (current.type !== "anchor" || current.scope) return def;
|
|
1774
|
+
const next = {};
|
|
1775
|
+
if (candidates?.areaType) next.areaType = candidates.areaType;
|
|
1776
|
+
if (candidates?.assetType) next.assetType = candidates.assetType;
|
|
1777
|
+
const { candidates: _drop, ...rest } = current;
|
|
1778
|
+
const slot = next.areaType || next.assetType ? { ...rest, candidates: next } : rest;
|
|
1779
|
+
return { ...def, slots: { ...def.slots, [slotName]: slot } };
|
|
1780
|
+
}
|
|
1663
1781
|
function widgetBinding(def, widget) {
|
|
1664
1782
|
const ds = widget.datasource;
|
|
1665
1783
|
if (ds?.kind === "slot") return def.slots?.[ds.slot]?.defaultBinding;
|
|
@@ -1682,88 +1800,10 @@ function resolveConcrete(def, widget) {
|
|
|
1682
1800
|
return binding.kind === "device" ? { kind: "device", deviceToken: binding.deviceToken, ...base } : { kind: "anchor", anchor: binding.anchor, ...base };
|
|
1683
1801
|
}
|
|
1684
1802
|
|
|
1685
|
-
// src/candidates.ts
|
|
1686
|
-
function ownGet2(map, key) {
|
|
1687
|
-
return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : void 0;
|
|
1688
|
-
}
|
|
1689
|
-
function sameBinding2(a, b) {
|
|
1690
|
-
if (!a) return false;
|
|
1691
|
-
if (a.kind === "device" && b.kind === "device") return a.deviceToken === b.deviceToken;
|
|
1692
|
-
if (a.kind === "anchor" && b.kind === "anchor") return a.anchor.targetToken === b.anchor.targetToken;
|
|
1693
|
-
return false;
|
|
1694
|
-
}
|
|
1695
|
-
async function resolveSlotCandidates(definition, slot, bindings, resolver, lister) {
|
|
1696
|
-
const slots = definition.slots ?? {};
|
|
1697
|
-
const def = ownGet2(slots, slot);
|
|
1698
|
-
if (!def) return [];
|
|
1699
|
-
const current = ownGet2(bindings, slot);
|
|
1700
|
-
const mark = (binding, label) => ({
|
|
1701
|
-
binding,
|
|
1702
|
-
label,
|
|
1703
|
-
selected: sameBinding2(current, binding)
|
|
1704
|
-
});
|
|
1705
|
-
if (def.scope) {
|
|
1706
|
-
if (def.scope.strategy === "first") return [];
|
|
1707
|
-
const parentBinding = ownGet2(bindings, def.scope.parent);
|
|
1708
|
-
if (!parentBinding || parentBinding.kind !== "anchor") return [];
|
|
1709
|
-
let members;
|
|
1710
|
-
try {
|
|
1711
|
-
members = [...await resolver.devicesForAnchor(parentBinding.anchor)].sort();
|
|
1712
|
-
} catch {
|
|
1713
|
-
return [];
|
|
1714
|
-
}
|
|
1715
|
-
return members.map((token) => mark({ kind: "device", deviceToken: token }, token));
|
|
1716
|
-
}
|
|
1717
|
-
if (def.type === "device") {
|
|
1718
|
-
let rows2;
|
|
1719
|
-
try {
|
|
1720
|
-
rows2 = await lister("device");
|
|
1721
|
-
} catch {
|
|
1722
|
-
return [];
|
|
1723
|
-
}
|
|
1724
|
-
return rows2.map((r) => mark({ kind: "device", deviceToken: r.token }, r.name || r.token));
|
|
1725
|
-
}
|
|
1726
|
-
const template = current?.kind === "anchor" ? current.anchor : def.defaultBinding?.kind === "anchor" ? def.defaultBinding.anchor : void 0;
|
|
1727
|
-
if (!template) return [];
|
|
1728
|
-
let rows;
|
|
1729
|
-
try {
|
|
1730
|
-
rows = await lister(template.targetType);
|
|
1731
|
-
} catch {
|
|
1732
|
-
return [];
|
|
1733
|
-
}
|
|
1734
|
-
return rows.map(
|
|
1735
|
-
(r) => mark(
|
|
1736
|
-
{
|
|
1737
|
-
kind: "anchor",
|
|
1738
|
-
anchor: {
|
|
1739
|
-
relationship: template.relationship,
|
|
1740
|
-
targetType: template.targetType,
|
|
1741
|
-
targetToken: r.token
|
|
1742
|
-
}
|
|
1743
|
-
},
|
|
1744
|
-
r.name || r.token
|
|
1745
|
-
)
|
|
1746
|
-
);
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
1803
|
// src/entity-lister.ts
|
|
1750
1804
|
import { gql } from "@devicechain/client";
|
|
1751
1805
|
|
|
1752
1806
|
// src/queries.ts
|
|
1753
|
-
var DEVICES_FOR_ANCHOR = `
|
|
1754
|
-
query DevicesForAnchor($criteria: EntityRelationshipSearchCriteria!) {
|
|
1755
|
-
entityRelationships(criteria: $criteria) {
|
|
1756
|
-
results {
|
|
1757
|
-
source {
|
|
1758
|
-
token
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
pagination {
|
|
1762
|
-
totalRecords
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
`;
|
|
1767
1807
|
var CUSTOMER_DEVICES = `
|
|
1768
1808
|
query DashboardCustomerDevices($criteria: CustomerDeviceSearchCriteria!) {
|
|
1769
1809
|
customerDevices(criteria: $criteria) {
|
|
@@ -1780,6 +1820,7 @@ var CUSTOMER_DEVICES = `
|
|
|
1780
1820
|
}
|
|
1781
1821
|
}
|
|
1782
1822
|
`;
|
|
1823
|
+
var ANCHOR_DEVICES = "query DashboardAnchorDevices($criteria: AnchorDeviceSearchCriteria!){anchorDevices(criteria:$criteria){results{token name deviceType{token}}pagination{totalRecords}}}";
|
|
1783
1824
|
var DEVICES_BY_TOKEN = `
|
|
1784
1825
|
query DashboardDevicesByToken($tokens: [String!]!) {
|
|
1785
1826
|
devicesByToken(tokens: $tokens) {
|
|
@@ -1871,28 +1912,29 @@ var LIST_PAGE_SIZE = 500;
|
|
|
1871
1912
|
function createEntityLister() {
|
|
1872
1913
|
const cache = /* @__PURE__ */ new Map();
|
|
1873
1914
|
const criteria = { pageNumber: 1, pageSize: LIST_PAGE_SIZE };
|
|
1874
|
-
return (kind) => {
|
|
1875
|
-
|
|
1915
|
+
return (kind, typeToken) => {
|
|
1916
|
+
const key = `${kind}|${typeToken}`;
|
|
1917
|
+
let pending = cache.get(key);
|
|
1876
1918
|
if (!pending) {
|
|
1877
|
-
pending = fetchKind(kind, criteria).catch((err) => {
|
|
1878
|
-
cache.delete(
|
|
1919
|
+
pending = fetchKind(kind, criteria, typeToken).catch((err) => {
|
|
1920
|
+
cache.delete(key);
|
|
1879
1921
|
throw err;
|
|
1880
1922
|
});
|
|
1881
|
-
cache.set(
|
|
1923
|
+
cache.set(key, pending);
|
|
1882
1924
|
}
|
|
1883
1925
|
return pending;
|
|
1884
1926
|
};
|
|
1885
1927
|
}
|
|
1886
|
-
function fetchKind(kind, criteria) {
|
|
1928
|
+
function fetchKind(kind, criteria, typeToken) {
|
|
1887
1929
|
switch (kind) {
|
|
1888
1930
|
case "device":
|
|
1889
1931
|
return gql("device-management", LIST_DEVICES, { criteria }).then((r) => r.devices.results);
|
|
1890
1932
|
case "customer":
|
|
1891
1933
|
return gql("device-management", LIST_CUSTOMERS, { criteria }).then((r) => r.customers.results);
|
|
1892
1934
|
case "area":
|
|
1893
|
-
return gql("device-management", LIST_AREAS, { criteria }).then((r) => r.areas.results);
|
|
1935
|
+
return gql("device-management", LIST_AREAS, { criteria: { ...criteria, areaTypeToken: typeToken } }).then((r) => r.areas.results);
|
|
1894
1936
|
case "asset":
|
|
1895
|
-
return gql("device-management", LIST_ASSETS, { criteria }).then((r) => r.assets.results);
|
|
1937
|
+
return gql("device-management", LIST_ASSETS, { criteria: { ...criteria, assetTypeToken: typeToken } }).then((r) => r.assets.results);
|
|
1896
1938
|
default:
|
|
1897
1939
|
return Promise.resolve([]);
|
|
1898
1940
|
}
|
|
@@ -2090,6 +2132,7 @@ var ALARM_STREAM = `
|
|
|
2090
2132
|
$state: String
|
|
2091
2133
|
$severity: String
|
|
2092
2134
|
$alarmKey: String
|
|
2135
|
+
$anchor: AlarmAnchor
|
|
2093
2136
|
) {
|
|
2094
2137
|
alarmStream(
|
|
2095
2138
|
originatorType: $originatorType
|
|
@@ -2097,6 +2140,7 @@ var ALARM_STREAM = `
|
|
|
2097
2140
|
state: $state
|
|
2098
2141
|
severity: $severity
|
|
2099
2142
|
alarmKey: $alarmKey
|
|
2143
|
+
anchor: $anchor
|
|
2100
2144
|
) {
|
|
2101
2145
|
alarmToken
|
|
2102
2146
|
eventType
|
|
@@ -2243,6 +2287,7 @@ var COMMAND_POLL_MS = 4e3;
|
|
|
2243
2287
|
var LOCATION_POLL_MS = 15e3;
|
|
2244
2288
|
var PRESENCE_POLL_MS = 15e3;
|
|
2245
2289
|
var PRESENCE_CHUNK = 1e3;
|
|
2290
|
+
var STAMPED = /* @__PURE__ */ new Set(["customer", "area", "asset"]);
|
|
2246
2291
|
var DashboardHub = class {
|
|
2247
2292
|
constructor(config) {
|
|
2248
2293
|
// Per alarm-subscription reconcile triggers — invoked after an ack/clear so the
|
|
@@ -2469,9 +2514,9 @@ var DashboardHub = class {
|
|
|
2469
2514
|
this.alarmDisposers.delete(dispose);
|
|
2470
2515
|
};
|
|
2471
2516
|
this.alarmDisposers.add(dispose);
|
|
2472
|
-
const reconcile = (
|
|
2517
|
+
const reconcile = (scope) => {
|
|
2473
2518
|
const gen = ++generation;
|
|
2474
|
-
this.queryAlarms(subscription,
|
|
2519
|
+
this.queryAlarms(subscription, scope).then((snapshot) => {
|
|
2475
2520
|
if (!disposed && gen === generation) sink.next(snapshot);
|
|
2476
2521
|
}).catch((err) => {
|
|
2477
2522
|
if (!disposed && gen === generation) sink.error?.(err);
|
|
@@ -2479,32 +2524,32 @@ var DashboardHub = class {
|
|
|
2479
2524
|
};
|
|
2480
2525
|
this.resolveAlarmScope(subscription.datasource).then((scope) => {
|
|
2481
2526
|
if (disposed) return;
|
|
2482
|
-
if (!scope.tenantWide && scope.tokens.length === 0) {
|
|
2527
|
+
if (!scope.tenantWide && !scope.anchor && scope.tokens.length === 0) {
|
|
2483
2528
|
sink.next({ alarms: [], total: 0 });
|
|
2484
2529
|
return;
|
|
2485
2530
|
}
|
|
2486
2531
|
const trigger = () => {
|
|
2487
2532
|
if (debounce) clearTimeout(debounce);
|
|
2488
|
-
debounce = setTimeout(() => reconcile(scope
|
|
2533
|
+
debounce = setTimeout(() => reconcile(scope), ALARM_RECONCILE_DEBOUNCE_MS);
|
|
2489
2534
|
};
|
|
2490
2535
|
const handle = this.connection.register();
|
|
2491
2536
|
const adapter = {
|
|
2492
2537
|
next: () => trigger(),
|
|
2493
2538
|
connected: (wasRetry) => {
|
|
2494
2539
|
handle.connected();
|
|
2495
|
-
if (wasRetry) reconcile(scope
|
|
2540
|
+
if (wasRetry) reconcile(scope);
|
|
2496
2541
|
},
|
|
2497
2542
|
closed: () => handle.closed()
|
|
2498
2543
|
};
|
|
2499
|
-
const closeSocket = subscribe(DEVICE_AREA, ALARM_STREAM, {}, adapter);
|
|
2544
|
+
const closeSocket = subscribe(DEVICE_AREA, ALARM_STREAM, { anchor: scope.anchor }, adapter);
|
|
2500
2545
|
unsubscribe = () => {
|
|
2501
2546
|
handle.release();
|
|
2502
2547
|
closeSocket();
|
|
2503
2548
|
};
|
|
2504
|
-
poll = setInterval(() => reconcile(scope
|
|
2505
|
-
reconciler = () => reconcile(scope
|
|
2549
|
+
poll = setInterval(() => reconcile(scope), ALARM_POLL_MS);
|
|
2550
|
+
reconciler = () => reconcile(scope);
|
|
2506
2551
|
this.alarmReconcilers.add(reconciler);
|
|
2507
|
-
reconcile(scope
|
|
2552
|
+
reconcile(scope);
|
|
2508
2553
|
}).catch((err) => {
|
|
2509
2554
|
if (!disposed) sink.error?.(err);
|
|
2510
2555
|
});
|
|
@@ -2518,6 +2563,9 @@ var DashboardHub = class {
|
|
|
2518
2563
|
// it resolves to zero devices like any other empty scope.
|
|
2519
2564
|
async resolveAlarmScope(datasource) {
|
|
2520
2565
|
if (!datasource) return { tenantWide: true, tokens: [] };
|
|
2566
|
+
if (datasource.kind === "anchor" && datasource.anchor.relationship === "assigned" && STAMPED.has(datasource.anchor.targetType)) {
|
|
2567
|
+
return { tenantWide: false, tokens: [], anchor: { type: datasource.anchor.targetType, token: datasource.anchor.targetToken } };
|
|
2568
|
+
}
|
|
2521
2569
|
const groups = await this.resolveDevices(datasource);
|
|
2522
2570
|
return { tenantWide: false, tokens: groups.map((g) => g.deviceToken) };
|
|
2523
2571
|
}
|
|
@@ -2525,13 +2573,13 @@ var DashboardHub = class {
|
|
|
2525
2573
|
// runs one query per originator device (the alarms query filters a single originator)
|
|
2526
2574
|
// and merges — deduped by token, newest first, capped to pageSize; total is the sum of
|
|
2527
2575
|
// per-originator match counts.
|
|
2528
|
-
async queryAlarms(sub, tokens,
|
|
2576
|
+
async queryAlarms(sub, { tenantWide, tokens, anchor }) {
|
|
2529
2577
|
const filters = {
|
|
2530
2578
|
state: sub.state ?? null,
|
|
2531
2579
|
severity: sub.severity ?? null,
|
|
2532
2580
|
acknowledged: sub.acknowledged ?? null
|
|
2533
2581
|
};
|
|
2534
|
-
const originators = tenantWide ? [{ originatorType: null, originator: null }] : tokens.map((token) => ({ originatorType: "device", originator: token }));
|
|
2582
|
+
const originators = tenantWide || anchor ? [{ originatorType: null, originator: null, anchor }] : tokens.map((token) => ({ originatorType: "device", originator: token }));
|
|
2535
2583
|
if (sub.countBySeverity) {
|
|
2536
2584
|
const pages2 = await Promise.all(
|
|
2537
2585
|
originators.map(
|
|
@@ -2889,9 +2937,9 @@ var DashboardHub = class {
|
|
|
2889
2937
|
}
|
|
2890
2938
|
}
|
|
2891
2939
|
// isDatasourceSupported reports whether the viewer is offered the binding at all: false only
|
|
2892
|
-
// when resolving an anchor is refused as SCOPE_UNSUPPORTED (an
|
|
2893
|
-
// customer user). The resolution is the same
|
|
2894
|
-
// supported anchor costs no second read. Every other answer, including a failure, is true.
|
|
2940
|
+
// when resolving an anchor is refused as SCOPE_UNSUPPORTED (an anchor the platform still
|
|
2941
|
+
// declines for a customer user; areas and assets are answered through anchorDevices). The resolution is the same
|
|
2942
|
+
// memoized one the widget's channel makes, so a supported anchor costs no second read. Every other answer, including a failure, is true.
|
|
2895
2943
|
async isDatasourceSupported(datasource) {
|
|
2896
2944
|
if (datasource?.kind !== "anchor") return true;
|
|
2897
2945
|
try {
|
|
@@ -3576,7 +3624,7 @@ function parseBoardRecording(input) {
|
|
|
3576
3624
|
"alarms",
|
|
3577
3625
|
"locations"
|
|
3578
3626
|
],
|
|
3579
|
-
["excerpt", "declaredMetrics"]
|
|
3627
|
+
["excerpt", "declaredMetrics", "anchors"]
|
|
3580
3628
|
);
|
|
3581
3629
|
if (root.kind !== RECORDING_KIND) {
|
|
3582
3630
|
throw new RecordingFormatError("$.kind", `expected ${JSON.stringify(RECORDING_KIND)}`);
|
|
@@ -3628,6 +3676,33 @@ function parseBoardRecording(input) {
|
|
|
3628
3676
|
declaredMetrics[kind] = names;
|
|
3629
3677
|
}
|
|
3630
3678
|
}
|
|
3679
|
+
let anchors;
|
|
3680
|
+
if (root.anchors !== void 0) {
|
|
3681
|
+
if (version === 1) throw new RecordingFormatError("$.anchors", "version 1 carries no anchors");
|
|
3682
|
+
const listed = column(root.anchors, "$.anchors", (x, p) => {
|
|
3683
|
+
const o = strict(x, p, ["relationship", "targetType", "targetToken", "devices"]);
|
|
3684
|
+
const members = column(o.devices, `${p}.devices`, str);
|
|
3685
|
+
if (members.length === 0) throw new RecordingFormatError(`${p}.devices`, "has no member; leave the anchor out instead");
|
|
3686
|
+
if (new Set(members).size !== members.length) throw new RecordingFormatError(`${p}.devices`, "duplicate member");
|
|
3687
|
+
for (const m2 of members) {
|
|
3688
|
+
if (!tokens.has(m2)) throw new RecordingFormatError(`${p}.devices`, `'${m2}' is not a device of the run`);
|
|
3689
|
+
}
|
|
3690
|
+
return {
|
|
3691
|
+
relationship: str(o.relationship, `${p}.relationship`),
|
|
3692
|
+
targetType: oneOf2(o.targetType, `${p}.targetType`, ["customer", "area", "asset"]),
|
|
3693
|
+
targetToken: str(o.targetToken, `${p}.targetToken`),
|
|
3694
|
+
devices: members
|
|
3695
|
+
};
|
|
3696
|
+
});
|
|
3697
|
+
if (listed.length === 0) throw new RecordingFormatError("$.anchors", "declares nothing; leave it out instead");
|
|
3698
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3699
|
+
listed.forEach((a, i) => {
|
|
3700
|
+
const key = `${a.relationship}\0${a.targetType}\0${a.targetToken}`;
|
|
3701
|
+
if (seen.has(key)) throw new RecordingFormatError(`$.anchors[${i}]`, "the same anchor is declared twice");
|
|
3702
|
+
seen.add(key);
|
|
3703
|
+
});
|
|
3704
|
+
anchors = listed;
|
|
3705
|
+
}
|
|
3631
3706
|
const ch = strict(root.channels, "$.channels", ["measurements", "alarms", "locations", "commands"]);
|
|
3632
3707
|
if (version === 1 && ch.locations !== false) {
|
|
3633
3708
|
throw new RecordingFormatError("$.channels.locations", "version 1 records no positions; must be false");
|
|
@@ -3832,6 +3907,7 @@ function parseBoardRecording(input) {
|
|
|
3832
3907
|
};
|
|
3833
3908
|
if (excerpt) rec.excerpt = excerpt;
|
|
3834
3909
|
if (declaredMetrics) rec.declaredMetrics = declaredMetrics;
|
|
3910
|
+
if (anchors) rec.anchors = anchors;
|
|
3835
3911
|
return rec;
|
|
3836
3912
|
}
|
|
3837
3913
|
|
|
@@ -3999,7 +4075,8 @@ function retimeShiftMs(rec, cursorMs, nowMs = Date.now()) {
|
|
|
3999
4075
|
function sameAnchor(a, b) {
|
|
4000
4076
|
return a.relationship === b.relationship && a.targetType === b.targetType && a.targetToken === b.targetToken;
|
|
4001
4077
|
}
|
|
4002
|
-
function declaredSite(options) {
|
|
4078
|
+
function declaredSite(options, rec) {
|
|
4079
|
+
if (rec?.anchors) return null;
|
|
4003
4080
|
const anchors = options.siteAnchors;
|
|
4004
4081
|
if (anchors === void 0) return null;
|
|
4005
4082
|
if (anchors.length !== 1) {
|
|
@@ -4012,7 +4089,17 @@ function declaredSite(options) {
|
|
|
4012
4089
|
}
|
|
4013
4090
|
return anchors[0];
|
|
4014
4091
|
}
|
|
4092
|
+
function recordedMembers(rec, anchor) {
|
|
4093
|
+
if (!rec.anchors) return null;
|
|
4094
|
+
const hit = rec.anchors.find((a) => sameAnchor(a, anchor));
|
|
4095
|
+
if (!hit) {
|
|
4096
|
+
throw new Error(`anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`);
|
|
4097
|
+
}
|
|
4098
|
+
return hit.devices;
|
|
4099
|
+
}
|
|
4015
4100
|
function anchorMembers(rec, site, anchor) {
|
|
4101
|
+
const own2 = recordedMembers(rec, anchor);
|
|
4102
|
+
if (own2) return own2;
|
|
4016
4103
|
if (!site || !sameAnchor(site, anchor)) {
|
|
4017
4104
|
throw new Error(
|
|
4018
4105
|
`anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`
|
|
@@ -4022,16 +4109,17 @@ function anchorMembers(rec, site, anchor) {
|
|
|
4022
4109
|
}
|
|
4023
4110
|
function createRecordedResolver(rec, options = {}) {
|
|
4024
4111
|
const devices = new Set(rec.devices.map((d) => d.token));
|
|
4025
|
-
const site = declaredSite(options);
|
|
4112
|
+
const site = declaredSite(options, rec);
|
|
4026
4113
|
return {
|
|
4027
4114
|
devicesForAnchor: async (anchor) => anchorMembers(rec, site, anchor),
|
|
4028
4115
|
deviceExists: async (token) => devices.has(token)
|
|
4029
4116
|
};
|
|
4030
4117
|
}
|
|
4031
4118
|
function createRecordedLister(rec, options = {}) {
|
|
4032
|
-
const site = declaredSite(options);
|
|
4119
|
+
const site = declaredSite(options, rec);
|
|
4033
4120
|
return async (kind) => {
|
|
4034
4121
|
if (kind === "device") return rec.devices.map((d) => ({ token: d.token, name: null }));
|
|
4122
|
+
if (rec.anchors) return rec.anchors.filter((a) => a.targetType === kind).map((a) => ({ token: a.targetToken, name: null }));
|
|
4035
4123
|
return site && site.targetType === kind ? [{ token: site.targetToken, name: null }] : [];
|
|
4036
4124
|
};
|
|
4037
4125
|
}
|
|
@@ -4073,7 +4161,7 @@ var RecordedDataSource = class {
|
|
|
4073
4161
|
this.subs = /* @__PURE__ */ new Set();
|
|
4074
4162
|
this.stale = false;
|
|
4075
4163
|
this.rec = rec;
|
|
4076
|
-
this.site = declaredSite(options);
|
|
4164
|
+
this.site = declaredSite(options, rec);
|
|
4077
4165
|
this.index = indexOf(rec);
|
|
4078
4166
|
this.cursor = clock.timeMs;
|
|
4079
4167
|
const shift = options.timeShiftMs ?? 0;
|
|
@@ -5169,7 +5257,7 @@ function pasteWidgets(def, clip, at) {
|
|
|
5169
5257
|
if (known) return known;
|
|
5170
5258
|
const source = own(clip.slots, from);
|
|
5171
5259
|
const target = own(slots, from);
|
|
5172
|
-
const keep = sameTenant && target !== void 0 && source !== void 0 && target.type === source.type && (sameBoard ||
|
|
5260
|
+
const keep = sameTenant && target !== void 0 && source !== void 0 && target.type === source.type && (sameBoard || sameBinding2(target.defaultBinding, source.defaultBinding));
|
|
5173
5261
|
let result;
|
|
5174
5262
|
if (keep) result = { slot: from };
|
|
5175
5263
|
else {
|
|
@@ -5231,7 +5319,7 @@ function duplicateWidget(def, id) {
|
|
|
5231
5319
|
}
|
|
5232
5320
|
|
|
5233
5321
|
// src/resolver.ts
|
|
5234
|
-
import { gql as gql3 } from "@devicechain/client";
|
|
5322
|
+
import { gql as gql3, isScopeUnsupportedError as isScopeUnsupportedError2 } from "@devicechain/client";
|
|
5235
5323
|
var ANCHOR_PAGE_SIZE = 500;
|
|
5236
5324
|
var DECLARED_BATCH_SIZE = 100;
|
|
5237
5325
|
var trueSize = (reported, page) => Math.max(reported ?? page, page);
|
|
@@ -5254,18 +5342,29 @@ function readAnchorPage(anchor) {
|
|
|
5254
5342
|
};
|
|
5255
5343
|
});
|
|
5256
5344
|
}
|
|
5257
|
-
return
|
|
5345
|
+
return readAreaOrAssetPage(anchor);
|
|
5346
|
+
}
|
|
5347
|
+
function readAreaOrAssetPage(anchor) {
|
|
5348
|
+
const read = (relationshipType) => gql3("device-management", ANCHOR_DEVICES, {
|
|
5258
5349
|
criteria: {
|
|
5259
5350
|
pageNumber: 1,
|
|
5260
5351
|
pageSize: ANCHOR_PAGE_SIZE,
|
|
5261
|
-
sourceType: "device",
|
|
5262
5352
|
targetType: anchor.targetType,
|
|
5263
5353
|
target: anchor.targetToken,
|
|
5264
|
-
relationshipType
|
|
5354
|
+
relationshipType
|
|
5265
5355
|
}
|
|
5356
|
+
});
|
|
5357
|
+
return read(anchor.relationship).catch((err) => {
|
|
5358
|
+
if (!isScopeUnsupportedError2(err)) throw err;
|
|
5359
|
+
return read();
|
|
5266
5360
|
}).then((r) => {
|
|
5267
|
-
const
|
|
5268
|
-
return {
|
|
5361
|
+
const rows = r.anchorDevices.results;
|
|
5362
|
+
return {
|
|
5363
|
+
tokens: rows.map((d) => d.token),
|
|
5364
|
+
total: trueSize(r.anchorDevices.pagination?.totalRecords, rows.length),
|
|
5365
|
+
names: new Map(rows.map((d) => [d.token, d.name])),
|
|
5366
|
+
types: new Map(rows.map((d) => [d.token, d.deviceType?.token ?? null]))
|
|
5367
|
+
};
|
|
5269
5368
|
});
|
|
5270
5369
|
}
|
|
5271
5370
|
function createDeviceResolver() {
|
|
@@ -5293,29 +5392,16 @@ function createDeviceResolver() {
|
|
|
5293
5392
|
devicesForAnchor(anchor) {
|
|
5294
5393
|
return anchorPage(anchor).then((page) => page.tokens);
|
|
5295
5394
|
},
|
|
5296
|
-
// The members of an anchor with their names and the anchor's true size
|
|
5297
|
-
// devicesForAnchor
|
|
5298
|
-
// size of the fleet. A failed name read is not a failed roster: the tokens still stream, and
|
|
5299
|
-
// the widget labels them by token.
|
|
5395
|
+
// The members of an anchor with their names and the anchor's true size: ONE page, shared with
|
|
5396
|
+
// devicesForAnchor, which carries every member's name and device type.
|
|
5300
5397
|
anchorMembers(anchor) {
|
|
5301
5398
|
const key = anchorKey(anchor);
|
|
5302
5399
|
let pending = memberCache.get(key);
|
|
5303
5400
|
if (!pending) {
|
|
5304
|
-
pending = anchorPage(anchor).then(
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
try {
|
|
5309
|
-
const r = await gql3("device-management", DEVICE_NAMES, { tokens });
|
|
5310
|
-
for (const d of r.devicesByToken) {
|
|
5311
|
-
names.set(d.token, d.name);
|
|
5312
|
-
types.set(d.token, d.deviceType?.token ?? null);
|
|
5313
|
-
}
|
|
5314
|
-
} catch {
|
|
5315
|
-
}
|
|
5316
|
-
}
|
|
5317
|
-
return { members: tokens.map((token) => ({ token, name: names.get(token) ?? null, deviceType: types.get(token) ?? null })), total };
|
|
5318
|
-
}).catch((err) => {
|
|
5401
|
+
pending = anchorPage(anchor).then(({ tokens, total, names, types }) => ({
|
|
5402
|
+
members: tokens.map((token) => ({ token, name: names.get(token) ?? null, deviceType: types.get(token) ?? null })),
|
|
5403
|
+
total
|
|
5404
|
+
})).catch((err) => {
|
|
5319
5405
|
memberCache.delete(key);
|
|
5320
5406
|
throw err;
|
|
5321
5407
|
});
|
|
@@ -6294,6 +6380,7 @@ export {
|
|
|
6294
6380
|
gridPlacementToPx,
|
|
6295
6381
|
gridRect,
|
|
6296
6382
|
gutterPx,
|
|
6383
|
+
hasRootAnchorSlots,
|
|
6297
6384
|
hasScopedSlots,
|
|
6298
6385
|
historyWindowFor,
|
|
6299
6386
|
initialTimeRange,
|
|
@@ -6353,10 +6440,11 @@ export {
|
|
|
6353
6440
|
resolveFrame,
|
|
6354
6441
|
resolvePlacement,
|
|
6355
6442
|
resolveSlotCandidates,
|
|
6443
|
+
resolveViewerBindings,
|
|
6356
6444
|
resolveWidgetDatasource,
|
|
6357
6445
|
resumeRange,
|
|
6358
6446
|
retimeShiftMs,
|
|
6359
|
-
sameBinding,
|
|
6447
|
+
sameBinding2 as sameBinding,
|
|
6360
6448
|
sameLabel,
|
|
6361
6449
|
sealHistory,
|
|
6362
6450
|
seedBucketCount,
|
|
@@ -6369,6 +6457,7 @@ export {
|
|
|
6369
6457
|
setCanvasSizing,
|
|
6370
6458
|
setRelativeAnchor,
|
|
6371
6459
|
setRelativeOffset,
|
|
6460
|
+
setSlotCandidates,
|
|
6372
6461
|
setSlotScope,
|
|
6373
6462
|
setTitle,
|
|
6374
6463
|
setWidgetPlacement,
|