Files
small-packages/luci-app-partexp/htdocs/luci-static/resources/view/partexp.js
T
2026-07-28 01:38:11 +08:00

921 lines
35 KiB
JavaScript

/*
* 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
});