From 356e2814ca35467b1c5d0a326c9c30b0e8b7f3ea Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sat, 28 Feb 2026 08:15:19 +0100 Subject: [PATCH] fix(streamlit): Add Re-upload and Gitea Sync buttons to Apps table Restores missing functionality in the Streamlit dashboard: - Re-upload button: Upload new .py/.zip to replace existing app code - Gitea Sync button: Pull latest changes from Gitea repository The buttons appear in the Apps Library table for each app. Co-Authored-By: Claude Opus 4.5 --- .../resources/view/streamlit/dashboard.js | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/package/secubox/luci-app-streamlit/htdocs/luci-static/resources/view/streamlit/dashboard.js b/package/secubox/luci-app-streamlit/htdocs/luci-static/resources/view/streamlit/dashboard.js index 8137d83e..837ade88 100644 --- a/package/secubox/luci-app-streamlit/htdocs/luci-static/resources/view/streamlit/dashboard.js +++ b/package/secubox/luci-app-streamlit/htdocs/luci-static/resources/view/streamlit/dashboard.js @@ -305,6 +305,7 @@ return view.extend({ var rows = apps.map(function(app) { var id = app.id || app.name; + var hasGitea = app.gitea_repo || app.git_url; return E('tr', {}, [ E('td', {}, E('strong', {}, app.name)), E('td', {}, app.path ? app.path.split('/').pop() : '-'), @@ -313,6 +314,16 @@ return view.extend({ 'class': 'cbi-button', 'click': function() { self.createInstanceFromApp(id); } }, _('+ Instance')), + E('button', { + 'class': 'cbi-button', + 'style': 'margin-left:4px', + 'click': function() { self.reuploadApp(id); } + }, _('Re-upload')), + hasGitea ? E('button', { + 'class': 'cbi-button cbi-button-action', + 'style': 'margin-left:4px', + 'click': function() { self.giteaPull(id); } + }, _('Gitea Sync')) : '', E('button', { 'class': 'cbi-button cbi-button-remove', 'style': 'margin-left:4px', @@ -645,5 +656,85 @@ return view.extend({ }, _('Delete')) ]) ]); + }, + + // Re-upload app code + reuploadApp: function(name) { + var self = this; + ui.showModal(_('Re-upload App: ') + name, [ + E('p', {}, _('Select a new .py or .zip file to replace the app code:')), + E('input', { 'type': 'file', 'id': 'reupload-file', 'accept': '.py,.zip' }), + E('div', { 'class': 'right', 'style': 'margin-top:16px' }, [ + E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')), + E('button', { + 'class': 'cbi-button cbi-button-positive', + 'style': 'margin-left:8px', + 'click': function() { + var fileInput = document.getElementById('reupload-file'); + if (!fileInput || !fileInput.files.length) { + ui.addNotification(null, E('p', {}, _('Select a file')), 'error'); + return; + } + + var file = fileInput.files[0]; + var isZip = file.name.endsWith('.zip'); + var reader = new FileReader(); + + reader.onload = function(e) { + var bytes = new Uint8Array(e.target.result); + var chunks = []; + for (var i = 0; i < bytes.length; i += 8192) { + chunks.push(String.fromCharCode.apply(null, bytes.slice(i, i + 8192))); + } + var content = btoa(chunks.join('')); + + ui.hideModal(); + poll.stop(); + ui.showModal(_('Uploading'), [ + E('p', { 'class': 'spinning' }, _('Uploading app code...')) + ]); + + api.chunkedUpload(name, content, isZip).then(function(r) { + poll.start(); + ui.hideModal(); + if (r && r.success) { + ui.addNotification(null, E('p', {}, _('App updated: ') + name), 'success'); + self.refresh().then(function() { self.updateStatus(); }); + } else { + ui.addNotification(null, E('p', {}, r.message || _('Upload failed')), 'error'); + } + }).catch(function(err) { + poll.start(); + ui.hideModal(); + ui.addNotification(null, E('p', {}, _('Error: ') + (err.message || err)), 'error'); + }); + }; + + reader.readAsArrayBuffer(file); + } + }, _('Upload')) + ]) + ]); + }, + + // Gitea pull/sync + giteaPull: function(name) { + var self = this; + ui.showModal(_('Syncing...'), [ + E('p', { 'class': 'spinning' }, _('Pulling from Gitea...')) + ]); + + api.giteaPull(name).then(function(r) { + ui.hideModal(); + if (r && r.success) { + ui.addNotification(null, E('p', {}, _('Synced from Gitea: ') + name), 'success'); + self.refresh().then(function() { self.updateStatus(); }); + } else { + ui.addNotification(null, E('p', {}, r.message || _('Sync failed')), 'error'); + } + }).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', {}, _('Error: ') + (err.message || err)), 'error'); + }); } });