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>
188 lines
8.4 KiB
JavaScript
188 lines
8.4 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require ui';
|
|
'require rpc';
|
|
'require hexojs/api as api';
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return Promise.all([
|
|
L.require('secubox/kiss-theme'),
|
|
api.listInstances(),
|
|
api.getStatus(),
|
|
api.getSiteStats(),
|
|
api.listBackups()
|
|
]).then(function(r) {
|
|
return { instances: r[1] || [], status: r[2] || {}, stats: r[3] || {}, backups: r[4] || [] };
|
|
});
|
|
},
|
|
|
|
render: function(d) {
|
|
var self = this;
|
|
var K = window.KissTheme;
|
|
K.apply();
|
|
|
|
return K.wrap([
|
|
// Header
|
|
K.E('div', { style: 'display:flex;justify-content:space-between;align-items:center;margin-bottom:20px' }, [
|
|
K.E('h2', { style: 'margin:0;font-size:24px' }, '📰 HexoJS'),
|
|
K.badge(d.status.running ? 'RUNNING' : 'STOPPED', d.status.running ? 'green' : 'red')
|
|
]),
|
|
|
|
// Stats
|
|
K.E('div', { class: 'kiss-grid kiss-grid-4' }, [
|
|
K.stat(d.instances.length, 'Instances', K.colors.cyan),
|
|
K.stat(d.stats.posts || 0, 'Posts', K.colors.green),
|
|
K.stat(d.stats.drafts || 0, 'Drafts', K.colors.yellow),
|
|
K.stat(d.backups.length, 'Backups', K.colors.purple)
|
|
]),
|
|
|
|
// Quick Actions
|
|
K.card('⚡ Actions', K.E('div', { style: 'display:flex;gap:8px;flex-wrap:wrap' }, [
|
|
K.btn('+ Instance', function() { self.createInstance(); }, 'green'),
|
|
K.btn('🐙 GitHub', function() { self.gitClone('github'); }),
|
|
K.btn('🍵 Gitea', function() { self.gitClone('gitea'); }),
|
|
K.E('a', { class: 'kiss-btn', href: L.url('admin', 'services', 'hexojs', 'editor') }, '✏ Post'),
|
|
K.E('a', { class: 'kiss-btn', href: L.url('admin', 'services', 'hexojs', 'settings') }, '⚙ Settings')
|
|
])),
|
|
|
|
// Instances
|
|
K.card('📦 Instances', d.instances.length ? K.E('div', {}, d.instances.map(function(i) {
|
|
return K.E('div', { style: 'display:flex;justify-content:space-between;align-items:center;padding:12px;background:var(--kiss-bg2);border-radius:8px;margin-bottom:8px' }, [
|
|
K.E('div', {}, [
|
|
K.E('strong', {}, i.title || i.name),
|
|
K.badge(i.running ? 'ON' : 'OFF', i.running ? 'green' : 'red'),
|
|
K.E('div', { style: 'font-size:12px;color:var(--kiss-muted);margin-top:4px' }, 'Port: ' + i.port)
|
|
]),
|
|
K.E('div', { style: 'display:flex;gap:4px' }, [
|
|
K.E('button', { class: 'kiss-btn', onClick: function() { self.toggle(i); } }, i.running ? '⏹' : '▶'),
|
|
K.E('button', { class: 'kiss-btn', onClick: function() { self.publish(i.name); } }, '🚀'),
|
|
K.E('button', { class: 'kiss-btn', onClick: function() { self.backup(i.name); } }, '💾'),
|
|
i.running ? K.E('a', { class: 'kiss-btn', href: 'http://' + location.hostname + ':' + i.port, target: '_blank' }, '👁') : null,
|
|
K.E('button', { class: 'kiss-btn kiss-btn-red', onClick: function() { self.deleteInst(i.name); } }, '✕')
|
|
])
|
|
]);
|
|
})) : K.E('div', { style: 'text-align:center;color:var(--kiss-muted);padding:20px' }, 'No instances')),
|
|
|
|
// Backups
|
|
K.card('💾 Backups', d.backups.length ? K.E('table', { class: 'kiss-table' }, [
|
|
K.E('thead', {}, K.E('tr', {}, [K.E('th', {}, 'Name'), K.E('th', {}, 'Size'), K.E('th', {}, 'Date'), K.E('th', {}, '')])),
|
|
K.E('tbody', {}, d.backups.map(function(b) {
|
|
return K.E('tr', {}, [
|
|
K.E('td', {}, b.name),
|
|
K.E('td', {}, b.size || '-'),
|
|
K.E('td', {}, b.timestamp ? new Date(b.timestamp * 1000).toLocaleDateString() : '-'),
|
|
K.E('td', { style: 'text-align:right' }, [
|
|
K.E('button', { class: 'kiss-btn', onClick: function() { self.restore(b.name); } }, '↩'),
|
|
K.E('button', { class: 'kiss-btn kiss-btn-red', style: 'margin-left:4px', onClick: function() { self.delBackup(b.name); } }, '✕')
|
|
])
|
|
]);
|
|
}))
|
|
]) : K.E('div', { style: 'text-align:center;color:var(--kiss-muted);padding:20px' }, 'No backups'))
|
|
], 'admin/services/hexojs');
|
|
},
|
|
|
|
createInstance: function() {
|
|
var name, title, port;
|
|
ui.showModal('New Instance', [
|
|
E('div', { style: 'margin-bottom:12px' }, [
|
|
E('label', { style: 'display:block;font-size:12px;color:#94a3b8;margin-bottom:4px' }, 'Name'),
|
|
name = E('input', { type: 'text', style: 'width:100%;padding:8px;background:#111827;border:1px solid #1e293b;border-radius:4px;color:#e2e8f0' })
|
|
]),
|
|
E('div', { style: 'margin-bottom:12px' }, [
|
|
E('label', { style: 'display:block;font-size:12px;color:#94a3b8;margin-bottom:4px' }, 'Title'),
|
|
title = E('input', { type: 'text', style: 'width:100%;padding:8px;background:#111827;border:1px solid #1e293b;border-radius:4px;color:#e2e8f0' })
|
|
]),
|
|
E('div', { style: 'margin-bottom:16px' }, [
|
|
E('label', { style: 'display:block;font-size:12px;color:#94a3b8;margin-bottom:4px' }, 'Port'),
|
|
port = E('input', { type: 'number', placeholder: '4000', style: 'width:100%;padding:8px;background:#111827;border:1px solid #1e293b;border-radius:4px;color:#e2e8f0' })
|
|
]),
|
|
E('div', { style: 'display:flex;gap:8px;justify-content:flex-end' }, [
|
|
E('button', { class: 'kiss-btn', click: ui.hideModal }, 'Cancel'),
|
|
E('button', { class: 'kiss-btn kiss-btn-green', click: function() {
|
|
if (!name.value) return;
|
|
ui.showModal('Creating...', [E('p', { class: 'spinning' }, 'Please wait...')]);
|
|
api.createInstance(name.value, title.value, port.value ? +port.value : null).then(function(r) {
|
|
ui.hideModal();
|
|
r.success ? location.reload() : ui.addNotification(null, E('p', r.error || 'Failed'));
|
|
});
|
|
}}, 'Create')
|
|
])
|
|
]);
|
|
},
|
|
|
|
gitClone: function(src) {
|
|
var repo, inst, branch;
|
|
ui.showModal('Clone from ' + src, [
|
|
E('div', { style: 'margin-bottom:12px' }, [
|
|
E('label', { style: 'display:block;font-size:12px;color:#94a3b8;margin-bottom:4px' }, 'Repository URL'),
|
|
repo = E('input', { type: 'text', style: 'width:100%;padding:8px;background:#111827;border:1px solid #1e293b;border-radius:4px;color:#e2e8f0' })
|
|
]),
|
|
E('div', { style: 'margin-bottom:12px' }, [
|
|
E('label', { style: 'display:block;font-size:12px;color:#94a3b8;margin-bottom:4px' }, 'Instance'),
|
|
inst = E('input', { type: 'text', value: 'default', style: 'width:100%;padding:8px;background:#111827;border:1px solid #1e293b;border-radius:4px;color:#e2e8f0' })
|
|
]),
|
|
E('div', { style: 'margin-bottom:16px' }, [
|
|
E('label', { style: 'display:block;font-size:12px;color:#94a3b8;margin-bottom:4px' }, 'Branch'),
|
|
branch = E('input', { type: 'text', value: 'main', style: 'width:100%;padding:8px;background:#111827;border:1px solid #1e293b;border-radius:4px;color:#e2e8f0' })
|
|
]),
|
|
E('div', { style: 'display:flex;gap:8px;justify-content:flex-end' }, [
|
|
E('button', { class: 'kiss-btn', click: ui.hideModal }, 'Cancel'),
|
|
E('button', { class: 'kiss-btn kiss-btn-green', click: function() {
|
|
if (!repo.value) return;
|
|
ui.showModal('Cloning...', [E('p', { class: 'spinning' }, 'Please wait...')]);
|
|
(src === 'github' ? api.gitHubClone : api.gitClone)(repo.value, inst.value, branch.value).then(function(r) {
|
|
ui.hideModal();
|
|
r.success ? location.reload() : ui.addNotification(null, E('p', r.error || 'Failed'));
|
|
});
|
|
}}, 'Clone')
|
|
])
|
|
]);
|
|
},
|
|
|
|
toggle: function(i) {
|
|
var fn = i.running ? api.stopInstance : api.startInstance;
|
|
ui.showModal('...', [E('p', { class: 'spinning' }, 'Please wait...')]);
|
|
fn(i.name).then(function() { ui.hideModal(); setTimeout(location.reload.bind(location), 1500); });
|
|
},
|
|
|
|
publish: function(n) {
|
|
ui.showModal('Publishing...', [E('p', { class: 'spinning' }, 'Building site...')]);
|
|
api.quickPublish(n).then(function(r) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', r.success ? 'Published!' : (r.error || 'Failed')));
|
|
});
|
|
},
|
|
|
|
backup: function(n) {
|
|
ui.showModal('Backup...', [E('p', { class: 'spinning' }, 'Creating backup...')]);
|
|
api.createBackup(n, null).then(function(r) {
|
|
ui.hideModal();
|
|
r.success ? location.reload() : ui.addNotification(null, E('p', r.error || 'Failed'));
|
|
});
|
|
},
|
|
|
|
restore: function(n) {
|
|
if (!confirm('Restore backup "' + n + '"? This will overwrite current data.')) return;
|
|
ui.showModal('Restoring...', [E('p', { class: 'spinning' }, 'Please wait...')]);
|
|
api.restoreBackup(n, 'default').then(function(r) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', r.success ? 'Restored' : (r.error || 'Failed')));
|
|
});
|
|
},
|
|
|
|
delBackup: function(n) {
|
|
if (!confirm('Delete backup "' + n + '"?')) return;
|
|
api.deleteBackup(n).then(function() { location.reload(); });
|
|
},
|
|
|
|
deleteInst: function(n) {
|
|
if (!confirm('Delete instance "' + n + '"?')) return;
|
|
api.deleteInstance(n, false).then(function() { location.reload(); });
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|