secubox-openwrt/luci-app-secubox/htdocs/luci-static/resources/view/secubox/apps.js

380 lines
11 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require secubox/api as API';
'require secubox-theme/theme as Theme';
'require secubox/nav as SecuNav';
'require secubox-theme/cascade as Cascade';
'require poll';
// Load global theme CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox-theme/secubox-theme.css')
}));
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox-theme/themes/cyberpunk.css')
}));
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox/apps.css')
}));
// Initialize global theme
var secuLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: secuLang });
return view.extend({
appsData: [],
categoriesData: {},
currentFilter: 'all',
filterLayer: null,
load: function() {
return this.refreshData();
},
refreshData: function() {
var self = this;
return API.getAppstoreApps().then(function(data) {
self.appsData = data.apps || [];
self.categoriesData = data.categories || {};
return data;
});
},
render: function(data) {
var self = this;
var apps = (data && data.apps) || this.appsData || [];
var categories = (data && data.categories) || this.categoriesData || {};
var defaultFilter = this.currentFilter || 'all';
var container = E('div', {
'class': 'secubox-apps-page',
'data-cascade-root': 'apps'
}, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/core/variables.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }),
SecuNav.renderTabs('apps'),
this.renderHeader(apps),
this.renderFilterTabs(categories),
E('div', {
'id': 'apps-grid',
'class': 'secubox-apps-grid sb-cascade-layer',
'data-cascade-layer': 'view',
'data-cascade-role': 'apps',
'data-cascade-depth': '3',
'data-cascade-filter': defaultFilter
}, this.renderAppCards(apps, defaultFilter))
]);
// Auto-refresh every 10 seconds
poll.add(function() {
return self.refreshData().then(function() {
self.updateAppsGrid();
});
}, 10);
return container;
},
renderHeader: function(apps) {
var installedCount = apps.filter(function(app) {
return app.installed;
}).length;
return E('div', { 'class': 'secubox-page-header' }, [
E('div', { 'class': 'header-content' }, [
E('div', { 'class': 'header-title' }, [
E('h1', {}, _('App Store')),
E('p', { 'class': 'subtitle' }, _('Browse and install SecuBox applications'))
]),
E('div', { 'class': 'header-stats' }, [
E('div', { 'class': 'stat-item' }, [
E('span', { 'class': 'stat-value' }, String(apps.length)),
E('span', { 'class': 'stat-label' }, _('Available Apps'))
]),
E('div', { 'class': 'stat-item' }, [
E('span', { 'class': 'stat-value' }, String(installedCount)),
E('span', { 'class': 'stat-label' }, _('Installed'))
])
])
])
]);
},
renderFilterTabs: function(categories) {
var self = this;
var filters = [
{ id: 'all', label: _('All Apps'), icon: '📦' }
];
// Add category filters
Object.keys(categories).forEach(function(catId) {
var cat = categories[catId];
filters.push({
id: catId,
label: cat.name,
icon: cat.icon
});
});
filters.push({ id: 'installed', label: _('Installed'), icon: '✓' });
var tabs = filters.map(function(filter) {
var isActive = filter.id === self.currentFilter;
return E('button', {
'class': isActive ? 'filter-tab active' : 'filter-tab',
'data-filter': filter.id,
'click': function(ev) {
self.switchFilter(filter.id);
}
}, [
E('span', { 'class': 'tab-icon' }, filter.icon),
E('span', { 'class': 'tab-label' }, filter.label)
]);
});
return E('div', {
'class': 'secubox-filter-tabs sb-cascade-layer',
'data-cascade-layer': 'nav',
'data-cascade-depth': '2'
}, tabs);
},
renderAppCards: function(apps, filter) {
var self = this;
var filteredApps = apps.filter(function(app) {
if (filter === 'all') return true;
if (filter === 'installed') return app.installed;
return app.category === filter;
});
if (filteredApps.length === 0) {
return E('div', { 'class': 'empty-state' }, [
E('div', { 'class': 'empty-icon' }, '📦'),
E('h3', {}, _('No apps found')),
E('p', {}, _('No applications match the selected filter'))
]);
}
return filteredApps.map(function(app) {
return self.renderAppCard(app);
});
},
renderAppCard: function(app) {
var self = this;
var statusClass = 'status-' + app.status;
var statusLabel = {
'stable': _('Stable'),
'beta': _('Beta'),
'alpha': _('Alpha'),
'dev': _('Development')
}[app.status] || app.status;
return E('div', {
'class': 'app-card ' + statusClass,
'data-app-id': app.id,
'data-category': app.category
}, [
E('div', { 'class': 'app-header' }, [
E('div', { 'class': 'app-icon' }, app.icon || '📦'),
E('div', { 'class': 'app-title' }, [
E('h3', {}, app.name),
E('span', { 'class': 'app-version' }, 'v' + app.version)
]),
E('span', {
'class': 'app-status ' + statusClass
}, statusLabel)
]),
E('div', { 'class': 'app-description' }, app.description),
app.notes ? E('div', { 'class': 'app-notes' }, [
E('strong', {}, _('Note: ')),
E('span', {}, app.notes)
]) : null,
app.luci_app ? E('div', { 'class': 'app-luci' }, [
E('span', { 'class': 'luci-icon' }, '🎛️'),
E('span', {}, _('Includes LuCI interface'))
]) : null,
E('div', { 'class': 'app-actions' }, [
app.installed ?
E('button', {
'class': 'btn btn-secondary',
'click': function(ev) {
self.removeApp(app.id, ev.target);
}
}, _('Remove')) :
E('button', {
'class': 'btn btn-primary',
'click': function(ev) {
self.installApp(app.id, ev.target);
}
}, _('Install')),
E('button', {
'class': 'btn btn-link',
'click': function(ev) {
self.showAppDetails(app.id);
}
}, _('Details'))
])
]);
},
switchFilter: function(filterId) {
this.currentFilter = filterId;
// Update active tab
var tabs = document.querySelectorAll('.filter-tab');
tabs.forEach(function(tab) {
if (tab.getAttribute('data-filter') === filterId) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
});
// Update grid
this.updateAppsGrid();
},
updateAppsGrid: function() {
var grid = document.getElementById('apps-grid');
if (!grid) return;
dom.content(grid, this.renderAppCards(this.appsData, this.currentFilter));
},
installApp: function(appId, button) {
var self = this;
button.disabled = true;
button.textContent = _('Installing...');
return API.installAppstoreApp(appId).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('App installed successfully')), 'info');
return self.refreshData().then(function() {
self.updateAppsGrid();
});
} else {
ui.addNotification(null, E('p', _('Installation failed: ') + (result.error || result.details)), 'error');
button.disabled = false;
button.textContent = _('Install');
}
}).catch(function(err) {
ui.addNotification(null, E('p', _('Installation error: ') + err.message), 'error');
button.disabled = false;
button.textContent = _('Install');
});
},
removeApp: function(appId, button) {
var self = this;
if (!confirm(_('Are you sure you want to remove this app?'))) {
return;
}
button.disabled = true;
button.textContent = _('Removing...');
return API.removeAppstoreApp(appId).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('App removed successfully')), 'info');
return self.refreshData().then(function() {
self.updateAppsGrid();
});
} else {
ui.addNotification(null, E('p', _('Removal failed: ') + (result.error || result.details)), 'error');
button.disabled = false;
button.textContent = _('Remove');
}
}).catch(function(err) {
ui.addNotification(null, E('p', _('Removal error: ') + err.message), 'error');
button.disabled = false;
button.textContent = _('Remove');
});
},
showAppDetails: function(appId) {
var self = this;
return API.getAppstoreApp(appId).then(function(app) {
if (!app || app.error) {
ui.addNotification(null, E('p', _('Failed to load app details')), 'error');
return;
}
var content = E('div', { 'class': 'app-details-modal' }, [
E('div', { 'class': 'modal-header' }, [
E('span', { 'class': 'app-icon-large' }, app.icon || '📦'),
E('div', {}, [
E('h2', {}, app.name),
E('p', {}, app.version)
])
]),
E('div', { 'class': 'modal-body' }, [
E('p', { 'class': 'app-description-full' }, app.description),
app.notes ? E('div', { 'class': 'app-notes-box' }, [
E('strong', {}, _('Important Notes:')),
E('p', {}, app.notes)
]) : null,
E('div', { 'class': 'app-meta' }, [
E('div', { 'class': 'meta-item' }, [
E('strong', {}, _('Author:')),
E('span', {}, app.author)
]),
E('div', { 'class': 'meta-item' }, [
E('strong', {}, _('License:')),
E('span', {}, app.license)
]),
E('div', { 'class': 'meta-item' }, [
E('strong', {}, _('Category:')),
E('span', {}, app.category)
]),
E('div', { 'class': 'meta-item' }, [
E('strong', {}, _('Status:')),
E('span', {}, app.status)
])
]),
app.dependencies && app.dependencies.length > 0 ? E('div', { 'class': 'app-dependencies' }, [
E('strong', {}, _('Dependencies:')),
E('ul', {}, app.dependencies.map(function(dep) {
return E('li', {}, dep);
}))
]) : null,
app.tags && app.tags.length > 0 ? E('div', { 'class': 'app-tags' }, [
E('strong', {}, _('Tags:')),
E('div', { 'class': 'tags-list' }, app.tags.map(function(tag) {
return E('span', { 'class': 'tag' }, tag);
}))
]) : null,
app.url ? E('div', { 'class': 'app-links' }, [
E('a', {
'href': app.url,
'target': '_blank',
'class': 'btn btn-link'
}, _('Visit Project Website →'))
]) : null
])
]);
ui.showModal(_('App Details'), content, 'max-content');
}).catch(function(err) {
ui.addNotification(null, E('p', _('Error loading app details: ') + err.message), 'error');
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});