mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
Update dashboard.patch
This commit is contained in:
1 parent
f298335639
commit
9ba99c62f8
1 file changed
-350
@@ -43,353 +43,3 @@
|
|||||||
head: [ _('Hostname'), _('IP Address'), _('MAC') ],
|
head: [ _('Hostname'), _('IP Address'), _('MAC') ],
|
||||||
rows: this.params.lan.devices.map(device => [
|
rows: this.params.lan.devices.map(device => [
|
||||||
device.hostname,
|
device.hostname,
|
||||||
diff --git a/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/include/30_wifi.js b/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/include/30_wifi.js
|
|
||||||
index 232241aca403..c081c073f717 100644
|
|
||||||
--- a/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/include/30_wifi.js
|
|
||||||
+++ b/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/include/30_wifi.js
|
|
||||||
@@ -210,6 +210,7 @@ return baseclass.extend({
|
|
||||||
|
|
||||||
renderClientTable() {
|
|
||||||
return charts.table({
|
|
||||||
+ id: 'dashboard-wifi-table',
|
|
||||||
head: [
|
|
||||||
_('Hostname'),
|
|
||||||
_('IP Address'),
|
|
||||||
@@ -242,11 +243,23 @@ return baseclass.extend({
|
|
||||||
},
|
|
||||||
|
|
||||||
renderTab() {
|
|
||||||
- return E('div', {}, [
|
|
||||||
- E('div', { 'class': 'dashboard-net-grid' }, this.params.wifi.radios.map(radio => this.renderNetworkCard(radio))),
|
|
||||||
+ const netGrid = E('div', { 'class': 'dashboard-net-grid' }, this.params.wifi.radios.map(radio => this.renderNetworkCard(radio)));
|
|
||||||
+ const clientTable = this.renderClientTable();
|
|
||||||
+
|
|
||||||
+ if (this.tabNode && this.tabNode.parentNode) {
|
|
||||||
+ const oldGrid = this.tabNode.querySelector('.dashboard-net-grid');
|
|
||||||
+ if (oldGrid)
|
|
||||||
+ this.tabNode.replaceChild(netGrid, oldGrid);
|
|
||||||
+ return this.tabNode;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ this.tabNode = E('div', {}, [
|
|
||||||
+ netGrid,
|
|
||||||
E('h3', {}, [ _('Wireless clients') ]),
|
|
||||||
- this.renderClientTable()
|
|
||||||
+ clientTable
|
|
||||||
]);
|
|
||||||
+
|
|
||||||
+ return this.tabNode;
|
|
||||||
},
|
|
||||||
|
|
||||||
clientAddresses(ipv4, hint) {
|
|
||||||
diff --git a/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/index.js b/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/index.js
|
|
||||||
index 5707919f514f..6a8e755bfc0d 100644
|
|
||||||
--- a/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/index.js
|
|
||||||
+++ b/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/index.js
|
|
||||||
@@ -122,48 +122,98 @@ function fillRows(nodes, slot) {
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
-function renderSections(sections, keep) {
|
|
||||||
- const nodes = [];
|
|
||||||
+function updateShell(root, sections, containers) {
|
|
||||||
+ const desired = [
|
|
||||||
+ sections.cards.length ? containers.cards : null,
|
|
||||||
+ sections.charts.length ? containers.charts : null,
|
|
||||||
+ sections.tabs.length ? containers.tabs : null,
|
|
||||||
+ sections.extra.length ? containers.extra : null
|
|
||||||
+ ].filter(Boolean);
|
|
||||||
+
|
|
||||||
+ desired.forEach((node, i) => {
|
|
||||||
+ if (root.children[i] !== node)
|
|
||||||
+ root.insertBefore(node, root.children[i] || null);
|
|
||||||
+ });
|
|
||||||
+
|
|
||||||
+ while (root.children.length > desired.length)
|
|
||||||
+ root.removeChild(root.lastChild);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+function updateCharts(container, chartNodes) {
|
|
||||||
+ const scrollPositions = {};
|
|
||||||
+ container.querySelectorAll('[data-chart]').forEach(el => {
|
|
||||||
+ if (el.dataset.chart)
|
|
||||||
+ scrollPositions[el.dataset.chart] = el.scrollLeft;
|
|
||||||
+ });
|
|
||||||
+
|
|
||||||
+ L.dom.content(container, fillRows(chartNodes, 'charts'));
|
|
||||||
|
|
||||||
- if (sections.cards.length)
|
|
||||||
- nodes.push(E('div', { 'class': 'dashboard-kpi-row' }, fillRows(sections.cards, 'cards')));
|
|
||||||
+ container.querySelectorAll('[data-chart]').forEach(el => {
|
|
||||||
+ if (el.dataset.chart && scrollPositions[el.dataset.chart] != null)
|
|
||||||
+ el.scrollLeft = scrollPositions[el.dataset.chart];
|
|
||||||
+ });
|
|
||||||
+}
|
|
||||||
|
|
||||||
- if (sections.charts.length)
|
|
||||||
- nodes.push(E('div', { 'class': 'dashboard-charts' }, fillRows(sections.charts, 'charts')));
|
|
||||||
+function updateTabs(tabMap, tabGroup, items, form) {
|
|
||||||
+ const menu = tabMap.querySelector('.cbi-tabmenu');
|
|
||||||
+ const existingPanes = Array.from(tabGroup.children);
|
|
||||||
+ const existingIds = existingPanes.map(pane => pane.dataset.tab);
|
|
||||||
+ const itemIds = items.map(tab => tab.id);
|
|
||||||
|
|
||||||
- if (keep)
|
|
||||||
- nodes.push(keep);
|
|
||||||
- else if (sections.tabs.length)
|
|
||||||
- nodes.push(E('div', { 'class': 'cbi-map' }, [
|
|
||||||
- E('div', { 'class': 'cbi-map-tabbed' }, sections.tabs.map(tab => E('div', {
|
|
||||||
+ const changed = !tabGroup.hasAttribute('data-initialized') ||
|
|
||||||
+ existingIds.length !== itemIds.length ||
|
|
||||||
+ existingIds.some((id, i) => id !== itemIds[i]);
|
|
||||||
+
|
|
||||||
+ if (changed) {
|
|
||||||
+ if (menu)
|
|
||||||
+ menu.remove();
|
|
||||||
+ tabGroup.removeAttribute('data-initialized');
|
|
||||||
+
|
|
||||||
+ const activePane = existingPanes.find(p => p.getAttribute('data-tab-active') === 'true');
|
|
||||||
+ const activeId = activePane ? activePane.dataset.tab : null;
|
|
||||||
+
|
|
||||||
+ while (tabGroup.firstChild)
|
|
||||||
+ tabGroup.removeChild(tabGroup.firstChild);
|
|
||||||
+
|
|
||||||
+ items.forEach(tab => {
|
|
||||||
+ const pane = (tab.id === 'layout' && form) ? form : E('div', {
|
|
||||||
'class': 'cbi-section',
|
|
||||||
'data-tab': tab.id,
|
|
||||||
'data-tab-title': (tab.count != null) ? '%s (%d)'.format(tab.title, tab.count) : tab.title
|
|
||||||
- }, tab.content)))
|
|
||||||
- ]));
|
|
||||||
+ }, (typeof(tab.content) == 'function') ? tab.content() : tab.content);
|
|
||||||
|
|
||||||
- if (sections.extra.length)
|
|
||||||
- nodes.push(E('div', {}, sections.extra));
|
|
||||||
-
|
|
||||||
- return nodes;
|
|
||||||
-}
|
|
||||||
+ if (tab.id === activeId)
|
|
||||||
+ pane.setAttribute('data-tab-active', 'true');
|
|
||||||
|
|
||||||
-function saveChartScroll(root) {
|
|
||||||
- const positions = {};
|
|
||||||
+ tabGroup.appendChild(pane);
|
|
||||||
+ });
|
|
||||||
|
|
||||||
- root.querySelectorAll('[data-chart]').forEach(node => {
|
|
||||||
- if (node.dataset.chart)
|
|
||||||
- positions[node.dataset.chart] = node.scrollLeft;
|
|
||||||
- });
|
|
||||||
+ ui.tabs.initTabGroup(tabGroup.childNodes);
|
|
||||||
+ }
|
|
||||||
+ else {
|
|
||||||
+ items.forEach(tab => {
|
|
||||||
+ const pane = tabGroup.querySelector(':scope > [data-tab="' + tab.id + '"]');
|
|
||||||
+ if (!pane)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ const newTitle = (tab.count != null) ? '%s (%d)'.format(tab.title, tab.count) : tab.title;
|
|
||||||
+ if (pane.dataset.tabTitle !== newTitle) {
|
|
||||||
+ pane.dataset.tabTitle = newTitle;
|
|
||||||
+ const link = menu ? menu.querySelector('li[data-tab="' + tab.id + '"] > a') : null;
|
|
||||||
+ if (link && link.textContent !== newTitle)
|
|
||||||
+ link.textContent = newTitle;
|
|
||||||
+ }
|
|
||||||
|
|
||||||
- return positions;
|
|
||||||
-}
|
|
||||||
+ if (tab.id === 'layout')
|
|
||||||
+ return;
|
|
||||||
|
|
||||||
-function restoreChartScroll(root, positions) {
|
|
||||||
- root.querySelectorAll('[data-chart]').forEach(node => {
|
|
||||||
- if (positions[node.dataset.chart])
|
|
||||||
- node.scrollLeft = positions[node.dataset.chart];
|
|
||||||
- });
|
|
||||||
+ const contentNode = (typeof(tab.content) == 'function') ? tab.content() : tab.content;
|
|
||||||
+ if (contentNode) {
|
|
||||||
+ if (pane.childNodes.length !== 1 || pane.firstChild !== contentNode)
|
|
||||||
+ L.dom.content(pane, contentNode);
|
|
||||||
+ }
|
|
||||||
+ });
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPolling(includes, layout, root, form) {
|
|
||||||
@@ -175,41 +225,45 @@ function startPolling(includes, layout, root, form) {
|
|
||||||
.then(node => L.dom.content(form, node));
|
|
||||||
};
|
|
||||||
|
|
||||||
+ if (form)
|
|
||||||
+ form.addEventListener('cbi-tab-active', showForm);
|
|
||||||
+
|
|
||||||
+ const cardsContainer = E('div', { 'class': 'dashboard-kpi-row' });
|
|
||||||
+ const chartsContainer = E('div', { 'class': 'dashboard-charts' });
|
|
||||||
+ const tabGroup = E('div', { 'class': 'cbi-map-tabbed' });
|
|
||||||
+ const tabMap = E('div', { 'class': 'cbi-map' }, [ tabGroup ]);
|
|
||||||
+ const extraContainer = E('div', {});
|
|
||||||
+
|
|
||||||
+ const containers = {
|
|
||||||
+ cards: cardsContainer,
|
|
||||||
+ charts: chartsContainer,
|
|
||||||
+ tabs: tabMap,
|
|
||||||
+ extra: extraContainer
|
|
||||||
+ };
|
|
||||||
+
|
|
||||||
const step = () => {
|
|
||||||
return network.flushCache().then(() => {
|
|
||||||
return invokeIncludesLoad(includes, layout);
|
|
||||||
}).then(results => {
|
|
||||||
- const positions = saveChartScroll(root);
|
|
||||||
-
|
|
||||||
const sections = collectSections(includes, results, layout);
|
|
||||||
|
|
||||||
if (form)
|
|
||||||
- sections.tabs.push({ id: 'layout', title: _('Layout'), content: form });
|
|
||||||
+ sections.tabs.push({ id: 'layout', title: _('Layout'), content: [] });
|
|
||||||
|
|
||||||
- // The tabs are left alone while the layout form is looked at,
|
|
||||||
- // for a redraw not to take away what is being edited.
|
|
||||||
- const tabs = root.querySelector(':scope > .cbi-map');
|
|
||||||
- const keep = (tabs && tabs.querySelector('[data-tab="layout"][data-tab-active="true"]')) ? tabs : null;
|
|
||||||
- let before = keep;
|
|
||||||
+ updateShell(root, sections, containers);
|
|
||||||
|
|
||||||
- Array.from(root.childNodes).filter(node => node !== keep).forEach(node => root.removeChild(node));
|
|
||||||
+ if (sections.cards.length)
|
|
||||||
+ L.dom.content(cardsContainer, fillRows(sections.cards, 'cards'));
|
|
||||||
|
|
||||||
- renderSections(sections, keep).forEach(node => {
|
|
||||||
- if (node === keep)
|
|
||||||
- before = null;
|
|
||||||
- else
|
|
||||||
- root.insertBefore(node, before);
|
|
||||||
- });
|
|
||||||
+ if (sections.charts.length)
|
|
||||||
+ updateCharts(chartsContainer, sections.charts);
|
|
||||||
|
|
||||||
- const group = keep ? null : root.querySelector('.cbi-map-tabbed');
|
|
||||||
- if (group) {
|
|
||||||
- if (form)
|
|
||||||
- form.parentNode.addEventListener('cbi-tab-active', showForm);
|
|
||||||
+ if (sections.tabs.length)
|
|
||||||
+ updateTabs(tabMap, tabGroup, sections.tabs, form);
|
|
||||||
|
|
||||||
- ui.tabs.initTabGroup(group.childNodes);
|
|
||||||
- }
|
|
||||||
+ if (sections.extra.length)
|
|
||||||
+ L.dom.content(extraContainer, sections.extra);
|
|
||||||
|
|
||||||
- restoreChartScroll(root, positions);
|
|
||||||
root.classList.add('fade-in');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -241,7 +295,7 @@ return view.extend({
|
|
||||||
|
|
||||||
document.addEventListener('uci-applied', () => window.location.reload());
|
|
||||||
|
|
||||||
- const form = L.hasViewPermission() ? E('div', {}, [ E('em', { 'class': 'spinning' }, [ _('Loading view…') ]) ]) : null;
|
|
||||||
+ const form = L.hasViewPermission() ? E('div', { 'class': 'cbi-section', 'data-tab': 'layout', 'data-tab-title': _('Layout') }, [ E('em', { 'class': 'spinning' }, [ _('Loading view…') ]) ]) : null;
|
|
||||||
|
|
||||||
return startPolling(includes, layout, root, form).then(() => root);
|
|
||||||
},
|
|
||||||
diff --git a/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/lib/charts.js b/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/lib/charts.js
|
|
||||||
index e8c5b191ff7a..96e629fa799c 100644
|
|
||||||
--- a/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/lib/charts.js
|
|
||||||
+++ b/luci-mod-dashboard/htdocs/luci-static/resources/view/dashboard/lib/charts.js
|
|
||||||
@@ -97,33 +97,77 @@ return baseclass.extend({
|
|
||||||
return E('em', {}, [ text ]);
|
|
||||||
},
|
|
||||||
|
|
||||||
- // Same markup as ui.Table: flat .table > .tr, header row .table-titles,
|
|
||||||
- // data-title on the cells so themes can label them on phones.
|
|
||||||
- table(opts) {
|
|
||||||
+ updateTable(table, opts) {
|
|
||||||
const cell = (tag, content, title) => E(tag, { 'class': tag, 'data-title': title || null }, [ content ?? '' ]);
|
|
||||||
- const table = E('table', { 'class': 'table ' + (opts.className || '') }, [
|
|
||||||
- E('tr', { 'class': 'tr table-titles' }, opts.head.map(title => cell('th', title)))
|
|
||||||
- ]);
|
|
||||||
-
|
|
||||||
- opts.rows.forEach((row, i) => {
|
|
||||||
- table.appendChild(E('tr', { 'class': 'tr ' + (i % 2 ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1') }, row.map((content, n) => cell('td', content, opts.head[n]))));
|
|
||||||
- });
|
|
||||||
+ const head = opts.head || [];
|
|
||||||
+ const rows = opts.rows || [];
|
|
||||||
+ const tbody = (table.tBodies && table.tBodies[0]) ? table.tBodies[0] : table;
|
|
||||||
+ const currentRows = tbody.querySelectorAll('tr:not(.table-titles):not(.table-foot):not(.placeholder)');
|
|
||||||
+ const placeholder = tbody.querySelector('tr.placeholder');
|
|
||||||
+ const foot = tbody.querySelector('tr.table-foot');
|
|
||||||
+
|
|
||||||
+ for (let i = 0; i < rows.length; i++) {
|
|
||||||
+ const tr = E('tr', { 'class': 'tr ' + (i % 2 ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1') },
|
|
||||||
+ rows[i].map((content, n) => cell('td', content, head[n])));
|
|
||||||
+ if (currentRows[i])
|
|
||||||
+ tbody.replaceChild(tr, currentRows[i]);
|
|
||||||
+ else if (foot)
|
|
||||||
+ tbody.insertBefore(tr, foot);
|
|
||||||
+ else
|
|
||||||
+ tbody.appendChild(tr);
|
|
||||||
+ }
|
|
||||||
|
|
||||||
- if (!opts.rows.length && opts.emptyText)
|
|
||||||
- table.appendChild(E('tr', { 'class': 'tr placeholder' }, [
|
|
||||||
- E('td', { 'class': 'td', 'colspan': opts.head.length }, [ E('em', {}, [ opts.emptyText ]) ])
|
|
||||||
- ]));
|
|
||||||
+ for (let i = rows.length; i < currentRows.length; i++)
|
|
||||||
+ tbody.removeChild(currentRows[i]);
|
|
||||||
+
|
|
||||||
+ if (!rows.length && opts.emptyText) {
|
|
||||||
+ if (!placeholder) {
|
|
||||||
+ const tr = E('tr', { 'class': 'tr placeholder' }, [
|
|
||||||
+ E('td', { 'class': 'td', 'colspan': head.length }, [ E('em', {}, [ opts.emptyText ]) ])
|
|
||||||
+ ]);
|
|
||||||
+ if (foot)
|
|
||||||
+ tbody.insertBefore(tr, foot);
|
|
||||||
+ else
|
|
||||||
+ tbody.appendChild(tr);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ else if (placeholder) {
|
|
||||||
+ tbody.removeChild(placeholder);
|
|
||||||
+ }
|
|
||||||
|
|
||||||
if (opts.foot) {
|
|
||||||
- const foot = E('tr', { 'class': 'tr' }, opts.foot.map(content => cell('td', content)));
|
|
||||||
-
|
|
||||||
- foot.lastChild.setAttribute('colspan', opts.head.length - opts.foot.length + 1);
|
|
||||||
- table.appendChild(foot);
|
|
||||||
+ if (!foot) {
|
|
||||||
+ const tr = E('tr', { 'class': 'tr table-foot' }, opts.foot.map(content => cell('td', content)));
|
|
||||||
+ tr.lastChild.setAttribute('colspan', head.length - opts.foot.length + 1);
|
|
||||||
+ tbody.appendChild(tr);
|
|
||||||
+ }
|
|
||||||
+ else {
|
|
||||||
+ while (foot.firstChild)
|
|
||||||
+ foot.removeChild(foot.firstChild);
|
|
||||||
+ opts.foot.map(content => cell('td', content)).forEach(td => foot.appendChild(td));
|
|
||||||
+ foot.lastChild.setAttribute('colspan', head.length - opts.foot.length + 1);
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
},
|
|
||||||
|
|
||||||
+ // Same markup as ui.Table: flat .table > .tr, header row .table-titles,
|
|
||||||
+ // data-title on the cells so themes can label them on phones.
|
|
||||||
+ table(opts) {
|
|
||||||
+ const existing = opts.node || (opts.id ? document.getElementById(opts.id) : null);
|
|
||||||
+ if (existing && existing.parentNode)
|
|
||||||
+ return this.updateTable(existing, opts);
|
|
||||||
+
|
|
||||||
+ const cell = (tag, content, title) => E(tag, { 'class': tag, 'data-title': title || null }, [ content ?? '' ]);
|
|
||||||
+ const head = opts.head || [];
|
|
||||||
+ const table = E('table', { 'id': opts.id || null, 'class': 'table ' + (opts.className || '') }, [
|
|
||||||
+ E('tr', { 'class': 'tr table-titles' }, head.map(title => cell('th', title)))
|
|
||||||
+ ]);
|
|
||||||
+
|
|
||||||
+ return this.updateTable(table, opts);
|
|
||||||
+ },
|
|
||||||
+
|
|
||||||
donut(opts) {
|
|
||||||
const radius = 40;
|
|
||||||
const circumference = 2 * Math.PI * radius;
|
|
||||||
Reference in new issue
Block a user