Add two new packages for self-hosted brewing controller support: secubox-app-picobrew: - LXC container-based PicoBrew Server installation - Alpine Linux rootfs with Python/Flask environment - UCI configuration for port, memory, brewing defaults - procd service management with respawn - Commands: install, uninstall, update, status, logs, shell luci-app-picobrew: - Modern dashboard UI with SecuBox styling - Service controls (start/stop/restart/install/update) - Real-time status monitoring and logs - Settings page for server and brewing configuration - RPCD backend with full API coverage Supports PicoBrew Zymatic, Z, Pico C, and Pico Pro devices. Repository: https://github.com/CyberMind-FR/picobrew-server Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
804 lines
18 KiB
JavaScript
804 lines
18 KiB
JavaScript
'use strict';
|
||
'require view';
|
||
'require ui';
|
||
'require dom';
|
||
'require poll';
|
||
'require rpc';
|
||
|
||
// RPC declarations
|
||
var callGetStatus = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'get_status',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callGetConfig = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'get_config',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callStart = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'start',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callStop = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'stop',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callRestart = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'restart',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callInstall = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'install',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callUninstall = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'uninstall',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callUpdate = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'update',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callGetLogs = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'get_logs',
|
||
params: ['lines'],
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callGetInstallProgress = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'get_install_progress',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callGetSessions = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'get_sessions',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
var callGetRecipes = rpc.declare({
|
||
object: 'luci.picobrew',
|
||
method: 'get_recipes',
|
||
expect: { result: {} }
|
||
});
|
||
|
||
// CSS styles
|
||
var styles = `
|
||
.picobrew-dashboard {
|
||
padding: 20px;
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
}
|
||
|
||
.pb-header {
|
||
background: linear-gradient(135deg, rgba(6, 182, 212, 0.1), rgba(139, 92, 246, 0.1));
|
||
border: 1px solid rgba(6, 182, 212, 0.3);
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.pb-header-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
}
|
||
|
||
.pb-logo {
|
||
font-size: 48px;
|
||
}
|
||
|
||
.pb-title {
|
||
margin: 0;
|
||
font-size: 24px;
|
||
color: #06b6d4;
|
||
}
|
||
|
||
.pb-subtitle {
|
||
margin: 4px 0 0 0;
|
||
color: #94a3b8;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.pb-stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.pb-stat-card {
|
||
background: rgba(15, 23, 42, 0.8);
|
||
border: 1px solid rgba(51, 65, 85, 0.5);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.pb-stat-card.success { border-color: rgba(16, 185, 129, 0.5); }
|
||
.pb-stat-card.warning { border-color: rgba(245, 158, 11, 0.5); }
|
||
.pb-stat-card.error { border-color: rgba(244, 63, 94, 0.5); }
|
||
|
||
.pb-stat-icon {
|
||
font-size: 32px;
|
||
width: 48px;
|
||
height: 48px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(6, 182, 212, 0.1);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.pb-stat-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.pb-stat-value {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: #f1f5f9;
|
||
}
|
||
|
||
.pb-stat-label {
|
||
font-size: 13px;
|
||
color: #94a3b8;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.pb-main-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 24px;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.pb-main-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.pb-card {
|
||
background: rgba(15, 23, 42, 0.8);
|
||
border: 1px solid rgba(51, 65, 85, 0.5);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.pb-card-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid rgba(51, 65, 85, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.pb-card-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #f1f5f9;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.pb-card-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.pb-btn {
|
||
padding: 10px 20px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.pb-btn-primary {
|
||
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
||
color: white;
|
||
}
|
||
|
||
.pb-btn-primary:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.4);
|
||
}
|
||
|
||
.pb-btn-success {
|
||
background: linear-gradient(135deg, #10b981, #059669);
|
||
color: white;
|
||
}
|
||
|
||
.pb-btn-danger {
|
||
background: linear-gradient(135deg, #f43f5e, #e11d48);
|
||
color: white;
|
||
}
|
||
|
||
.pb-btn-warning {
|
||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||
color: white;
|
||
}
|
||
|
||
.pb-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
transform: none !important;
|
||
}
|
||
|
||
.pb-btn-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.pb-status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 12px;
|
||
border-radius: 20px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.pb-status-badge.running {
|
||
background: rgba(16, 185, 129, 0.2);
|
||
color: #10b981;
|
||
}
|
||
|
||
.pb-status-badge.stopped {
|
||
background: rgba(244, 63, 94, 0.2);
|
||
color: #f43f5e;
|
||
}
|
||
|
||
.pb-status-badge.not-installed {
|
||
background: rgba(245, 158, 11, 0.2);
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.pb-info-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.pb-info-list li {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 10px 0;
|
||
border-bottom: 1px solid rgba(51, 65, 85, 0.3);
|
||
}
|
||
|
||
.pb-info-list li:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.pb-info-label {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.pb-info-value {
|
||
color: #f1f5f9;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.pb-info-value a {
|
||
color: #06b6d4;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.pb-info-value a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.pb-logs {
|
||
background: #0f172a;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
font-family: "Monaco", "Consolas", monospace;
|
||
font-size: 12px;
|
||
color: #94a3b8;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.pb-logs-line {
|
||
margin: 4px 0;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.pb-progress {
|
||
background: rgba(51, 65, 85, 0.5);
|
||
border-radius: 8px;
|
||
height: 8px;
|
||
overflow: hidden;
|
||
margin: 16px 0;
|
||
}
|
||
|
||
.pb-progress-bar {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #06b6d4, #8b5cf6);
|
||
border-radius: 8px;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.pb-progress-text {
|
||
text-align: center;
|
||
color: #94a3b8;
|
||
font-size: 13px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.pb-empty {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.pb-empty-icon {
|
||
font-size: 48px;
|
||
margin-bottom: 12px;
|
||
}
|
||
`;
|
||
|
||
return view.extend({
|
||
statusData: null,
|
||
configData: null,
|
||
logsData: null,
|
||
installProgress: null,
|
||
sessionsData: null,
|
||
recipesData: null,
|
||
|
||
load: function() {
|
||
return this.refreshData();
|
||
},
|
||
|
||
refreshData: function() {
|
||
var self = this;
|
||
return Promise.all([
|
||
callGetStatus(),
|
||
callGetConfig(),
|
||
callGetLogs(50),
|
||
callGetSessions(),
|
||
callGetRecipes()
|
||
]).then(function(data) {
|
||
self.statusData = data[0] || {};
|
||
self.configData = data[1] || {};
|
||
self.logsData = data[2] || {};
|
||
self.sessionsData = data[3] || {};
|
||
self.recipesData = data[4] || {};
|
||
return data;
|
||
});
|
||
},
|
||
|
||
render: function() {
|
||
var self = this;
|
||
|
||
// Inject styles
|
||
var styleEl = E('style', {}, styles);
|
||
|
||
var container = E('div', { 'class': 'picobrew-dashboard' }, [
|
||
styleEl,
|
||
this.renderHeader(),
|
||
this.renderStatsGrid(),
|
||
this.renderMainGrid()
|
||
]);
|
||
|
||
// Poll for updates
|
||
poll.add(function() {
|
||
return self.refreshData().then(function() {
|
||
self.updateDynamicContent();
|
||
});
|
||
}, 10);
|
||
|
||
return container;
|
||
},
|
||
|
||
renderHeader: function() {
|
||
var status = this.statusData;
|
||
var statusClass = !status.installed ? 'not-installed' : (status.running ? 'running' : 'stopped');
|
||
var statusText = !status.installed ? _('Not Installed') : (status.running ? _('Running') : _('Stopped'));
|
||
|
||
return E('div', { 'class': 'pb-header' }, [
|
||
E('div', { 'class': 'pb-header-content' }, [
|
||
E('div', { 'class': 'pb-logo' }, '🍺'),
|
||
E('div', {}, [
|
||
E('h1', { 'class': 'pb-title' }, _('PicoBrew Server')),
|
||
E('p', { 'class': 'pb-subtitle' }, _('Self-hosted brewing controller for PicoBrew devices'))
|
||
]),
|
||
E('div', { 'class': 'pb-status-badge ' + statusClass, 'id': 'pb-status-badge' }, [
|
||
E('span', {}, statusClass === 'running' ? '●' : '○'),
|
||
statusText
|
||
])
|
||
])
|
||
]);
|
||
},
|
||
|
||
renderStatsGrid: function() {
|
||
var status = this.statusData;
|
||
var sessions = (this.sessionsData.sessions || []).length;
|
||
var recipes = (this.recipesData.recipes || []).length;
|
||
|
||
var stats = [
|
||
{
|
||
icon: '🔌',
|
||
label: _('Status'),
|
||
value: status.running ? _('Online') : _('Offline'),
|
||
id: 'stat-status',
|
||
cardClass: status.running ? 'success' : 'error'
|
||
},
|
||
{
|
||
icon: '🌐',
|
||
label: _('Port'),
|
||
value: status.http_port || '8080',
|
||
id: 'stat-port'
|
||
},
|
||
{
|
||
icon: '📊',
|
||
label: _('Sessions'),
|
||
value: sessions,
|
||
id: 'stat-sessions'
|
||
},
|
||
{
|
||
icon: '📖',
|
||
label: _('Recipes'),
|
||
value: recipes,
|
||
id: 'stat-recipes'
|
||
}
|
||
];
|
||
|
||
return E('div', { 'class': 'pb-stats-grid' },
|
||
stats.map(function(stat) {
|
||
return E('div', { 'class': 'pb-stat-card ' + (stat.cardClass || '') }, [
|
||
E('div', { 'class': 'pb-stat-icon' }, stat.icon),
|
||
E('div', { 'class': 'pb-stat-content' }, [
|
||
E('div', { 'class': 'pb-stat-value', 'id': stat.id }, String(stat.value)),
|
||
E('div', { 'class': 'pb-stat-label' }, stat.label)
|
||
])
|
||
]);
|
||
})
|
||
);
|
||
},
|
||
|
||
renderMainGrid: function() {
|
||
return E('div', { 'class': 'pb-main-grid' }, [
|
||
this.renderControlCard(),
|
||
this.renderInfoCard(),
|
||
this.renderLogsCard()
|
||
]);
|
||
},
|
||
|
||
renderControlCard: function() {
|
||
var self = this;
|
||
var status = this.statusData;
|
||
|
||
var buttons = [];
|
||
|
||
if (!status.installed) {
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'pb-btn pb-btn-primary',
|
||
'id': 'btn-install',
|
||
'click': function() { self.handleInstall(); }
|
||
}, [E('span', {}, '📥'), _('Install')])
|
||
);
|
||
} else {
|
||
if (status.running) {
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'pb-btn pb-btn-danger',
|
||
'id': 'btn-stop',
|
||
'click': function() { self.handleStop(); }
|
||
}, [E('span', {}, '⏹'), _('Stop')])
|
||
);
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'pb-btn pb-btn-warning',
|
||
'id': 'btn-restart',
|
||
'click': function() { self.handleRestart(); }
|
||
}, [E('span', {}, '🔄'), _('Restart')])
|
||
);
|
||
} else {
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'pb-btn pb-btn-success',
|
||
'id': 'btn-start',
|
||
'click': function() { self.handleStart(); }
|
||
}, [E('span', {}, '▶'), _('Start')])
|
||
);
|
||
}
|
||
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'pb-btn pb-btn-primary',
|
||
'id': 'btn-update',
|
||
'click': function() { self.handleUpdate(); }
|
||
}, [E('span', {}, '⬆'), _('Update')])
|
||
);
|
||
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'pb-btn pb-btn-danger',
|
||
'id': 'btn-uninstall',
|
||
'click': function() { self.handleUninstall(); }
|
||
}, [E('span', {}, '🗑'), _('Uninstall')])
|
||
);
|
||
}
|
||
|
||
return E('div', { 'class': 'pb-card' }, [
|
||
E('div', { 'class': 'pb-card-header' }, [
|
||
E('div', { 'class': 'pb-card-title' }, [
|
||
E('span', {}, '🎮'),
|
||
_('Controls')
|
||
])
|
||
]),
|
||
E('div', { 'class': 'pb-card-body' }, [
|
||
E('div', { 'class': 'pb-btn-group', 'id': 'pb-controls' }, buttons),
|
||
E('div', { 'class': 'pb-progress', 'id': 'pb-progress-container', 'style': 'display:none' }, [
|
||
E('div', { 'class': 'pb-progress-bar', 'id': 'pb-progress-bar', 'style': 'width:0%' })
|
||
]),
|
||
E('div', { 'class': 'pb-progress-text', 'id': 'pb-progress-text', 'style': 'display:none' })
|
||
])
|
||
]);
|
||
},
|
||
|
||
renderInfoCard: function() {
|
||
var status = this.statusData;
|
||
|
||
var infoItems = [
|
||
{ label: _('Container'), value: status.container_name || 'picobrew' },
|
||
{ label: _('Data Path'), value: status.data_path || '/srv/picobrew' },
|
||
{ label: _('Memory Limit'), value: status.memory_limit || '512M' },
|
||
{ label: _('Web Interface'), value: status.web_url, isLink: true }
|
||
];
|
||
|
||
return E('div', { 'class': 'pb-card' }, [
|
||
E('div', { 'class': 'pb-card-header' }, [
|
||
E('div', { 'class': 'pb-card-title' }, [
|
||
E('span', {}, 'ℹ️'),
|
||
_('Information')
|
||
])
|
||
]),
|
||
E('div', { 'class': 'pb-card-body' }, [
|
||
E('ul', { 'class': 'pb-info-list', 'id': 'pb-info-list' },
|
||
infoItems.map(function(item) {
|
||
var valueEl;
|
||
if (item.isLink && item.value) {
|
||
valueEl = E('a', { 'href': item.value, 'target': '_blank' }, item.value);
|
||
} else {
|
||
valueEl = item.value || '-';
|
||
}
|
||
return E('li', {}, [
|
||
E('span', { 'class': 'pb-info-label' }, item.label),
|
||
E('span', { 'class': 'pb-info-value' }, valueEl)
|
||
]);
|
||
})
|
||
)
|
||
])
|
||
]);
|
||
},
|
||
|
||
renderLogsCard: function() {
|
||
var logs = this.logsData.logs || [];
|
||
|
||
return E('div', { 'class': 'pb-card', 'style': 'grid-column: span 2' }, [
|
||
E('div', { 'class': 'pb-card-header' }, [
|
||
E('div', { 'class': 'pb-card-title' }, [
|
||
E('span', {}, '📜'),
|
||
_('Logs')
|
||
])
|
||
]),
|
||
E('div', { 'class': 'pb-card-body' }, [
|
||
logs.length > 0 ?
|
||
E('div', { 'class': 'pb-logs', 'id': 'pb-logs' },
|
||
logs.map(function(line) {
|
||
return E('div', { 'class': 'pb-logs-line' }, line);
|
||
})
|
||
) :
|
||
E('div', { 'class': 'pb-empty' }, [
|
||
E('div', { 'class': 'pb-empty-icon' }, '📭'),
|
||
E('div', {}, _('No logs available'))
|
||
])
|
||
])
|
||
]);
|
||
},
|
||
|
||
updateDynamicContent: function() {
|
||
var status = this.statusData;
|
||
|
||
// Update status badge
|
||
var badge = document.getElementById('pb-status-badge');
|
||
if (badge) {
|
||
var statusClass = !status.installed ? 'not-installed' : (status.running ? 'running' : 'stopped');
|
||
var statusText = !status.installed ? _('Not Installed') : (status.running ? _('Running') : _('Stopped'));
|
||
badge.className = 'pb-status-badge ' + statusClass;
|
||
badge.innerHTML = '';
|
||
badge.appendChild(E('span', {}, statusClass === 'running' ? '●' : '○'));
|
||
badge.appendChild(document.createTextNode(' ' + statusText));
|
||
}
|
||
|
||
// Update stats
|
||
var statStatus = document.getElementById('stat-status');
|
||
if (statStatus) {
|
||
statStatus.textContent = status.running ? _('Online') : _('Offline');
|
||
}
|
||
|
||
// Update logs
|
||
var logsContainer = document.getElementById('pb-logs');
|
||
if (logsContainer && this.logsData.logs) {
|
||
logsContainer.innerHTML = '';
|
||
this.logsData.logs.forEach(function(line) {
|
||
logsContainer.appendChild(E('div', { 'class': 'pb-logs-line' }, line));
|
||
});
|
||
}
|
||
},
|
||
|
||
handleInstall: function() {
|
||
var self = this;
|
||
var btn = document.getElementById('btn-install');
|
||
if (btn) btn.disabled = true;
|
||
|
||
ui.showModal(_('Installing PicoBrew Server'), [
|
||
E('p', {}, _('This will download and install PicoBrew Server in an LXC container. This may take several minutes.')),
|
||
E('div', { 'class': 'pb-progress' }, [
|
||
E('div', { 'class': 'pb-progress-bar', 'id': 'modal-progress', 'style': 'width:0%' })
|
||
]),
|
||
E('p', { 'id': 'modal-status' }, _('Starting installation...'))
|
||
]);
|
||
|
||
callInstall().then(function(result) {
|
||
if (result && result.started) {
|
||
self.pollInstallProgress();
|
||
} else {
|
||
ui.hideModal();
|
||
ui.addNotification(null, E('p', {}, result.message || _('Installation failed')), 'error');
|
||
}
|
||
}).catch(function(err) {
|
||
ui.hideModal();
|
||
ui.addNotification(null, E('p', {}, _('Installation failed: ') + err.message), 'error');
|
||
});
|
||
},
|
||
|
||
pollInstallProgress: function() {
|
||
var self = this;
|
||
|
||
var checkProgress = function() {
|
||
callGetInstallProgress().then(function(result) {
|
||
var progressBar = document.getElementById('modal-progress');
|
||
var statusText = document.getElementById('modal-status');
|
||
|
||
if (progressBar) {
|
||
progressBar.style.width = (result.progress || 0) + '%';
|
||
}
|
||
if (statusText) {
|
||
statusText.textContent = result.message || '';
|
||
}
|
||
|
||
if (result.status === 'completed') {
|
||
ui.hideModal();
|
||
ui.addNotification(null, E('p', {}, _('PicoBrew Server installed successfully!')), 'success');
|
||
self.refreshData();
|
||
} else if (result.status === 'error') {
|
||
ui.hideModal();
|
||
ui.addNotification(null, E('p', {}, _('Installation failed: ') + result.message), 'error');
|
||
} else if (result.status === 'running') {
|
||
setTimeout(checkProgress, 3000);
|
||
} else {
|
||
setTimeout(checkProgress, 3000);
|
||
}
|
||
}).catch(function() {
|
||
setTimeout(checkProgress, 5000);
|
||
});
|
||
};
|
||
|
||
setTimeout(checkProgress, 2000);
|
||
},
|
||
|
||
handleStart: function() {
|
||
var self = this;
|
||
callStart().then(function(result) {
|
||
if (result && result.success) {
|
||
ui.addNotification(null, E('p', {}, _('PicoBrew Server started')), 'success');
|
||
} else {
|
||
ui.addNotification(null, E('p', {}, result.message || _('Failed to start')), 'error');
|
||
}
|
||
self.refreshData();
|
||
});
|
||
},
|
||
|
||
handleStop: function() {
|
||
var self = this;
|
||
callStop().then(function(result) {
|
||
if (result && result.success) {
|
||
ui.addNotification(null, E('p', {}, _('PicoBrew Server stopped')), 'info');
|
||
} else {
|
||
ui.addNotification(null, E('p', {}, result.message || _('Failed to stop')), 'error');
|
||
}
|
||
self.refreshData();
|
||
});
|
||
},
|
||
|
||
handleRestart: function() {
|
||
var self = this;
|
||
callRestart().then(function(result) {
|
||
if (result && result.success) {
|
||
ui.addNotification(null, E('p', {}, _('PicoBrew Server restarted')), 'success');
|
||
} else {
|
||
ui.addNotification(null, E('p', {}, result.message || _('Failed to restart')), 'error');
|
||
}
|
||
self.refreshData();
|
||
});
|
||
},
|
||
|
||
handleUpdate: function() {
|
||
var self = this;
|
||
|
||
ui.showModal(_('Updating PicoBrew Server'), [
|
||
E('p', {}, _('Updating PicoBrew Server to the latest version...')),
|
||
E('div', { 'class': 'spinner' })
|
||
]);
|
||
|
||
callUpdate().then(function(result) {
|
||
ui.hideModal();
|
||
if (result && result.started) {
|
||
ui.addNotification(null, E('p', {}, _('Update started. The server will restart automatically.')), 'info');
|
||
} else {
|
||
ui.addNotification(null, E('p', {}, result.message || _('Update failed')), 'error');
|
||
}
|
||
self.refreshData();
|
||
});
|
||
},
|
||
|
||
handleUninstall: function() {
|
||
var self = this;
|
||
|
||
ui.showModal(_('Confirm Uninstall'), [
|
||
E('p', {}, _('Are you sure you want to uninstall PicoBrew Server? Your data will be preserved.')),
|
||
E('div', { 'class': 'right' }, [
|
||
E('button', {
|
||
'class': 'btn',
|
||
'click': ui.hideModal
|
||
}, _('Cancel')),
|
||
E('button', {
|
||
'class': 'btn cbi-button-negative',
|
||
'click': function() {
|
||
ui.hideModal();
|
||
callUninstall().then(function(result) {
|
||
if (result && result.success) {
|
||
ui.addNotification(null, E('p', {}, _('PicoBrew Server uninstalled')), 'info');
|
||
} else {
|
||
ui.addNotification(null, E('p', {}, result.message || _('Uninstall failed')), 'error');
|
||
}
|
||
self.refreshData();
|
||
});
|
||
}
|
||
}, _('Uninstall'))
|
||
])
|
||
]);
|
||
}
|
||
});
|