New packages: - secubox-app-cyberfeed: Core RSS aggregator service - Pure shell script, OpenWrt compatible - Cyberpunk emoji injection based on content keywords - Caching with configurable TTL - JSON and HTML output with neon/glitch effects - RSS-Bridge support for social media (Facebook, Twitter, YouTube) - luci-app-cyberfeed: LuCI dashboard with cyberpunk theme - Dashboard with stats, quick actions, recent items - Feed management with add/delete - RSS-Bridge templates for easy social media setup - Preview with category filtering - Settings page for service configuration Features: - Auto-emojification (security, tech, mystical themes) - Dark neon UI with scanlines and glitch effects - RSS-Bridge integration for Facebook/Twitter/YouTube - Category-based filtering - Auto-refresh via cron (5 min default) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
252 lines
7.9 KiB
JavaScript
252 lines
7.9 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require cyberfeed.api as api';
|
|
|
|
return view.extend({
|
|
title: _('CyberFeed Dashboard'),
|
|
pollRegistered: false,
|
|
|
|
load: function() {
|
|
var cssLink = document.createElement('link');
|
|
cssLink.rel = 'stylesheet';
|
|
cssLink.href = L.resource('cyberfeed/dashboard.css');
|
|
document.head.appendChild(cssLink);
|
|
|
|
return api.getDashboardData();
|
|
},
|
|
|
|
render: function(data) {
|
|
var self = this;
|
|
var status = data.status || {};
|
|
var feeds = Array.isArray(data.feeds) ? data.feeds : [];
|
|
var items = Array.isArray(data.items) ? data.items : [];
|
|
var rssbridge = data.rssbridge || {};
|
|
|
|
var lastSync = status.last_sync ? new Date(status.last_sync * 1000).toLocaleString() : 'Never';
|
|
|
|
var content = [];
|
|
|
|
// Header
|
|
content.push(E('div', { 'class': 'cf-header' }, [
|
|
E('h1', {}, '\u26A1 CYBERFEED \u26A1'),
|
|
E('p', { 'class': 'subtitle' }, 'NEURAL RSS MATRIX INTERFACE')
|
|
]));
|
|
|
|
// Stats Grid
|
|
content.push(E('div', { 'class': 'cf-stats-grid' }, [
|
|
E('div', { 'class': 'cf-stat-card' }, [
|
|
E('div', { 'class': 'icon' }, '\uD83D\uDCE1'),
|
|
E('div', { 'class': 'value' }, String(status.feed_count || 0)),
|
|
E('div', { 'class': 'label' }, 'Feed Sources')
|
|
]),
|
|
E('div', { 'class': 'cf-stat-card' }, [
|
|
E('div', { 'class': 'icon' }, '\uD83D\uDCCB'),
|
|
E('div', { 'class': 'value' }, String(status.item_count || 0)),
|
|
E('div', { 'class': 'label' }, 'Total Items')
|
|
]),
|
|
E('div', { 'class': 'cf-stat-card' }, [
|
|
E('div', { 'class': 'icon' }, '\u23F0'),
|
|
E('div', { 'class': 'value' }, lastSync.split(' ')[1] || '--:--'),
|
|
E('div', { 'class': 'label' }, 'Last Sync')
|
|
]),
|
|
E('div', { 'class': 'cf-stat-card' }, [
|
|
E('div', { 'class': 'icon' }, status.enabled ? '\u2705' : '\u26D4'),
|
|
E('div', { 'class': 'value' }, status.enabled ? 'ON' : 'OFF'),
|
|
E('div', { 'class': 'label' }, 'Service Status')
|
|
])
|
|
]));
|
|
|
|
// Quick Actions Card
|
|
content.push(E('div', { 'class': 'cf-card' }, [
|
|
E('div', { 'class': 'cf-card-header' }, [
|
|
E('div', { 'class': 'cf-card-title' }, [
|
|
E('span', { 'class': 'cf-card-title-icon' }, '\u26A1'),
|
|
'Quick Actions'
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cf-card-body' }, [
|
|
E('div', { 'class': 'cf-quick-actions' }, [
|
|
E('button', {
|
|
'class': 'cf-btn cf-btn-primary',
|
|
'click': function() { self.handleSync(); }
|
|
}, ['\uD83D\uDD04', ' Sync Now']),
|
|
E('a', {
|
|
'href': L.url('admin/services/cyberfeed/feeds'),
|
|
'class': 'cf-btn cf-btn-secondary'
|
|
}, ['\u2795', ' Add Feed']),
|
|
E('a', {
|
|
'href': '/cyberfeed/',
|
|
'target': '_blank',
|
|
'class': 'cf-btn'
|
|
}, ['\uD83C\uDF10', ' Open Web View']),
|
|
E('a', {
|
|
'href': L.url('admin/services/cyberfeed/preview'),
|
|
'class': 'cf-btn'
|
|
}, ['\uD83D\uDC41', ' Preview'])
|
|
])
|
|
])
|
|
]));
|
|
|
|
// RSS-Bridge Card
|
|
var rssbridgeStatus = rssbridge.running ? 'Running' : (rssbridge.installed ? 'Stopped' : 'Not Installed');
|
|
var rssbridgeBadgeClass = rssbridge.running ? 'cf-badge-success' : (rssbridge.installed ? 'cf-badge-warning' : 'cf-badge-danger');
|
|
|
|
content.push(E('div', { 'class': 'cf-card cf-rssbridge-card' }, [
|
|
E('div', { 'class': 'cf-card-header' }, [
|
|
E('div', { 'class': 'cf-card-title' }, [
|
|
E('span', { 'class': 'cf-card-title-icon' }, '\uD83C\uDF09'),
|
|
'RSS-Bridge (Social Media)'
|
|
]),
|
|
E('span', { 'class': 'cf-badge ' + rssbridgeBadgeClass }, rssbridgeStatus)
|
|
]),
|
|
E('div', { 'class': 'cf-card-body' }, [
|
|
E('p', { 'style': 'margin-bottom: 16px; color: var(--cf-text-dim);' },
|
|
'RSS-Bridge converts Facebook, Twitter, YouTube and other platforms to RSS feeds.'),
|
|
E('div', { 'class': 'cf-quick-actions' },
|
|
rssbridge.installed ? [
|
|
E('button', {
|
|
'class': 'cf-btn ' + (rssbridge.running ? 'cf-btn-danger' : 'cf-btn-primary'),
|
|
'click': function() { self.handleRssBridge(rssbridge.running ? 'stop' : 'start'); }
|
|
}, rssbridge.running ? ['\u23F9', ' Stop'] : ['\u25B6', ' Start']),
|
|
rssbridge.running ? E('a', {
|
|
'href': 'http://' + window.location.hostname + ':' + (rssbridge.port || 3000),
|
|
'target': '_blank',
|
|
'class': 'cf-btn'
|
|
}, ['\uD83C\uDF10', ' Open Bridge UI']) : null
|
|
].filter(Boolean) : [
|
|
E('button', {
|
|
'class': 'cf-btn cf-btn-primary',
|
|
'click': function() { self.handleRssBridgeInstall(); }
|
|
}, ['\uD83D\uDCE5', ' Install RSS-Bridge'])
|
|
]
|
|
)
|
|
])
|
|
]));
|
|
|
|
// Recent Items Card
|
|
var recentItems = items.slice(0, 5);
|
|
var itemsContent;
|
|
|
|
if (recentItems.length === 0) {
|
|
itemsContent = E('div', { 'class': 'cf-empty' }, [
|
|
E('div', { 'class': 'cf-empty-icon' }, '\uD83D\uDD2E'),
|
|
E('div', { 'class': 'cf-empty-text' }, 'No feed items yet'),
|
|
E('div', { 'class': 'cf-empty-hint' }, 'Add feeds and sync to see content')
|
|
]);
|
|
} else {
|
|
itemsContent = E('div', {}, recentItems.map(function(item) {
|
|
return E('div', { 'class': 'cf-feed-item' }, [
|
|
E('div', { 'class': 'meta' }, [
|
|
E('span', { 'class': 'timestamp' }, '\u23F0 ' + (item.date || 'Unknown')),
|
|
E('div', {}, [
|
|
E('span', { 'class': 'cf-badge cf-badge-info' }, item.source || 'RSS'),
|
|
item.category ? E('span', { 'class': 'cf-badge cf-badge-category', 'style': 'margin-left: 6px;' }, item.category) : null
|
|
].filter(Boolean))
|
|
]),
|
|
E('div', { 'class': 'title' }, [
|
|
item.link ? E('a', { 'href': item.link, 'target': '_blank' }, item.title || 'Untitled') : (item.title || 'Untitled')
|
|
]),
|
|
item.desc ? E('div', { 'class': 'description' }, item.desc) : null
|
|
].filter(Boolean));
|
|
}));
|
|
}
|
|
|
|
content.push(E('div', { 'class': 'cf-card' }, [
|
|
E('div', { 'class': 'cf-card-header' }, [
|
|
E('div', { 'class': 'cf-card-title' }, [
|
|
E('span', { 'class': 'cf-card-title-icon' }, '\uD83D\uDCCB'),
|
|
'Recent Items (' + items.length + ')'
|
|
]),
|
|
E('a', {
|
|
'href': L.url('admin/services/cyberfeed/preview'),
|
|
'class': 'cf-btn cf-btn-sm'
|
|
}, 'View All')
|
|
]),
|
|
E('div', { 'class': 'cf-card-body' }, [itemsContent])
|
|
]));
|
|
|
|
var view = E('div', { 'class': 'cyberfeed-dashboard' }, content);
|
|
|
|
if (!this.pollRegistered) {
|
|
this.pollRegistered = true;
|
|
poll.add(function() {
|
|
return api.getDashboardData().then(function(newData) {
|
|
var container = document.querySelector('.cyberfeed-dashboard');
|
|
if (container) {
|
|
var newView = self.render(newData);
|
|
container.parentNode.replaceChild(newView, container);
|
|
}
|
|
});
|
|
}, 60);
|
|
}
|
|
|
|
return view;
|
|
},
|
|
|
|
handleSync: function() {
|
|
var self = this;
|
|
this.showToast('Syncing feeds...', 'info');
|
|
|
|
return api.syncFeeds().then(function(res) {
|
|
if (res && res.success) {
|
|
self.showToast('Sync started', 'success');
|
|
} else {
|
|
self.showToast('Sync failed: ' + (res.error || 'Unknown error'), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
handleRssBridge: function(action) {
|
|
var self = this;
|
|
return api.controlRssBridge(action).then(function(res) {
|
|
if (res && res.success) {
|
|
self.showToast('RSS-Bridge ' + action + 'ed', 'success');
|
|
window.location.reload();
|
|
} else {
|
|
self.showToast('Failed: ' + (res.error || 'Unknown error'), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
handleRssBridgeInstall: function() {
|
|
var self = this;
|
|
this.showToast('Installing RSS-Bridge...', 'info');
|
|
|
|
return api.installRssBridge().then(function(res) {
|
|
if (res && res.success) {
|
|
self.showToast('Installation started', 'success');
|
|
} else {
|
|
self.showToast('Failed: ' + (res.error || 'Unknown error'), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
showToast: function(message, type) {
|
|
var existing = document.querySelector('.cf-toast');
|
|
if (existing) existing.remove();
|
|
|
|
var iconMap = {
|
|
'success': '\u2705',
|
|
'error': '\u274C',
|
|
'warning': '\u26A0\uFE0F',
|
|
'info': '\u2139\uFE0F'
|
|
};
|
|
|
|
var toast = E('div', { 'class': 'cf-toast ' + (type || '') }, [
|
|
E('span', {}, iconMap[type] || '\u2139\uFE0F'),
|
|
message
|
|
]);
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(function() {
|
|
toast.remove();
|
|
}, 4000);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|