From 1585975e90ec424bb6e54cc6beb51b10306efea3 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 16 Feb 2026 10:36:56 +0100 Subject: [PATCH] refactor(hexojs): Use shared kiss-theme module Replace inline CSS with shared secubox/kiss-theme module for simpler, faster, more efficient rendering. Code reduced from 320 to 188 lines. Co-Authored-By: Claude Opus 4.5 --- .../resources/view/hexojs/overview.js | 391 ++++++------------ 1 file changed, 129 insertions(+), 262 deletions(-) diff --git a/package/secubox/luci-app-hexojs/htdocs/luci-static/resources/view/hexojs/overview.js b/package/secubox/luci-app-hexojs/htdocs/luci-static/resources/view/hexojs/overview.js index bcb2706e..fd8aead7 100644 --- a/package/secubox/luci-app-hexojs/htdocs/luci-static/resources/view/hexojs/overview.js +++ b/package/secubox/luci-app-hexojs/htdocs/luci-static/resources/view/hexojs/overview.js @@ -1,318 +1,185 @@ 'use strict'; 'require view'; -'require poll'; 'require ui'; 'require rpc'; 'require hexojs/api as api'; -return view.extend({ - pollInterval: 10, +var K = null; // KissTheme reference +return view.extend({ load: function() { return Promise.all([ + L.require('secubox/kiss-theme').then(function(m) { K = new m(); }), api.listInstances(), api.getStatus(), api.getSiteStats(), api.listBackups() - ]).then(function(results) { - return { - instances: results[0] || [], - status: results[1] || {}, - stats: results[2] || {}, - backups: results[3] || [] - }; + ]).then(function(r) { + return { instances: r[1] || [], status: r[2] || {}, stats: r[3] || {}, backups: r[4] || [] }; }); }, - css: function() { - return ` -:root { --k-bg:#0d1117; --k-surface:#161b22; --k-card:#1c2128; --k-line:#30363d; - --k-text:#e6edf3; --k-muted:#8b949e; --k-accent:#58a6ff; --k-green:#3fb950; - --k-red:#f85149; --k-yellow:#d29922; --k-purple:#a371f7; } -.k-wrap { max-width:1200px; margin:0 auto; padding:20px; color:var(--k-text); font-family:system-ui,-apple-system,sans-serif; } -.k-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:24px; } -.k-title { font-size:24px; font-weight:600; display:flex; align-items:center; gap:12px; } -.k-title span { color:var(--k-accent); } -.k-badge { padding:4px 12px; border-radius:20px; font-size:12px; font-weight:500; } -.k-badge-green { background:rgba(63,185,80,0.15); color:var(--k-green); } -.k-badge-red { background:rgba(248,81,73,0.15); color:var(--k-red); } -.k-grid { display:grid; gap:16px; margin-bottom:24px; } -.k-grid-4 { grid-template-columns:repeat(4,1fr); } -.k-stat { background:var(--k-card); border:1px solid var(--k-line); border-radius:8px; padding:20px; text-align:center; } -.k-stat-value { font-size:32px; font-weight:700; color:var(--k-accent); } -.k-stat-label { color:var(--k-muted); font-size:13px; margin-top:4px; } -.k-card { background:var(--k-card); border:1px solid var(--k-line); border-radius:8px; padding:20px; margin-bottom:16px; } -.k-card-title { font-size:14px; font-weight:600; color:var(--k-muted); margin-bottom:16px; text-transform:uppercase; letter-spacing:0.5px; } -.k-actions { display:flex; gap:8px; flex-wrap:wrap; } -.k-btn { padding:8px 16px; border-radius:6px; border:1px solid var(--k-line); background:var(--k-surface); - color:var(--k-text); cursor:pointer; font-size:13px; text-decoration:none; display:inline-flex; align-items:center; gap:6px; } -.k-btn:hover { border-color:var(--k-accent); color:var(--k-accent); } -.k-btn-green { background:var(--k-green); border-color:var(--k-green); color:#fff; } -.k-btn-green:hover { background:#2ea043; } -.k-btn-sm { padding:6px 10px; font-size:12px; } -.k-table { width:100%; border-collapse:collapse; } -.k-table th { text-align:left; padding:12px; color:var(--k-muted); font-size:12px; text-transform:uppercase; - border-bottom:1px solid var(--k-line); } -.k-table td { padding:12px; border-bottom:1px solid var(--k-line); } -.k-table tr:hover { background:var(--k-surface); } -.k-instance { display:flex; justify-content:space-between; align-items:center; padding:16px; - background:var(--k-surface); border-radius:8px; margin-bottom:8px; } -.k-instance-info h4 { margin:0 0 4px; font-size:15px; } -.k-instance-info p { margin:0; color:var(--k-muted); font-size:13px; } -.k-instance-actions { display:flex; gap:6px; } -.k-empty { text-align:center; padding:40px; color:var(--k-muted); } -@media(max-width:768px) { .k-grid-4{grid-template-columns:repeat(2,1fr);} } -`; + render: function(d) { + var self = this; + 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'); }, - handleCreateInstance: function() { - var nameInput, titleInput, portInput; - ui.showModal(_('New Instance'), [ - E('div', { style: 'margin-bottom:16px' }, [ - E('label', { style: 'display:block;margin-bottom:4px;color:#8b949e;font-size:13px' }, 'Name'), - nameInput = E('input', { type: 'text', placeholder: 'myblog', style: 'width:100%;padding:8px;border-radius:4px;border:1px solid #30363d;background:#161b22;color:#e6edf3' }) + 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;margin-bottom:4px;color:#8b949e;font-size:13px' }, 'Title'), - titleInput = E('input', { type: 'text', placeholder: 'My Blog', style: 'width:100%;padding:8px;border-radius:4px;border:1px solid #30363d;background:#161b22;color:#e6edf3' }) + 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: 'margin-bottom:16px' }, [ - E('label', { style: 'display:block;margin-bottom:4px;color:#8b949e;font-size:13px' }, 'Port (auto)'), - portInput = E('input', { type: 'number', placeholder: '4000', style: 'width:100%;padding:8px;border-radius:4px;border:1px solid #30363d;background:#161b22;color:#e6edf3' }) - ]), - E('div', { style: 'display:flex;gap:8px;justify-content:flex-end;margin-top:20px' }, [ + E('div', { style: 'display:flex;gap:8px;justify-content:flex-end' }, [ E('button', { class: 'cbi-button', click: ui.hideModal }, 'Cancel'), E('button', { class: 'cbi-button cbi-button-positive', click: function() { - var name = nameInput.value.trim(); - if (!name) return; - ui.showModal(_('Creating...'), [E('p', { class: 'spinning' }, 'Creating instance...')]); - api.createInstance(name, titleInput.value || null, portInput.value ? parseInt(portInput.value) : null).then(function(r) { + 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(); - if (r.success) window.location.reload(); - else ui.addNotification(null, E('p', r.error || 'Failed')); + r.success ? location.reload() : ui.addNotification(null, E('p', r.error || 'Failed')); }); }}, 'Create') ]) ]); }, - handleGitClone: function(source) { - var repoInput, instInput, branchInput; - var title = source === 'github' ? 'Clone from GitHub' : 'Clone from Gitea'; - ui.showModal(_(title), [ - E('div', { style: 'margin-bottom:16px' }, [ - E('label', { style: 'display:block;margin-bottom:4px;color:#8b949e;font-size:13px' }, 'Repository URL'), - repoInput = E('input', { type: 'text', placeholder: source === 'github' ? 'https://github.com/user/repo' : 'http://gitea.local/user/repo', style: 'width:100%;padding:8px;border-radius:4px;border:1px solid #30363d;background:#161b22;color:#e6edf3' }) + 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;margin-bottom:4px;color:#8b949e;font-size:13px' }, 'Instance'), - instInput = E('input', { type: 'text', placeholder: 'default', style: 'width:100%;padding:8px;border-radius:4px;border:1px solid #30363d;background:#161b22;color:#e6edf3' }) + 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: 'margin-bottom:16px' }, [ - E('label', { style: 'display:block;margin-bottom:4px;color:#8b949e;font-size:13px' }, 'Branch'), - branchInput = E('input', { type: 'text', placeholder: 'main', style: 'width:100%;padding:8px;border-radius:4px;border:1px solid #30363d;background:#161b22;color:#e6edf3' }) - ]), - E('div', { style: 'display:flex;gap:8px;justify-content:flex-end;margin-top:20px' }, [ + E('div', { style: 'display:flex;gap:8px;justify-content:flex-end' }, [ E('button', { class: 'cbi-button', click: ui.hideModal }, 'Cancel'), E('button', { class: 'cbi-button cbi-button-positive', click: function() { - var repo = repoInput.value.trim(); - if (!repo) return; - ui.showModal(_('Cloning...'), [E('p', { class: 'spinning' }, 'Cloning repository...')]); - var fn = source === 'github' ? api.gitHubClone : api.gitClone; - fn(repo, instInput.value || 'default', branchInput.value || 'main').then(function(r) { + 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(); - if (r.success) window.location.reload(); - else ui.addNotification(null, E('p', r.error || 'Clone failed')); + r.success ? location.reload() : ui.addNotification(null, E('p', r.error || 'Failed')); }); }}, 'Clone') ]) ]); }, - handleBackup: function(instance) { - ui.showModal(_('Backup'), [E('p', { class: 'spinning' }, 'Creating backup...')]); - api.createBackup(instance, null).then(function(r) { - ui.hideModal(); - if (r.success) { - ui.addNotification(null, E('p', 'Backup created: ' + (r.name || 'success'))); - window.location.reload(); - } else { - ui.addNotification(null, E('p', r.error || 'Backup failed')); - } - }); + 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); }); }, - handleRestore: function(name) { - ui.showModal(_('Restore'), [ - E('p', {}, 'Restore backup "' + name + '"?'), - E('p', { style: 'color:#d29922' }, 'This will overwrite current data!'), - E('div', { style: 'display:flex;gap:8px;justify-content:flex-end;margin-top:20px' }, [ - E('button', { class: 'cbi-button', click: ui.hideModal }, 'Cancel'), - E('button', { class: 'cbi-button cbi-button-negative', click: function() { - ui.showModal(_('Restoring...'), [E('p', { class: 'spinning' }, 'Restoring...')]); - api.restoreBackup(name, 'default').then(function(r) { - ui.hideModal(); - if (r.success) ui.addNotification(null, E('p', 'Restored')); - else ui.addNotification(null, E('p', r.error || 'Failed')); - }); - }}, 'Restore') - ]) - ]); - }, - - handleDeleteBackup: function(name) { - ui.showModal(_('Delete Backup'), [ - E('p', {}, 'Delete backup "' + name + '"?'), - E('div', { style: 'display:flex;gap:8px;justify-content:flex-end;margin-top:20px' }, [ - E('button', { class: 'cbi-button', click: ui.hideModal }, 'Cancel'), - E('button', { class: 'cbi-button cbi-button-negative', click: function() { - api.deleteBackup(name).then(function() { ui.hideModal(); window.location.reload(); }); - }}, 'Delete') - ]) - ]); - }, - - handleToggle: function(inst) { - var action = inst.running ? api.stopInstance : api.startInstance; - var msg = inst.running ? 'Stopping...' : 'Starting...'; - ui.showModal(_(msg), [E('p', { class: 'spinning' }, msg)]); - action(inst.name).then(function(r) { - ui.hideModal(); - setTimeout(function() { window.location.reload(); }, 1500); - }); - }, - - handleDelete: function(name) { - ui.showModal(_('Delete Instance'), [ - E('p', {}, 'Delete instance "' + name + '"?'), - E('label', { style: 'display:block;margin:12px 0' }, [ - E('input', { type: 'checkbox', id: 'del-data' }), - E('span', { style: 'margin-left:8px' }, 'Also delete data') - ]), - E('div', { style: 'display:flex;gap:8px;justify-content:flex-end;margin-top:20px' }, [ - E('button', { class: 'cbi-button', click: ui.hideModal }, 'Cancel'), - E('button', { class: 'cbi-button cbi-button-negative', click: function() { - var delData = document.getElementById('del-data').checked; - api.deleteInstance(name, delData).then(function() { ui.hideModal(); window.location.reload(); }); - }}, 'Delete') - ]) - ]); - }, - - handleQuickPublish: function(instance) { - ui.showModal(_('Publishing'), [E('p', { class: 'spinning' }, 'Building and publishing...')]); - api.quickPublish(instance).then(function(r) { + 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'))); }); }, - handleServiceToggle: function(status) { - var action = status.running ? api.serviceStop : api.serviceStart; - var msg = status.running ? 'Stopping...' : 'Starting...'; - ui.showModal(_(msg), [E('p', { class: 'spinning' }, msg)]); - action().then(function() { ui.hideModal(); setTimeout(function() { window.location.reload(); }, 2000); }); + 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')); + }); }, - render: function(data) { - var self = this; - var instances = data.instances || []; - var status = data.status || {}; - var stats = data.stats || {}; - var backups = data.backups || []; + 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'))); + }); + }, - return E('div', { class: 'k-wrap' }, [ - E('style', {}, this.css()), + delBackup: function(n) { + if (!confirm('Delete backup "' + n + '"?')) return; + api.deleteBackup(n).then(function() { location.reload(); }); + }, - // Header - E('div', { class: 'k-header' }, [ - E('div', { class: 'k-title' }, ['๐Ÿ“ Hexo ', E('span', {}, 'CMS')]), - E('button', { - class: 'k-btn ' + (status.running ? 'k-btn-green' : ''), - click: function() { self.handleServiceToggle(status); } - }, status.running ? 'โ— Running' : 'โ—‹ Stopped') - ]), - - // Stats - E('div', { class: 'k-grid k-grid-4' }, [ - E('div', { class: 'k-stat' }, [E('div', { class: 'k-stat-value' }, String(instances.length)), E('div', { class: 'k-stat-label' }, 'Instances')]), - E('div', { class: 'k-stat' }, [E('div', { class: 'k-stat-value' }, String(stats.posts || 0)), E('div', { class: 'k-stat-label' }, 'Posts')]), - E('div', { class: 'k-stat' }, [E('div', { class: 'k-stat-value' }, String(stats.drafts || 0)), E('div', { class: 'k-stat-label' }, 'Drafts')]), - E('div', { class: 'k-stat' }, [E('div', { class: 'k-stat-value' }, String(backups.length)), E('div', { class: 'k-stat-label' }, 'Backups')]) - ]), - - // Quick Actions - E('div', { class: 'k-card' }, [ - E('div', { class: 'k-card-title' }, 'Quick Actions'), - E('div', { class: 'k-actions' }, [ - E('button', { class: 'k-btn k-btn-green', click: function() { self.handleCreateInstance(); } }, '+ New Instance'), - E('button', { class: 'k-btn', click: function() { self.handleGitClone('github'); } }, '๐Ÿ™ GitHub'), - E('button', { class: 'k-btn', click: function() { self.handleGitClone('gitea'); } }, '๐Ÿต Gitea'), - E('a', { class: 'k-btn', href: L.url('admin', 'services', 'hexojs', 'editor') }, 'โœ New Post'), - E('a', { class: 'k-btn', href: L.url('admin', 'services', 'hexojs', 'settings') }, 'โš™ Settings') - ]) - ]), - - // Instances - E('div', { class: 'k-card' }, [ - E('div', { class: 'k-card-title' }, 'Instances'), - instances.length > 0 - ? E('div', {}, instances.map(function(inst) { - return E('div', { class: 'k-instance' }, [ - E('div', { class: 'k-instance-info' }, [ - E('h4', {}, [ - inst.title || inst.name, - E('span', { class: 'k-badge ' + (inst.running ? 'k-badge-green' : 'k-badge-red'), style: 'margin-left:8px' }, - inst.running ? 'Running' : 'Stopped') - ]), - E('p', {}, 'Port: ' + inst.port + (inst.domain ? ' ยท ' + inst.domain : '')) - ]), - E('div', { class: 'k-instance-actions' }, [ - E('button', { class: 'k-btn k-btn-sm', title: inst.running ? 'Stop' : 'Start', - click: function() { self.handleToggle(inst); } }, inst.running ? 'โน' : 'โ–ถ'), - E('button', { class: 'k-btn k-btn-sm', title: 'Publish', - click: function() { self.handleQuickPublish(inst.name); } }, '๐Ÿš€'), - E('button', { class: 'k-btn k-btn-sm', title: 'Backup', - click: function() { self.handleBackup(inst.name); } }, '๐Ÿ’พ'), - E('a', { class: 'k-btn k-btn-sm', title: 'Editor', - href: L.url('admin', 'services', 'hexojs', 'editor') + '?instance=' + inst.name }, 'โœ'), - inst.running ? E('a', { class: 'k-btn k-btn-sm', title: 'Preview', target: '_blank', - href: 'http://' + window.location.hostname + ':' + inst.port }, '๐Ÿ‘') : '', - E('button', { class: 'k-btn k-btn-sm', title: 'Delete', style: 'color:#f85149', - click: function() { self.handleDelete(inst.name); } }, 'โœ•') - ]) - ]); - })) - : E('div', { class: 'k-empty' }, 'No instances yet') - ]), - - // Backups - E('div', { class: 'k-card' }, [ - E('div', { class: 'k-card-title' }, 'Backups'), - backups.length > 0 - ? E('table', { class: 'k-table' }, [ - E('thead', {}, E('tr', {}, [ - E('th', {}, 'Name'), - E('th', {}, 'Size'), - E('th', {}, 'Date'), - E('th', { style: 'text-align:right' }, 'Actions') - ])), - E('tbody', {}, backups.map(function(bk) { - return E('tr', {}, [ - E('td', {}, bk.name), - E('td', {}, bk.size || '-'), - E('td', {}, bk.timestamp ? new Date(bk.timestamp * 1000).toLocaleDateString() : '-'), - E('td', { style: 'text-align:right' }, [ - E('button', { class: 'k-btn k-btn-sm', click: function() { self.handleRestore(bk.name); } }, 'โ†ฉ'), - E('button', { class: 'k-btn k-btn-sm', style: 'color:#f85149;margin-left:4px', - click: function() { self.handleDeleteBackup(bk.name); } }, 'โœ•') - ]) - ]); - })) - ]) - : E('div', { class: 'k-empty' }, 'No backups yet') - ]) - ]); + deleteInst: function(n) { + if (!confirm('Delete instance "' + n + '"?')) return; + api.deleteInstance(n, false).then(function() { location.reload(); }); }, handleSaveApply: null,