secubox-openwrt/package/secubox/luci-app-localai/htdocs/luci-static/resources/view/localai/models.js
CyberMind-FR e58f479cd4 feat(waf): Update WAF scenarios with 2024-2025 CVEs and OWASP threats
Add detection patterns for latest actively exploited vulnerabilities:
- CVE-2025-55182 (React2Shell, CVSS 10.0)
- CVE-2025-8110 (Gogs RCE), CVE-2025-53770 (SharePoint)
- CVE-2025-52691 (SmarterMail), CVE-2025-40551 (SolarWinds)
- CVE-2024-47575 (FortiManager), CVE-2024-21887 (Ivanti)
- CVE-2024-3400, CVE-2024-0012, CVE-2024-9474 (PAN-OS)

New attack categories based on OWASP Top 10 2025:
- HTTP Request Smuggling (TE.CL/CL.TE conflicts)
- AI/LLM Prompt Injection (ChatML, instruction markers)
- WAF Bypass techniques (Unicode normalization, double encoding)
- Supply Chain attacks (CI/CD poisoning, dependency confusion)
- Extended SSTI (Jinja2, Freemarker, Velocity, Thymeleaf)
- API Abuse (BOLA/IDOR, mass assignment)

CrowdSec scenarios split into 11 separate files for reliability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 05:02:57 +01:00

267 lines
7.5 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require rpc';
'require secubox/kiss-theme';
var callModels = rpc.declare({
object: 'luci.localai',
method: 'models',
expect: { models: [] }
});
var callModelInstall = rpc.declare({
object: 'luci.localai',
method: 'model_install',
params: ['name'],
expect: { }
});
var callModelRemove = rpc.declare({
object: 'luci.localai',
method: 'model_remove',
params: ['name'],
expect: { }
});
function formatBytes(bytes) {
if (!bytes || bytes === 0) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
return view.extend({
title: _('LocalAI Models'),
load: function() {
return callModels().then(function(result) {
console.log('LocalAI models RPC result:', JSON.stringify(result));
return result;
}).catch(function(err) {
console.error('LocalAI models RPC error:', err);
return { models: [] };
});
},
render: function(data) {
var self = this;
console.log('LocalAI render data:', JSON.stringify(data));
// RPC with expect returns array directly, not {models: [...]}
var models = Array.isArray(data) ? data : (data && data.models ? data.models : []);
var presets = [
{ name: 'tinyllama', desc: 'TinyLlama 1.1B - Ultra-lightweight', size: '669 MB' },
{ name: 'phi2', desc: 'Microsoft Phi-2 - Compact and efficient', size: '1.6 GB' },
{ name: 'mistral', desc: 'Mistral 7B Instruct - High quality assistant', size: '4.1 GB' },
{ name: 'gte-small', desc: 'GTE Small - Fast embeddings', size: '67 MB' }
];
var container = E('div', { 'class': 'localai-models' }, [
E('style', {}, this.getCSS()),
E('div', { 'class': 'models-header' }, [
E('h2', {}, [E('span', {}, '🧠 '), _('Model Management')]),
E('p', {}, _('Install and manage AI models for LocalAI'))
]),
// Installed Models
E('div', { 'class': 'models-section' }, [
E('h3', {}, _('Installed Models')),
models.length > 0 ?
E('div', { 'class': 'models-grid' },
models.map(function(m) {
var displayId = m.id || m.name;
return E('div', { 'class': 'model-card installed' + (m.loaded ? ' active' : '') }, [
E('div', { 'class': 'model-card-icon' }, m.loaded ? '✅' : '🤖'),
E('div', { 'class': 'model-card-info' }, [
E('div', { 'class': 'model-card-name' }, displayId),
E('div', { 'class': 'model-card-meta' }, [
m.size > 0 ? E('span', {}, formatBytes(m.size)) : null,
E('span', {}, m.loaded ? _('Loaded') : m.type)
].filter(Boolean))
]),
E('button', {
'class': 'model-btn danger',
'click': function() { self.removeModel(m.name || displayId); }
}, _('Remove'))
]);
})
) :
E('div', { 'class': 'empty-state' }, [
E('span', {}, '📦'),
E('p', {}, _('No models installed yet'))
])
]),
// Available Presets
E('div', { 'class': 'models-section' }, [
E('h3', {}, _('Available Presets')),
E('div', { 'class': 'models-grid' },
presets.map(function(p) {
var isInstalled = models.some(function(m) {
var mId = (m.id || '').toLowerCase();
var mName = (m.name || '').toLowerCase();
return mId.includes(p.name) || mName.includes(p.name);
});
return E('div', { 'class': 'model-card preset' + (isInstalled ? ' installed' : '') }, [
E('div', { 'class': 'model-card-icon' }, isInstalled ? '✅' : '📥'),
E('div', { 'class': 'model-card-info' }, [
E('div', { 'class': 'model-card-name' }, p.name),
E('div', { 'class': 'model-card-desc' }, p.desc),
E('div', { 'class': 'model-card-size' }, p.size)
]),
!isInstalled ?
E('button', {
'class': 'model-btn install',
'click': function() { self.installModel(p.name); }
}, _('Install')) :
E('span', { 'class': 'model-installed-badge' }, _('Installed'))
]);
})
)
])
]);
return KissTheme.wrap(container, 'admin/services/localai/models');
},
installModel: function(name) {
ui.showModal(_('Installing Model'), [
E('p', {}, _('Downloading and installing model: ') + name),
E('p', { 'class': 'note' }, _('This may take several minutes...')),
E('div', { 'class': 'spinning' })
]);
callModelInstall(name).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Model installed successfully')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Installation failed')), 'error');
}
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', err.message), 'error');
});
},
removeModel: function(name) {
var self = this;
ui.showModal(_('Remove Model'), [
E('p', {}, _('Remove model: ') + name + '?'),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
E('button', {
'class': 'btn danger',
'click': function() {
callModelRemove(name).then(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', _('Model removed')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.error || _('Removal failed')), 'error');
}
});
}
}, _('Remove'))
])
]);
},
getCSS: function() {
return `
.localai-models {
font-family: 'Inter', -apple-system, sans-serif;
background: #030712;
color: #f8fafc;
min-height: 100vh;
padding: 20px;
}
.models-header { margin-bottom: 30px; }
.models-header h2 { font-size: 24px; margin-bottom: 8px; }
.models-header p { color: #94a3b8; }
.models-section {
background: #0f172a;
border: 1px solid #334155;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.models-section h3 {
margin: 0 0 16px 0;
font-size: 16px;
color: #a855f7;
}
.models-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
}
.model-card {
display: flex;
align-items: center;
gap: 14px;
padding: 16px;
background: #1e293b;
border: 1px solid #334155;
border-radius: 10px;
}
.model-card.active {
border-color: rgba(16, 185, 129, 0.4);
background: rgba(16, 185, 129, 0.08);
}
.model-card-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #a855f7, #6366f1);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.model-card-info { flex: 1; }
.model-card-name { font-weight: 600; margin-bottom: 4px; }
.model-card-desc { font-size: 12px; color: #94a3b8; }
.model-card-size { font-size: 11px; color: #64748b; margin-top: 4px; }
.model-card-meta {
display: flex;
gap: 10px;
font-size: 12px;
color: #94a3b8;
}
.model-btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
}
.model-btn.install {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
}
.model-btn.danger {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
}
.model-installed-badge {
font-size: 12px;
color: #10b981;
}
.empty-state {
text-align: center;
padding: 40px;
color: #64748b;
}
.empty-state span { font-size: 48px; }
`;
}
});