secubox-openwrt/package/secubox/luci-app-photoprism/htdocs/luci-static/resources/view/photoprism/overview.js
CyberMind-FR d01828d632 feat(avatar-tap): Add session capture and replay package
New packages for passive network tap with session replay capabilities:

secubox-avatar-tap:
- Mitmproxy-based passive session capture
- Captures authenticated sessions (cookies, auth headers, tokens)
- SQLite database for session storage
- CLI tool (avatar-tapctl) for management
- Transparent proxy mode support
- Runs inside streamlit LXC container

luci-app-avatar-tap:
- KISS-style dashboard for session management
- Real-time stats (sessions, domains, replays)
- Replay/Label/Delete actions per session
- Start/Stop controls

Designed for SecuBox Avatar authentication relay system
with future Nitrokey/GPG integration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 20:41:21 +01:00

641 lines
17 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
var callStatus = rpc.declare({
object: 'luci.photoprism',
method: 'status',
expect: {}
});
var callGetStats = rpc.declare({
object: 'luci.photoprism',
method: 'get_stats',
expect: {}
});
var callGetIndexProgress = rpc.declare({
object: 'luci.photoprism',
method: 'get_index_progress',
expect: {}
});
var callStart = rpc.declare({
object: 'luci.photoprism',
method: 'start',
expect: {}
});
var callStop = rpc.declare({
object: 'luci.photoprism',
method: 'stop',
expect: {}
});
var callInstall = rpc.declare({
object: 'luci.photoprism',
method: 'install',
expect: {}
});
var callUninstall = rpc.declare({
object: 'luci.photoprism',
method: 'uninstall',
expect: {}
});
var callIndex = rpc.declare({
object: 'luci.photoprism',
method: 'index',
expect: {}
});
var callImport = rpc.declare({
object: 'luci.photoprism',
method: 'import',
expect: {}
});
var callEmancipate = rpc.declare({
object: 'luci.photoprism',
method: 'emancipate',
params: ['domain'],
expect: {}
});
var callGetConfig = rpc.declare({
object: 'luci.photoprism',
method: 'get_config',
expect: {}
});
var callSetConfig = rpc.declare({
object: 'luci.photoprism',
method: 'set_config',
params: ['originals_path'],
expect: {}
});
return view.extend({
css: `
:root {
--kiss-bg: #1a1a2e;
--kiss-card: #16213e;
--kiss-border: #0f3460;
--kiss-accent: #e94560;
--kiss-text: #eee;
--kiss-muted: #888;
--kiss-success: #00d26a;
--kiss-warning: #ffc107;
}
.kiss-container {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--kiss-bg);
color: var(--kiss-text);
padding: 20px;
min-height: 100vh;
}
.kiss-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid var(--kiss-border);
}
.kiss-header h2 {
margin: 0;
font-size: 1.8em;
color: var(--kiss-text);
}
.kiss-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 0.8em;
font-weight: 600;
}
.kiss-badge-success { background: var(--kiss-success); color: #000; }
.kiss-badge-danger { background: var(--kiss-accent); color: #fff; }
.kiss-badge-warning { background: var(--kiss-warning); color: #000; }
.kiss-stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-bottom: 25px;
}
@media (max-width: 768px) { .kiss-stats-grid { grid-template-columns: repeat(2, 1fr); } }
.kiss-stat-card {
background: var(--kiss-card);
border: 1px solid var(--kiss-border);
border-radius: 12px;
padding: 20px;
text-align: center;
}
.kiss-stat-value {
font-size: 2em;
font-weight: 700;
color: var(--kiss-accent);
}
.kiss-stat-label {
font-size: 0.85rem;
color: var(--kiss-muted);
margin-top: 5px;
}
.kiss-index-bar {
background: var(--kiss-card);
border: 1px solid var(--kiss-border);
border-radius: 12px;
padding: 15px 20px;
margin-bottom: 25px;
}
.kiss-index-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.kiss-index-title {
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.kiss-index-file {
font-size: 0.8rem;
color: var(--kiss-muted);
font-family: monospace;
}
.kiss-progress-track {
height: 8px;
background: #333;
border-radius: 4px;
overflow: hidden;
}
.kiss-progress-bar {
height: 100%;
background: linear-gradient(90deg, var(--kiss-accent), #8b5cf6);
border-radius: 4px;
transition: width 0.5s;
}
.kiss-index-idle {
color: var(--kiss-success);
}
.kiss-pulse {
animation: pulse 1.5s infinite;
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} }
.kiss-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.kiss-card {
background: var(--kiss-card);
border: 1px solid var(--kiss-border);
border-radius: 10px;
padding: 20px;
}
.kiss-card h4 {
margin: 0 0 10px 0;
color: var(--kiss-muted);
font-size: 0.9em;
text-transform: uppercase;
}
.kiss-card .value {
font-size: 2em;
font-weight: 700;
color: var(--kiss-text);
}
.kiss-card .value.accent { color: var(--kiss-accent); }
.kiss-card .value.success { color: var(--kiss-success); }
.kiss-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 25px;
}
.kiss-btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 0.9em;
transition: all 0.2s;
}
.kiss-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.kiss-btn-primary {
background: var(--kiss-accent);
color: #fff;
}
.kiss-btn-primary:hover:not(:disabled) {
background: #ff6b6b;
}
.kiss-btn-secondary {
background: var(--kiss-border);
color: var(--kiss-text);
}
.kiss-btn-secondary:hover:not(:disabled) {
background: #1a4a7a;
}
.kiss-btn-success {
background: var(--kiss-success);
color: #000;
}
.kiss-btn-danger {
background: #dc3545;
color: #fff;
}
.kiss-section {
margin-bottom: 25px;
}
.kiss-section h3 {
margin: 0 0 15px 0;
font-size: 1.2em;
color: var(--kiss-text);
}
.kiss-features {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.kiss-feature {
padding: 8px 15px;
background: var(--kiss-border);
border-radius: 20px;
font-size: 0.85em;
}
.kiss-feature.active {
background: var(--kiss-success);
color: #000;
}
.kiss-input-group {
display: flex;
gap: 10px;
margin-top: 15px;
}
.kiss-input {
flex: 1;
padding: 10px 15px;
border: 1px solid var(--kiss-border);
border-radius: 6px;
background: var(--kiss-bg);
color: var(--kiss-text);
font-size: 0.95em;
}
.kiss-link {
color: var(--kiss-accent);
text-decoration: none;
}
.kiss-link:hover {
text-decoration: underline;
}
.kiss-install-card {
text-align: center;
padding: 40px;
}
.kiss-install-card h3 {
margin-bottom: 15px;
}
.kiss-install-card p {
color: var(--kiss-muted);
margin-bottom: 25px;
}
`,
status: null,
stats: null,
config: null,
indexProgress: null,
load: function() {
return Promise.all([
callStatus(),
callGetStats(),
callGetConfig(),
callGetIndexProgress()
]);
},
render: function(data) {
var self = this;
this.status = data[0] || {};
this.stats = data[1] || {};
this.config = data[2] || {};
this.indexProgress = data[3] || {};
var container = E('div', { 'class': 'kiss-container' }, [
E('style', {}, this.css),
this.renderHeader(),
this.status.installed ? this.renderDashboard() : this.renderInstallPrompt()
]);
poll.add(function() {
return Promise.all([callStatus(), callGetStats(), callGetConfig(), callGetIndexProgress()]).then(function(results) {
self.status = results[0] || {};
self.stats = results[1] || {};
self.config = results[2] || {};
self.indexProgress = results[3] || {};
self.updateView();
});
}, 5);
return container;
},
renderHeader: function() {
var status = this.status;
var badge = !status.installed
? E('span', { 'class': 'kiss-badge kiss-badge-warning' }, 'Not Installed')
: status.running
? E('span', { 'class': 'kiss-badge kiss-badge-success' }, 'Running')
: E('span', { 'class': 'kiss-badge kiss-badge-danger' }, 'Stopped');
return E('div', { 'class': 'kiss-header' }, [
E('h2', {}, 'PhotoPrism Gallery'),
badge
]);
},
renderInstallPrompt: function() {
var self = this;
return E('div', { 'class': 'kiss-card kiss-install-card' }, [
E('h3', {}, 'PhotoPrism Not Installed'),
E('p', {}, 'Self-hosted Google Photos alternative with AI-powered face recognition, search, and albums.'),
E('button', {
'class': 'kiss-btn kiss-btn-primary',
'click': function() {
this.disabled = true;
this.textContent = 'Installing...';
callInstall().then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'PhotoPrism installed successfully!'), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', {}, 'Installation failed: ' + (res.output || 'Unknown error')), 'error');
}
});
}
}, 'Install PhotoPrism')
]);
},
formatNumber: function(n) {
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
return n.toString();
},
renderDashboard: function() {
var self = this;
var status = this.status;
var stats = this.stats;
var idx = this.indexProgress;
return E('div', {}, [
// Live Stats Grid
E('div', { 'class': 'kiss-stats-grid', 'id': 'stats-grid' }, [
E('div', { 'class': 'kiss-stat-card' }, [
E('div', { 'class': 'kiss-stat-value', 'data-stat': 'sidecars' }, this.formatNumber(idx.sidecar_count || 0)),
E('div', { 'class': 'kiss-stat-label' }, 'Indexed')
]),
E('div', { 'class': 'kiss-stat-card' }, [
E('div', { 'class': 'kiss-stat-value', 'data-stat': 'thumbnails' }, this.formatNumber(idx.thumbnail_count || 0)),
E('div', { 'class': 'kiss-stat-label' }, 'Thumbnails')
]),
E('div', { 'class': 'kiss-stat-card' }, [
E('div', { 'class': 'kiss-stat-value', 'data-stat': 'photos' }, this.formatNumber(stats.photo_count || 0)),
E('div', { 'class': 'kiss-stat-label' }, 'Photos')
]),
E('div', { 'class': 'kiss-stat-card' }, [
E('div', { 'class': 'kiss-stat-value', 'data-stat': 'videos' }, this.formatNumber(stats.video_count || 0)),
E('div', { 'class': 'kiss-stat-label' }, 'Videos')
])
]),
// Index Progress Bar
E('div', { 'class': 'kiss-index-bar', 'id': 'index-bar' },
idx.indexing ?
this.renderIndexProgress(idx) :
E('div', { 'class': 'kiss-index-header' }, [
E('span', { 'class': 'kiss-index-title kiss-index-idle' }, ['✓ ', 'Ready']),
E('span', { 'class': 'kiss-index-file' }, 'DB: ' + (idx.db_size || '0'))
])
),
// Storage Stats
E('div', { 'class': 'kiss-grid' }, [
E('div', { 'class': 'kiss-card' }, [
E('h4', {}, 'Originals Size'),
E('div', { 'class': 'value', 'data-stat': 'originals' }, stats.originals_size || '0')
]),
E('div', { 'class': 'kiss-card' }, [
E('h4', {}, 'Cache Size'),
E('div', { 'class': 'value', 'data-stat': 'cache' }, stats.storage_size || '0')
])
]),
// Actions
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'Actions'),
E('div', { 'class': 'kiss-actions' }, [
E('button', {
'class': 'kiss-btn ' + (status.running ? 'kiss-btn-danger' : 'kiss-btn-success'),
'data-action': 'toggle',
'click': function() {
var fn = status.running ? callStop : callStart;
this.disabled = true;
fn().then(function() {
window.location.reload();
});
}
}, status.running ? 'Stop' : 'Start'),
E('button', {
'class': 'kiss-btn kiss-btn-secondary',
'disabled': !status.running,
'click': function() {
this.disabled = true;
this.textContent = 'Indexing...';
callIndex().then(function(res) {
ui.addNotification(null, E('p', {}, 'Indexing started'), 'success');
});
}
}, 'Index Photos'),
E('button', {
'class': 'kiss-btn kiss-btn-secondary',
'disabled': !status.running,
'click': function() {
this.disabled = true;
this.textContent = 'Importing...';
callImport().then(function(res) {
ui.addNotification(null, E('p', {}, 'Import complete'), 'success');
window.location.reload();
});
}
}, 'Import'),
status.running ? E('a', {
'class': 'kiss-btn kiss-btn-primary',
'href': 'http://' + window.location.hostname + ':' + (status.port || 2342),
'target': '_blank'
}, 'Open Gallery') : E('span')
])
]),
// Features
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'AI Features'),
E('div', { 'class': 'kiss-features' }, [
E('span', { 'class': 'kiss-feature ' + (status.face_recognition ? 'active' : '') }, 'Face Recognition'),
E('span', { 'class': 'kiss-feature ' + (status.object_detection ? 'active' : '') }, 'Object Detection'),
E('span', { 'class': 'kiss-feature ' + (status.places ? 'active' : '') }, 'Places / Maps')
])
]),
// Settings
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'Storage Settings'),
E('div', { 'class': 'kiss-input-group' }, [
E('label', { 'style': 'color: var(--kiss-muted); margin-right: 10px;' }, 'Photos Path:'),
E('input', {
'class': 'kiss-input',
'type': 'text',
'id': 'originals-path',
'value': self.config.originals_path || '/srv/photoprism/originals',
'placeholder': '/mnt/PHOTO'
}),
E('button', {
'class': 'kiss-btn kiss-btn-secondary',
'click': function() {
var path = document.getElementById('originals-path').value;
if (!path) {
ui.addNotification(null, E('p', {}, 'Please enter a path'), 'warning');
return;
}
this.disabled = true;
this.textContent = 'Saving...';
var btn = this;
callSetConfig(path).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'Path updated. Restart PhotoPrism to apply.'), 'success');
}
btn.disabled = false;
btn.textContent = 'Save';
});
}
}, 'Save')
])
]),
// Emancipate
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'Public Exposure'),
status.domain
? E('p', {}, ['Gallery available at: ', E('a', { 'class': 'kiss-link', 'href': 'https://' + status.domain, 'target': '_blank' }, 'https://' + status.domain)])
: E('div', { 'class': 'kiss-input-group' }, [
E('input', {
'class': 'kiss-input',
'type': 'text',
'id': 'emancipate-domain',
'placeholder': 'photos.example.com'
}),
E('button', {
'class': 'kiss-btn kiss-btn-primary',
'click': function() {
var domain = document.getElementById('emancipate-domain').value;
if (!domain) {
ui.addNotification(null, E('p', {}, 'Please enter a domain'), 'warning');
return;
}
this.disabled = true;
this.textContent = 'Configuring...';
callEmancipate(domain).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'Gallery exposed at ' + res.url), 'success');
window.location.reload();
}
});
}
}, 'Emancipate')
])
]),
// Danger Zone
E('div', { 'class': 'kiss-section' }, [
E('h3', {}, 'Danger Zone'),
E('button', {
'class': 'kiss-btn kiss-btn-danger',
'click': function() {
if (confirm('Remove PhotoPrism container? Photos will be preserved.')) {
this.disabled = true;
callUninstall().then(function() {
ui.addNotification(null, E('p', {}, 'PhotoPrism uninstalled'), 'success');
window.location.reload();
});
}
}
}, 'Uninstall')
])
]);
},
renderIndexProgress: function(idx) {
return E('div', {}, [
E('div', { 'class': 'kiss-index-header' }, [
E('span', { 'class': 'kiss-index-title' }, [
E('span', { 'class': 'kiss-pulse' }, '⏳'),
' Indexing...'
]),
E('span', { 'class': 'kiss-index-file' }, idx.current_file || 'Processing...')
]),
E('div', { 'class': 'kiss-progress-track' }, [
E('div', { 'class': 'kiss-progress-bar', 'style': 'width: 100%' })
])
]);
},
updateView: function() {
var stats = this.stats;
var idx = this.indexProgress;
// Update stat cards
var sidecarEl = document.querySelector('[data-stat="sidecars"]');
var thumbEl = document.querySelector('[data-stat="thumbnails"]');
var photosEl = document.querySelector('[data-stat="photos"]');
var videosEl = document.querySelector('[data-stat="videos"]');
var originalsEl = document.querySelector('[data-stat="originals"]');
var cacheEl = document.querySelector('[data-stat="cache"]');
if (sidecarEl) sidecarEl.textContent = this.formatNumber(idx.sidecar_count || 0);
if (thumbEl) thumbEl.textContent = this.formatNumber(idx.thumbnail_count || 0);
if (photosEl) photosEl.textContent = this.formatNumber(stats.photo_count || 0);
if (videosEl) videosEl.textContent = this.formatNumber(stats.video_count || 0);
if (originalsEl) originalsEl.textContent = stats.originals_size || '0';
if (cacheEl) cacheEl.textContent = stats.storage_size || '0';
// Update index bar
var indexBar = document.getElementById('index-bar');
if (indexBar) {
if (idx.indexing) {
indexBar.innerHTML = '';
indexBar.appendChild(this.renderIndexProgress(idx));
} else {
indexBar.innerHTML = '<div class="kiss-index-header"><span class="kiss-index-title kiss-index-idle">✓ Ready</span><span class="kiss-index-file">DB: ' + (idx.db_size || '0') + '</span></div>';
}
}
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});