commit
585b7f0f2f
@ -119,6 +119,13 @@ var callGetWidgetData = rpc.declare({
|
||||
expect: { }
|
||||
});
|
||||
|
||||
// Services Discovery
|
||||
var callGetServices = rpc.declare({
|
||||
object: 'luci.secubox',
|
||||
method: 'get_services',
|
||||
expect: { services: [] }
|
||||
});
|
||||
|
||||
// ===== State Management API =====
|
||||
|
||||
var callGetComponentState = rpc.declare({
|
||||
@ -334,6 +341,9 @@ return baseclass.extend({
|
||||
// Widget Data
|
||||
getWidgetData: debugRPC('getWidgetData', callGetWidgetData, { retries: 1 }),
|
||||
|
||||
// Services Discovery
|
||||
getServices: debugRPC('getServices', callGetServices, { retries: 1 }),
|
||||
|
||||
// ===== State Management =====
|
||||
getComponentState: debugRPC('getComponentState', callGetComponentState, { retries: 2 }),
|
||||
setComponentState: debugRPC('setComponentState', callSetComponentState, { retries: 1 }),
|
||||
|
||||
@ -198,6 +198,70 @@ return view.extend({
|
||||
]);
|
||||
},
|
||||
|
||||
renderServicesSection: function(services) {
|
||||
if (!services || services.length === 0) {
|
||||
return E('div', { 'class': 'services-section card' }, [
|
||||
E('h3', {}, '🔌 Active Services'),
|
||||
E('p', { 'class': 'text-muted' }, 'No services detected')
|
||||
]);
|
||||
}
|
||||
|
||||
// Filter for external services only (accessible from network)
|
||||
var externalServices = services.filter(function(s) {
|
||||
return s.external && s.url;
|
||||
});
|
||||
|
||||
// Group by category
|
||||
var categories = {};
|
||||
externalServices.forEach(function(s) {
|
||||
var cat = s.category || 'other';
|
||||
if (!categories[cat]) categories[cat] = [];
|
||||
categories[cat].push(s);
|
||||
});
|
||||
|
||||
var categoryOrder = ['app', 'monitoring', 'system', 'proxy', 'security', 'media', 'privacy', 'other'];
|
||||
var categoryLabels = {
|
||||
app: '📦 Applications',
|
||||
monitoring: '📊 Monitoring',
|
||||
system: '⚙️ System',
|
||||
proxy: '🔀 Proxy',
|
||||
security: '🛡️ Security',
|
||||
media: '🎵 Media',
|
||||
privacy: '🧅 Privacy',
|
||||
other: '⚡ Other'
|
||||
};
|
||||
|
||||
var serviceLinks = [];
|
||||
categoryOrder.forEach(function(cat) {
|
||||
if (categories[cat] && categories[cat].length > 0) {
|
||||
categories[cat].forEach(function(svc) {
|
||||
var url = window.location.protocol + '//' + window.location.hostname + svc.url;
|
||||
serviceLinks.push(E('a', {
|
||||
'href': url,
|
||||
'target': '_blank',
|
||||
'class': 'service-link',
|
||||
'style': 'display:inline-flex;align-items:center;gap:8px;padding:10px 16px;' +
|
||||
'background:rgba(102,126,234,0.1);border:1px solid rgba(102,126,234,0.3);' +
|
||||
'border-radius:8px;text-decoration:none;color:#e0e0e0;font-size:14px;' +
|
||||
'transition:all 0.2s;margin:4px;'
|
||||
}, [
|
||||
E('span', { 'style': 'font-size:18px;' }, svc.icon || '⚡'),
|
||||
E('span', {}, svc.name),
|
||||
E('span', { 'style': 'color:#888;font-size:12px;' }, ':' + svc.port)
|
||||
]));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'services-section card' }, [
|
||||
E('h3', {}, '🔌 Active Services'),
|
||||
E('div', { 'class': 'services-grid', 'style': 'display:flex;flex-wrap:wrap;gap:8px;' },
|
||||
serviceLinks.length > 0 ? serviceLinks :
|
||||
[E('p', { 'class': 'text-muted' }, 'No external services available')]
|
||||
)
|
||||
]);
|
||||
},
|
||||
|
||||
renderQuickActions: function() {
|
||||
return E('div', { 'class': 'quick-actions card' }, [
|
||||
E('h3', {}, 'Quick Actions'),
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
"get_app_versions",
|
||||
"get_changelog",
|
||||
"get_widget_data",
|
||||
"get_wan_access"
|
||||
"get_wan_access",
|
||||
"get_services"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
|
||||
@ -23,6 +23,12 @@ var callCrowdSecStats = rpc.declare({
|
||||
method: 'nftables_stats'
|
||||
});
|
||||
|
||||
var callGetServices = rpc.declare({
|
||||
object: 'luci.secubox',
|
||||
method: 'get_services',
|
||||
expect: { services: [] }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
currentSection: 'dashboard',
|
||||
appStatuses: {},
|
||||
@ -35,10 +41,14 @@ return view.extend({
|
||||
callSystemInfo(),
|
||||
this.loadAppStatuses(),
|
||||
callCrowdSecStats().catch(function() { return null; }),
|
||||
portal.checkInstalledApps()
|
||||
portal.checkInstalledApps(),
|
||||
callGetServices().catch(function() { return []; })
|
||||
]).then(function(results) {
|
||||
// Store installed apps info from the last promise
|
||||
self.installedApps = results[4] || {};
|
||||
// RPC expect unwraps the services array directly
|
||||
var svcResult = results[5] || [];
|
||||
self.detectedServices = Array.isArray(svcResult) ? svcResult : (svcResult.services || []);
|
||||
return results;
|
||||
});
|
||||
},
|
||||
@ -407,9 +417,95 @@ return view.extend({
|
||||
},
|
||||
|
||||
renderServicesSection: function() {
|
||||
var apps = portal.getInstalledAppsBySection('services', this.installedApps);
|
||||
return this.renderAppSection('services', 'Services',
|
||||
'Application services and server platforms', apps);
|
||||
var self = this;
|
||||
var services = this.detectedServices || [];
|
||||
|
||||
// Filter for external services with URLs
|
||||
var externalServices = services.filter(function(s) {
|
||||
return s.external && s.url;
|
||||
});
|
||||
|
||||
// Group by category
|
||||
var categories = {};
|
||||
externalServices.forEach(function(s) {
|
||||
var cat = s.category || 'other';
|
||||
if (!categories[cat]) categories[cat] = [];
|
||||
categories[cat].push(s);
|
||||
});
|
||||
|
||||
var categoryOrder = ['app', 'monitoring', 'system', 'proxy', 'security', 'media', 'privacy', 'other'];
|
||||
var categoryLabels = {
|
||||
app: '📦 Applications',
|
||||
monitoring: '📊 Monitoring',
|
||||
system: '⚙️ System',
|
||||
proxy: '🔀 Proxy',
|
||||
security: '🛡️ Security',
|
||||
media: '🎵 Media',
|
||||
privacy: '🧅 Privacy',
|
||||
other: '⚡ Other'
|
||||
};
|
||||
|
||||
var serviceCards = [];
|
||||
|
||||
// Map icon names to emojis
|
||||
var iconMap = {
|
||||
'lock': '🔐', 'globe': '🌐', 'arrow': '🔀', 'shield': '🔒',
|
||||
'git': '📦', 'blog': '📝', 'security': '🛡️', 'settings': '⚙️',
|
||||
'feed': '📡', 'chart': '📊', 'stats': '📈', 'admin': '🔧',
|
||||
'app': '🎨', 'music': '🎵', 'onion': '🧅', '': '⚡'
|
||||
};
|
||||
|
||||
categoryOrder.forEach(function(cat) {
|
||||
if (categories[cat] && categories[cat].length > 0) {
|
||||
categories[cat].forEach(function(svc) {
|
||||
var url = window.location.protocol + '//' + window.location.hostname + svc.url;
|
||||
var emoji = iconMap[svc.icon] || '⚡';
|
||||
serviceCards.push(E('a', {
|
||||
'class': 'sb-app-card sb-service-card',
|
||||
'href': url,
|
||||
'target': '_blank'
|
||||
}, [
|
||||
E('div', { 'class': 'sb-app-card-header' }, [
|
||||
E('div', {
|
||||
'class': 'sb-app-card-icon',
|
||||
'style': 'background: linear-gradient(135deg, #667eea, #764ba2); font-size: 24px;'
|
||||
}, emoji),
|
||||
E('div', {}, [
|
||||
E('h4', { 'class': 'sb-app-card-title' }, svc.name),
|
||||
E('span', { 'class': 'sb-app-card-version' }, ':' + svc.port)
|
||||
])
|
||||
]),
|
||||
E('p', { 'class': 'sb-app-card-desc' }, categoryLabels[svc.category] || 'Service'),
|
||||
E('div', { 'class': 'sb-app-card-status' }, [
|
||||
E('span', { 'class': 'sb-app-card-status-dot running' }),
|
||||
E('span', { 'class': 'sb-app-card-status-text' }, 'Listening')
|
||||
])
|
||||
]));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (serviceCards.length === 0) {
|
||||
return E('div', { 'class': 'sb-portal-section', 'data-section': 'services' }, [
|
||||
E('div', { 'class': 'sb-section-header' }, [
|
||||
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Services'),
|
||||
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
|
||||
]),
|
||||
E('div', { 'class': 'sb-section-empty' }, [
|
||||
E('div', { 'class': 'sb-empty-icon' }, '🔌'),
|
||||
E('p', { 'class': 'sb-empty-text' }, 'No external services detected'),
|
||||
E('p', { 'class': 'sb-empty-hint' }, 'Services listening on localhost only are not shown')
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
return E('div', { 'class': 'sb-portal-section', 'data-section': 'services' }, [
|
||||
E('div', { 'class': 'sb-section-header' }, [
|
||||
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Services'),
|
||||
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
|
||||
]),
|
||||
E('div', { 'class': 'sb-app-grid' }, serviceCards)
|
||||
]);
|
||||
},
|
||||
|
||||
renderAppSection: function(sectionId, title, subtitle, apps) {
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
"getModuleInfo",
|
||||
"get_theme",
|
||||
"getStatus",
|
||||
"getHealth"
|
||||
"getHealth",
|
||||
"get_services"
|
||||
],
|
||||
"luci.system-hub": [
|
||||
"status",
|
||||
|
||||
@ -40,5 +40,5 @@ config theme_config 'theme'
|
||||
option logo_text 'Blog_'
|
||||
|
||||
config portal 'portal'
|
||||
option enabled '1'
|
||||
option enabled '0'
|
||||
option path '/www'
|
||||
|
||||
@ -19,7 +19,7 @@ config hexo 'hexo'
|
||||
option source_path '/srv/hexojs/site/source/_posts'
|
||||
option public_path '/srv/hexojs/site/public'
|
||||
option portal_path '/www'
|
||||
option auto_publish '1'
|
||||
option auto_publish '0'
|
||||
|
||||
config portal 'portal'
|
||||
option enabled '1'
|
||||
|
||||
@ -221,6 +221,10 @@ case "$1" in
|
||||
json_add_object "apply_wan_access"
|
||||
json_close_object
|
||||
|
||||
# Services discovery
|
||||
json_add_object "get_services"
|
||||
json_close_object
|
||||
|
||||
json_dump
|
||||
;;
|
||||
|
||||
@ -1174,6 +1178,70 @@ case "$1" in
|
||||
json_dump
|
||||
;;
|
||||
|
||||
get_services)
|
||||
# Discover listening services from netstat
|
||||
# Save to temp file to avoid subshell issues with json
|
||||
TMP_SERVICES="/tmp/services_$$"
|
||||
netstat -tlnp 2>/dev/null | grep LISTEN | awk '{
|
||||
split($4, a, ":")
|
||||
port = a[length(a)]
|
||||
if (!seen[port]++) {
|
||||
split($7, p, "/")
|
||||
proc = p[2]
|
||||
if (proc == "") proc = "unknown"
|
||||
print port, $4, proc
|
||||
}
|
||||
}' | sort -n -u > "$TMP_SERVICES"
|
||||
|
||||
json_init
|
||||
json_add_array "services"
|
||||
|
||||
while read port local proc; do
|
||||
addr=$(echo "$local" | sed 's/:[^:]*$//')
|
||||
name="Service"; icon=""; category="other"; path=""
|
||||
|
||||
case "$port" in
|
||||
22) name="SSH"; icon="lock"; category="system" ;;
|
||||
53) name="DNS"; icon="globe"; category="system" ;;
|
||||
80) name="HTTP"; icon="arrow"; path="/"; category="proxy" ;;
|
||||
443) name="HTTPS"; icon="shield"; path="/"; category="proxy" ;;
|
||||
3000) name="Gitea"; icon="git"; path=":3000"; category="app" ;;
|
||||
4000) name="HexoJS"; icon="blog"; path=":4000"; category="app" ;;
|
||||
8080) name="CrowdSec"; icon="security"; category="security" ;;
|
||||
8081) name="LuCI"; icon="settings"; path=":8081"; category="system" ;;
|
||||
8082) name="CyberFeed"; icon="feed"; path=":8082"; category="app" ;;
|
||||
8086) name="Netifyd"; icon="chart"; path=":8086"; category="monitoring" ;;
|
||||
8404) name="HAProxy Stats"; icon="stats"; path=":8404/stats"; category="monitoring" ;;
|
||||
8444) name="LuCI HTTPS"; icon="admin"; path=":8444"; category="system" ;;
|
||||
8501) name="Streamlit"; icon="app"; path=":8501"; category="app" ;;
|
||||
9000) name="Lyrion"; icon="music"; path=":9000"; category="media" ;;
|
||||
9050) name="Tor SOCKS"; icon="onion"; category="privacy" ;;
|
||||
9090) name="Lyrion CLI"; icon="music"; category="media" ;;
|
||||
2222) name="Gitea SSH"; icon="git"; category="app" ;;
|
||||
3483) name="Squeezebox"; icon="music"; category="media" ;;
|
||||
esac
|
||||
|
||||
external=0
|
||||
case "$addr" in 0.0.0.0|::) external=1 ;; 127.0.0.1|::1) ;; *) external=1 ;; esac
|
||||
|
||||
json_add_object ""
|
||||
json_add_int "port" "$port"
|
||||
json_add_string "address" "$addr"
|
||||
json_add_string "name" "$name"
|
||||
json_add_string "icon" "$icon"
|
||||
json_add_string "process" "$proc"
|
||||
json_add_string "category" "$category"
|
||||
json_add_boolean "external" "$external"
|
||||
[ -n "$path" ] && [ "$external" = "1" ] && json_add_string "url" "$path"
|
||||
json_close_object
|
||||
done < "$TMP_SERVICES"
|
||||
|
||||
rm -f "$TMP_SERVICES"
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
|
||||
|
||||
*)
|
||||
json_init
|
||||
json_add_boolean "error" true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user