@datalabz_/dashboard-kit 6.0.2 → 6.0.4
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 +29 -0
- package/package.json +1 -1
- package/v4.js +1 -1
- package/v5.js +1 -1
- package/v6.js +94 -1
- package/wrapper.css +9 -0
- package/wrapper.js +35 -5
package/README.md
CHANGED
|
@@ -53,6 +53,35 @@ A major's files are one served bundle; the repo's per-file line budget
|
|
|
53
53
|
(AGENTS.md #2) does not apply to it — it cannot be split without changing the
|
|
54
54
|
URLs already pinned by live dashboards.
|
|
55
55
|
|
|
56
|
+
## Asking a dashboard for its filters
|
|
57
|
+
|
|
58
|
+
A parent may post `{ type: 'DK_GET_FILTERS_INFO' }` to a dashboard's frame; the kit answers the parent with
|
|
59
|
+
`{ type: 'DK_FILTERS_INFO', filters: [...] }`. It needs no token or period, so a parent can ask as soon as
|
|
60
|
+
the frame has loaded. The list is the header controls the dashboard can be opened with, which is
|
|
61
|
+
what the kit can write into a header control at its first boot, one entry each:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
{ id: 'playerUsername', label: 'Player username', type: 'text', // type: the input's type, 'select' or 'textarea'
|
|
65
|
+
required: true, value: '',
|
|
66
|
+
placeholder, min, max, // only when the control sets them
|
|
67
|
+
disabled: true, // only when it is disabled
|
|
68
|
+
multiple: false, optionsTotal: 2, // select only
|
|
69
|
+
options: [{ value: 'BRL', label: 'Real' }] } // select only: the first 100 options
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The list follows the fill, so it holds only what a filter can actually be given:
|
|
73
|
+
|
|
74
|
+
- Left out: a control with no `id`, a second control with an `id` already listed, and radio, file,
|
|
75
|
+
hidden, password and button controls, which the kit refuses to fill.
|
|
76
|
+
- A select lists only options with a value, since an empty value is refused, and `optionsTotal` counts
|
|
77
|
+
those. A select with none is left out: it takes no value.
|
|
78
|
+
- What exists when the dashboard boots is what counts. Once it has booted, the answer is the list taken
|
|
79
|
+
at that moment, so options a section loads later by query, and controls added later, are not in it, as
|
|
80
|
+
they were not there for the fill. Only `value` and `disabled` are read again. A filter whose options
|
|
81
|
+
come from a query must therefore use static `<option>`s or a plain input to be openable.
|
|
82
|
+
|
|
83
|
+
The answer always goes to the dashboard's parent, whoever asked.
|
|
84
|
+
|
|
56
85
|
## Rules
|
|
57
86
|
|
|
58
87
|
- **Where a change lands — the current `vN` or a new major — is decided by
|
package/package.json
CHANGED
package/v4.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
let _activeTabId = '';
|
|
21
21
|
let _sections = []; // { id, label, load, loaded, errors }
|
|
22
22
|
let _title = '';
|
|
23
|
-
let _gitVer = '
|
|
23
|
+
let _gitVer = 'ce29793'; // replaced at deploy: sed -i "s/ce29793/$(git rev-parse --short HEAD)/g" v4.js
|
|
24
24
|
let _calls = []; // System Info call log
|
|
25
25
|
let _sectionErrors = {}; // sectionId → error count
|
|
26
26
|
let _generation = 0;
|
package/v5.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
let _activeTabId = '';
|
|
21
21
|
let _sections = []; // { id, label, load, loaded, errors }
|
|
22
22
|
let _title = '';
|
|
23
|
-
let _gitVer = '
|
|
23
|
+
let _gitVer = 'ce29793'; // replaced at deploy: sed -i "s/ce29793/$(git rev-parse --short HEAD)/g" v4.js
|
|
24
24
|
let _calls = []; // System Info call log
|
|
25
25
|
let _sectionErrors = {}; // sectionId → error count
|
|
26
26
|
let _generation = 0;
|
package/v6.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
let _activeTabId = '';
|
|
21
21
|
let _sections = []; // { id, label, load, loaded, errors }
|
|
22
22
|
let _title = '';
|
|
23
|
-
let _gitVer = '
|
|
23
|
+
let _gitVer = 'ce29793'; // replaced at deploy: sed -i "s/ce29793/$(git rev-parse --short HEAD)/g" v6.js
|
|
24
24
|
let _calls = []; // System Info call log
|
|
25
25
|
let _sectionErrors = {}; // sectionId → error count
|
|
26
26
|
let _generation = 0;
|
|
@@ -931,8 +931,100 @@ details.dk-call summary::-webkit-details-marker {
|
|
|
931
931
|
} catch { return false; }
|
|
932
932
|
}
|
|
933
933
|
|
|
934
|
+
// ── Filters a parent can ask for ──────────────────────────────────────────────
|
|
935
|
+
// DK_GET_FILTERS_INFO from the parent is answered with DK_FILTERS_INFO: the header controls a
|
|
936
|
+
// dashboard can be opened with, which is what the kit can write into a header control at its first boot. So
|
|
937
|
+
// the list follows that flow and nothing else: a control it cannot fill is not listed (radio,
|
|
938
|
+
// file, hidden, password, buttons; no id; a second control with an id already listed), a select
|
|
939
|
+
// lists only options a value may name, since an empty value is refused, and a select with none
|
|
940
|
+
// is left out. What exists when the dashboard boots is what counts: once it has booted, the list
|
|
941
|
+
// is the one taken at that moment, so options a section loads later by query are not in it, as
|
|
942
|
+
// they were not there for the fill. Only value and disabled are read again. It needs no token or
|
|
943
|
+
// period, so a parent can ask as soon as the frame has loaded.
|
|
944
|
+
const UNFILLABLE_TYPES = ['radio', 'file', 'hidden', 'password', 'button', 'submit', 'reset', 'image'];
|
|
945
|
+
let _bootFilters = null; // the list as it was when the dashboard booted
|
|
946
|
+
const FILTER_MAX_OPTIONS = 100;
|
|
947
|
+
const FILTER_MAX_TEXT = 500;
|
|
948
|
+
|
|
949
|
+
function _clip(s, n) {
|
|
950
|
+
s = String(s == null ? '' : s);
|
|
951
|
+
return s.length > n ? s.slice(0, n) : s;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function _filterLabel(el) {
|
|
955
|
+
const wrap = el.labels && el.labels[0];
|
|
956
|
+
if (wrap) {
|
|
957
|
+
const c = wrap.cloneNode(true);
|
|
958
|
+
c.querySelectorAll('input, select, textarea, .dk-tip, [aria-hidden="true"], script, style').forEach(function (n) { n.remove(); });
|
|
959
|
+
const t = c.textContent.replace(/\s+/g, ' ').trim();
|
|
960
|
+
if (t) return _clip(t, 200);
|
|
961
|
+
}
|
|
962
|
+
return _clip(el.getAttribute('aria-label') || el.getAttribute('placeholder') || '', 200);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function _controlValue(el) {
|
|
966
|
+
if (el.tagName === 'SELECT') {
|
|
967
|
+
return el.multiple ? Array.from(el.selectedOptions).map(function (o) { return o.value; }) : el.value;
|
|
968
|
+
}
|
|
969
|
+
return el.type === 'checkbox' ? el.checked : _clip(el.value, FILTER_MAX_TEXT);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function _dashboardFilters() {
|
|
973
|
+
const header = document.querySelector('[data-dk-header]');
|
|
974
|
+
const out = [];
|
|
975
|
+
if (!header) return out;
|
|
976
|
+
const seen = {};
|
|
977
|
+
header.querySelectorAll('input, select, textarea').forEach(function (el) {
|
|
978
|
+
if (!el.id || seen[el.id] || UNFILLABLE_TYPES.indexOf(el.type) >= 0) return;
|
|
979
|
+
const isSelect = el.tagName === 'SELECT';
|
|
980
|
+
const named = isSelect ? Array.from(el.options).filter(function (o) { return o.value !== ''; }) : null;
|
|
981
|
+
if (isSelect && !named.length) return;
|
|
982
|
+
seen[el.id] = true;
|
|
983
|
+
const f = {
|
|
984
|
+
id: el.id,
|
|
985
|
+
label: _filterLabel(el),
|
|
986
|
+
type: isSelect ? 'select' : el.tagName === 'TEXTAREA' ? 'textarea' : el.type,
|
|
987
|
+
required: el.required,
|
|
988
|
+
};
|
|
989
|
+
if (el.disabled) f.disabled = true;
|
|
990
|
+
if (!isSelect) {
|
|
991
|
+
['placeholder', 'min', 'max'].forEach(function (a) {
|
|
992
|
+
if (el.getAttribute(a) !== null) f[a] = _clip(el.getAttribute(a), 100);
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (isSelect) {
|
|
996
|
+
f.multiple = el.multiple;
|
|
997
|
+
f.optionsTotal = named.length;
|
|
998
|
+
f.options = named.slice(0, FILTER_MAX_OPTIONS).map(function (o) {
|
|
999
|
+
return { value: _clip(o.value, 200), label: _clip(o.textContent.trim(), 200) };
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
f.value = _controlValue(el);
|
|
1003
|
+
out.push(f);
|
|
1004
|
+
});
|
|
1005
|
+
return out;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// What DK_FILTERS_INFO carries: the booted list once there is one, with the values as they are now.
|
|
1009
|
+
function _filtersInfo() {
|
|
1010
|
+
const list = _bootFilters || _dashboardFilters();
|
|
1011
|
+
const header = document.querySelector('[data-dk-header]');
|
|
1012
|
+
list.forEach(function (f) {
|
|
1013
|
+
const el = header ? header.querySelector('#' + CSS.escape(f.id)) : null;
|
|
1014
|
+
if (!el) return;
|
|
1015
|
+
f.value = _controlValue(el);
|
|
1016
|
+
if (el.disabled) f.disabled = true; else delete f.disabled;
|
|
1017
|
+
});
|
|
1018
|
+
return list;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
|
|
934
1022
|
function _onMessage(e) {
|
|
935
1023
|
if (!e.data || typeof e.data !== 'object') return;
|
|
1024
|
+
if (e.data.type === 'DK_GET_FILTERS_INFO') {
|
|
1025
|
+
window.parent.postMessage({ type: 'DK_FILTERS_INFO', filters: _filtersInfo() }, '*');
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
936
1028
|
if (e.data.type === 'RK_SNAPSHOT') { _snapshot(); return; }
|
|
937
1029
|
if (e.data.type === 'CHAT_MODE') { document.documentElement.dataset.chatMode = 'true'; return; }
|
|
938
1030
|
if (e.data.type !== 'CUSTOM_DASHBOARDS_PARAMS') return;
|
|
@@ -960,6 +1052,7 @@ details.dk-call summary::-webkit-details-marker {
|
|
|
960
1052
|
}
|
|
961
1053
|
|
|
962
1054
|
if (!_hasBooted && _authToken && _dateFromIso && _dateToIso && _timezone) {
|
|
1055
|
+
_bootFilters = _dashboardFilters(); // before any section runs
|
|
963
1056
|
_hasBooted = true;
|
|
964
1057
|
const wait = document.getElementById('dk-wait');
|
|
965
1058
|
if (wait) wait.hidden = true;
|
package/wrapper.css
CHANGED
|
@@ -52,6 +52,15 @@ header.topbar {
|
|
|
52
52
|
flex-wrap: wrap;
|
|
53
53
|
flex-shrink: 0;
|
|
54
54
|
}
|
|
55
|
+
.platform-badge {
|
|
56
|
+
font-size: 14px;
|
|
57
|
+
font-weight: 700;
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
color: var(--text);
|
|
60
|
+
}
|
|
61
|
+
.platform-badge b {
|
|
62
|
+
color: var(--brand);
|
|
63
|
+
}
|
|
55
64
|
header .filters {
|
|
56
65
|
display: flex;
|
|
57
66
|
gap: 10px;
|
package/wrapper.js
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
// Where each platform's BackOffice lives; the page picks one with <script src=".../wrapper.js" data-platform="poker">.
|
|
5
|
+
// No data-platform means igaming, as before. An unknown name is an error, never a guess.
|
|
6
|
+
// apiBase — sign-in (the BackOffice's own API)
|
|
7
|
+
// gatewayEndpoint — the SQL gateway a dashboard's queries go to, sent as `endpoint`
|
|
8
|
+
// storagePrefix — localStorage key prefix, so the two keep their own sign-in on one origin
|
|
9
|
+
// label — the name in the top bar, before the word "Platform", that says which BackOffice this page talks to
|
|
10
|
+
const PLATFORMS = {
|
|
11
|
+
igaming: {
|
|
12
|
+
label: "iGaming",
|
|
13
|
+
apiBase: "https://bcproxy.draftplaza.com",
|
|
14
|
+
gatewayEndpoint: "https://aidqgateway.draftplaza.com/aidq",
|
|
15
|
+
storagePrefix: "iqg.harness.",
|
|
16
|
+
},
|
|
17
|
+
poker: {
|
|
18
|
+
label: "Poker",
|
|
19
|
+
apiBase: "https://pokerreporting.pokerplaza.com",
|
|
20
|
+
gatewayEndpoint: "https://aidqgateway.pokerplaza.com/aidq",
|
|
21
|
+
storagePrefix: "iqg.harness.poker.",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const platformName = (document.currentScript && document.currentScript.dataset.platform) || "igaming";
|
|
25
|
+
const CFG = PLATFORMS[platformName];
|
|
26
|
+
if (!CFG) throw new Error('wrapper: data-platform "' + platformName + '" is not one of ' + Object.keys(PLATFORMS).join(", "));
|
|
27
|
+
const API_BASE = CFG.apiBase;
|
|
28
|
+
const LS_TIMEZONE = CFG.storagePrefix + "timezone";
|
|
29
|
+
const LS_TOKEN = CFG.storagePrefix + "token";
|
|
30
|
+
const LS_USER = CFG.storagePrefix + "user";
|
|
31
|
+
const LS_THEME = CFG.storagePrefix + "theme";
|
|
9
32
|
|
|
10
33
|
// Capture srcdoc from the inline iframe before DOM rebuild.
|
|
11
34
|
const _srcdoc = document.getElementById("iframe")?.getAttribute("srcdoc") || "";
|
|
12
35
|
|
|
13
36
|
document.body.innerHTML = `
|
|
14
37
|
<header class="topbar">
|
|
38
|
+
<span class="platform-badge" id="platformBadge"></span>
|
|
15
39
|
<div class="filters">
|
|
16
40
|
<label>Date
|
|
17
41
|
<select id="datePreset">
|
|
@@ -94,6 +118,10 @@
|
|
|
94
118
|
<div class="toast" id="toast"></div>`;
|
|
95
119
|
|
|
96
120
|
document.getElementById("iframe").setAttribute("srcdoc", _srcdoc);
|
|
121
|
+
const badge = document.getElementById("platformBadge");
|
|
122
|
+
badge.innerHTML = "<b></b> Platform";
|
|
123
|
+
badge.firstChild.textContent = CFG.label;
|
|
124
|
+
document.title = CFG.label + " · " + document.title;
|
|
97
125
|
|
|
98
126
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
99
127
|
|
|
@@ -179,6 +207,8 @@
|
|
|
179
207
|
const hid = State.filters.hierarchyId;
|
|
180
208
|
return {
|
|
181
209
|
type: "CUSTOM_DASHBOARDS_PARAMS", token: State.token, theme: State.theme,
|
|
210
|
+
// The SQL gateway of this BackOffice; the kit would otherwise use its own default.
|
|
211
|
+
endpoint: CFG.gatewayEndpoint,
|
|
182
212
|
filters: {
|
|
183
213
|
dateFrom: r.dateFromIso, dateTo: r.dateToIso, timezone: tz,
|
|
184
214
|
hierarchyId: hid == null || Number.isNaN(hid) ? null : Number(hid)
|