Convert 90+ LuCI view files from legacy cbi-button-* classes to KissTheme kiss-btn-* classes for consistent dark theme styling. Pattern conversions applied: - cbi-button-positive → kiss-btn-green - cbi-button-negative/remove → kiss-btn-red - cbi-button-apply → kiss-btn-cyan - cbi-button-action → kiss-btn-blue - cbi-button (plain) → kiss-btn Also replaced hardcoded colors (#080, #c00, #888, etc.) with CSS variables (--kiss-green, --kiss-red, --kiss-muted, etc.) for proper dark theme compatibility. Apps updated include: ai-gateway, auth-guardian, bandwidth-manager, cloner, config-advisor, crowdsec-dashboard, dns-provider, exposure, glances, haproxy, hexojs, iot-guard, jellyfin, ksm-manager, mac-guardian, magicmirror2, master-link, meshname-dns, metablogizer, metabolizer, mqtt-bridge, netdata-dashboard, picobrew, routes-status, secubox-admin, secubox-mirror, secubox-p2p, secubox-security-threats, service-registry, simplex, streamlit, system-hub, tor-shield, traffic-shaper, vhost-manager, vortex-dns, vortex-firewall, webradio, wireguard-dashboard, zigbee2mqtt, zkp, and more. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
428 lines
20 KiB
JavaScript
428 lines
20 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require ui';
|
|
'require rpc';
|
|
'require secubox/kiss-theme';
|
|
|
|
var callUploadHTML = rpc.declare({
|
|
object: 'luci.hexojs',
|
|
method: 'upload_html',
|
|
params: ['base64_data', 'title', 'visibility', 'category', 'tags'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
var callUploadPDF = rpc.declare({
|
|
object: 'luci.hexojs',
|
|
method: 'upload_pdf',
|
|
params: ['base64_data', 'title', 'visibility', 'category'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
var callUploadMD = rpc.declare({
|
|
object: 'luci.hexojs',
|
|
method: 'upload_article',
|
|
params: ['filename', 'content', 'title', 'visibility', 'category', 'tags'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
var callWizardUpload = rpc.declare({
|
|
object: 'luci.hexojs',
|
|
method: 'wizard_upload',
|
|
params: ['base64_data', 'filename', 'title', 'visibility', 'category', 'tags', 'target', 'options'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
var callGiteaUpload = rpc.declare({
|
|
object: 'luci.gitea',
|
|
method: 'upload_file',
|
|
params: ['repo', 'path', 'content', 'message'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
var callStreamlitCreate = rpc.declare({
|
|
object: 'luci.streamlit',
|
|
method: 'create_app',
|
|
params: ['name', 'source_file'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
var callMetablogizerCreate = rpc.declare({
|
|
object: 'luci.metablogizer',
|
|
method: 'create_entry',
|
|
params: ['name', 'source_file', 'category'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
function fileToBase64(file) {
|
|
return new Promise(function(resolve, reject) {
|
|
var reader = new FileReader();
|
|
reader.onload = function() {
|
|
var base64 = reader.result.split(',')[1];
|
|
resolve(base64);
|
|
};
|
|
reader.onerror = reject;
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
return view.extend({
|
|
render: function() {
|
|
var self = this;
|
|
|
|
var content = E('div', { style: 'padding:20px;max-width:800px;margin:0 auto;' }, [
|
|
E('h2', { style: 'text-align:center;margin-bottom:30px;' }, '📤 Content Upload Wizard'),
|
|
|
|
E('div', { class: 'kiss-card', style: 'padding:30px;' }, [
|
|
// Step 1: File Selection
|
|
E('div', { class: 'wizard-step', id: 'step-file' }, [
|
|
E('h3', {}, '1. Select File'),
|
|
E('p', { style: 'color:#888;' }, 'Supported: HTML, PDF, Markdown (.md)'),
|
|
E('div', { style: 'border:2px dashed #3498db;border-radius:12px;padding:40px;text-align:center;margin:20px 0;' }, [
|
|
E('input', {
|
|
type: 'file',
|
|
id: 'file-input',
|
|
accept: '.html,.htm,.pdf,.md,.markdown',
|
|
style: 'display:none;'
|
|
}),
|
|
E('label', { for: 'file-input', style: 'cursor:pointer;display:block;' }, [
|
|
E('div', { style: 'font-size:3em;margin-bottom:10px;' }, '📄'),
|
|
E('div', { style: 'color:#3498db;font-size:1.2em;' }, 'Click to select file'),
|
|
E('div', { id: 'file-name', style: 'margin-top:10px;color:#27ae60;font-weight:bold;' }, '')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Step 2: Metadata
|
|
E('div', { class: 'wizard-step', id: 'step-meta', style: 'display:none;' }, [
|
|
E('h3', {}, '2. Article Details'),
|
|
E('div', { style: 'margin:15px 0;' }, [
|
|
E('label', { style: 'display:block;margin-bottom:5px;' }, 'Title'),
|
|
E('input', {
|
|
type: 'text',
|
|
id: 'input-title',
|
|
style: 'width:100%;padding:10px;border-radius:6px;border:1px solid #444;background:#1a1a2e;color:#fff;',
|
|
placeholder: 'Article title...'
|
|
})
|
|
]),
|
|
E('div', { style: 'margin:15px 0;' }, [
|
|
E('label', { style: 'display:block;margin-bottom:5px;' }, 'Category'),
|
|
E('input', {
|
|
type: 'text',
|
|
id: 'input-category',
|
|
style: 'width:100%;padding:10px;border-radius:6px;border:1px solid #444;background:#1a1a2e;color:#fff;',
|
|
placeholder: 'e.g., Blog, Tutorial, News',
|
|
value: 'Uploads'
|
|
})
|
|
]),
|
|
E('div', { style: 'margin:15px 0;' }, [
|
|
E('label', { style: 'display:block;margin-bottom:5px;' }, 'Tags (comma-separated)'),
|
|
E('input', {
|
|
type: 'text',
|
|
id: 'input-tags',
|
|
style: 'width:100%;padding:10px;border-radius:6px;border:1px solid #444;background:#1a1a2e;color:#fff;',
|
|
placeholder: 'tag1, tag2, tag3'
|
|
})
|
|
]),
|
|
E('div', { style: 'margin:20px 0;' }, [
|
|
E('label', { style: 'display:block;margin-bottom:10px;' }, 'Visibility'),
|
|
E('div', { style: 'display:flex;gap:20px;' }, [
|
|
E('label', { style: 'display:flex;align-items:center;gap:8px;cursor:pointer;' }, [
|
|
E('input', { type: 'radio', name: 'visibility', value: 'public', checked: true }),
|
|
E('span', {}, '🌍 Public')
|
|
]),
|
|
E('label', { style: 'display:flex;align-items:center;gap:8px;cursor:pointer;' }, [
|
|
E('input', { type: 'radio', name: 'visibility', value: 'private' }),
|
|
E('span', {}, '🔒 Private (Draft)')
|
|
])
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Step 3: Target Selection
|
|
E('div', { class: 'wizard-step', id: 'step-target', style: 'display:none;' }, [
|
|
E('h3', {}, '3. Publish To'),
|
|
E('div', { style: 'display:grid;grid-template-columns:repeat(2,1fr);gap:15px;margin:20px 0;' }, [
|
|
E('div', {
|
|
class: 'target-option selected',
|
|
'data-target': 'hexojs',
|
|
style: 'padding:20px;border:2px solid #3498db;border-radius:12px;cursor:pointer;text-align:center;'
|
|
}, [
|
|
E('div', { style: 'font-size:2em;' }, '📝'),
|
|
E('div', { style: 'font-weight:bold;' }, 'HexoJS Blog'),
|
|
E('div', { style: 'color:#888;font-size:0.9em;' }, 'Static blog post')
|
|
]),
|
|
E('div', {
|
|
class: 'target-option',
|
|
'data-target': 'gitea',
|
|
style: 'padding:20px;border:2px solid #444;border-radius:12px;cursor:pointer;text-align:center;'
|
|
}, [
|
|
E('div', { style: 'font-size:2em;' }, '🐙'),
|
|
E('div', { style: 'font-weight:bold;' }, 'Gitea'),
|
|
E('div', { style: 'color:#888;font-size:0.9em;' }, 'Version control')
|
|
]),
|
|
E('div', {
|
|
class: 'target-option',
|
|
'data-target': 'streamlit',
|
|
style: 'padding:20px;border:2px solid #444;border-radius:12px;cursor:pointer;text-align:center;'
|
|
}, [
|
|
E('div', { style: 'font-size:2em;' }, '📊'),
|
|
E('div', { style: 'font-weight:bold;' }, 'Streamlit'),
|
|
E('div', { style: 'color:#888;font-size:0.9em;' }, 'Interactive app')
|
|
]),
|
|
E('div', {
|
|
class: 'target-option',
|
|
'data-target': 'metablogizer',
|
|
style: 'padding:20px;border:2px solid #444;border-radius:12px;cursor:pointer;text-align:center;'
|
|
}, [
|
|
E('div', { style: 'font-size:2em;' }, '🔗'),
|
|
E('div', { style: 'font-weight:bold;' }, 'MetaBlogizer'),
|
|
E('div', { style: 'color:#888;font-size:0.9em;' }, 'Multi-platform')
|
|
])
|
|
]),
|
|
// Gitea options (shown when gitea selected)
|
|
E('div', { id: 'gitea-options', style: 'display:none;margin-top:20px;padding:15px;background:#1a1a2e;border-radius:8px;' }, [
|
|
E('h4', { style: 'margin-bottom:10px;' }, 'Gitea Repository'),
|
|
E('input', {
|
|
type: 'text',
|
|
id: 'gitea-repo',
|
|
style: 'width:100%;padding:10px;border-radius:6px;border:1px solid #444;background:#16213e;color:#fff;',
|
|
placeholder: 'owner/repo (e.g., admin/my-docs)'
|
|
}),
|
|
E('input', {
|
|
type: 'text',
|
|
id: 'gitea-path',
|
|
style: 'width:100%;padding:10px;border-radius:6px;border:1px solid #444;background:#16213e;color:#fff;margin-top:10px;',
|
|
placeholder: 'path/in/repo (e.g., docs/articles/)'
|
|
})
|
|
])
|
|
]),
|
|
|
|
// Navigation Buttons
|
|
E('div', { style: 'display:flex;justify-content:space-between;margin-top:30px;' }, [
|
|
E('button', {
|
|
id: 'btn-prev',
|
|
class: 'kiss-btn',
|
|
style: 'display:none;',
|
|
click: ui.createHandlerFn(this, 'prevStep')
|
|
}, '← Back'),
|
|
E('button', {
|
|
id: 'btn-next',
|
|
class: 'kiss-btn kiss-btn-blue',
|
|
style: 'margin-left:auto;',
|
|
click: ui.createHandlerFn(this, 'nextStep')
|
|
}, 'Next →')
|
|
]),
|
|
|
|
// Progress dots
|
|
E('div', { style: 'display:flex;justify-content:center;gap:10px;margin-top:20px;' }, [
|
|
E('span', { class: 'step-dot', style: 'width:12px;height:12px;border-radius:50%;background:#3498db;' }),
|
|
E('span', { class: 'step-dot', style: 'width:12px;height:12px;border-radius:50%;background:#444;' }),
|
|
E('span', { class: 'step-dot', style: 'width:12px;height:12px;border-radius:50%;background:#444;' })
|
|
])
|
|
]),
|
|
|
|
// Result Card
|
|
E('div', { id: 'result-card', class: 'kiss-card', style: 'display:none;padding:30px;text-align:center;' }, [
|
|
E('div', { style: 'font-size:4em;margin-bottom:20px;' }, '✅'),
|
|
E('h3', {}, 'Upload Successful!'),
|
|
E('p', { id: 'result-message', style: 'color:#888;' }, ''),
|
|
E('div', { style: 'margin-top:20px;' }, [
|
|
E('a', {
|
|
id: 'result-link',
|
|
href: '#',
|
|
class: 'kiss-btn kiss-btn-blue',
|
|
target: '_blank',
|
|
style: 'text-decoration:none;'
|
|
}, 'View Article'),
|
|
E('button', {
|
|
class: 'kiss-btn',
|
|
style: 'margin-left:10px;',
|
|
click: function() { window.location.reload(); }
|
|
}, 'Upload Another')
|
|
])
|
|
])
|
|
]);
|
|
|
|
// Initialize
|
|
setTimeout(function() {
|
|
self.currentStep = 1;
|
|
self.selectedFile = null;
|
|
self.selectedTarget = 'hexojs';
|
|
|
|
var fileInput = document.getElementById('file-input');
|
|
fileInput.addEventListener('change', function(e) {
|
|
if (e.target.files.length > 0) {
|
|
self.selectedFile = e.target.files[0];
|
|
document.getElementById('file-name').textContent = self.selectedFile.name;
|
|
var title = self.selectedFile.name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
|
|
document.getElementById('input-title').value = title;
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('.target-option').forEach(function(opt) {
|
|
opt.addEventListener('click', function() {
|
|
document.querySelectorAll('.target-option').forEach(function(o) {
|
|
o.style.borderColor = '#444';
|
|
});
|
|
this.style.borderColor = '#3498db';
|
|
self.selectedTarget = this.dataset.target;
|
|
// Show/hide gitea options
|
|
var giteaOpts = document.getElementById('gitea-options');
|
|
if (giteaOpts) {
|
|
giteaOpts.style.display = self.selectedTarget === 'gitea' ? 'block' : 'none';
|
|
}
|
|
});
|
|
});
|
|
}, 100);
|
|
|
|
return KissTheme.wrap([content], 'admin/services/hexojs/upload');
|
|
},
|
|
|
|
nextStep: function() {
|
|
var self = this;
|
|
|
|
if (this.currentStep === 1) {
|
|
if (!this.selectedFile) {
|
|
ui.addNotification(null, E('p', 'Please select a file'), 'warning');
|
|
return;
|
|
}
|
|
document.getElementById('step-file').style.display = 'none';
|
|
document.getElementById('step-meta').style.display = 'block';
|
|
document.getElementById('btn-prev').style.display = 'block';
|
|
this.currentStep = 2;
|
|
this.updateDots();
|
|
} else if (this.currentStep === 2) {
|
|
var title = document.getElementById('input-title').value;
|
|
if (!title) {
|
|
ui.addNotification(null, E('p', 'Please enter a title'), 'warning');
|
|
return;
|
|
}
|
|
document.getElementById('step-meta').style.display = 'none';
|
|
document.getElementById('step-target').style.display = 'block';
|
|
document.getElementById('btn-next').textContent = '📤 Publish';
|
|
this.currentStep = 3;
|
|
this.updateDots();
|
|
} else if (this.currentStep === 3) {
|
|
this.doUpload();
|
|
}
|
|
},
|
|
|
|
prevStep: function() {
|
|
if (this.currentStep === 2) {
|
|
document.getElementById('step-meta').style.display = 'none';
|
|
document.getElementById('step-file').style.display = 'block';
|
|
document.getElementById('btn-prev').style.display = 'none';
|
|
this.currentStep = 1;
|
|
} else if (this.currentStep === 3) {
|
|
document.getElementById('step-target').style.display = 'none';
|
|
document.getElementById('step-meta').style.display = 'block';
|
|
document.getElementById('btn-next').textContent = 'Next →';
|
|
this.currentStep = 2;
|
|
}
|
|
this.updateDots();
|
|
},
|
|
|
|
updateDots: function() {
|
|
var dots = document.querySelectorAll('.step-dot');
|
|
var step = this.currentStep;
|
|
dots.forEach(function(dot, i) {
|
|
dot.style.background = i < step ? '#3498db' : '#444';
|
|
});
|
|
},
|
|
|
|
doUpload: function() {
|
|
var self = this;
|
|
var title = document.getElementById('input-title').value;
|
|
var category = document.getElementById('input-category').value || 'Uploads';
|
|
var tags = document.getElementById('input-tags').value || '';
|
|
var visibility = document.querySelector('input[name="visibility"]:checked').value;
|
|
var target = this.selectedTarget;
|
|
|
|
var ext = this.selectedFile.name.split('.').pop().toLowerCase();
|
|
|
|
ui.showModal('Uploading...', [
|
|
E('p', { class: 'spinning' }, 'Processing ' + this.selectedFile.name + '...')
|
|
]);
|
|
|
|
fileToBase64(this.selectedFile).then(function(base64) {
|
|
// Route based on target
|
|
if (target === 'hexojs') {
|
|
// Default HexoJS upload
|
|
if (ext === 'pdf') {
|
|
return callUploadPDF(base64, title, visibility, category);
|
|
} else if (ext === 'html' || ext === 'htm') {
|
|
return callUploadHTML(base64, title, visibility, category, tags);
|
|
} else {
|
|
return callUploadMD(self.selectedFile.name, atob(base64), title, visibility, category, tags);
|
|
}
|
|
} else if (target === 'gitea') {
|
|
// Gitea upload
|
|
var repo = document.getElementById('gitea-repo').value || 'admin/uploads';
|
|
var path = document.getElementById('gitea-path').value || 'uploads/';
|
|
var fullPath = path.replace(/\/$/, '') + '/' + self.selectedFile.name;
|
|
return callGiteaUpload(repo, fullPath, base64, 'Upload: ' + title);
|
|
} else if (target === 'streamlit') {
|
|
// Create Streamlit app from markdown/python
|
|
var appName = title.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-');
|
|
return callStreamlitCreate(appName, base64).then(function(result) {
|
|
result.target = 'streamlit';
|
|
result.appUrl = '/admin/services/streamlit/apps/' + appName;
|
|
return result;
|
|
});
|
|
} else if (target === 'metablogizer') {
|
|
// Create MetaBlogizer entry
|
|
var entryName = title.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-');
|
|
return callMetablogizerCreate(entryName, base64, category).then(function(result) {
|
|
result.target = 'metablogizer';
|
|
return result;
|
|
});
|
|
} else {
|
|
// Fallback: use wizard_upload for unified handling
|
|
var options = {};
|
|
if (target === 'gitea') {
|
|
options.repo = document.getElementById('gitea-repo').value;
|
|
options.path = document.getElementById('gitea-path').value;
|
|
}
|
|
return callWizardUpload(base64, self.selectedFile.name, title, visibility, category, tags, target, JSON.stringify(options));
|
|
}
|
|
}).then(function(result) {
|
|
ui.hideModal();
|
|
if (result.success) {
|
|
document.querySelector('.kiss-card').style.display = 'none';
|
|
document.getElementById('result-card').style.display = 'block';
|
|
|
|
var message = 'Your content has been uploaded.';
|
|
var linkText = 'View';
|
|
var linkHref = '#';
|
|
|
|
if (target === 'hexojs') {
|
|
message = 'Your ' + (visibility === 'public' ? 'article' : 'draft') + ' has been created.';
|
|
linkText = 'View Article';
|
|
if (result.slug) linkHref = '/' + result.slug + '/';
|
|
} else if (target === 'gitea') {
|
|
message = 'File uploaded to Gitea repository.';
|
|
linkText = 'Open Gitea';
|
|
linkHref = result.url || '/admin/services/gitea/overview';
|
|
} else if (target === 'streamlit') {
|
|
message = 'Streamlit app created successfully.';
|
|
linkText = 'Open App';
|
|
linkHref = result.appUrl || '/admin/services/streamlit/overview';
|
|
} else if (target === 'metablogizer') {
|
|
message = 'MetaBlogizer entry created.';
|
|
linkText = 'View Entry';
|
|
linkHref = result.url || '/admin/services/metablogizer/overview';
|
|
}
|
|
|
|
document.getElementById('result-message').textContent = message;
|
|
document.getElementById('result-link').textContent = linkText;
|
|
document.getElementById('result-link').href = linkHref;
|
|
} else {
|
|
ui.addNotification(null, E('p', result.error || 'Upload failed'), 'error');
|
|
}
|
|
}).catch(function(err) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
|
});
|
|
}
|
|
});
|