mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 02:25:13 +08:00
🌴 Sync 2026-03-05 23:51:42
This commit is contained in:
1 parent
97c049ed0a
commit
0acff03804
14253 files changed
+3893239
No files matched your search
@@ -0,0 +1,275 @@
|
||||
/* This is free software, licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
load: function() {
|
||||
return uci.load('droidnet').then(function() {
|
||||
var device = uci.get('droidnet', 'device', 'id');
|
||||
return Promise.all([
|
||||
fs.exec('adb', ['-s', device, 'shell', 'getprop']).then(function(result) {
|
||||
var deviceInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
var properties = {
|
||||
'ro.serialno': 'device_id',
|
||||
'ro.product.brand': 'device_brand',
|
||||
'ro.product.model': 'device_model',
|
||||
'ro.product.device': 'device_code',
|
||||
'ro.board.platform': 'device_soc',
|
||||
'dalvik.vm.isa.arm.variant': 'device_cpu',
|
||||
'ro.build.version.release': 'device_version',
|
||||
'ro.build.version.sdk': 'device_sdk',
|
||||
'ro.build.version.security_patch': 'device_security'
|
||||
};
|
||||
if (stderr) {
|
||||
deviceInfo['device_section'] = stderr.trim();
|
||||
} else {
|
||||
var lines = stdout.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
for (var property in properties) {
|
||||
if (lines[i].includes('[' + property + ']')) {
|
||||
var parts = lines[i].split(']: [');
|
||||
var value = parts[1].substring(0, parts[1].length - 1);
|
||||
if (value.includes(',')) {
|
||||
value = value.split(',').map(function(item) {return item.trim();});
|
||||
};
|
||||
deviceInfo[properties[property]] = value;
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return deviceInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'uptime']).then(function(result) {
|
||||
var uptimeInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
uptimeInfo['uptime_section'] = stderr.trim();
|
||||
} else {
|
||||
var parts = stdout.trim().split(/\s+/);
|
||||
var uptimeParts = parts.slice(0, 4);
|
||||
uptimeInfo['device_uptime'] = uptimeParts.join(' ').replace(/,$/, '');
|
||||
};
|
||||
return uptimeInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'uname', '-a']).then(function(result) {
|
||||
var kernelInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
kernelInfo['uname_section'] = stderr.trim();
|
||||
} else {
|
||||
var parts = stdout.trim().split(/\s+/);
|
||||
kernelInfo['device_uname'] = {'kernel': parts[2], 'arch': parts[12]};
|
||||
};
|
||||
return kernelInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'cat', '/proc/meminfo']).then(function(result) {
|
||||
var memmoryInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
memmoryInfo['memory_section'] = stderr.trim();
|
||||
} else {
|
||||
var parts = stdout.trim().split(/\s+/);
|
||||
var kbValue = parseInt(parts[1]) + parseInt(parts[4]);
|
||||
var gbValue = kbValue / (1024 * 1024);
|
||||
memmoryInfo['device_memory'] = gbValue.toFixed(0) + ' GB';
|
||||
};
|
||||
return memmoryInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'dumpsys', 'battery']).then(function(result) {
|
||||
var batteryInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
var properties = {
|
||||
'level': 'battery_level',
|
||||
'voltage': 'battery_voltage',
|
||||
'technology': 'battery_technology',
|
||||
'temperature': 'battery_temperature',
|
||||
'Charge counter': 'battery_counter'
|
||||
};
|
||||
if (stderr) {
|
||||
batteryInfo['battery_section'] = stderr.trim();
|
||||
} else {
|
||||
var lines = stdout.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
for (var property in properties) {
|
||||
if (line.startsWith(property + ':')) {
|
||||
var parts = line.split(':');
|
||||
var value = parts[1].trim();
|
||||
if (property === 'level') {
|
||||
value = parseInt(value) + ' %';
|
||||
} else if (property === 'voltage') {
|
||||
value = (parseInt(value) / 1000).toFixed(2) + ' V';
|
||||
} else if (property === 'temperature') {
|
||||
value = (parseInt(value) / 10) + ' °C';
|
||||
} else if (property === 'Charge counter') {
|
||||
value = parseInt(value) / 1000 + ' μAh';
|
||||
};
|
||||
batteryInfo[properties[property]] = value;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return batteryInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'su', '-v']).then(function(result) {
|
||||
var rootInfo = {};
|
||||
var stdout = result.stdout ? result.stdout.trim() : '';
|
||||
var stderr = result.stderr;
|
||||
if (stderr || stdout === '/system/bin/sh: su: not found' || stdout === '/system/bin/sh: su: inaccessible or not found') {
|
||||
rootInfo['device_root'] = false;
|
||||
} else {
|
||||
var parts = stdout.split(':');
|
||||
rootInfo['device_root'] = {'version': parts[0], 'name': parts[1]};
|
||||
};
|
||||
return rootInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
})
|
||||
]).then(function(results) {
|
||||
var deviceInfo = results[0];
|
||||
var uptimeInfo = results[1];
|
||||
var kernelInfo = results[2];
|
||||
var memmoryInfo = results[3];
|
||||
var batteryInfo = results[4];
|
||||
var rootInfo = results[5];
|
||||
if (device && deviceInfo && uptimeInfo && kernelInfo && memmoryInfo && batteryInfo && rootInfo) {
|
||||
return Object.assign({}, {device: device}, deviceInfo, uptimeInfo, kernelInfo, memmoryInfo, batteryInfo, rootInfo)
|
||||
} else {
|
||||
throw new Error(_('Failed to get complete device information.'));
|
||||
};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
var header = [
|
||||
E('h2', {'class': 'section-title'}, _('DroidNet')),
|
||||
E('div', {'class': 'cbi-map-descr'}, _('Manage Android modem and optimize network settings.'))
|
||||
];
|
||||
if (data.device_section) {
|
||||
ui.addNotification(_('Error: Device conflict!'),
|
||||
E('p', _('Please check your settings, the configured device and ADB devices are conflicting.')), 'danger'
|
||||
);
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('div', {'class': 'cbi-value', 'style': 'text-align: center; display: block;'}, [
|
||||
E('em', _('No device detected or connected.'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
var device = [
|
||||
E('h3', {'class': 'section-title'}, _('Device Information')),
|
||||
E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr table-title', 'style': 'display: none;'}),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Device ID')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_id || '-'),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Processors')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_cpu ? (data.device_cpu.toUpperCase()) : '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Root status')),
|
||||
E('td', {'class': 'td left', 'width': '25%'},
|
||||
data.device_root === false ? _('Non-root') :
|
||||
data.device_root ? (_('Root with %s (%s)').format(data.device_root.name, data.device_root.version)) :
|
||||
'-'
|
||||
),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('RAM')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_memory || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Brand name')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_brand ? (data.device_brand.charAt(0).toUpperCase() + data.device_brand.slice(1)) : '-'),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Architecture')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_uname ? (data.device_uname.arch) : '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Code name')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_code || '-'),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Android version')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_version || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Model number')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_model ? (data.device_model.charAt(0).toUpperCase() + data.device_model.slice(1)) : '-'),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('SDK version')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_sdk || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Used time')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_uptime || '-'),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Security patch level')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_security || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('System on Chip (SoC)')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_soc ? (data.device_soc.toUpperCase()) : '-'),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, _('Kernel version')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.device_uname ? (data.device_uname.kernel) : '-')
|
||||
])
|
||||
])
|
||||
];
|
||||
var battery = [
|
||||
E('h3', {'class': 'section-title'}, _('Battery Information')),
|
||||
E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr table-title', 'style': 'display: none;'}),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Level')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.battery_level || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Charge counter')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.battery_counter || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Voltage')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.battery_voltage || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Temperature')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.battery_temperature || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Technology')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.battery_technology || '-')
|
||||
]),
|
||||
])
|
||||
];
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, device),
|
||||
E('div', {'class': 'cbi-section'}, battery)
|
||||
]);
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,228 @@
|
||||
/* This is free software, licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
var page = 1;
|
||||
function renderTable(data, display) {
|
||||
var startIndex = (page - 1) * display;
|
||||
var endIndex = Math.min(startIndex + display, data.length);
|
||||
var currentPageData = data.slice(startIndex, endIndex);
|
||||
var tableRows = currentPageData.map(function(inbox, index) {
|
||||
var rowClass = index % 2 === 0 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2';
|
||||
var message = inbox.message.replace(/\n/g, '<br>');
|
||||
return E('tr', {'class': 'tr ' + rowClass }, [
|
||||
E('td', {'class': 'td'}, inbox.received.date),
|
||||
E('td', {'class': 'td'}, inbox.address),
|
||||
E('td', {'class': 'td'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(inbox.address, [
|
||||
E('p', [
|
||||
E('em', `${inbox.received.date} - ${inbox.received.time}`)
|
||||
]),
|
||||
E('p', message),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('OK'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('View'))
|
||||
])
|
||||
]);
|
||||
});
|
||||
return E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr table-titles'}, [
|
||||
E('th', {'class': 'th'}, _('Date')),
|
||||
E('th', {'class': 'th'}, _('Address')),
|
||||
E('th', {'class': 'th'})
|
||||
]),
|
||||
E(tableRows)
|
||||
]);
|
||||
};
|
||||
function updateTable(data, display) {
|
||||
var container = document.getElementsByClassName('table-container');
|
||||
var prev = document.querySelector('.prev');
|
||||
var next = document.querySelector('.next');
|
||||
for (var i = 0; i < container.length; i++) {
|
||||
container[i].innerHTML = '';
|
||||
var table = renderTable(data, display);
|
||||
container[i].appendChild(table);
|
||||
};
|
||||
var total = data.length;
|
||||
var pages = Math.ceil(total / display);
|
||||
if (pages <= 1) {
|
||||
prev.disabled = true;
|
||||
next.disabled = true;
|
||||
} else if (page <= 1) {
|
||||
prev.disabled = true;
|
||||
next.disabled = false;
|
||||
} else if (page >= pages) {
|
||||
prev.disabled = false;
|
||||
next.disabled = true;
|
||||
} else {
|
||||
prev.disabled = false;
|
||||
next.disabled = false;
|
||||
};
|
||||
var start = (page - 1) * display + 1;
|
||||
var end = Math.min(start + display - 1, total);
|
||||
document.getElementById('page-info').innerText = _('Displaying %s - %s of %s').format(start, end, total);
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
load: function() {
|
||||
return uci.load('droidnet').then(function() {
|
||||
var device = uci.get('droidnet', 'device', 'id');
|
||||
var display = uci.get('droidnet', 'device', 'display_msg');
|
||||
return Promise.all([
|
||||
fs.exec('adb', ['-s', device, 'shell', 'content', 'query', '--uri', 'content://sms', '--projection', 'date_sent,date,address,body']).then(function(result) {
|
||||
var messages = [];
|
||||
var stdout = result.stdout;
|
||||
var stderr = result.stderr;
|
||||
if (stderr) {
|
||||
messages['messages_section'] = stderr.trim();
|
||||
} else if (stdout.includes('Error')) {
|
||||
messages['messages_info'] = stdout.trim();
|
||||
} else {
|
||||
var lines = stdout.trim().split('Row: ');
|
||||
lines.shift();
|
||||
var properties = {
|
||||
'body': 'message',
|
||||
'address': 'address',
|
||||
'date': 'received'
|
||||
};
|
||||
lines.forEach(function(line) {
|
||||
var pairs = line.split(',');
|
||||
var inbox = [];
|
||||
pairs.forEach(function(pair) {
|
||||
var keyValue = pair.split('=');
|
||||
if (keyValue.length === 2) {
|
||||
var key = keyValue[0].trim();
|
||||
var value = keyValue[1].trim();
|
||||
if (properties.hasOwnProperty(key)) {
|
||||
if (key === 'body') {
|
||||
var start = line.indexOf('body=') + 5;
|
||||
var message = line.substring(start);
|
||||
inbox[properties[key]] = message;
|
||||
} else if (key === 'date') {
|
||||
var timestamp = parseInt(value);
|
||||
var date = new Date(timestamp).toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: '2-digit'
|
||||
});
|
||||
var time = new Date(timestamp).toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
inbox[properties[key]] = {'date': date, 'time': time}
|
||||
} else {
|
||||
inbox[properties[key]] = value;
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
messages.push(inbox);
|
||||
});
|
||||
};
|
||||
return messages;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
})
|
||||
]).then(function(result) {
|
||||
var messages = result[0];
|
||||
if (device && display && messages) {
|
||||
return Object.assign({device: device}, {display: display}, messages);
|
||||
} else {
|
||||
throw new Error(_('Failed to get complete device information.'));
|
||||
};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
var header = [
|
||||
E('h2', {'class': 'section-title'}, _('DroidNet')),
|
||||
E('div', {'class': 'cbi-map-descr'}, _('Manage Android modem and optimize network settings.'))
|
||||
];
|
||||
if (data.messages_section) {
|
||||
ui.addNotification(_('Error: Device conflict!'),
|
||||
E('p', _('Please check your settings, the configured device and ADB devices are conflicting.')), 'danger'
|
||||
);
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('div', {'class': 'cbi-value', 'style': 'text-align: center; display: block;'}, [
|
||||
E('em', _('No device detected or connected.'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
} else if (data.messages_info) {
|
||||
ui.addNotification(_('Error: Device not supported!'),
|
||||
E('p', _('Unable to read message because the device version cannot execute the command. Please ensure the device is rooted or has Android version 10 or above.')), 'danger'
|
||||
);
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('h3', {'class': 'section-title'}, _('Error Information')),
|
||||
E('textarea', {
|
||||
'id': 'syslog',
|
||||
'class': 'cbi-input-textarea',
|
||||
'style': 'height: 500px; overflow-y: scroll;',
|
||||
'readonly': 'readonly',
|
||||
'wrap': 'off',
|
||||
'rows': 1
|
||||
}, data.messages_info)
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
var value = Object.values(data).filter(function(obj) {
|
||||
return typeof obj === 'object' && !('device' in obj);
|
||||
});
|
||||
var display = parseInt(data.display);
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('h3', {'class': 'section-title'}, _('Inbox Messages')),
|
||||
E('div', {'class': 'cbi-section-descr'}, _('Displays incoming messages with sender, subject, content, and date and time of receipt.')),
|
||||
E('div', {'class': 'controls', 'style': 'display: flex; flex-wrap: wrap; justify-content: space-around; padding: 1em 0;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-neutral prev',
|
||||
'style': 'flex-basis: 20%; text-align: center;',
|
||||
'disabled': true,
|
||||
'click': function() {
|
||||
page--;
|
||||
updateTable(value, display);
|
||||
}
|
||||
}, '«'),
|
||||
E('div', {'class': 'text', 'id': 'page-info', 'style': 'flex-grow: 1; align-self: center; text-align: center;'}, _('Displaying 1-%s of %s').format(display, value.length)),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-neutral next',
|
||||
'style': 'flex-basis: 20%; text-align: center;',
|
||||
'click': function() {
|
||||
page++;
|
||||
updateTable(value, display);
|
||||
}
|
||||
}, '»')
|
||||
]),
|
||||
E('div', {'class': 'table-container'}, renderTable(value, display))
|
||||
])
|
||||
]);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/* This is free software, licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require poll';
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
load: function() {
|
||||
var notification = false;
|
||||
return Promise.all([
|
||||
poll.add(function() {
|
||||
fs.read('/var/log/droidnet.log').then(function(result) {
|
||||
if (!result || result.trim() === '') {
|
||||
ui.addNotification(_('Error: Read log file!'),
|
||||
E('p', _('Unable to read the interface info from /var/log/droidnet.log.')), 'danger'
|
||||
);
|
||||
notification = true;
|
||||
} else {
|
||||
var data = result.trim();
|
||||
var syslog = document.getElementById('syslog');
|
||||
var filter = document.getElementById('log-filter').value;
|
||||
var direction = document.getElementById('log-direction').value;
|
||||
if (filter !== 'all') {
|
||||
var value = data.split('\n').filter(function(log) {
|
||||
return log.includes(filter);
|
||||
}).join('\n');
|
||||
data = value;
|
||||
};
|
||||
if (direction === 'up') {
|
||||
var value = data.split('\n').reverse().join('\n');
|
||||
syslog.innerHTML = value;
|
||||
} else {
|
||||
syslog.innerHTML = data;
|
||||
};
|
||||
notification = false;
|
||||
};
|
||||
}).catch(function(error) {
|
||||
ui.addNotification(_('Error: Read log file!'),
|
||||
E('p', _('An error occurred while reading the file') + `: ${error}`), 'danger'
|
||||
);
|
||||
notification = true;
|
||||
});
|
||||
})
|
||||
]);
|
||||
},
|
||||
render: function() {
|
||||
var header = [
|
||||
E('h2', {'class': 'section-title'}, _('DroidNet')),
|
||||
E('div', {'class': 'cbi-map-descr'}, _('Manage Android modem and optimize network settings.'))
|
||||
];
|
||||
var menu = [
|
||||
E('label', {'for': 'log-filter', 'style': 'margin-right: 8px;'}, _('Filter by service') + ' : '),
|
||||
E('select', {'id': 'log-filter', 'style': 'margin: 8px 8px 8px 0;'}, [
|
||||
E('option', {'value': 'all', 'selected': 'selected'}, _('All')),
|
||||
E('option', {'value': 'Application' }, _('Application')),
|
||||
E('option', {'value': 'Monitoring' }, _('Monitoring')),
|
||||
E('option', {'value': 'Network' }, _('Network')),
|
||||
E('option', {'value': 'Power' }, _('Power'))
|
||||
]),
|
||||
E('label', {'for': 'log-direction', 'style': 'margin-right: 8px;'}, _('Log direction') + ' : '),
|
||||
E('select', {'id': 'log-direction', 'style': 'margin: 8px 8px 8px 0;'}, [
|
||||
E('option', {'value': 'down', 'selected': 'selected'}, _('Down')),
|
||||
E('option', {'value': 'up' }, _('Up'))
|
||||
]),
|
||||
E('div', {'class': 'log-button', 'style': 'display: inline-block; margin: 0 8px 8px 0;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'style': 'margin-right: 10px',
|
||||
'click': function() {
|
||||
fs.read('/var/log/droidnet.log').then(function(result) {
|
||||
var message = _('DroidNet logs have been successfully cleared.');
|
||||
var date = new Date().toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: '2-digit'
|
||||
});
|
||||
var time = new Date().toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
var notif = `${date}, ${time} - ${message}\n`;
|
||||
fs.write('/var/log/droidnet.log', notif);
|
||||
});
|
||||
}
|
||||
}, _('Clear')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-save',
|
||||
'click': function() {
|
||||
var logs = document.getElementById('syslog').value;
|
||||
var blob = new Blob([logs], {type: 'text/plain'});
|
||||
var link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'droidnet.log';
|
||||
link.click();
|
||||
}
|
||||
}, _('Download'))
|
||||
])
|
||||
];
|
||||
var body = [
|
||||
E('textarea', {
|
||||
'id': 'syslog',
|
||||
'class': 'cbi-input-textarea',
|
||||
'style': 'height: 500px; overflow-y: scroll;',
|
||||
'readonly': 'readonly',
|
||||
'wrap': 'off',
|
||||
'rows': 1
|
||||
})
|
||||
];
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('div', {'class': 'cbi-control'}, menu),
|
||||
E('div', {'class': 'cbi-body'}, body)
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
/* This is free software, licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com>, Anas Fanani <anas@anasfanani.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
function closeUi(type) {
|
||||
if (type === 'OK') {
|
||||
return E('button', {
|
||||
'class': 'btn',
|
||||
'click': function() {
|
||||
return window.location.reload();
|
||||
}
|
||||
}, _('OK'));
|
||||
} else {
|
||||
return E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'style': 'margin-right: 10px',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel'));
|
||||
};
|
||||
};
|
||||
function writeLog(message) {
|
||||
var logFile = '/var/log/droidnet.log';
|
||||
fs.read(logFile).then(function(result) {
|
||||
var service = _('Network service');
|
||||
var date = new Date().toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: '2-digit'
|
||||
});
|
||||
var time = new Date().toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
var notif = `${date}, ${time} - ${service}: ${message}`;
|
||||
var newData = result.trim() + '\n' + notif;
|
||||
return fs.write(logFile, newData);
|
||||
});
|
||||
};
|
||||
function renderTableCell(data, id) {
|
||||
if (id === 'sim-1') {
|
||||
var tab = 1;
|
||||
var sim = 0;
|
||||
var display = 'table';
|
||||
var imei = data.imei_sim01;
|
||||
} else {
|
||||
var tab = 2;
|
||||
var sim = 1;
|
||||
var display = 'none';
|
||||
var imei = data.imei_sim02;
|
||||
};
|
||||
return E('table', {'class': 'table cbi-section-table', 'id': `sim-${tab}`, 'style': `display: ${display}`}, [
|
||||
E('tr', {'class': 'tr table-titles', 'style': 'display: none;'}),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Operator name')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.operator[sim] || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Network type')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.signal[sim] || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Roaming mode')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.roaming[sim] === 'true' ? _('On') :
|
||||
_('Off')
|
||||
)
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('MCC')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.mcc[sim] || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('IMEI')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, imei || '-')
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Driver')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, data.driver || '-')
|
||||
])
|
||||
]);
|
||||
};
|
||||
function renderTableWiFi(device) {
|
||||
fs.exec('adb', ['-s', device, 'shell', 'dumpsys', 'wifi', '|', 'grep', 'mWifiInfo SSID']).then(function(result) {
|
||||
var table, wifiInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
var properties = {
|
||||
'mWifiInfo SSID': 'ssid',
|
||||
'BSSID': 'bssid',
|
||||
'MAC': 'mac',
|
||||
'RSSI': 'rssi',
|
||||
'Link speed': 'speed',
|
||||
'Frequency': 'frequency'
|
||||
};
|
||||
if (stderr) {
|
||||
table = E('p', stderr);
|
||||
} else {
|
||||
var lines = stdout.trim().split('\n')[0];
|
||||
var parts = lines.split(', ');
|
||||
parts.forEach(function(part) {
|
||||
var keyValue = part.split(': ');
|
||||
var key = keyValue[0].trim();
|
||||
var value = keyValue[1].replace(/"/g, '');
|
||||
if (properties[key]) {
|
||||
if (key === 'Frequency' || key === 'Link speed') {
|
||||
value = value.replace(/(\d+)([A-Za-z]+)/g, '$1 $2');
|
||||
};
|
||||
if (key === 'RSSI') {
|
||||
value += ' dBm';
|
||||
};
|
||||
wifiInfo[properties[key]] = value;
|
||||
};
|
||||
});
|
||||
};
|
||||
table = [
|
||||
E('h3', {'class': 'section-title'}, _('Wireless Information')),
|
||||
E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr table-titles', 'style': 'display: none;'}),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('SSID')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, wifiInfo.ssid)
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('BSSID')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, wifiInfo.bssid)
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('MAC address')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, wifiInfo.mac)
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('RSSI')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, wifiInfo.rssi)
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Link speed')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, wifiInfo.speed)
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Frequency')),
|
||||
E('td', {'class': 'td left', 'width': '50%'}, wifiInfo.frequency)
|
||||
])
|
||||
])
|
||||
];
|
||||
return document.getElementById('wirelessMenu').append(...table);
|
||||
}).catch(function(error) {
|
||||
var message = E('p', error);
|
||||
return document.getElementById('wirelessMenu').append(...message);
|
||||
});
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
load: function() {
|
||||
return uci.load('droidnet').then(function() {
|
||||
var device = uci.get('droidnet', 'device', 'id');
|
||||
return Promise.all([
|
||||
fs.exec('adb', ['-s', device, 'shell', 'getprop']).then(function(result) {
|
||||
var networkInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
var properties = {
|
||||
'gsm.operator.alpha': 'operator',
|
||||
'gsm.network.type': 'signal',
|
||||
'gsm.version.ril-impl': 'driver',
|
||||
'gsm.operator.isroaming': 'roaming',
|
||||
'gsm.sim.operator.numeric': 'mcc'
|
||||
};
|
||||
if (stderr) {
|
||||
networkInfo['network_section'] = stderr.trim();
|
||||
} else {
|
||||
var lines = stdout.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
for (var property in properties) {
|
||||
if (lines[i].includes('[' + property + ']')) {
|
||||
var parts = lines[i].split(']: [');
|
||||
var value = parts[1].substring(0, parts[1].length - 1);
|
||||
if (value.includes(',')) {
|
||||
value = value.split(',').map(function(item) {
|
||||
return item.trim();
|
||||
});
|
||||
}else {
|
||||
value = [value.trim(), ''];
|
||||
};
|
||||
networkInfo[properties[property]] = value;
|
||||
break;
|
||||
};
|
||||
if (!networkInfo.hasOwnProperty(properties[property])) {
|
||||
networkInfo[properties[property]] = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return networkInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'service', 'call', 'iphonesubinfo', '1', 's16', 'com.android.shell']).then(function(result) {
|
||||
var imeisim01Info = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
imeisim01Info['imei_sim01_section'] = stderr.trim();
|
||||
} else {
|
||||
var matches = stdout.match(/'([^']+)'/g);
|
||||
var value = matches.map(function(match) {
|
||||
return match.slice(1, -1);
|
||||
});
|
||||
var combined = value.join('');
|
||||
var imei = combined.replace(/[.\s]/g, '');
|
||||
imeisim01Info['imei_sim01'] = imei;
|
||||
};
|
||||
return imeisim01Info;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'ip', 'route']).then(function(result) {
|
||||
var ipInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (result && stderr) {
|
||||
ipInfo['ip_section'] = stderr.trim();
|
||||
} else if (result && stdout) {
|
||||
var parts = stdout.trim().split(/\s+/);
|
||||
var value = parts.slice(-1)[0];
|
||||
ipInfo['ip'] = value;
|
||||
} else {
|
||||
ipInfo['ip'] = false;
|
||||
};
|
||||
return ipInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'dumpsys', 'wifi', '|', 'grep', 'Wi-Fi is']).then(function(result) {
|
||||
var wifiInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
wifiInfo['wifi_section'] = stderr.trim();
|
||||
} else {
|
||||
var status = stdout.trim() === 'Wi-Fi is enabled' ? true : false;
|
||||
wifiInfo['wifi'] = status;
|
||||
};
|
||||
return wifiInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'dumpsys', 'telephony.registry', '|', 'grep', 'mDataConnectionState=']).then(function(result) {
|
||||
var dataInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
dataInfo['data_section'] = stderr.trim();
|
||||
} else {
|
||||
if (stdout.includes('mDataConnectionState=2')) {
|
||||
var dataStatus = true;
|
||||
} else {
|
||||
var dataStatus = false;
|
||||
};
|
||||
dataInfo['data'] = dataStatus;
|
||||
};
|
||||
return dataInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'settings', 'get', 'global', 'airplane_mode_on']).then(function(result) {
|
||||
var airplaneInfo = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
airplaneInfo['airplane_section'] = stderr.trim();
|
||||
} else {
|
||||
var status = stdout.trim() === '1' ? true : false;
|
||||
airplaneInfo['airplane'] = status;
|
||||
};
|
||||
return airplaneInfo;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
]).then(function(results) {
|
||||
var networkInfo = results[0];
|
||||
var imeisim01Info = results[1];
|
||||
var ipInfo = results[2];
|
||||
var wifiInfo = results[3];
|
||||
var dataInfo = results[4];
|
||||
var airplaneInfo = results[5];
|
||||
if (device && networkInfo && imeisim01Info && ipInfo && wifiInfo && dataInfo && airplaneInfo) {
|
||||
return Object.assign({device: device}, networkInfo, imeisim01Info, ipInfo, wifiInfo, dataInfo, airplaneInfo);
|
||||
} else {
|
||||
throw new Error(_('Failed to get complete device information.'));
|
||||
};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
var header = [
|
||||
E('h2', {'class': 'section-title'}, _('DroidNet')),
|
||||
E('div', {'class': 'cbi-map-descr'}, _('Manage Android modem and optimize network settings.'))
|
||||
];
|
||||
if (data.network_section) {
|
||||
ui.addNotification(_('Error: Device conflict!'),
|
||||
E('p', _('Please check your settings, the configured device and ADB devices are conflicting.')), 'danger'
|
||||
);
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('div', {'class': 'cbi-value', 'style': 'text-align: center; display: block;'}, [
|
||||
E('em', _('No device detected or connected.'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
var networkMenu = [
|
||||
E('h3', {'class': 'section-title'}, _('Mobile Network')),
|
||||
E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr table-titles', 'style': 'display: none;'}),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('IP address')),
|
||||
E('td', {'class': 'td left'}, data.ip || '-'),
|
||||
E('td', {'class': 'td'})
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2', 'style': 'display: table-row;'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Wireless connection')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.wifi === true ? _('On') :
|
||||
_('Off')
|
||||
),
|
||||
E('td', {'class': 'td center', 'width': '25%'}, [
|
||||
data.wifi === false ? E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Wireless network'), [
|
||||
E('p', _('Are you sure want to switched on wireless connection?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Turning on wireless connection '), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for wireless connection to be turned on…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'svc', 'wifi', 'enable']);
|
||||
writeLog(_('Wireless connection has been switched on.'));
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Wireless connection has been switched on'), [
|
||||
E('p', _('Wireless connection has been successfully switched on.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
}, 5000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Enable')) :
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'click': function() {
|
||||
ui.showModal(_('Wireless network'), [
|
||||
E('p', _('Are you sure want to switched off wireless connection?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Turning off wireless connection '), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for wireless connection to be turned off…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'svc', 'wifi', 'disable']);
|
||||
writeLog(_('Wireless connection has been switched off.'));
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Wireless connection has been switched off'), [
|
||||
E('p', _('Wireless connection has been successfully switched off.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
}, 5000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Disable'))
|
||||
])
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-1', 'style': 'display: table-row;'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Mobile data')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.data === true ? _('On') :
|
||||
_('Off')
|
||||
),
|
||||
E('td', {'class': 'td center', 'width': '25%'}, [
|
||||
data.data === false ? E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Mobile network'), [
|
||||
E('p', _('Are you sure want to switched on mobile data?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
if (data.airplane === true) {
|
||||
ui.showModal(_('Failed to switch on mobile data'), [
|
||||
E('p', _('Failed to switch on mobile data because the airplane mode is active.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
writeLog(_('Failed to swich on mobile data because the airplane mode is active.'));
|
||||
} else {
|
||||
ui.showModal(_('Turning on mobile data'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for mobile data to be turned on…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'svc', 'data', 'enable']);
|
||||
writeLog(_('Mobile data has been switched on.'));
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Mobile data has been switched on'), [
|
||||
E('p', _('Mobile data has been successfully switched on.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
}, 5000);
|
||||
};
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Enable')) :
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'click': function() {
|
||||
ui.showModal(_('Mobile network'), [
|
||||
E('p', _('Are you sure want to switched off mobile data?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Turning off mobile data'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for mobile data to be turned off…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'svc', 'data', 'disable']);
|
||||
writeLog(_('Mobile data has been switched off.'));
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Mobile data has been switched off'), [
|
||||
E('p', _('Mobile data has been successfully switched off.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
}, 5000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Disable'))
|
||||
])
|
||||
]),
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2', 'style': 'display: table-row;'}, [
|
||||
E('td', {'class': 'td left', 'width': '50%'}, _('Airplane mode')),
|
||||
E('td', {'class': 'td left', 'width': '25%'}, data.airplane === true ? _('On') :
|
||||
_('Off')
|
||||
),
|
||||
E('td', {'class': 'td center', 'width': '25%'}, [
|
||||
data.airplane === false ? E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Airplane mode'), [
|
||||
E('p', _('Are you sure want to switched on airplane mode?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'cmd', 'connectivity', 'airplane-mode', 'enable']).then(function(result) {
|
||||
if (result.stdout) {
|
||||
ui.showModal(_('Failed to switch on aiplane mode'), [
|
||||
E('p', _('Failed to switch on airplane mode because device version cannot execute the command, make sure device is rooted or android version is 10 or above.')),
|
||||
E('em', {'style': 'color: red;'}, result.stdout),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
writeLog(_('Failed to switch on airplane mode because android version is below 10.'));
|
||||
} else {
|
||||
ui.showModal(_('Turning on airplane mode'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for airplane mode to be turned on…'))
|
||||
]);
|
||||
writeLog(_('Airplane mode has been switched on.'));
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Airplane mode has been switched on'), [
|
||||
E('p', _('Airplane mode has been successfully switched on.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
}, 5000);
|
||||
};
|
||||
});
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Enable')) :
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'click': function() {
|
||||
ui.showModal(_('Airplane mode'), [
|
||||
E('p', _('Are you sure want to switched off airplane mode?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'cmd', 'connectivity', 'airplane-mode', 'disable']).then(function(result) {
|
||||
if (result.stdout) {
|
||||
ui.showModal(_('Failed to switch off aiplane mode'), [
|
||||
E('p', _('Failed to switch off airplane mode because device version cannot execute the command, make sure device is rooted or android version is 10 or above.')),
|
||||
E('em', {'style': 'color: red;'}, result.stdout),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
writeLog(_('Failed to switch off airplane mode because android version is below 10.'));
|
||||
} else {
|
||||
ui.showModal(_('Turning off Airplane mode'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for airplane mode to be turned off…'))
|
||||
]);
|
||||
writeLog(_('Airplane mode has been switched off.'));
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Airplane mode has been switched off'), [
|
||||
E('p', _('Airplane mode has been successfully switched off.')),
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
]);
|
||||
}, 5000);
|
||||
};
|
||||
});
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Disable'))
|
||||
])
|
||||
])
|
||||
])
|
||||
];
|
||||
var cellularMenu = [
|
||||
E('h3', {'class': 'section-title'}, _('Cellular Information')),
|
||||
E('ul', {'class': 'cbi-tabmenu'}, [
|
||||
data.operator && data.operator[0] ? E('li', {'class': 'cbi-tab', 'id': 'tab-1'}, [
|
||||
E('a', {
|
||||
'href': '#',
|
||||
'click': function() {
|
||||
document.getElementById('tab-1').className = 'cbi-tab';
|
||||
document.getElementById('tab-2').className = 'cbi-tab-disabled';
|
||||
document.getElementById('sim-1').style.display = 'table';
|
||||
document.getElementById('sim-2').style.display = 'none';
|
||||
}
|
||||
}, _('SIM 1'))
|
||||
]) : '',
|
||||
data.operator && data.operator[1] ? E('li', {'class': 'cbi-tab-disabled', 'id': 'tab-2'}, [
|
||||
E('a', {
|
||||
'href': '#',
|
||||
'click': function() {
|
||||
document.getElementById('tab-1').className = 'cbi-tab-disabled';
|
||||
document.getElementById('tab-2').className = 'cbi-tab';
|
||||
document.getElementById('sim-1').style.display = 'none';
|
||||
document.getElementById('sim-2').style.display = 'table';
|
||||
}
|
||||
}, _('SIM 2'))
|
||||
]) : ''
|
||||
]),
|
||||
E(renderTableCell(data, 'sim-1')),
|
||||
E(renderTableCell(data, 'sim-2'))
|
||||
];
|
||||
var wirelessMenu = [
|
||||
data.wifi === true ? E('div', {'id': 'wirelessMenu'}, renderTableWiFi(data.device)) : ''
|
||||
];
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, networkMenu),
|
||||
E('div', {'class': 'cbi-section'}, wirelessMenu),
|
||||
E('div', {'class': 'cbi-section'}, cellularMenu)
|
||||
]);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
/* This is free software, licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
var page = 1;
|
||||
var filter = '';
|
||||
var apkFile = '/tmp/upload.apk';
|
||||
function closeUi(type) {
|
||||
if (type === 'OK') {
|
||||
return E('button', {
|
||||
'class': 'btn',
|
||||
'click': function() {
|
||||
return window.location.reload();
|
||||
}
|
||||
}, _('OK'));
|
||||
} else {
|
||||
return E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'style': 'margin-right: 10px',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel'));
|
||||
};
|
||||
};
|
||||
function writeLog(message, service, error) {
|
||||
var logFile = '/var/log/droidnet.log';
|
||||
if (service === 'power') {
|
||||
var serviceName = _('Power service');
|
||||
} else {
|
||||
var serviceName = _('Application service');
|
||||
};
|
||||
fs.read(logFile).then(function(result) {
|
||||
var date = new Date().toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: '2-digit'
|
||||
});
|
||||
var time = new Date().toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
if (error === false) {
|
||||
var notif = `${date}, ${time} - ${serviceName}: ${message}`;
|
||||
} else {
|
||||
var notif = `${date}, ${time} - ${serviceName}: ${message}: ${error}`;
|
||||
};
|
||||
var newData = result.trim() + '\n' + notif;
|
||||
return fs.write(logFile, newData);
|
||||
});
|
||||
};
|
||||
function renderTable(data, display) {
|
||||
var tableRows, value = data.application;
|
||||
var start = (page - 1) * display;
|
||||
var packages = value.filter(function(item) {
|
||||
return item.toLowerCase().includes(filter.toLowerCase());
|
||||
});
|
||||
var end = Math.min(start + display, packages.length);
|
||||
var current = packages.slice(start, end);
|
||||
if (packages.length === 0) {
|
||||
tableRows = [
|
||||
E('tr', {'class': 'tr cbi-rowstyle-2'}, [
|
||||
E('td', {'class': 'td center', 'colspan': '2'}, [
|
||||
E('em', _('Application not found'))
|
||||
])
|
||||
])
|
||||
];
|
||||
} else {
|
||||
tableRows = current.map(function(value, index) {
|
||||
var rowClass = index % 2 === 0 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2';
|
||||
return E('tr', {'class': 'tr ' + rowClass }, [
|
||||
E('td', {'class': 'td left'}, value),
|
||||
E('td', {'class': 'td right'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'click': function() {
|
||||
ui.showModal(_('Remove application') + ` ${value}`, [
|
||||
E('p', _('Are you sure want to remove this application?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Removing application'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for application removal to complete…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'pm', 'uninstall', '-k', '--user', '0', value]).then(function(result) {
|
||||
var stdout = result.stdout;
|
||||
if (stdout.trim() !== 'Success') {
|
||||
writeLog(_('Failed to remove %s application.').format(value), 'application', stdout);
|
||||
ui.showModal(_('Package removal failed'), [
|
||||
E('p', _('Failed to remove <em>%s</em> application.').format(value)),
|
||||
E('em', {'style': 'color: red;'}, stdout),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('button', {'class': 'btn', 'click': ui.hideModal}, _('OK'))
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
writeLog(_('Removing %s application successfully.').format(value), 'application', false);
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Removing application completed'), [
|
||||
E('p', _('Application <em>%s</em> has been successfully removed.').format(value)),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
}, 10000);
|
||||
};
|
||||
});
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Remove'))
|
||||
])
|
||||
]);
|
||||
});
|
||||
};
|
||||
return E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr table-titles'}, [
|
||||
E('th', {'class': 'th left'}, _('Application name')),
|
||||
E('th', {'class': 'th'})
|
||||
]),
|
||||
E(tableRows)
|
||||
]);
|
||||
};
|
||||
function updateTable(data, display) {
|
||||
var value = data.application;
|
||||
var container = document.getElementsByClassName('table-container');
|
||||
var prev = document.querySelector('.prev');
|
||||
var next = document.querySelector('.next');
|
||||
for (var i = 0; i < container.length; i++) {
|
||||
container[i].innerHTML = '';
|
||||
var table = renderTable(data, display);
|
||||
container[i].appendChild(table);
|
||||
};
|
||||
var packages = value.filter(function(item) {
|
||||
return item.toLowerCase().includes(filter.toLowerCase());
|
||||
});
|
||||
var total = packages.length;
|
||||
var pages = Math.ceil(total / display);
|
||||
if (pages <= 1) {
|
||||
prev.disabled = true;
|
||||
next.disabled = true;
|
||||
} else if (page <= 1) {
|
||||
prev.disabled = true;
|
||||
next.disabled = false;
|
||||
} else if (page >= pages) {
|
||||
prev.disabled = false;
|
||||
next.disabled = true;
|
||||
} else {
|
||||
prev.disabled = false;
|
||||
next.disabled = false;
|
||||
};
|
||||
var start = (page - 1) * display + 1;
|
||||
var end = Math.min(start + display - 1, total);
|
||||
document.getElementById('page-info').innerText = _('Displaying %s - %s of %s').format(start, end, total);
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
load: function() {
|
||||
return uci.load('droidnet').then(function() {
|
||||
var device = uci.get('droidnet', 'device', 'id');
|
||||
var display = uci.get('droidnet', 'device', 'display_app');
|
||||
return Promise.all([
|
||||
fs.exec('adb', ['-s', device, 'shell', 'df', 'sdcard', '-h']).then(function(result) {
|
||||
var storage = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
var properties = {
|
||||
'Size': 'size',
|
||||
'Used': 'use',
|
||||
'Avail': 'free',
|
||||
'Use%': 'percentage',
|
||||
'Mounted': 'mounted'
|
||||
};
|
||||
if (stderr) {
|
||||
storage['storage_section'] = stderr.trim();
|
||||
} else {
|
||||
var data = [];
|
||||
var lines = stdout.split('\n');
|
||||
var header = lines[0].split(/\s+/);
|
||||
var value = lines[1].split(/\s+/);
|
||||
for (var i = 0; i < header.length; i++) {
|
||||
var property = properties[header[i]];
|
||||
if (property) {
|
||||
data[property] = value[i];
|
||||
};
|
||||
};
|
||||
storage['storage'] = data;
|
||||
};
|
||||
return storage;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('adb', ['-s', device, 'shell', 'pm', 'list', 'packages']).then(function(result) {
|
||||
var application = {};
|
||||
var stderr = result.stderr;
|
||||
var stdout = result.stdout;
|
||||
if (stderr) {
|
||||
application['application_section'] = stderr.trim();
|
||||
} else {
|
||||
var parts = stdout.trim().split('\n').map(function(line) {
|
||||
return line.replace('package:', '');
|
||||
});
|
||||
application['application'] = parts;
|
||||
};
|
||||
return application;
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
})
|
||||
]).then(function(results) {
|
||||
var storage = results[0];
|
||||
var application = results[1];
|
||||
if (device && display && storage && application) {
|
||||
return Object.assign({device: device}, {display: display}, storage, application)
|
||||
} else {
|
||||
throw new Error(_('Failed to get complete device information.'));
|
||||
};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
var display = parseInt(data.display);
|
||||
var application = data.application;
|
||||
var header = [
|
||||
E('h2', {'class': 'section-title'}, _('DroidNet')),
|
||||
E('div', {'class': 'cbi-map-descr'}, _('Manage Android modem and optimize network settings.'))
|
||||
];
|
||||
if (data.storage_section) {
|
||||
ui.addNotification(_('Error: Device conflict!'),
|
||||
E('p', _('Please check your settings, the configured device and ADB devices are conflicting.')), 'danger'
|
||||
);
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, [
|
||||
E('div', {'class': 'cbi-value', 'style': 'text-align: center; display: block;'}, [
|
||||
E('em', _('No device detected or connected.'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
var powerMenu = [
|
||||
E('h3', {'class': 'section-title'}, _('Power Options')),
|
||||
E('div', {'class': 'cbi-section-descr'}, _('Let you shutdown, restart, access fastboot mode or recovery mode, all in one place.')),
|
||||
E('table', {'class': 'table cbi-section-table'}, [
|
||||
E('tr', {'class': 'tr'}, [
|
||||
E('td', {'class': 'td center', 'style': 'border: none;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-save',
|
||||
'style': 'margin: 10px 0!important;',
|
||||
'click': function() {
|
||||
ui.showModal(_('Fastboot mode'), [
|
||||
E('p', _('Are you sure want rebooting device to fastboot mode?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Rebooting to fastboot mode'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for device to reboot into fastboot mode…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'reboot', 'bootloader']);
|
||||
writeLog(_('Device entered fastboot mode.'), 'power', false);
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Fastboot mode completed'), [
|
||||
E('p', _('Device has been successfully to fastboot mode.')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
}, 10000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Fastboot mode'))
|
||||
]),
|
||||
E('td', {'class': 'td center', 'style': 'border: none;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-save',
|
||||
'style': 'margin: 10px 0!important;',
|
||||
'click': function() {
|
||||
ui.showModal(_('Recovery mode'), [
|
||||
E('p', _('Are you sure want rebooting device to recovery mode?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Rebooting to recovery mode'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for device to reboot into recovery mode…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'reboot', 'recovery']);
|
||||
writeLog(_('Device entered recovery mode.'), 'power', false);
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Recovery mode completed'), [
|
||||
E('p', _('Device has been successfully to recovery mode.')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
}, 10000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Recovery mode'))
|
||||
]),
|
||||
E('td', {'class': 'td center', 'style': 'border: none;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'style': 'margin: 10px 0!important;',
|
||||
'click': function() {
|
||||
ui.showModal(_('Restart device'), [
|
||||
E('p', _('Are you sure want to restart device?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Restarting device'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for device restart to complete…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'reboot']);
|
||||
writeLog(_('Device restarted successfully.'), 'power', false);
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Device restart completed'), [
|
||||
E('p', _('Device has been successfully restarted.')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
}, 30000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Restart'))
|
||||
]),
|
||||
E('td', {'class': 'td center', 'style': 'border: none;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'style': 'margin: 10px 0!important;',
|
||||
'click': function() {
|
||||
ui.showModal(_('Shutdown device'), [
|
||||
E('p', _('Are you sure want to shutdown device?')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E(closeUi('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Shutting down device'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for device to shut down…'))
|
||||
]);
|
||||
fs.exec('adb', ['-s', data.device, 'shell', 'reboot', '-p']);
|
||||
writeLog(_('Device powered off successfully.'), 'power', false);
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Device shutdown completed'), [
|
||||
E('p', _('Device has been successfully shut down.')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
}, 15000);
|
||||
}
|
||||
}, _('Yes'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Shutdown'))
|
||||
])
|
||||
])
|
||||
])
|
||||
];
|
||||
var applicationMenu = [
|
||||
E('h3', {'class': 'section-title'}, _('Application Manager')),
|
||||
E('div', {'class': 'cbi-section-descr'}, _('Lists installed apps on device.')),
|
||||
E('div', {'class': 'controls', 'style': 'display: flex; flex-wrap: wrap;'}, [
|
||||
E('div', {'class': 'disk-application', 'style': 'flex-basis: 100%; min-width: 250px; padding: .25em;'}, [
|
||||
E('label', _('Disk space') + ' : '),
|
||||
E('div', {'class': 'cbi-progressbar', 'title': _('%s used (%s used of %s, %s free)').format(data.storage.percentage, data.storage.use, data.storage.size, data.storage.free)}, [
|
||||
E('div', {'style': `width: ${data.storage.percentage};`}, ' ')
|
||||
])
|
||||
]),
|
||||
E('div', {'class': 'filter-application', 'style': 'padding: .25em;'}, [
|
||||
E('label', _('Filter') + ' : '),
|
||||
E('span', {'class': 'control-group', 'style': 'display: flex;'}, [
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'class': 'filter-input',
|
||||
'placeholder': 'Type to filter…',
|
||||
'keyup': function(event) {
|
||||
filter = event.target.value;
|
||||
page = 1;
|
||||
updateTable(data, display);
|
||||
}
|
||||
}),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button',
|
||||
'click': function() {
|
||||
filter = '';
|
||||
page = 1;
|
||||
updateTable(data, display);
|
||||
document.querySelector('.filter-input').value = '';
|
||||
}
|
||||
}, _('Clear'))
|
||||
])
|
||||
]),
|
||||
E('div', {'class': 'action-application', 'style': 'padding: .25em;'}, [
|
||||
E('label', _('Actions') + ' : '),
|
||||
E('span', {'class': 'control-group', 'style': 'display: flex;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-save',
|
||||
'style': 'margin-right: 10px',
|
||||
'click': function() {
|
||||
ui.showModal(_('Updating application list'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for update application list command to complete…'))
|
||||
]);
|
||||
setTimeout(function() {
|
||||
ui.showModal(_('Update application list completed'), [
|
||||
E('p', _('Application list has been successfully updated.')),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
}, 10000);
|
||||
}
|
||||
}, _('Update list')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.uploadFile(apkFile).then(function(result) {
|
||||
var apkName = result.name;
|
||||
ui.showModal(_('Install application'), [
|
||||
E('p', _('Installing application from untrusted sources is a potential security risk, really attempt to install <em>%s</em>?').format(apkName)),
|
||||
E('ul', [
|
||||
result.size ? E('li', '%s: %1024.2mB'.format(_('Size'), result.size)) : '',
|
||||
result.checksum ? E('li', '%s: %s'.format(_('MD5'), result.checksum)) : '',
|
||||
result.sha256sum ? E('li', '%s: %s'.format(_('SHA256'), result.sha256sum)) : ''
|
||||
]),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'style': 'margin-right: 10px',
|
||||
'click': function() {
|
||||
fs.remove(apkFile);
|
||||
ui.hideModal();
|
||||
}
|
||||
}, _('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
ui.showModal(_('Installing application'), [
|
||||
E('p', {'class': 'spinning'}, _('Waiting for application installation to complete…'))
|
||||
]);
|
||||
fs.exec_direct('adb', ['-s', data.device, 'install', apkFile]).then(function(result) {
|
||||
var apk = result.trim();
|
||||
if (apk === 'Success') {
|
||||
writeLog(_('Application %s has been successfully installed.').format(apkName), 'application', false);
|
||||
ui.showModal(_('Application installation completed'), [
|
||||
E('p', _('Application <em>%s</em> has been successfully installed.').format(apkName)),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
writeLog(_('Failed to install %s application').format(apkName), 'application', apk);
|
||||
ui.showModal(_('Application installation failed'), [
|
||||
E('p', _('Failed to install <em>%s</em> application.').format(apkName)),
|
||||
E('em', {'style': 'color: red;'}, apk),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
};
|
||||
return fs.remove(apkFile);
|
||||
});
|
||||
}
|
||||
}, _('Install'))
|
||||
])
|
||||
])
|
||||
}).catch(function(error) {
|
||||
if (error.message === 'Upload has been cancelled') {
|
||||
ui.hideModal();
|
||||
} else {
|
||||
writeLog(_('Failed to upload %s application.').format(apkName), 'application', error);
|
||||
ui.showModal(_('Error uploading application'), [
|
||||
E('p', _('Failed to upload <em>%s</em> application.').format(apkName)),
|
||||
E('em', {'style': 'color: red;'}, error),
|
||||
E('div', {'class': 'right'}, [
|
||||
E('div', {'class': 'right'}, [E(closeUi('OK'))])
|
||||
])
|
||||
]);
|
||||
};
|
||||
});
|
||||
}
|
||||
}, _('Upload application'))
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', {'class': 'controls', 'style': 'display: flex; flex-wrap: wrap; justify-content: space-around; padding: 1em 0;'}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-neutral prev',
|
||||
'style': 'flex-basis: 20%; text-align: center;',
|
||||
'disabled': true,
|
||||
'click': function() {
|
||||
page--;
|
||||
updateTable(data, display);
|
||||
}
|
||||
}, '«'),
|
||||
E('div', {'class': 'text', 'id': 'page-info', 'style': 'flex-grow: 1; align-self: center; text-align: center;'}, _('Displaying 1 - %s of %s').format(display, application.length)),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-neutral next',
|
||||
'style': 'flex-basis: 20%; text-align: center;',
|
||||
'click': function() {
|
||||
page++;
|
||||
updateTable(data, display);
|
||||
}
|
||||
}, '»')
|
||||
]),
|
||||
E('div', {'class': 'table-container'}, renderTable(data, display))
|
||||
];
|
||||
return E('div', {'class': 'cbi-map'}, [
|
||||
E(header),
|
||||
E('div', {'class': 'cbi-section'}, powerMenu),
|
||||
E('div', {'class': 'cbi-section'}, applicationMenu)
|
||||
]);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/* This is free software, licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return uci.load('droidnet').then(function() {
|
||||
return Promise.all([
|
||||
fs.exec('adb', ['devices', '-l']).then(function(result) {
|
||||
var devices = {};
|
||||
var stdout = result.stdout;
|
||||
var stderr = result.stderr;
|
||||
if (stderr || stdout === 'List of devices attached \n\n') {
|
||||
devices = false;
|
||||
} else {
|
||||
var lines = stdout.trim().split('\n');
|
||||
lines.shift();
|
||||
lines.forEach(function(line) {
|
||||
var model = '';
|
||||
var parts = line.split(/\s+/);
|
||||
var device = parts[0].trim();
|
||||
parts.forEach(function(part) {
|
||||
if (part.startsWith('model:')) {
|
||||
model = part.substring(6);
|
||||
};
|
||||
});
|
||||
devices[device] = model;
|
||||
});
|
||||
};
|
||||
return {devices};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.list('/etc/init.d').then(function(result) {
|
||||
var tunnelService = {};
|
||||
var fileNames = {
|
||||
'neko': 'Neko',
|
||||
'openclash': 'OpenClash',
|
||||
'passwall': 'PassWall',
|
||||
'v2ray': 'V2Ray'
|
||||
};
|
||||
result.forEach(function(file) {
|
||||
if (fileNames[file.name]) {
|
||||
tunnelService[file.name] = fileNames[file.name];
|
||||
};
|
||||
});
|
||||
if (Object.keys(tunnelService).length === 0) {
|
||||
tunnelService = false;
|
||||
}
|
||||
return {tunnelService};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
}),
|
||||
fs.exec('pgrep', ['-f', '/usr/share/droidnet/monitoring']).then(function(result) {
|
||||
var status = {};
|
||||
if (result && result.code === 1) {
|
||||
status = false;
|
||||
} else {
|
||||
status = true;
|
||||
};
|
||||
return {status};
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
})
|
||||
]).then(function(results) {
|
||||
var devices = results[0];
|
||||
var tunnelService = results[1];
|
||||
var status = results[2];
|
||||
return Object.assign(devices, tunnelService, status)
|
||||
}).catch(function(error) {
|
||||
throw new Error(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
var m, s, o;
|
||||
m = new form.Map('droidnet', _('DroidNet'),
|
||||
_('Manage Android modem and optimize network settings.'));
|
||||
|
||||
s = m.section(form.NamedSection, 'device', 'droidnet', _('Base Setting'));
|
||||
if (data.devices == false) {
|
||||
o = s.option(form.DummyValue, 'dummy', _('Device'));
|
||||
o.default = _('No device detected.');
|
||||
} else {
|
||||
o = s.option(form.ListValue, 'id', _('Device'));
|
||||
Object.keys(data.devices).forEach(function(deviceID) {
|
||||
o.value(deviceID, data.devices[deviceID]);
|
||||
});
|
||||
o.rmempty = false;
|
||||
};
|
||||
o = s.option(form.Value, 'display_app', _('Application limit'),
|
||||
_('Set display limit per page for application manager.'));
|
||||
o.placeholder = _('1 - 100');
|
||||
o.datatype = 'range(1,100)';
|
||||
o.rmempty = false;
|
||||
o = s.option(form.Value, 'display_msg', _('Messages limit'),
|
||||
_('Set display limit per page for messages information.'));
|
||||
o.placeholder = _('1 - 100');
|
||||
o.datatype = 'range(1,100)';
|
||||
o.rmempty = false;
|
||||
|
||||
s = m.section(form.NamedSection, 'monitoring', 'droidnet', _('Monitoring Service'),
|
||||
_('Monitor network performance on android modem to ensure optimal connectivity stability.'));
|
||||
o = s.option(form.DummyValue, 'dummy', _('Status'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function(section_id) {
|
||||
var span = '<b><span style="color:%s">%s</span></b>';
|
||||
var renderHTML = data.status ?
|
||||
String.format(span, 'green', _('Running')) :
|
||||
String.format(span, 'red', _('Not Running'));
|
||||
return renderHTML;
|
||||
};
|
||||
o = s.option(form.Flag, 'enable', _('Enable'));
|
||||
o.rmempty = false;
|
||||
o = s.option(form.ListValue, 'ping', _('Ping method'),
|
||||
_('Set method for pinging host address.'));
|
||||
o.value('http', _('HTTP'));
|
||||
o.value('https', _('HTTPS'));
|
||||
o.value('icmp', _('ICMP'));
|
||||
o.value('tcp', _('TCP'));
|
||||
o.rmempty = false;
|
||||
o = s.option(form.Value, 'host', _('Host'),
|
||||
_('Host address you want to ping. Recommended to use bug on Tun.'));
|
||||
o.placeholder = _('Host address');
|
||||
o.rmempty = false;
|
||||
o = s.option(form.ListValue, 'success_limit', _('Max ping successes'),
|
||||
_('Maximum number of successful ping attempts to log "Host reachable" message.'));
|
||||
o.value('1', _('1 successes'));
|
||||
o.value('2', _('2 successes'));
|
||||
o.value('3', _('3 successes'));
|
||||
o.value('4', _('4 successes'));
|
||||
o.value('5', _('5 successes'));
|
||||
o.value('unlimited', _('Unlimited'));
|
||||
o.rmempty = false;
|
||||
o = s.option(form.ListValue, 'failure_limit', _('Max ping attempts'),
|
||||
_('Maximum number of unsuccessful ping attempts to trigger service.'));
|
||||
o.value('1', _('1 attempts'));
|
||||
o.value('2', _('2 attempts'));
|
||||
o.value('3', _('3 attempts'));
|
||||
o.value('4', _('4 attempts'));
|
||||
o.value('5', _('5 attempts'));
|
||||
o.rmempty = false;
|
||||
o = s.option(form.ListValue, 'wait_time', _('Waiting time'),
|
||||
_('Time to wait (in seconds) before activating airplane mode after ping failure.'));
|
||||
o.value('1', _('1 seconds'));
|
||||
o.value('2', _('2 seconds'));
|
||||
o.value('3', _('3 seconds'));
|
||||
o.value('4', _('4 seconds'));
|
||||
o.value('5', _('5 seconds'));
|
||||
o.rmempty = false;
|
||||
o = s.option(widgets.NetworkSelect, 'interface', _('Interface'),
|
||||
_('Name of interface to be restarted.'));
|
||||
o.nocreate = true;
|
||||
o.rmempty = false;
|
||||
o = s.option(form.Flag, 'restart', _('Restart the tunnel'),
|
||||
_('Enable to automatically restart tunneling tool.'));
|
||||
o.rmempty = false;
|
||||
if (data.tunnelService === false) {
|
||||
o = s.option(form.DummyValue, 'dummy', _('Tunneling tool'),
|
||||
_('Set tunneling tool to be restarted.'));
|
||||
o.default = _('No tunneling tools found.');
|
||||
} else {
|
||||
o = s.option(form.ListValue, 'tunnel_service', _('Tunneling tool'),
|
||||
_('Set tunneling tool to be restarted.'));
|
||||
Object.keys(data.tunnelService).forEach(function(pathID) {
|
||||
o.value(pathID, data.tunnelService[pathID]);
|
||||
});
|
||||
};
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user