small-packages/luci-app-partexp/htdocs/luci-static/resources/view/partexp/manual.js
2026-05-10 09:48:30 +08:00

921 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2022-2025 Sirpdboy <herboy2008@gmail.com>
*
* Licensed to the public under the Apache License 2.0
*/
'use strict';
'require form';
'require fs';
'require rpc';
'require uci';
'require ui';
'require view';
// 声明 RPC 接口
var callPartExpAutopart = rpc.declare({
object: 'partexp',
method: 'autopart'
});
var callPartExpGetLog = rpc.declare({
object: 'partexp',
method: 'get_log',
params: ['position']
});
var callPartExpGetDevices = rpc.declare({
object: 'partexp',
method: 'get_devices'
});
var callPartExpGetStatus = rpc.declare({
object: 'partexp',
method: 'get_status'
});
// 添加保存配置的 RPC 声明
var callPartExpSaveConfig = rpc.declare({
object: 'partexp',
method: 'save_config',
params: ['target_function', 'target_disk', 'keep_config', 'format_type']
});
return view.extend({
load: function() {
return Promise.all([
L.resolveDefault(fs.stat('/usr/bin/partexp'), null),
L.resolveDefault(fs.stat('/tmp/partexp.log'), null)
]);
},
render: function(data) {
var container = E('div', { class: 'cbi-map' });
var htmlParts = [
'<style>',
'.state-ctl .state { display: none !important; }',
'.state-ctl.state-ctl-ready .state.state-ready,',
'.state-ctl.state-ctl-executing .state.state-executing {',
' display: block !important;',
'}',
'.progress-container {',
' width: 100%;',
' height: 20px;',
' background: rgba(0,0,0,0.2);',
' border-radius: 10px;',
' display: inline-block;',
' margin: 10px 0;',
' vertical-align: middle;',
' position: relative;',
' overflow: hidden;',
'}',
'.progress-bar {',
' height: 100%;',
' background: linear-gradient(90deg, #4CAF50, #8BC34A);',
' transition: width 0.3s ease-out;',
' position: absolute;',
' left: 0;',
' top: 0;',
'}',
'.progress-text {',
' position: absolute;',
' width: 100%;',
' text-align: center;',
' line-height: 20px;',
' font-size: 12px;',
' font-weight: bold;',
' color: #eee;',
' z-index: 1;',
' text-shadow: 1px 1px 2px rgba(0,0,0,0.5);',
'}',
'.error-message {',
' color: #dc3545;',
'}',
'.info-note {',
' padding: 10px;',
' margin: 10px 0;',
' border-radius: 4px;',
'}',
'.log-view {',
' font-family: "Courier New", monospace;',
' font-size: 12px;',
' height: 300px;',
' overflow-y: auto;',
' white-space: pre-wrap;',
'}',
'</style>',
'<h2 name="content">' + _('One click partition expansion mounting tool') + '</h2>',
'<div class="cbi-section-descr">',
' <div class="info-note">',
' ' + _('Automatically format and mount the target device partition. If there are multiple partitions, it is recommended to manually delete all partitions before using this tool.') + '<br>',
' ' + _('For specific usage, see:') + ' ',
' <a href="https://github.com/sirpdboy/luci-app-partexp.git" target="_blank">',
' GitHub @partexp',
' </a>',
' </div>',
'</div>',
'<div class="state-ctl state-ctl-ready" id="state-container">',
' <div class="cbi-section cbi-section-node">',
' <div class="state state-ready">',
' <form id="partexp-form">',
' <div class="cbi-value">',
' <label class="cbi-value-title" for="target_function">' + _('Select function') + '</label>',
' <div class="cbi-value-field">',
' <select class="cbi-input-select" id="target_function" name="target_function">',
' <option value="/">' + _('Used to extend to the root directory of EXT4 firmware(Ext4 /)') + '</option>',
' <option value="/overlay">' + _('Expand application space overlay (/overlay)') + '</option>',
' <option value="/opt">' + _('Used as Docker data disk (/opt)') + '</option>',
' <option value="/mnt">' + _('Normal mount and use by device name(/mnt/x1)') + '</option>',
' </select>',
' <div class="cbi-value-description">' + _('Select the function to be performed') + '</div>',
' </div>',
' </div>',
' <div class="cbi-value">',
' <label class="cbi-value-title" for="target_disk">' + _('Destination hard disk') + '</label>',
' <div class="cbi-value-field">',
' <select class="cbi-input-select" id="target_disk" name="target_disk">',
' <option value="">' + _('Loading devices...') + '</option>',
' </select>',
' <div class="cbi-value-description">' + _('Select the hard disk device to operate') + '</div>',
' </div>',
' </div>',
' <div class="cbi-value">',
' <label class="cbi-value-title" for="keep_config">' + _('Keep configuration') + '</label>',
' <div class="cbi-value-field">',
' <input type="checkbox" class="cbi-input-checkbox" id="keep_config" name="keep_config" value="1" />',
' <label for="keep_config">' + _('Tick means to retain the settings') + '</label>',
' </div>',
' </div>',
' <div class="cbi-value">',
' <label class="cbi-value-title" for="format_type">' + _('Format system type') + '</label>',
' <div class="cbi-value-field">',
' <select class="cbi-input-select" id="format_type" name="format_type">',
' <option value="0">' + _('No formatting required') + '</option>',
' <option value="ext4">' + _('Linux system partition(EXT4)') + '</option>',
' <option value="btrfs">' + _('Large capacity storage devices(Btrfs)') + '</option>',
' <option value="ntfs">' + _('Windows system partition(NTFS)') + '</option>',
' </select>',
' </div>',
' </div>',
' <div class="cbi-value cbi-value-last">',
' <label class="cbi-value-title">' + _('Perform operation') + '</label>',
' <div class="cbi-value-field">',
' <button type="button" class="cbi-button cbi-button-apply" id="execute-btn">',
' ' + _('Click to execute') + '',
' </button>',
' </div>',
' </div>',
' </form>',
' </div>',
' <div class="state state-executing">',
' <div class="cbi-value">',
' <label class="cbi-value-title" id="execute_status">' + _('Starting operation...') + '</label>',
' <div class="cbi-value-field">',
' <div class="progress-container">',
' <div id="progress-bar" class="progress-bar" style="width: 0%"></div>',
' <div id="progress-text" class="progress-text">0%</div>',
' </div>',
' </div>',
' </div>',
' </div>',
' </div>',
' <div id="log-section" style="display: block; margin-top: 20px;">',
' <div class="cbi-value">',
' <label class="cbi-value-title">' + _('Operation Log') + '</label>',
' <div class="cbi-value-field">',
' <textarea id="log-view" class="log-view" readonly="readonly" rows="15"></textarea>',
' </div>',
' </div>',
' </div>',
'</div>'
];
container.innerHTML = htmlParts.join('');
var self = this;
var uci = self.uci || window.uci;
setTimeout(function() {
self.initDOM();
self.bindEvents();
self.loadDevices();
self.loadSavedConfig();
self.checkOperationStatus();
self.loadExistingLog();
}, 100);
return container;
},
initDOM: function() {
this.dom = {
stateContainer: document.querySelector('#state-container'),
targetFunction: document.querySelector('#target_function'),
targetDisk: document.querySelector('#target_disk'),
keepConfig: document.querySelector('#keep_config'),
formatType: document.querySelector('#format_type'),
executeBtn: document.querySelector('#execute-btn'),
logView: document.querySelector('#log-view'),
progressBar: document.querySelector('#progress-bar'),
progressText: document.querySelector('#progress-text'),
executeStatus: document.querySelector('#execute_status')
};
this.logPosition = '0';
this.logPolling = null;
this.isRunning = false;
this.operationComplete = false;
this.pollErrorCount = 0;
this.pollingStartTime = 0;
this.lastPollTime = 0;
this.currentProgress = 0;
this.autoSaveTimer = null;
this.isNewOperation = false; // 标记是否是新操作
},
bindEvents: function() {
var self = this;
if (this.dom.executeBtn) {
this.dom.executeBtn.addEventListener('click', function(e) {
e.preventDefault();
self.executeOperation();
});
}
[this.dom.targetFunction, this.dom.targetDisk, this.dom.formatType].forEach(function(element) {
if (element) {
element.addEventListener('change', function() {
self.autoSaveConfig();
self.updateFormVisibility();
});
}
});
if (this.dom.keepConfig) {
this.dom.keepConfig.addEventListener('click', function() {
self.autoSaveConfig();
});
}
if (this.dom.targetFunction) {
this.updateFormVisibility();
}
},
loadDevices: function() {
var self = this;
if (self.dom.targetDisk) {
var loadingOption = document.createElement('option');
loadingOption.value = '';
loadingOption.textContent = _('Loading devices...');
loadingOption.disabled = true;
loadingOption.selected = true;
self.dom.targetDisk.innerHTML = '';
self.dom.targetDisk.appendChild(loadingOption);
}
function loadDevicesWithRetry(retryCount = 0) {
callPartExpGetDevices().then(function(response) {
if (!response) {
throw new Error('Empty response');
}
if (self.dom.targetDisk) {
self.dom.targetDisk.innerHTML = '';
if (response.devices && response.devices.length > 0) {
response.devices.forEach(function(device) {
var option = document.createElement('option');
option.value = device.name;
option.textContent = device.name + ' (' + device.dev + ', ' + device.size + ' MB)';
self.dom.targetDisk.appendChild(option);
});
} else {
var noDeviceOption = document.createElement('option');
noDeviceOption.value = '';
noDeviceOption.textContent = _('no find device');
noDeviceOption.disabled = true;
noDeviceOption.selected = true;
self.dom.targetDisk.appendChild(noDeviceOption);
}
}
}).catch(function(error) {
console.error('Failed to load devices:', error);
if (retryCount < 3) {
setTimeout(function() {
loadDevicesWithRetry(retryCount + 1);
}, 1000 * (retryCount + 1));
} else {
if (self.dom.targetDisk) {
self.dom.targetDisk.innerHTML = '';
var errorOption = document.createElement('option');
errorOption.value = '';
errorOption.textContent = _('load error');
errorOption.disabled = true;
errorOption.selected = true;
self.dom.targetDisk.appendChild(errorOption);
}
ui.addNotification({
title: _('load device error'),
text: _('Failed to load devices:'),
type: 'error',
delay: 5000
});
}
});
}
loadDevicesWithRetry();
},
loadExistingLog: function() {
var self = this;
callPartExpGetLog('0').then(function(response) {
if (response && response.log) {
var logContent = response.log.toString().trim();
if (logContent && self.dom.logView) {
self.dom.logView.value = logContent;
setTimeout(function() {
if (self.dom.logView && self.dom.logView.value) {
self.dom.logView.scrollTop = self.dom.logView.scrollHeight;
}
}, 100);
if (response.position) {
self.logPosition = response.position;
}
if (!self.isRunning && logContent.includes('正在执行') && !logContent.includes('操作完成')) {
self.isRunning = true;
self.switchState('executing');
self.startLogPolling();
}
}
}
}).catch(function(error) {
console.error('Failed to load existing log:', error);
});
},
loadSavedConfig: function() {
var self = this;
return fs.read('/etc/config/partexp').then(function(content) {
if (!content) {
self.setDefaultConfig();
return;
}
var lines = content.split('\n');
var config = {};
lines.forEach(function(line) {
line = line.trim();
if (line.startsWith('option')) {
var parts = line.split(/\s+/);
if (parts.length >= 3) {
var key = parts[1];
var value = parts.slice(2).join(' ').replace(/^['"]|['"]$/g, '');
config[key] = value;
}
}
});
if (self.dom.targetFunction) {
self.dom.targetFunction.value = config.target_function || '/opt';
}
if (self.dom.targetDisk && config.target_disk) {
setTimeout(function() {
if (self.dom.targetDisk) {
self.dom.targetDisk.value = config.target_disk;
}
}, 500);
}
if (self.dom.keepConfig) {
self.dom.keepConfig.checked = (config.keep_config === '1');
}
if (self.dom.formatType) {
self.dom.formatType.value = config.format_type || '0';
}
self.configCache = config;
self.updateFormVisibility();
}).catch(function(error) {
console.log('Failed to load config:', error);
self.setDefaultConfig();
});
},
setDefaultConfig: function() {
if (this.dom.targetFunction) {
this.dom.targetFunction.value = '/opt';
}
if (this.dom.formatType) {
this.dom.formatType.value = '0';
}
if (this.dom.keepConfig) {
this.dom.keepConfig.checked = false;
}
this.updateFormVisibility();
this.configCache = {
target_function: '/opt',
target_disk: '',
keep_config: '0',
format_type: '0'
};
},
autoSaveConfig: function() {
var self = this;
if (this.autoSaveTimer) {
clearTimeout(this.autoSaveTimer);
}
this.autoSaveTimer = setTimeout(function() {
self.saveCurrentConfig();
}, 1500);
},
saveCurrentConfig: function() {
var self = this;
var targetFunction = this.dom.targetFunction ? this.dom.targetFunction.value : '/opt';
var targetDisk = this.dom.targetDisk ? this.dom.targetDisk.value : '';
var keepConfig = this.dom.keepConfig ? this.dom.keepConfig.checked : false;
var formatType = this.dom.formatType ? this.dom.formatType.value : '0';
if (callPartExpSaveConfig) {
return callPartExpSaveConfig(
targetFunction,
targetDisk,
keepConfig ? '1' : '0',
formatType
).then(function(response) {
if (response && response.success) {
self.configCache = {
target_function: targetFunction,
target_disk: targetDisk,
keep_config: keepConfig ? '1' : '0',
format_type: formatType
};
return true;
} else {
console.warn('RPC save failed, falling back to file write');
return self.saveConfigToFile(targetFunction, targetDisk, keepConfig, formatType);
}
}).catch(function(error) {
console.error('RPC save config error:', error);
return self.saveConfigToFile(targetFunction, targetDisk, keepConfig, formatType);
});
} else {
return self.saveConfigToFile(targetFunction, targetDisk, keepConfig, formatType);
}
},
saveConfigToFile: function(targetFunction, targetDisk, keepConfig, formatType) {
var configContent = [
'# Auto-generated by partexp',
'',
'config global global',
"\toption target_function '" + targetFunction + "'",
"\toption target_disk '" + targetDisk + "'",
"\toption keep_config '" + (keepConfig ? '1' : '0') + "'",
"\toption format_type '" + formatType + "'",
''
].join('\n');
return fs.write('/etc/config/partexp', configContent).then(function() {
console.log('Settings saved to file /etc/config/partexp');
return true;
}).catch(function(error) {
console.error('Failed to save settings to file:', error);
return false;
});
},
executeOperation: function() {
var self = this;
this.saveCurrentConfig();
var target_function = this.dom.targetFunction.value;
var target_disk = this.dom.targetDisk.value;
if (target_function !== '/' && (!target_disk || target_disk.trim() === '')) {
alert(_('Please select a target disk'));
return;
}
var confirmMessage = _('Are you sure you want to execute partition expansion?') + '\n\n' +
_('Function:') + ' ' + this.getFunctionDescription(target_function) + '\n' +
(target_function !== '/' ? _('Disk:') + ' ' + target_disk + '\n' : '') +
(target_function === '/' || target_function === '/overlay' ?
_('Keep config:') + ' ' + (this.dom.keepConfig.checked ? _('Yes') : _('No')) + '\n' : '') +
(target_function === '/opt' || target_function === '/dev' ?
_('Format type:') + ' ' + this.getFormatTypeDescription(this.dom.formatType.value) + '\n' : '') +
'\n' + _('This operation may take several minutes.');
if (!confirm(confirmMessage)) {
return;
}
this.resetOperationState();
this.isNewOperation = true;
if (this.dom.logView) {
this.dom.logView.value = _('正在启动操作...');
}
if (this.dom.executeBtn) {
this.dom.executeBtn.disabled = true;
this.dom.executeBtn.textContent = _('Executing...');
}
this.switchState('executing');
this.updateProgress(5, _('Starting operation...'));
callPartExpAutopart()
.then(function(response) {
if (response && response.success) {
self.isRunning = true;
self.operationComplete = false;
self.startLogPolling();
if (self.dom.executeStatus) {
self.dom.executeStatus.textContent = _('Operation started successfully');
}
} else {
var errorMsg = response && response.message ? response.message : _('Operation failed');
self.handleOperationError(errorMsg);
}
})
.catch(function(error) {
console.error('Operation failed:', error);
self.handleOperationError(_('Failed to start operation:') + ' ' + (error.message || _('Unknown error')));
});
},
resetOperationState: function() {
this.logPosition = '0';
this.isRunning = true;
this.operationComplete = false;
this.pollErrorCount = 0;
this.pollingStartTime = Date.now();
this.lastPollTime = 0;
this.currentProgress = 0;
this.updateProgress(0, _('Starting operation...'));
},
handleOperationError: function(errorMsg) {
alert(errorMsg);
if (this.dom.executeBtn) {
this.dom.executeBtn.disabled = false;
this.dom.executeBtn.textContent = _('Click to execute');
}
this.switchState('ready');
this.stopLogPolling();
if (this.dom.logView) {
var currentLog = this.dom.logView.value || '';
this.dom.logView.value = currentLog + '\n\n' + _('操作失败:') + ' ' + errorMsg;
setTimeout(() => {
if (this.dom.logView) {
this.dom.logView.scrollTop = this.dom.logView.scrollHeight;
}
}, 100);
}
},
updateFormVisibility: function() {
if (!this.dom.targetFunction || !this.dom.targetDisk ||
!this.dom.keepConfig || !this.dom.formatType) return;
var func = this.dom.targetFunction.value;
var diskDiv = this.dom.targetDisk.closest('.cbi-value');
var keepDiv = this.dom.keepConfig.closest('.cbi-value');
var formatDiv = this.dom.formatType.closest('.cbi-value');
if (!diskDiv || !keepDiv || !formatDiv) return;
if (func === '/') {
diskDiv.style.display = 'none';
formatDiv.style.display = 'none';
keepDiv.style.display = 'block';
} else if (func === '/overlay') {
diskDiv.style.display = 'block';
formatDiv.style.display = 'none';
keepDiv.style.display = 'block';
} else {
diskDiv.style.display = 'block';
formatDiv.style.display = 'block';
keepDiv.style.display = 'none';
}
},
checkOperationStatus: function() {
var self = this;
callPartExpGetStatus().then(function(response) {
if (response && response.running) {
self.isRunning = true;
self.switchState('executing');
self.startLogPolling();
if (self.dom.executeBtn) {
self.dom.executeBtn.disabled = true;
self.dom.executeBtn.textContent = _('Operation in progress...');
}
if (self.dom.executeStatus) {
self.dom.executeStatus.textContent = _('Operation in progress...');
}
}
}).catch(function(error) {
console.error('Failed to check operation status:', error);
});
},
startLogPolling: function() {
var self = this;
this.stopLogPolling();
this.pollErrorCount = 0;
this.pollingStartTime = Date.now();
this.lastPollTime = 0;
this.updateProgress(10, _('Operation in progress...'));
this.logPolling = setInterval(function() {
if (Date.now() - self.pollingStartTime > 20 * 60 * 1000) {
console.error('Operation timeout');
self.stopLogPolling();
self.isRunning = false;
if (self.dom.logView) {
var currentLog = self.dom.logView.value || '';
self.dom.logView.value = currentLog + '\n\n[超时] 操作超过20分钟未完成请检查系统';
setTimeout(() => {
if (self.dom.logView) {
self.dom.logView.scrollTop = self.dom.logView.scrollHeight;
}
}, 100);
}
self.switchState('ready');
if (self.dom.executeBtn) {
self.dom.executeBtn.disabled = false;
self.dom.executeBtn.textContent = _('Click to execute');
}
return;
}
self.pollLog();
}, 3000); // 每3秒轮询一次减少频率
},
pollLog: function() {
var self = this;
if (!this.isRunning) {
this.stopLogPolling();
return;
}
var pollStartTime = Date.now();
callPartExpGetLog('0').then(function(response) {
if (!response) {
console.error('No response from log polling');
return;
}
if (pollStartTime < self.lastPollTime) {
return;
}
self.lastPollTime = pollStartTime;
if (response.log !== undefined) {
var logContent = response.log.toString().trim();
if (response.position) {
self.logPosition = response.position;
}
if (self.dom.logView) {
if (logContent !== '') {
self.dom.logView.value = logContent;
setTimeout(function() {
if (self.dom.logView && self.dom.logView.value) {
self.dom.logView.scrollTop = self.dom.logView.scrollHeight;
}
}, 50);
}
}
self.parseAndUpdateProgress(logContent);
if (self.checkOperationComplete(logContent)) {
self.handleOperationComplete();
}
}
if (response.complete) {
self.handleOperationComplete();
}
self.pollErrorCount = 0;
}).catch(function(error) {
console.error('Log polling error:', error);
self.pollErrorCount = (self.pollErrorCount || 0) + 1;
if (self.pollErrorCount > 5) {
console.error('Too many polling errors, stopping');
self.stopLogPolling();
self.isRunning = false;
self.switchState('ready');
if (self.dom.executeBtn) {
self.dom.executeBtn.disabled = false;
self.dom.executeBtn.textContent = _('Click to execute');
}
if (self.dom.logView) {
var currentLog = self.dom.logView.value || '';
self.dom.logView.value = currentLog + '\n\n[错误] 日志轮询失败,请刷新页面查看最新状态';
setTimeout(() => {
if (self.dom.logView) {
self.dom.logView.scrollTop = self.dom.logView.scrollHeight;
}
}, 100);
}
}
});
},
checkOperationComplete: function(logText) {
if (!logText) return false;
var completeMarkers = [
'重启设备',
'操作完成'
];
for (var i = 0; i < completeMarkers.length; i++) {
if (logText.includes(completeMarkers[i])) {
return true;
}
}
return false;
},
handleOperationComplete: function() {
if (this.operationComplete) {
return;
}
this.operationComplete = true;
this.isRunning = false;
this.isNewOperation = false;
this.stopLogPolling();
if (this.dom.logView) {
var currentLog = this.dom.logView.value || '';
if (!currentLog.includes('操作完成')) {
this.dom.logView.value = currentLog;
setTimeout(() => {
if (this.dom.logView) {
this.dom.logView.scrollTop = this.dom.logView.scrollHeight;
}
}, 100);
}
}
this.updateProgress(100, _('Operation completed'));
setTimeout(() => {
if (this.dom.executeBtn) {
this.dom.executeBtn.disabled = false;
this.dom.executeBtn.textContent = _('Click to execute');
}
setTimeout(() => {
this.switchState('ready');
}, 3000);
}, 2000);
},
parseAndUpdateProgress: function(logText) {
if (!logText || !this.dom.executeStatus) return;
var percent = 0;
var statusMessage = _('Operation in progress...');
if (logText.includes('100%') || logText.includes('操作完成') || logText.includes('扩容成功')) {
percent = 100;
statusMessage = _('Operation completed');
} else if ( logText.includes('错误') || logText.includes('error')) {
return;
} else if (logText.includes('分区扩容和挂载到') || logText.includes('正在挂载')) {
percent = 90;
statusMessage = _('Getting device information');
} else if (logText.includes('检测设备')) {
percent = 60;
statusMessage = _('Checking partition format');
} else if (logText.includes('开始检测目标')) {
percent = 50;
statusMessage = _('Checking target device');
} else if (logText.includes('定位到操作目标设备分区')) {
percent = 40;
statusMessage = _('Locating target partition');
} else if (logText.includes('目标盘') && logText.includes('有剩余空间')) {
percent = 30;
statusMessage = _('Checking free space');
} else if (logText.includes('操作功能')) {
percent = 20;
statusMessage = _('Starting operation');
} else if (logText.includes('开始执行') || logText.includes('Starting')) {
percent = 10;
statusMessage = _('Initializing...');
}
if (percent > 0) {
this.currentProgress = Math.max(this.currentProgress || 0, percent);
} else {
this.currentProgress = Math.min(90, (this.currentProgress || 0) + 1);
}
this.updateProgress(this.currentProgress, statusMessage);
},
updateProgress: function(percent, message) {
if (!this.dom.progressBar || !this.dom.progressText || !this.dom.executeStatus) {
return;
}
percent = Math.max(0, Math.min(100, percent));
this.dom.progressBar.style.width = percent + '%';
this.dom.progressText.textContent = percent + '%';
this.dom.executeStatus.textContent = message;
},
stopLogPolling: function() {
if (this.logPolling) {
clearInterval(this.logPolling);
this.logPolling = null;
}
},
// 切换状态
switchState: function(to) {
if (!this.dom.stateContainer) return;
this.dom.stateContainer.classList.remove(
'state-ctl-ready',
'state-ctl-executing'
);
this.dom.stateContainer.classList.add('state-ctl-' + to);
},
getFunctionDescription: function(func) {
switch(func) {
case '/': return _('Extend to root directory');
case '/overlay': return _('Expand overlay');
case '/opt': return _('Docker data disk');
case '/dev': return _('Normal mount');
default: return func;
}
},
getFormatTypeDescription: function(type) {
switch(type) {
case '0': return _('No formatting');
case 'ext4': return _('EXT4');
case 'btrfs': return _('Btrfs');
case 'ntfs': return _('NTFS');
default: return type;
}
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});