secubox-openwrt/package/secubox/luci-app-streamlit/htdocs/luci-static/resources/view/streamlit/dashboard.js
CyberMind-FR 5317f37e7a refactor(streamlit): KISS UI redesign
Simplify LuCI interface from 5 tabs to 2:
- Dashboard: status, controls, apps list, upload (all-in-one)
- Settings: configuration options

Remove complex custom CSS, use standard LuCI styles.

Deleted: overview.js, apps.js, instances.js, logs.js
Added: dashboard.js (single-page dashboard)
Updated: settings.js (simplified form), menu.json

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 07:17:20 +01:00

346 lines
9.1 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require poll';
'require streamlit.api as api';
return view.extend({
status: {},
apps: [],
activeApp: '',
load: function() {
return this.refresh();
},
refresh: function() {
var self = this;
return Promise.all([
api.getStatus(),
api.listApps()
]).then(function(r) {
self.status = r[0] || {};
self.apps = (r[1] && r[1].apps) || [];
self.activeApp = (r[1] && r[1].active_app) || '';
});
},
render: function() {
var self = this;
var s = this.status;
var running = s.running;
var installed = s.installed;
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Streamlit Platform')),
E('div', { 'class': 'cbi-map-descr' }, _('Python data app hosting')),
// Status Section
E('div', { 'class': 'cbi-section', 'id': 'status-section' }, [
E('h3', {}, _('Service Status')),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Status')),
E('div', { 'class': 'cbi-value-field', 'id': 'svc-status' },
!installed ? E('em', { 'style': 'color:#999' }, _('Not installed')) :
running ? E('span', { 'style': 'color:#0a0' }, _('Running')) :
E('span', { 'style': 'color:#a00' }, _('Stopped'))
)
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Active App')),
E('div', { 'class': 'cbi-value-field', 'id': 'active-app' },
this.activeApp || E('em', {}, '-'))
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Web URL')),
E('div', { 'class': 'cbi-value-field' },
s.web_url ? E('a', { 'href': s.web_url, 'target': '_blank' }, s.web_url) : '-')
]),
E('div', { 'class': 'cbi-page-actions' }, this.renderControls(installed, running))
]),
// Apps Section
E('div', { 'class': 'cbi-section', 'id': 'apps-section' }, [
E('h3', {}, _('Applications')),
E('div', { 'id': 'apps-table' }, this.renderAppsTable())
]),
// Upload Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Upload App')),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Python File')),
E('div', { 'class': 'cbi-value-field' }, [
E('input', { 'type': 'file', 'id': 'upload-file', 'accept': '.py' }),
E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'margin-left: 8px',
'click': function() { self.uploadApp(); }
}, _('Upload'))
])
])
])
]);
poll.add(function() {
return self.refresh().then(function() {
self.updateStatus();
});
}, 5);
return view;
},
renderControls: function(installed, running) {
var self = this;
var btns = [];
if (!installed) {
btns.push(E('button', {
'class': 'cbi-button cbi-button-positive',
'click': function() { self.doInstall(); }
}, _('Install')));
} else {
if (running) {
btns.push(E('button', {
'class': 'cbi-button cbi-button-negative',
'click': function() { self.doStop(); }
}, _('Stop')));
btns.push(E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'margin-left: 8px',
'click': function() { self.doRestart(); }
}, _('Restart')));
} else {
btns.push(E('button', {
'class': 'cbi-button cbi-button-positive',
'click': function() { self.doStart(); }
}, _('Start')));
}
btns.push(E('button', {
'class': 'cbi-button',
'style': 'margin-left: 8px',
'click': function() { self.doUninstall(); }
}, _('Uninstall')));
}
return btns;
},
renderAppsTable: function() {
var self = this;
var apps = this.apps;
if (!apps.length) {
return E('em', {}, _('No apps found'));
}
var rows = apps.map(function(app) {
var isActive = app.name === self.activeApp;
return E('tr', { 'class': isActive ? 'cbi-rowstyle-2' : '' }, [
E('td', {}, [
E('strong', {}, app.name),
isActive ? E('span', { 'style': 'color:#0a0; margin-left:8px' }, _('(active)')) : ''
]),
E('td', {}, app.path ? app.path.split('/').pop() : '-'),
E('td', {}, [
!isActive ? E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.activateApp(app.name); }
}, _('Activate')) : '',
E('button', {
'class': 'cbi-button cbi-button-remove',
'style': 'margin-left: 4px',
'click': function() { self.deleteApp(app.name); }
}, _('Delete'))
])
]);
});
return E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Name')),
E('th', { 'class': 'th' }, _('File')),
E('th', { 'class': 'th' }, _('Actions'))
])
].concat(rows));
},
updateStatus: function() {
var s = this.status;
var statusEl = document.getElementById('svc-status');
var activeEl = document.getElementById('active-app');
var appsEl = document.getElementById('apps-table');
if (statusEl) {
statusEl.innerHTML = '';
if (!s.installed) {
statusEl.appendChild(E('em', { 'style': 'color:#999' }, _('Not installed')));
} else if (s.running) {
statusEl.appendChild(E('span', { 'style': 'color:#0a0' }, _('Running')));
} else {
statusEl.appendChild(E('span', { 'style': 'color:#a00' }, _('Stopped')));
}
}
if (activeEl) {
activeEl.textContent = this.activeApp || '-';
}
if (appsEl) {
appsEl.innerHTML = '';
appsEl.appendChild(this.renderAppsTable());
}
},
doStart: function() {
var self = this;
api.start().then(function(r) {
if (r && r.success) {
ui.addNotification(null, E('p', {}, _('Service started')), 'info');
}
self.refresh();
});
},
doStop: function() {
var self = this;
api.stop().then(function(r) {
if (r && r.success) {
ui.addNotification(null, E('p', {}, _('Service stopped')), 'info');
}
self.refresh();
});
},
doRestart: function() {
var self = this;
api.restart().then(function(r) {
if (r && r.success) {
ui.addNotification(null, E('p', {}, _('Service restarted')), 'info');
}
self.refresh();
});
},
doInstall: function() {
var self = this;
ui.showModal(_('Installing'), [
E('p', { 'class': 'spinning' }, _('Installing Streamlit platform...'))
]);
api.install().then(function(r) {
if (r && r.started) {
self.pollInstall();
} else {
ui.hideModal();
ui.addNotification(null, E('p', {}, r.message || _('Install failed')), 'error');
}
});
},
pollInstall: function() {
var self = this;
var check = function() {
api.getInstallProgress().then(function(r) {
if (r.status === 'completed') {
ui.hideModal();
ui.addNotification(null, E('p', {}, _('Installation complete')), 'success');
self.refresh();
location.reload();
} else if (r.status === 'error') {
ui.hideModal();
ui.addNotification(null, E('p', {}, r.message || _('Install failed')), 'error');
} else {
setTimeout(check, 3000);
}
});
};
setTimeout(check, 2000);
},
doUninstall: function() {
var self = this;
ui.showModal(_('Confirm'), [
E('p', {}, _('Uninstall Streamlit platform?')),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
E('button', {
'class': 'cbi-button cbi-button-negative',
'style': 'margin-left: 8px',
'click': function() {
ui.hideModal();
api.uninstall().then(function() {
ui.addNotification(null, E('p', {}, _('Uninstalled')), 'info');
self.refresh();
location.reload();
});
}
}, _('Uninstall'))
])
]);
},
activateApp: function(name) {
var self = this;
api.setActiveApp(name).then(function(r) {
if (r && r.success) {
ui.addNotification(null, E('p', {}, _('App activated: ') + name), 'info');
return api.restart();
}
}).then(function() {
self.refresh();
});
},
deleteApp: function(name) {
var self = this;
ui.showModal(_('Confirm'), [
E('p', {}, _('Delete app: ') + name + '?'),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
E('button', {
'class': 'cbi-button cbi-button-negative',
'style': 'margin-left: 8px',
'click': function() {
ui.hideModal();
api.removeApp(name).then(function(r) {
if (r && r.success) {
ui.addNotification(null, E('p', {}, _('App deleted')), 'info');
}
self.refresh();
});
}
}, _('Delete'))
])
]);
},
uploadApp: function() {
var self = this;
var fileInput = document.getElementById('upload-file');
if (!fileInput || !fileInput.files.length) {
ui.addNotification(null, E('p', {}, _('Select a file first')), 'error');
return;
}
var file = fileInput.files[0];
var name = file.name.replace(/\.py$/, '');
var reader = new FileReader();
reader.onload = function(e) {
var content = btoa(e.target.result);
api.uploadApp(name, content).then(function(r) {
if (r && r.success) {
ui.addNotification(null, E('p', {}, _('App uploaded: ') + name), 'success');
fileInput.value = '';
self.refresh();
} else {
ui.addNotification(null, E('p', {}, r.message || _('Upload failed')), 'error');
}
});
};
reader.readAsText(file);
}
});