mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 21:13:00 +08:00
🌴 Sync 2026-11-10 18:25:40
This commit is contained in:
1 parent
2d7e7f3abc
commit
ad8b8b0fd1
39 files changed
+1297
-450
No files matched your search
+9
-2
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
/* global feedback */
|
||||
'require device-manager.feedback as feedback';
|
||||
'require baseclass';
|
||||
'require ui';
|
||||
'require device-manager.model as model';
|
||||
@@ -252,7 +254,7 @@ return baseclass.extend({
|
||||
})
|
||||
.catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', [ _('Clear failed: %s').format(err.message || err) ]), 'danger');
|
||||
feedback.notify(_('Clear failed: %s').format(err.message || err), 'danger');
|
||||
}).finally(function() {
|
||||
button.classList.remove('spinning');
|
||||
button.disabled = false;
|
||||
@@ -265,6 +267,7 @@ return baseclass.extend({
|
||||
|
||||
showDetailModal: function(dev) {
|
||||
const self = this;
|
||||
const hero = deviceHero(dev);
|
||||
const addresses = key => (dev[key + 'Addresses'] || []).length
|
||||
? dev[key + 'Addresses'].join('\n') : dev[key];
|
||||
const pingStatus = E('strong', { 'class': 'dm-ping-status', 'role': 'status', 'aria-live': 'polite' }, [ _('Probing…') ]);
|
||||
@@ -284,6 +287,10 @@ return baseclass.extend({
|
||||
pingDetail.textContent = _('Sending one Ping from the router to this device.');
|
||||
pingLog.style.display = 'none';
|
||||
pending = Promise.resolve().then(() => self.handlePingDevice(dev)).then(reply => {
|
||||
const status = hero.querySelector('.dm-dialog-status');
|
||||
for (const value of [ 'online', 'offline', 'unknown' ]) status.classList.remove('dm-dialog-status-' + value);
|
||||
status.classList.add('dm-dialog-status-' + dev.status);
|
||||
status.textContent = statusLabel(dev.status);
|
||||
pingStatus.textContent = reply.reachable ? _('Online · Ping replied') : _('No Ping reply');
|
||||
pingStatus.classList.add(reply.reachable ? 'dm-ping-success' : 'dm-ping-warning');
|
||||
pingDetail.textContent = _('Probe address: %s').format(reply.ip + (reply.interface ? ' (' + reply.interface + ')' : '')) +
|
||||
@@ -311,7 +318,7 @@ return baseclass.extend({
|
||||
const neighbors = (dev.neighbors || []).map(neighbor => [ neighbor.ip, neighbor.dev, neighbor.state ].filter(Boolean).join(' · ')).join('\n');
|
||||
ui.showModal(_('Device details'), [ E('div', { 'class': 'dm-dialog' }, [
|
||||
closeButton(),
|
||||
deviceHero(dev),
|
||||
hero,
|
||||
E('section', { 'class': 'dm-ping-card' }, [
|
||||
E('div', { 'class': 'dm-ping-heading' }, [ pingStatus, pingButton ]), pingDetail, pingLog
|
||||
]),
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require ui';
|
||||
|
||||
const TIMEOUT = 12000;
|
||||
|
||||
return baseclass.extend({
|
||||
createWarning: function() {
|
||||
let previous = '', timer = null;
|
||||
const message = E('span', { 'class': 'dm-warning-message' });
|
||||
const dismiss = () => {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
node.style.display = 'none';
|
||||
};
|
||||
const node = E('div', {
|
||||
'id': 'dm-source-warning', 'class': 'alert-message warning dm-source-warning',
|
||||
'role': 'status', 'style': 'display:none;'
|
||||
}, [ message, E('button', {
|
||||
'type': 'button', 'class': 'btn cbi-button dm-warning-close',
|
||||
'click': dismiss
|
||||
}, [ _('Close') ]) ]);
|
||||
return {
|
||||
node: node,
|
||||
destroy: dismiss,
|
||||
update: errors => {
|
||||
const text = errors.length
|
||||
? _('Some device data could not be read. Unconfirmed devices are shown as unknown: %s').format(errors.join('; ')) : '';
|
||||
if (text === previous) return;
|
||||
previous = text;
|
||||
dismiss();
|
||||
message.textContent = text;
|
||||
if (text) {
|
||||
node.style.display = 'flex';
|
||||
timer = setTimeout(dismiss, TIMEOUT);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
notify: function(message, level) {
|
||||
const node = ui.addNotification(null, E('p', {}, [ message ]), level);
|
||||
if (node) setTimeout(() => node.remove(), TIMEOUT);
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
/* global feedback */
|
||||
'require device-manager.feedback as feedback';
|
||||
'require baseclass';
|
||||
'require ui';
|
||||
'require device-manager.model as model';
|
||||
@@ -226,7 +228,7 @@ return baseclass.extend({
|
||||
})
|
||||
.catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', [ _('Could not delete group: %s').format(err.message || err) ]), 'danger');
|
||||
feedback.notify(_('Could not delete group: %s').format(err.message || err), 'danger');
|
||||
}).finally(function() {
|
||||
button.classList.remove('spinning');
|
||||
button.disabled = false;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
|
||||
const MAX_DEVICES = 16;
|
||||
const MAX_DURATION = 12000;
|
||||
|
||||
return baseclass.extend({
|
||||
run: function(devices, ping, isCurrent) {
|
||||
// ping_device has a shared router lock, so check one device at a time.
|
||||
const task = (this.pending || Promise.resolve()).then(() => {
|
||||
const targets = devices.filter(device => device.status === 'unknown' && (device.ipv4 || device.ipv6));
|
||||
const started = Date.now(), limit = Math.min(targets.length, MAX_DEVICES);
|
||||
let count = 0;
|
||||
const next = () => {
|
||||
if (!isCurrent() || count >= limit || Date.now() - started >= MAX_DURATION) return;
|
||||
const index = (this.cursor || 0) % targets.length;
|
||||
this.cursor = index + 1;
|
||||
count++;
|
||||
const device = targets[index];
|
||||
return Promise.resolve().then(() => {
|
||||
if (isCurrent() && device.status === 'unknown') return ping(device);
|
||||
}).then(next, next);
|
||||
};
|
||||
return next();
|
||||
});
|
||||
this.pending = task.then(() => {}, () => {});
|
||||
return task;
|
||||
}
|
||||
});
|
||||
@@ -1,10 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
/* global snapshotCache */
|
||||
'require baseclass';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require device-manager.model as model';
|
||||
'require device-manager.snapshot-cache as snapshotCache';
|
||||
|
||||
const CONFIG = 'device_manager';
|
||||
const callHints = rpc.declare({ object: 'luci-rpc', method: 'getHostHints', reject: true });
|
||||
@@ -13,6 +15,7 @@ const callNeighbors = rpc.declare({ object: 'luci.device-manager', method: 'get_
|
||||
const callScan = rpc.declare({ object: 'luci.device-manager', method: 'scan_devices', reject: true });
|
||||
const callPing = rpc.declare({ object: 'luci.device-manager', method: 'ping_device', params: [ 'mac' ], reject: true });
|
||||
const callWireless = rpc.declare({ object: 'luci-rpc', method: 'getWirelessDevices', reject: true });
|
||||
const callNetworkDevices = rpc.declare({ object: 'luci-rpc', method: 'getNetworkDevices', reject: true });
|
||||
const callAssoc = rpc.declare({ object: 'iwinfo', method: 'assoclist', params: [ 'device' ], reject: true });
|
||||
const callCommit = rpc.declare({ object: 'uci', method: 'commit', params: [ 'config' ], reject: true });
|
||||
const callRevert = rpc.declare({ object: 'uci', method: 'revert', params: [ 'config' ], reject: true });
|
||||
@@ -28,7 +31,7 @@ function readSource(name, read) {
|
||||
}
|
||||
|
||||
function wifiStations() {
|
||||
return callWireless().then(requireObject).then(radios => {
|
||||
return Promise.all([ callWireless().then(requireObject), callNetworkDevices().then(requireObject) ]).then(([ radios, devices ]) => {
|
||||
const interfaces = new Set();
|
||||
for (const radio of Object.values(radios)) {
|
||||
for (const iface of radio.interfaces || []) {
|
||||
@@ -38,7 +41,9 @@ function wifiStations() {
|
||||
for (const vlan of iface.vlans || []) if (vlan.ifname) interfaces.add(vlan.ifname);
|
||||
}
|
||||
}
|
||||
return Promise.all(Array.from(interfaces).map(device => readSource(device, () => callAssoc(device).then(reply => {
|
||||
// netifd may retain an ifname after the actual wireless device has disappeared.
|
||||
const present = Array.from(interfaces).filter(device => Object.prototype.hasOwnProperty.call(devices, device));
|
||||
return Promise.all(present.map(device => readSource(device, () => callAssoc(device).then(reply => {
|
||||
if (!Array.isArray(requireObject(reply).results)) throw new Error(_('Invalid discovery response'));
|
||||
return reply.results.map(station => ({ mac: station.mac, dev: device }));
|
||||
})))).then(results => ({
|
||||
@@ -75,7 +80,7 @@ function requireGroup(id) {
|
||||
return baseclass.extend({
|
||||
scanDevices: function() {
|
||||
if (L.hasViewPermission() !== true) return Promise.reject(new Error(_('Read-only access')));
|
||||
return this.queue(() => callScan().then(reply => {
|
||||
return this.queue(() => snapshotCache.clear().then(() => callScan()).then(reply => {
|
||||
if (requireObject(reply).ok !== true)
|
||||
throw new Error(_('LAN scanning is unavailable. Check the selected interfaces and probe dependencies.'));
|
||||
}));
|
||||
@@ -136,10 +141,16 @@ return baseclass.extend({
|
||||
data.errors.push(...result.value.errors);
|
||||
} else data[result.name] = result.value;
|
||||
}
|
||||
return data;
|
||||
return snapshotCache.save(data).then(() => data);
|
||||
});
|
||||
}));
|
||||
},
|
||||
loadCached: function(cacheOnly) {
|
||||
return snapshotCache.load().then(entry => entry
|
||||
? { snapshot: entry.snapshot, cached: true }
|
||||
: cacheOnly ? { snapshot: null, cached: false }
|
||||
: this.load().then(snapshot => ({ snapshot: snapshot, cached: false })));
|
||||
},
|
||||
recoverPending: function() {
|
||||
if (!this.rollbackPending) return Promise.resolve();
|
||||
return callRevert(CONFIG).then(() => { this.rollbackPending = false; });
|
||||
@@ -148,7 +159,7 @@ return baseclass.extend({
|
||||
return this.queue(() => {
|
||||
if (L.hasViewPermission() !== true) throw new Error(_('Read-only access'));
|
||||
let staged = false;
|
||||
return this.recoverPending().then(reloadConfig).then(change).then(() => {
|
||||
return this.recoverPending().then(() => snapshotCache.clear()).then(reloadConfig).then(change).then(() => {
|
||||
staged = true;
|
||||
return uci.save();
|
||||
}).then(() => callCommit(CONFIG)).catch(error => {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require rpc';
|
||||
|
||||
const MAX_AGE = 5 * 60 * 1000;
|
||||
|
||||
// Keep device data within this tab and the current LuCI session and access level.
|
||||
function key() {
|
||||
return 'luci-device-manager-snapshot-v1:' + rpc.getSessionID() + ':' + (L.hasViewPermission() === true ? 'write' : 'read');
|
||||
}
|
||||
|
||||
return baseclass.extend({
|
||||
load: function() {
|
||||
return Promise.resolve().then(() => window.sessionStorage.getItem(key())).then(raw => {
|
||||
const entry = raw ? JSON.parse(raw) : null;
|
||||
if (!entry || !Number.isFinite(entry.time) || Date.now() < entry.time || Date.now() - entry.time >= MAX_AGE)
|
||||
return null;
|
||||
const data = entry.snapshot;
|
||||
if (!data || !Array.isArray(data.devices) || !Array.isArray(data.groups) || !Array.isArray(data.errors)
|
||||
|| typeof data.discoveryComplete !== 'boolean') return null;
|
||||
return entry;
|
||||
}).catch(() => null);
|
||||
},
|
||||
save: function(snapshot) {
|
||||
return Promise.resolve().then(() => window.sessionStorage.setItem(key(), JSON.stringify({ time: Date.now(), snapshot: snapshot })))
|
||||
.catch(() => {});
|
||||
},
|
||||
clear: function() {
|
||||
return Promise.resolve().then(() => window.sessionStorage.removeItem(key())).catch(() => {});
|
||||
}
|
||||
});
|
||||
@@ -35,7 +35,9 @@ body.dark-mode .dm-status-dot-unknown { background-color:#fdd663; box-shadow:0 0
|
||||
.dm-table .dm-action-link:focus-visible { outline:2px solid currentColor; outline-offset:2px; border-radius:2px; }
|
||||
|
||||
.dm-status-tab:focus-visible { outline:2px solid #1a73e8; outline-offset:2px; }
|
||||
.dm-source-warning { overflow-wrap:anywhere; }
|
||||
.dm-source-warning { overflow-wrap:anywhere; align-items:flex-start; gap:12px; }
|
||||
.dm-warning-message { flex:1; min-width:0; }
|
||||
.dm-warning-close { flex-shrink:0; margin-left:auto; }
|
||||
|
||||
.dm-type-th { width: 56px; text-align: center; }
|
||||
.dm-type-cell { text-align: center; vertical-align: middle; padding: 6px 8px; }
|
||||
|
||||
@@ -127,10 +127,23 @@ return baseclass.extend({
|
||||
renderTable: function() {
|
||||
if (!this.tableBodyNode) return;
|
||||
dom.content(this.tableBodyNode, null);
|
||||
|
||||
const filtered = this.devices.filter(device =>
|
||||
(this.activeTab === 'all' || device.status === this.activeTab) &&
|
||||
model.matchesDevice(device, this.groups, this.activeGroup, this.filterText));
|
||||
const loading = this.initialLoading || this.listRefreshing || this.checkingOnline || (this.showingCache && !this.cacheRefreshFailed);
|
||||
if (!filtered.length && (loading || this.loadFailed)) {
|
||||
this.tableBodyNode.appendChild(E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td', 'colspan': 8, 'style': 'text-align:center; padding:36px 16px;' }, [
|
||||
E('span', { 'class': loading ? 'spinning' : '', 'role': 'status' }, [
|
||||
loading ? _('Loading list...') : _('Could not load the device list.')
|
||||
]),
|
||||
...(loading ? [] : [ E('button', {
|
||||
'type': 'button', 'class': 'cbi-button', 'style': 'margin-left:12px;', 'click': () => this.refresh()
|
||||
}, [ _('Retry') ]) ])
|
||||
])
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (filtered.length === 0) {
|
||||
let emptyMsg = _('No devices found. Refresh the list to try again.');
|
||||
|
||||
+83
-17
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
'use strict';
|
||||
/* global deviceDialog, groupDialog, preferences, service, table */
|
||||
/* global deviceDialog, feedback, groupDialog, onlineCheck, preferences, service, table */
|
||||
'require view';
|
||||
'require ui';
|
||||
'require device-manager.model as model';
|
||||
@@ -9,6 +9,8 @@
|
||||
'require device-manager.table as table';
|
||||
'require device-manager.device-dialog as deviceDialog';
|
||||
'require device-manager.group-dialog as groupDialog';
|
||||
'require device-manager.feedback as feedback';
|
||||
'require device-manager.online-check as onlineCheck';
|
||||
|
||||
return view.extend({
|
||||
devices: [],
|
||||
@@ -25,13 +27,14 @@ return view.extend({
|
||||
handleReset: null,
|
||||
|
||||
load: function() {
|
||||
return Promise.all([ service.load(), preferences.load() ])
|
||||
.then(data => ({ snapshot: data[0], preferences: data[1] }));
|
||||
return Promise.all([ service.loadCached(true), preferences.load() ])
|
||||
.then(data => Object.assign({ preferences: data[1] }, data[0]));
|
||||
},
|
||||
|
||||
acceptData: function(data) {
|
||||
this.groups = model.parseGroups(data.groups);
|
||||
this.devices = model.parseDevices(data, this.groups);
|
||||
this.checkingOnline = !this.showingCache && this.devices.some(device => device.status === 'unknown' && (device.ipv4 || device.ipv6));
|
||||
this.sourceErrors = data.errors || [];
|
||||
if (this.activeGroup !== 'all' && this.activeGroup !== 'ungrouped' && !this.getGroupById(this.activeGroup)) {
|
||||
this.activeGroup = 'all';
|
||||
@@ -43,10 +46,21 @@ return view.extend({
|
||||
const self = this;
|
||||
|
||||
this.readonly = L.hasViewPermission() !== true;
|
||||
this.acceptData(data.snapshot);
|
||||
this.hasSnapshot = Boolean(data.snapshot);
|
||||
this.initialLoading = !this.hasSnapshot;
|
||||
this.loadFailed = false;
|
||||
this.listRefreshing = false;
|
||||
this.checkingOnline = false;
|
||||
this.showingCache = Boolean(data.cached);
|
||||
this.cacheRefreshFailed = false;
|
||||
if (this.sourceWarning) this.sourceWarning.destroy();
|
||||
this.sourceWarning = feedback.createWarning();
|
||||
if (data.snapshot) this.acceptData(data.snapshot);
|
||||
else { this.devices = []; this.groups = []; this.sourceErrors = []; }
|
||||
const savedView = data.preferences || {};
|
||||
this.activeTab = ['all', 'online', 'offline', 'unknown'].includes(savedView.tab) ? savedView.tab : 'online';
|
||||
this.activeGroup = (savedView.group === 'all' || savedView.group === 'ungrouped' || this.getGroupById(savedView.group)) ? savedView.group : 'all';
|
||||
this.activeGroup = this.initialLoading ? (savedView.group || 'all')
|
||||
: (savedView.group === 'all' || savedView.group === 'ungrouped' || this.getGroupById(savedView.group)) ? savedView.group : 'all';
|
||||
this.maskInfo = Boolean(savedView.maskInfo);
|
||||
|
||||
const sortHeader = (key, label, width, centered) => E('th', {
|
||||
@@ -87,7 +101,8 @@ return view.extend({
|
||||
E('h2', {}, [ _('LAN Device Management') ]),
|
||||
E('div', { 'class': 'cbi-map-descr' }, [ _('Discover network devices and save custom names, remarks and groups by MAC address.') ]),
|
||||
|
||||
E('div', { 'id': 'dm-source-warning', 'class': 'alert-message warning dm-source-warning', 'style': 'display:none;' }),
|
||||
E('div', { 'id': 'dm-cache-status', 'class': 'cbi-map-descr', 'role': 'status', 'style': 'display:none;' }),
|
||||
this.sourceWarning.node,
|
||||
|
||||
// Row 1: Status Tabs
|
||||
E('div', { 'class': 'dm-status-tabs', 'id': 'dm-tabs-container', 'role': 'tablist', 'aria-label': _('Device status') }),
|
||||
@@ -120,13 +135,13 @@ return view.extend({
|
||||
]),
|
||||
E('div', { 'class': 'dm-btn-group' }, [
|
||||
E('button', { 'type': 'button',
|
||||
'class': 'cbi-button cbi-button-neutral',
|
||||
'disabled': self.readonly || null,
|
||||
'class': 'cbi-button cbi-button-neutral dm-requires-data',
|
||||
'disabled': self.readonly || self.initialLoading || null,
|
||||
'click': function() { self.showGroupModal(); }
|
||||
}, [ _('Manage groups') ]),
|
||||
E('button', { 'type': 'button',
|
||||
'class': 'cbi-button cbi-button-action dm-btn-blue',
|
||||
'disabled': self.readonly || null,
|
||||
'class': 'cbi-button cbi-button-action dm-btn-blue dm-requires-data',
|
||||
'disabled': self.readonly || self.initialLoading || null,
|
||||
'click': function() { self.showEditModal(null); }
|
||||
}, [ _('Add device') ]),
|
||||
E('button', { 'type': 'button',
|
||||
@@ -183,12 +198,16 @@ return view.extend({
|
||||
|
||||
this.tableHeadNode = theadNode;
|
||||
this.warningNode = viewNode.querySelector('#dm-source-warning');
|
||||
this.cacheStatusNode = viewNode.querySelector('#dm-cache-status');
|
||||
this.tabMenuNode = viewNode.querySelector('#dm-tabs-container');
|
||||
this.tableBodyNode = viewNode.querySelector('#device_manager_tbody');
|
||||
this.groupSelectNode = viewNode.querySelector('#dm-group-select');
|
||||
this.maskBtnNode = viewNode.querySelector('#dm-btn-mask');
|
||||
this.dataActionButtons = viewNode.querySelectorAll('.dm-requires-data');
|
||||
|
||||
this.updateView();
|
||||
clearTimeout(this.loadTimer);
|
||||
this.loadTimer = setTimeout(() => this.initialLoading || this.showingCache ? this.refresh() : this.confirmUnknownDevices(), 0);
|
||||
return viewNode;
|
||||
},
|
||||
|
||||
@@ -202,7 +221,30 @@ return view.extend({
|
||||
updateSortHeaders: function() { return table.updateSortHeaders.call(this); },
|
||||
showEditModal: function(device) { return deviceDialog.showEditModal.call(this, device); },
|
||||
showDetailModal: function(device) { return deviceDialog.showDetailModal.call(this, device); },
|
||||
handlePingDevice: function(device) { return service.pingDevice(device.mac); },
|
||||
handlePingDevice: function(device) {
|
||||
return service.pingDevice(device.mac).then(reply => {
|
||||
// A reply for an older snapshot must not change the current list.
|
||||
if (reply.reachable && device.status === 'unknown' && this.devices.includes(device)) {
|
||||
device.status = 'online';
|
||||
device.statusDetail = _('Online · Ping replied');
|
||||
this.updateView();
|
||||
}
|
||||
return reply;
|
||||
});
|
||||
},
|
||||
confirmUnknownDevices: function() {
|
||||
const devices = this.devices;
|
||||
if (this.initialLoading || this.showingCache || this.confirmingDevices === devices) return this.confirming || Promise.resolve();
|
||||
this.confirmingDevices = devices;
|
||||
this.confirming = onlineCheck.run(devices, device => this.handlePingDevice(device),
|
||||
() => this.devices === devices && !this.showingCache).finally(() => {
|
||||
if (this.devices === devices) {
|
||||
this.checkingOnline = false;
|
||||
this.updateView();
|
||||
}
|
||||
});
|
||||
return this.confirming;
|
||||
},
|
||||
confirmDelete: function(device) { return deviceDialog.confirmDelete.call(this, device); },
|
||||
showGroupModal: function() { return groupDialog.showGroupModal.call(this); },
|
||||
promptRenameGroup: function(group) { return groupDialog.promptRenameGroup.call(this, group); },
|
||||
@@ -216,33 +258,57 @@ return view.extend({
|
||||
updateView: function() {
|
||||
this.renderTabs();
|
||||
this.renderGroupSelect();
|
||||
if (this.groupSelectNode) this.groupSelectNode.disabled = this.initialLoading;
|
||||
for (const button of this.dataActionButtons || []) button.disabled = this.readonly || !this.hasSnapshot;
|
||||
this.updateSortHeaders();
|
||||
if (this.maskBtnNode) {
|
||||
this.maskBtnNode.textContent = this.maskInfo ? _('Show info') : _('Hide info');
|
||||
}
|
||||
this.renderTable();
|
||||
if (this.warningNode) {
|
||||
this.warningNode.textContent = this.sourceErrors.length
|
||||
? _('Some device data could not be read. Unconfirmed devices are shown as unknown: %s').format(this.sourceErrors.join('; ')) : '';
|
||||
this.warningNode.style.display = this.sourceErrors.length ? '' : 'none';
|
||||
if (this.sourceWarning) this.sourceWarning.update(this.sourceErrors);
|
||||
if (this.cacheStatusNode) {
|
||||
this.cacheStatusNode.textContent = this.showingCache && this.cacheRefreshFailed
|
||||
? _('Showing cached device data. Refresh to retry.') : '';
|
||||
this.cacheStatusNode.style.display = this.showingCache && this.cacheRefreshFailed ? '' : 'none';
|
||||
}
|
||||
},
|
||||
|
||||
scan: function() {
|
||||
if (this.scanning) return this.scanning;
|
||||
this.scanning = service.scanDevices().then(() => this.refresh()).catch(error => {
|
||||
ui.addNotification(null, E('p', {}, [ _('Could not scan LAN devices: %s').format(error.message || error) ]), 'danger');
|
||||
feedback.notify(_('Could not scan LAN devices: %s').format(error.message || error), 'danger');
|
||||
}).finally(() => { this.scanning = null; });
|
||||
return this.scanning;
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
if (this.refreshing) return this.refreshing;
|
||||
clearTimeout(this.loadTimer);
|
||||
this.loadTimer = null;
|
||||
this.listRefreshing = true;
|
||||
if (!this.hasSnapshot) {
|
||||
this.initialLoading = true;
|
||||
this.loadFailed = false;
|
||||
}
|
||||
this.updateView();
|
||||
this.refreshing = service.load().then(data => {
|
||||
this.listRefreshing = false;
|
||||
this.initialLoading = false;
|
||||
this.loadFailed = false;
|
||||
this.hasSnapshot = true;
|
||||
this.showingCache = false;
|
||||
this.cacheRefreshFailed = false;
|
||||
this.acceptData(data);
|
||||
this.updateView();
|
||||
this.loadTimer = setTimeout(() => this.confirmUnknownDevices(), 0);
|
||||
}).catch(error => {
|
||||
ui.addNotification(null, E('p', {}, [ _('Could not refresh the device list: %s').format(error.message || error) ]), 'danger');
|
||||
this.listRefreshing = false;
|
||||
this.checkingOnline = false;
|
||||
this.initialLoading = false;
|
||||
this.loadFailed = true;
|
||||
this.cacheRefreshFailed = true;
|
||||
this.updateView();
|
||||
feedback.notify(_('Could not refresh the device list: %s').format(error.message || error), 'danger');
|
||||
}).finally(() => { this.refreshing = null; });
|
||||
return this.refreshing;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user