secubox-openwrt/package/secubox/luci-app-cyberfeed/htdocs/luci-static/resources/view/cyberfeed/overview.js
CyberMind-FR 22f6f26a01 feat(cyberfeed): Add CyberFeed RSS aggregator packages for OpenWrt
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>
2026-01-23 22:02:07 +01:00

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
});