- Changed button logic from exp.vhost_exists to exp.emancipated - Non-emancipated sites now correctly show "Expose" button - Emancipated sites show "Unpublish" button - vhost_exists was incorrectly true for some non-emancipated sites Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
658 lines
22 KiB
JavaScript
658 lines
22 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require ui';
|
|
'require metablogizer.api as api';
|
|
'require metablogizer.qrcode as qrcode';
|
|
'require secubox/kiss-theme';
|
|
|
|
return view.extend({
|
|
status: {},
|
|
sites: [],
|
|
exposure: [],
|
|
|
|
load: function() {
|
|
var self = this;
|
|
return api.getDashboardData().then(function(data) {
|
|
self.status = data.status || {};
|
|
self.sites = data.sites || [];
|
|
self.exposure = data.exposure || [];
|
|
});
|
|
},
|
|
|
|
render: function() {
|
|
var self = this;
|
|
var sites = this.sites;
|
|
var exposure = this.exposure;
|
|
|
|
// Merge exposure data into sites
|
|
var exposureMap = {};
|
|
exposure.forEach(function(e) {
|
|
exposureMap[e.id] = e;
|
|
});
|
|
|
|
return KissTheme.wrap([
|
|
E('div', { 'class': 'cbi-map' }, [
|
|
E('h2', {}, _('MetaBlogizer')),
|
|
E('div', { 'class': 'cbi-map-descr' }, _('Static site publisher with HAProxy vhosts and SSL')),
|
|
|
|
// One-Click Deploy Section
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('One-Click Deploy')),
|
|
E('div', { 'class': 'cbi-section-descr' }, _('Upload HTML/ZIP to create a new static site with auto-configured SSL')),
|
|
E('div', { 'style': 'display:flex; gap:1em; flex-wrap:wrap; align-items:flex-end' }, [
|
|
E('div', { 'style': 'flex:1; min-width:150px' }, [
|
|
E('label', { 'style': 'display:block; margin-bottom:0.25em; font-weight:500' }, _('Site Name')),
|
|
E('input', { 'type': 'text', 'id': 'deploy-name', 'class': 'cbi-input-text',
|
|
'placeholder': 'myblog', 'style': 'width:100%' })
|
|
]),
|
|
E('div', { 'style': 'flex:2; min-width:200px' }, [
|
|
E('label', { 'style': 'display:block; margin-bottom:0.25em; font-weight:500' }, _('Domain')),
|
|
E('input', { 'type': 'text', 'id': 'deploy-domain', 'class': 'cbi-input-text',
|
|
'placeholder': 'blog.example.com', 'style': 'width:100%' })
|
|
]),
|
|
E('div', { 'style': 'flex:2; min-width:200px' }, [
|
|
E('label', { 'style': 'display:block; margin-bottom:0.25em; font-weight:500' }, _('Content (HTML or ZIP)')),
|
|
E('input', { 'type': 'file', 'id': 'deploy-file', 'accept': '.html,.htm,.zip',
|
|
'style': 'width:100%' })
|
|
]),
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-positive',
|
|
'style': 'white-space:nowrap',
|
|
'click': ui.createHandlerFn(this, 'handleOneClickDeploy')
|
|
}, _('Deploy'))
|
|
])
|
|
]),
|
|
|
|
// Sites Table
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Sites')),
|
|
sites.length > 0 ?
|
|
this.renderSitesTable(sites, exposureMap) :
|
|
E('div', { 'class': 'cbi-section-descr' }, _('No sites configured'))
|
|
])
|
|
])
|
|
], 'admin/services/metablogizer');
|
|
},
|
|
|
|
renderSitesTable: function(sites, exposureMap) {
|
|
var self = this;
|
|
|
|
return E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, _('Site')),
|
|
E('th', { 'class': 'th' }, _('Status')),
|
|
E('th', { 'class': 'th' }, _('Exposure')),
|
|
E('th', { 'class': 'th', 'style': 'text-align:center' }, _('Actions'))
|
|
])
|
|
].concat(sites.map(function(site) {
|
|
var exp = exposureMap[site.id] || {};
|
|
return self.renderSiteRow(site, exp);
|
|
})));
|
|
},
|
|
|
|
renderSiteRow: function(site, exp) {
|
|
var self = this;
|
|
|
|
// Backend status badge
|
|
var backendBadge;
|
|
if (exp.backend_running) {
|
|
backendBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.85em; background:#d4edda; color:#155724'
|
|
}, 'Running');
|
|
} else {
|
|
backendBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.85em; background:#f8d7da; color:#721c24'
|
|
}, 'Stopped');
|
|
}
|
|
|
|
// Exposure badge
|
|
var exposureBadge;
|
|
if (exp.vhost_exists && exp.cert_status === 'valid') {
|
|
exposureBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.85em; background:#d4edda; color:#155724',
|
|
'title': 'SSL certificate valid'
|
|
}, 'SSL OK');
|
|
} else if (exp.vhost_exists && exp.cert_status === 'warning') {
|
|
exposureBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.85em; background:#fff3cd; color:#856404',
|
|
'title': 'Certificate expiring soon'
|
|
}, 'SSL Warn');
|
|
} else if (exp.vhost_exists) {
|
|
exposureBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.85em; background:#f8d7da; color:#721c24',
|
|
'title': exp.cert_status || 'No certificate'
|
|
}, 'No SSL');
|
|
} else {
|
|
exposureBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 8px; border-radius:4px; font-size:0.85em; background:#e2e3e5; color:#383d41'
|
|
}, 'Private');
|
|
}
|
|
|
|
// Auth badge
|
|
var authBadge = '';
|
|
if (exp.auth_required) {
|
|
authBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 6px; border-radius:4px; font-size:0.85em; background:#cce5ff; color:#004085; margin-left:4px'
|
|
}, 'Auth');
|
|
}
|
|
|
|
// WAF badge (from site.waf_enabled returned by list_sites)
|
|
var wafBadge = '';
|
|
if (site.waf_enabled) {
|
|
wafBadge = E('span', {
|
|
'style': 'display:inline-block; padding:2px 6px; border-radius:4px; font-size:0.85em; background:#d1ecf1; color:#0c5460; margin-left:4px',
|
|
'title': _('Traffic inspected by WAF (mitmproxy)')
|
|
}, 'WAF');
|
|
}
|
|
|
|
// Domain link
|
|
var domainEl;
|
|
if (site.domain) {
|
|
domainEl = E('a', {
|
|
'href': 'https://' + site.domain,
|
|
'target': '_blank',
|
|
'style': 'color:#0066cc'
|
|
}, site.domain);
|
|
} else {
|
|
domainEl = E('em', { 'style': 'color:#888' }, '-');
|
|
}
|
|
|
|
return E('tr', { 'class': 'tr' }, [
|
|
// Site column
|
|
E('td', { 'class': 'td' }, [
|
|
E('strong', {}, site.name),
|
|
E('br'),
|
|
domainEl,
|
|
site.port ? E('span', { 'style': 'color:#888; font-size:0.9em; margin-left:0.5em' }, ':' + site.port) : ''
|
|
]),
|
|
// Status column
|
|
E('td', { 'class': 'td' }, [
|
|
backendBadge,
|
|
exp.has_content ? '' : E('span', {
|
|
'style': 'display:inline-block; padding:2px 6px; border-radius:4px; font-size:0.85em; background:#fff3cd; color:#856404; margin-left:4px'
|
|
}, 'Empty')
|
|
]),
|
|
// Exposure column
|
|
E('td', { 'class': 'td' }, [
|
|
exposureBadge,
|
|
authBadge,
|
|
wafBadge
|
|
]),
|
|
// Actions column
|
|
E('td', { 'class': 'td', 'style': 'text-align:center; white-space:nowrap' }, [
|
|
// Edit button
|
|
E('button', {
|
|
'class': 'cbi-button',
|
|
'style': 'padding:0.25em 0.5em; margin:2px',
|
|
'title': _('Edit site settings'),
|
|
'click': ui.createHandlerFn(self, 'showEditModal', site)
|
|
}, _('Edit')),
|
|
// Share button
|
|
E('button', {
|
|
'class': 'cbi-button',
|
|
'style': 'padding:0.25em 0.5em; margin:2px',
|
|
'title': _('Share / QR Code'),
|
|
'click': ui.createHandlerFn(self, 'showShareModal', site)
|
|
}, _('Share')),
|
|
// Upload button
|
|
E('button', {
|
|
'class': 'cbi-button',
|
|
'style': 'padding:0.25em 0.5em; margin:2px',
|
|
'title': _('Upload content'),
|
|
'click': ui.createHandlerFn(self, 'showUploadModal', site)
|
|
}, _('Upload')),
|
|
// Expose/Unpublish button - use emancipated flag, not vhost_exists
|
|
exp.emancipated ?
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-remove',
|
|
'style': 'padding:0.25em 0.5em; margin:2px',
|
|
'title': _('Unpublish site'),
|
|
'click': ui.createHandlerFn(self, 'handleUnpublish', site)
|
|
}, _('Unpublish')) :
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-apply',
|
|
'style': 'padding:0.25em 0.5em; margin:2px',
|
|
'title': _('Expose with SSL'),
|
|
'click': ui.createHandlerFn(self, 'handleEmancipate', site)
|
|
}, _('Expose')),
|
|
// Auth toggle button
|
|
E('button', {
|
|
'class': 'cbi-button',
|
|
'style': 'padding:0.25em 0.5em; margin:2px; ' + (exp.auth_required ? 'background:#cce5ff' : ''),
|
|
'title': exp.auth_required ? _('Authentication required - click to disable') : _('No authentication - click to enable'),
|
|
'click': ui.createHandlerFn(self, 'handleToggleAuth', site, exp)
|
|
}, exp.auth_required ? _('Unlock') : _('Lock')),
|
|
// Delete button
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-remove',
|
|
'style': 'padding:0.25em 0.5em; margin:2px',
|
|
'title': _('Delete site'),
|
|
'click': ui.createHandlerFn(self, 'handleDelete', site)
|
|
}, 'X')
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleOneClickDeploy: function() {
|
|
var self = this;
|
|
var name = document.getElementById('deploy-name').value.trim();
|
|
var domain = document.getElementById('deploy-domain').value.trim();
|
|
var fileInput = document.getElementById('deploy-file');
|
|
var file = fileInput.files[0];
|
|
|
|
if (!name) {
|
|
ui.addNotification(null, E('p', _('Site name is required')), 'error');
|
|
return;
|
|
}
|
|
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
ui.addNotification(null, E('p', _('Name must be lowercase letters, numbers, and hyphens only')), 'error');
|
|
return;
|
|
}
|
|
if (!domain) {
|
|
ui.addNotification(null, E('p', _('Domain is required')), 'error');
|
|
return;
|
|
}
|
|
|
|
ui.showModal(_('Deploying Site'), [
|
|
E('p', { 'class': 'spinning' }, _('Creating site and configuring HAProxy...'))
|
|
]);
|
|
|
|
var deployFn = function(content, isZip) {
|
|
return api.uploadAndCreateSite(name, domain, content, isZip).then(function(r) {
|
|
ui.hideModal();
|
|
if (r.success) {
|
|
ui.addNotification(null, E('p', _('Site created: ') + r.url));
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown error')), 'error');
|
|
}
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
};
|
|
|
|
if (file) {
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
var bytes = new Uint8Array(e.target.result);
|
|
var chunks = [];
|
|
for (var i = 0; i < bytes.length; i += 8192) {
|
|
chunks.push(String.fromCharCode.apply(null, bytes.slice(i, i + 8192)));
|
|
}
|
|
var content = btoa(chunks.join(''));
|
|
var isZip = file.name.toLowerCase().endsWith('.zip');
|
|
deployFn(content, isZip);
|
|
};
|
|
reader.onerror = function() {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Failed to read file')), 'error');
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
} else {
|
|
// No file - create site with default content
|
|
deployFn('', false);
|
|
}
|
|
},
|
|
|
|
showUploadModal: function(site) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Upload to: ') + site.name, [
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('File')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'file', 'id': 'upload-file-input' }),
|
|
E('div', { 'class': 'cbi-value-description' }, _('HTML, CSS, JS, images, etc.'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Destination')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'text', 'id': 'upload-dest', 'class': 'cbi-input-text',
|
|
'placeholder': 'index.html' }),
|
|
E('div', { 'class': 'cbi-value-description' }, _('Leave empty to use original filename'))
|
|
])
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-positive',
|
|
'click': function() {
|
|
var fileInput = document.getElementById('upload-file-input');
|
|
var destInput = document.getElementById('upload-dest');
|
|
var file = fileInput.files[0];
|
|
|
|
if (!file) {
|
|
ui.addNotification(null, E('p', _('Please select a file')), 'error');
|
|
return;
|
|
}
|
|
|
|
var dest = destInput.value.trim() || file.name;
|
|
|
|
ui.hideModal();
|
|
ui.showModal(_('Uploading'), [E('p', { 'class': 'spinning' }, _('Uploading file...'))]);
|
|
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
var bytes = new Uint8Array(e.target.result);
|
|
var chunks = [];
|
|
for (var i = 0; i < bytes.length; i += 8192) {
|
|
chunks.push(String.fromCharCode.apply(null, bytes.slice(i, i + 8192)));
|
|
}
|
|
var content = btoa(chunks.join(''));
|
|
|
|
var uploadFn = content.length > 40000 ?
|
|
api.chunkedUpload(site.id, dest, content) :
|
|
api.uploadFile(site.id, dest, content);
|
|
|
|
uploadFn.then(function(r) {
|
|
ui.hideModal();
|
|
if (r && r.success) {
|
|
ui.addNotification(null, E('p', _('File uploaded: ') + dest));
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Upload failed')), 'error');
|
|
}
|
|
}).catch(function(err) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
|
|
});
|
|
};
|
|
reader.onerror = function() {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Failed to read file')), 'error');
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
}, _('Upload'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleEmancipate: function(site) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Expose Site'), [
|
|
E('p', {}, _('This will configure:')),
|
|
E('ul', {}, [
|
|
E('li', {}, _('HAProxy vhost for ') + site.domain),
|
|
E('li', {}, _('ACME SSL certificate')),
|
|
E('li', {}, _('DNS + Vortex mesh publication'))
|
|
]),
|
|
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-apply',
|
|
'click': function() {
|
|
ui.hideModal();
|
|
self.runEmancipateAsync(site);
|
|
}
|
|
}, _('Expose'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
runEmancipateAsync: function(site) {
|
|
var self = this;
|
|
var outputPre = E('pre', {
|
|
'style': 'max-height:300px; overflow:auto; background:#f5f5f5; padding:10px; font-size:11px; white-space:pre-wrap'
|
|
}, _('Starting...'));
|
|
|
|
ui.showModal(_('Exposing Site'), [
|
|
E('p', { 'class': 'spinning' }, _('Running KISS ULTIME MODE workflow...')),
|
|
outputPre
|
|
]);
|
|
|
|
api.emancipate(site.id).then(function(r) {
|
|
if (!r.success) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
|
return;
|
|
}
|
|
|
|
var jobId = r.job_id;
|
|
var pollInterval = setInterval(function() {
|
|
api.emancipateStatus(jobId).then(function(status) {
|
|
if (status.output) {
|
|
outputPre.textContent = status.output;
|
|
outputPre.scrollTop = outputPre.scrollHeight;
|
|
}
|
|
|
|
if (status.complete) {
|
|
clearInterval(pollInterval);
|
|
ui.hideModal();
|
|
|
|
if (status.status === 'success') {
|
|
ui.addNotification(null, E('p', _('Site exposed successfully!')));
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Exposure failed')), 'error');
|
|
}
|
|
}
|
|
}).catch(function(e) {
|
|
clearInterval(pollInterval);
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Poll error: ') + e.message), 'error');
|
|
});
|
|
}, 2000);
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
},
|
|
|
|
handleUnpublish: function(site) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Unpublish Site'), [
|
|
E('p', {}, _('Remove public exposure for "') + site.name + '"?'),
|
|
E('p', { 'style': 'color:#666' }, _('The site content will be preserved but the HAProxy vhost will be removed.')),
|
|
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-remove',
|
|
'click': function() {
|
|
ui.hideModal();
|
|
ui.showModal(_('Unpublishing'), [E('p', { 'class': 'spinning' }, _('Removing exposure...'))]);
|
|
|
|
api.unpublishSite(site.id).then(function(r) {
|
|
ui.hideModal();
|
|
if (r.success) {
|
|
ui.addNotification(null, E('p', _('Site unpublished')));
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
|
}
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
}
|
|
}, _('Unpublish'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleToggleAuth: function(site, exp) {
|
|
var self = this;
|
|
var newAuth = !exp.auth_required;
|
|
|
|
ui.showModal(_('Updating'), [E('p', { 'class': 'spinning' }, _('Setting authentication...'))]);
|
|
|
|
api.setAuthRequired(site.id, newAuth).then(function(r) {
|
|
ui.hideModal();
|
|
if (r.success) {
|
|
ui.addNotification(null, E('p', newAuth ? _('Authentication enabled') : _('Authentication disabled')));
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
|
}
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
},
|
|
|
|
handleDelete: function(site) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Delete Site'), [
|
|
E('p', {}, _('Are you sure you want to delete "') + site.name + '"?'),
|
|
E('p', { 'style': 'color:#a00' }, _('This will remove the site, HAProxy vhost, and all files.')),
|
|
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-remove',
|
|
'click': function() {
|
|
ui.hideModal();
|
|
ui.showModal(_('Deleting'), [E('p', { 'class': 'spinning' }, _('Removing site...'))]);
|
|
|
|
api.deleteSite(site.id).then(function(r) {
|
|
ui.hideModal();
|
|
if (r.success) {
|
|
ui.addNotification(null, E('p', _('Site deleted')));
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
|
}
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
}
|
|
}, _('Delete'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
showEditModal: function(site) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Edit Site: ') + site.name, [
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Name')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'text', 'id': 'edit-name', 'class': 'cbi-input-text',
|
|
'value': site.name, 'style': 'width:100%' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'text', 'id': 'edit-domain', 'class': 'cbi-input-text',
|
|
'value': site.domain || '', 'style': 'width:100%' }),
|
|
E('div', { 'class': 'cbi-value-description' }, _('e.g. blog.example.com'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Description')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'text', 'id': 'edit-description', 'class': 'cbi-input-text',
|
|
'value': site.description || '', 'style': 'width:100%' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Enabled')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'checkbox', 'id': 'edit-enabled',
|
|
'checked': site.enabled !== '0' && site.enabled !== false })
|
|
])
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-positive',
|
|
'click': function() {
|
|
var name = document.getElementById('edit-name').value.trim();
|
|
var domain = document.getElementById('edit-domain').value.trim();
|
|
var description = document.getElementById('edit-description').value.trim();
|
|
var enabled = document.getElementById('edit-enabled').checked ? '1' : '0';
|
|
|
|
if (!name) {
|
|
ui.addNotification(null, E('p', _('Name is required')), 'error');
|
|
return;
|
|
}
|
|
|
|
ui.hideModal();
|
|
ui.showModal(_('Saving'), [E('p', { 'class': 'spinning' }, _('Updating site...'))]);
|
|
|
|
api.updateSite(site.id, name, domain, '', '1', enabled, description).then(function(r) {
|
|
ui.hideModal();
|
|
if (r.success) {
|
|
ui.addNotification(null, E('p', _('Site updated')));
|
|
window.location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
|
}
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
}
|
|
}, _('Save'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
showShareModal: function(site) {
|
|
var self = this;
|
|
var url = 'https://' + site.domain;
|
|
var title = site.name + ' - SecuBox';
|
|
var enc = encodeURIComponent;
|
|
|
|
var qrSvg = '';
|
|
try {
|
|
qrSvg = qrcode.generateSVG(url, 180);
|
|
} catch (e) {
|
|
qrSvg = '<p>QR code unavailable</p>';
|
|
}
|
|
|
|
ui.showModal(_('Share: ') + site.name, [
|
|
E('div', { 'style': 'text-align:center' }, [
|
|
E('div', { 'style': 'display:flex; gap:0.5em; margin-bottom:1em' }, [
|
|
E('input', { 'type': 'text', 'readonly': true, 'value': url, 'id': 'share-url',
|
|
'class': 'cbi-input-text', 'style': 'flex:1' }),
|
|
E('button', { 'class': 'cbi-button cbi-button-action', 'click': function() {
|
|
if (navigator.clipboard) {
|
|
navigator.clipboard.writeText(url).then(function() {
|
|
ui.addNotification(null, E('p', _('URL copied')));
|
|
});
|
|
}
|
|
}}, _('Copy'))
|
|
]),
|
|
E('div', { 'style': 'display:inline-block; padding:1em; background:#f8f8f8; border-radius:8px' }, [
|
|
E('div', { 'innerHTML': qrSvg })
|
|
]),
|
|
E('div', { 'style': 'margin-top:1em; display:flex; gap:0.5em; justify-content:center; flex-wrap:wrap' }, [
|
|
E('a', { 'href': 'https://twitter.com/intent/tweet?url=' + enc(url) + '&text=' + enc(title),
|
|
'target': '_blank', 'class': 'cbi-button' }, 'Twitter'),
|
|
E('a', { 'href': 'https://t.me/share/url?url=' + enc(url) + '&text=' + enc(title),
|
|
'target': '_blank', 'class': 'cbi-button' }, 'Telegram'),
|
|
E('a', { 'href': 'https://wa.me/?text=' + enc(title + ' ' + url),
|
|
'target': '_blank', 'class': 'cbi-button' }, 'WhatsApp'),
|
|
E('a', { 'href': 'mailto:?subject=' + enc(title) + '&body=' + enc(url),
|
|
'class': 'cbi-button' }, 'Email')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
|
E('a', { 'href': url, 'target': '_blank', 'class': 'cbi-button cbi-button-positive' }, _('Visit Site')),
|
|
' ',
|
|
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Close'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|