Testing/CI/ACL
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
<?php
|
||||
/** @var \MageSail\Magesail\Block\Adminhtml\Nginx\Map $block */
|
||||
$mapPath = $block->getMapFilePath();
|
||||
$mapContent = $block->getMapContent();
|
||||
$mapUrl = $block->getMapUrl();
|
||||
$formKey = $block->escapeHtml($block->getFormKey());
|
||||
$configUrl = $block->escapeUrl($block->getUrl('adminhtml/system_config/edit/section/magesail'));
|
||||
?>
|
||||
<div class="magesail-nginx-map">
|
||||
<div class="magesail-nginx-map" id="magesail-nginx-map-root">
|
||||
<div class="page-title-wrapper">
|
||||
<h1 class="page-title"><?= $block->escapeHtml(__('NGINX Global Map Editor')) ?></h1>
|
||||
</div>
|
||||
@@ -81,320 +78,12 @@ $configUrl = $block->escapeUrl($block->getUrl('adminhtml/system_config/edit/sect
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
require(['jquery', 'domReady!'], function ($) {
|
||||
var mapUrl = <?= json_encode($mapUrl) ?>;
|
||||
var formKey = <?= json_encode($formKey) ?>;
|
||||
var mapConfigured = <?= $mapPath !== null ? 'true' : 'false' ?>;
|
||||
var msgLoadingBlocks = <?= json_encode((string) __('Loading map blocks…')) ?>;
|
||||
var msgParseFailed = <?= json_encode((string) __('Could not parse the map file. Click Parse & Load Blocks to retry.')) ?>;
|
||||
var parsedData = null;
|
||||
/** @type {string|null} Full file text from last successful parse (also updated after raw save / read). */
|
||||
var lastMapRaw = null;
|
||||
|
||||
function flash(msg, isError) {
|
||||
var el = $('#magesail-flash');
|
||||
el.removeClass('message-success success message-error error message-warning warning')
|
||||
.addClass(isError ? 'message-error error' : 'message-success success')
|
||||
.html('<span>' + $('<div/>').text(msg).html() + '</span>')
|
||||
.show();
|
||||
if (!isError) {
|
||||
setTimeout(function () { el.fadeOut(); }, 5000);
|
||||
}
|
||||
<script type="text/x-magento-init">
|
||||
{
|
||||
"#magesail-nginx-map-root": {
|
||||
"MageSail_Magesail/js/magesail-nginx-map": <?= /* @noEscape */ $block->getNginxMapInitJson() ?>
|
||||
}
|
||||
|
||||
function renderBlocks(blocks) {
|
||||
var html = '<div class="magesail-map-blocks-container">';
|
||||
blocks.forEach(function(block, blockIdx) {
|
||||
html += '<div class="admin__fieldset magesail-map-block" data-block-index="' + blockIdx + '">';
|
||||
html += '<div class="admin__fieldset-header">';
|
||||
html += '<strong>map $' + $('<div/>').text(block.source).html() + ' $' + $('<div/>').text(block.target).html() + ' {</strong>';
|
||||
html += '<button type="button" class="action-secondary magesail-add-entry" data-block="' + blockIdx + '">Add Entry</button>';
|
||||
html += '</div>';
|
||||
html += '<table class="admin__table-primary magesail-map-entries">';
|
||||
html += '<thead><tr><th>Hostname</th><th>Value</th><th>Comment</th><th>Actions</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
block.entries.forEach(function(entry, entryIdx) {
|
||||
html += '<tr data-entry-index="' + entryIdx + '">';
|
||||
html += '<td><input type="text" class="admin__control-text magesail-hostname" value="' + $('<div/>').text(entry.hostname).html() + '" /></td>';
|
||||
html += '<td><input type="text" class="admin__control-text magesail-value" value="' + $('<div/>').text(entry.value).html() + '" /></td>';
|
||||
html += '<td><input type="text" class="admin__control-text magesail-comment" value="' + $('<div/>').text(entry.comment || '').html() + '" /></td>';
|
||||
html += '<td><button type="button" class="action-delete magesail-delete-entry">Delete</button></td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
html += '<div class="magesail-block-footer">}</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '<div class="magesail-actions" style="margin-top: 1rem;">';
|
||||
html += '<button type="button" class="action-primary" id="magesail-btn-save-blocks">';
|
||||
html += '<span>Save Blocks & Validate</span>';
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="action-secondary" id="magesail-btn-switch-raw">';
|
||||
html += '<span>Switch to Raw Editor</span>';
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
$('#magesail-map-blocks').html(html).show();
|
||||
$('#magesail-map-loading').hide();
|
||||
$('#magesail-map-raw-editor').hide();
|
||||
|
||||
$('.magesail-add-entry').on('click', function() {
|
||||
var blockIdx = $(this).data('block');
|
||||
var tbody = $(this).closest('.magesail-map-block').find('tbody');
|
||||
var row = '<tr data-entry-index="new">';
|
||||
row += '<td><input type="text" class="admin__control-text magesail-hostname" value="" /></td>';
|
||||
row += '<td><input type="text" class="admin__control-text magesail-value" value="" /></td>';
|
||||
row += '<td><input type="text" class="admin__control-text magesail-comment" value="" /></td>';
|
||||
row += '<td><button type="button" class="action-delete magesail-delete-entry">Delete</button></td>';
|
||||
row += '</tr>';
|
||||
tbody.append(row);
|
||||
});
|
||||
|
||||
$('.magesail-delete-entry').on('click', function() {
|
||||
$(this).closest('tr').remove();
|
||||
});
|
||||
}
|
||||
|
||||
function updateDiagnostics() {
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'diagnostics', ajax: '1' }
|
||||
}).done(function (res) {
|
||||
if (res.success && res.diagnostics) {
|
||||
var d = res.diagnostics;
|
||||
var html = '<p><strong>Path:</strong> ' + $('<div/>').text(d.path || 'Not configured').html() + '</p>';
|
||||
html += '<p><strong>Exists:</strong> ' + (d.exists ? 'Yes' : 'No') + '</p>';
|
||||
html += '<p><strong>Readable:</strong> ' + (d.readable ? 'Yes' : 'No') + '</p>';
|
||||
html += '<p><strong>Writable:</strong> ' + (d.writable ? 'Yes' : 'No') + '</p>';
|
||||
if (d.error) {
|
||||
html += '<p class="message message-error"><strong>Issue:</strong> ' + $('<div/>').text(d.error).html() + '</p>';
|
||||
}
|
||||
$('#magesail-diagnostics-content').html(html);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showParseLoadingState() {
|
||||
$('#magesail-map-raw-editor').hide();
|
||||
$('#magesail-map-blocks').hide();
|
||||
$('#magesail-map-loading').removeClass('message-error error').addClass('message-info info');
|
||||
$('#magesail-map-loading span').text(msgLoadingBlocks);
|
||||
$('#magesail-map-loading').show();
|
||||
}
|
||||
|
||||
function showParseFailureBanner(detailMsg) {
|
||||
$('#magesail-map-loading').removeClass('message-info info').addClass('message-error error');
|
||||
$('#magesail-map-loading span').text(detailMsg || msgParseFailed);
|
||||
$('#magesail-map-loading').show();
|
||||
}
|
||||
|
||||
function parseAndLoadBlocks(silentSuccess) {
|
||||
showParseLoadingState();
|
||||
updateDiagnostics();
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'parse', ajax: '1' }
|
||||
}).done(function (res) {
|
||||
if (res.success && res.parsed) {
|
||||
parsedData = res.parsed;
|
||||
if (typeof res.raw === 'string') {
|
||||
lastMapRaw = res.raw;
|
||||
}
|
||||
renderBlocks(res.parsed.blocks);
|
||||
if (!silentSuccess) {
|
||||
flash('Map file parsed successfully.', false);
|
||||
}
|
||||
} else {
|
||||
showParseFailureBanner(res.message || '');
|
||||
flash(res.message || 'Failed to parse map file', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
showParseFailureBanner(m);
|
||||
flash(m, true);
|
||||
});
|
||||
}
|
||||
|
||||
$('#magesail-btn-parse').on('click', function() {
|
||||
parseAndLoadBlocks(false);
|
||||
});
|
||||
|
||||
$(document).on('click', '#magesail-btn-save-blocks', function() {
|
||||
var blocks = [];
|
||||
$('.magesail-map-block').each(function() {
|
||||
var block = $(this);
|
||||
var headerText = block.find('.admin__fieldset-header strong').text();
|
||||
var matches = headerText.match(/\$(\w+)\s+\$(\w+)/);
|
||||
if (!matches || matches.length < 3) {
|
||||
flash('Invalid map block header format', true);
|
||||
return false;
|
||||
}
|
||||
var source = matches[1];
|
||||
var target = matches[2];
|
||||
var entries = [];
|
||||
block.find('tbody tr').each(function() {
|
||||
var hostname = $(this).find('.magesail-hostname').val().trim();
|
||||
var value = $(this).find('.magesail-value').val().trim();
|
||||
var comment = $(this).find('.magesail-comment').val().trim();
|
||||
if (hostname && value) {
|
||||
entries.push({
|
||||
hostname: hostname,
|
||||
value: value,
|
||||
comment: comment || null
|
||||
});
|
||||
}
|
||||
});
|
||||
blocks.push({
|
||||
source: source,
|
||||
target: target,
|
||||
entries: entries
|
||||
});
|
||||
});
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: {
|
||||
form_key: formKey,
|
||||
action: 'save_blocks',
|
||||
ajax: '1',
|
||||
blocks: JSON.stringify(blocks)
|
||||
}
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || 'Blocks saved successfully.', false);
|
||||
if (res.validation && !res.validation.valid) {
|
||||
flash('Warning: ' + (res.validation.error || 'NGINX validation failed'), true);
|
||||
}
|
||||
} else {
|
||||
flash(res.message || 'Failed to save blocks', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
flash(m, true);
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#magesail-btn-switch-raw', function() {
|
||||
$('#magesail-map-blocks').hide();
|
||||
$('#magesail-map-raw-editor').show();
|
||||
var $ta = $('#magesail-map-content');
|
||||
if ($ta.val() === '') {
|
||||
if (lastMapRaw !== null) {
|
||||
$ta.val(lastMapRaw);
|
||||
} else {
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'read', ajax: '1' }
|
||||
}).done(function (res) {
|
||||
if (res.success && typeof res.content === 'string') {
|
||||
lastMapRaw = res.content;
|
||||
$ta.val(res.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#magesail-btn-switch-gui').on('click', function() {
|
||||
parseAndLoadBlocks(false);
|
||||
});
|
||||
|
||||
if (mapConfigured) {
|
||||
parseAndLoadBlocks(true);
|
||||
}
|
||||
|
||||
$('#magesail-btn-save-raw').on('click', function() {
|
||||
var content = $('#magesail-map-content').val();
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'write', ajax: '1', content: content }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
if (typeof content === 'string') {
|
||||
lastMapRaw = content;
|
||||
}
|
||||
flash(res.message || 'Raw content saved.', false);
|
||||
if (res.validation && !res.validation.valid) {
|
||||
flash('Warning: ' + (res.validation.error || 'NGINX validation failed'), true);
|
||||
}
|
||||
} else {
|
||||
flash(res.message || 'Failed to save', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
flash(m, true);
|
||||
});
|
||||
});
|
||||
|
||||
function ajaxAction(action, data, successMsg) {
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: $.extend({ form_key: formKey, action: action, ajax: '1' }, data || {})
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(successMsg || res.message || 'Operation completed successfully.', false);
|
||||
if (res.validation && !res.validation.valid) {
|
||||
flash('Warning: ' + (res.validation.error || 'NGINX validation failed'), true);
|
||||
}
|
||||
} else {
|
||||
flash(res.message || 'Operation failed', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
flash(m, true);
|
||||
});
|
||||
}
|
||||
|
||||
$('#magesail-btn-validate').on('click', function() {
|
||||
ajaxAction('validate', null, 'NGINX configuration validated.');
|
||||
});
|
||||
|
||||
$('#magesail-btn-reload').on('click', function() {
|
||||
ajaxAction('reload', null, 'NGINX reloaded.');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
/** @var \MageSail\Magesail\Block\Adminhtml\Tunnel $block */
|
||||
$tunnels = $block->getAllTunnelStatuses();
|
||||
$registry = $block->listTunnelsFromRegistry();
|
||||
$formKey = $block->escapeHtml($block->getFormKey());
|
||||
$statusUrl = $block->getStatusUrl();
|
||||
$indexUrl = $block->getIndexUrl();
|
||||
$websiteOptions = $block->getWebsiteOptions();
|
||||
$groupsJson = $block->escapeHtmlAttr($block->getStoreGroupsByWebsiteJson());
|
||||
$portUi = $block->getInitialLocalPortUiState();
|
||||
$portPresets = $block->getLocalPortPresetValues();
|
||||
$defaultHolesail = $block->getDefaultHolesailSecure() ? '1' : '0';
|
||||
@@ -17,13 +13,7 @@ $nginxReloadHint = (string) __(
|
||||
);
|
||||
$defaultMagesailTab = \count($registry) === 0 ? 'add' : 'list';
|
||||
?>
|
||||
<div class="magesail-tunnel"
|
||||
id="magesail-root"
|
||||
data-status-url="<?= $block->escapeUrl($statusUrl) ?>"
|
||||
data-index-url="<?= $block->escapeUrl($indexUrl) ?>"
|
||||
data-form-key="<?= $formKey ?>"
|
||||
data-groups-by-website="<?= $groupsJson ?>"
|
||||
data-default-tab="<?= $block->escapeHtmlAttr($defaultMagesailTab) ?>">
|
||||
<div class="magesail-tunnel" id="magesail-root">
|
||||
<div class="page-title-wrapper">
|
||||
<h1 class="page-title"><?= $block->escapeHtml(__('Holesail Tunnel Management')) ?></h1>
|
||||
</div>
|
||||
@@ -268,393 +258,10 @@ $defaultMagesailTab = \count($registry) === 0 ? 'add' : 'list';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
require(['jquery', 'domReady!'], function ($) {
|
||||
var root = $('#magesail-root');
|
||||
var statusUrl = root.data('status-url');
|
||||
var indexUrl = root.data('index-url');
|
||||
var formKey = root.data('form-key');
|
||||
var groupsByWebsite = root.data('groups-by-website') || {};
|
||||
var pollTimers = {};
|
||||
var logPollTimers = {};
|
||||
var copiedLabel = <?= json_encode((string) __('Copied!')) ?>;
|
||||
|
||||
var $tabList = root.find('.magesail-tab-bar');
|
||||
var $tabs = $tabList.find('.magesail-tab');
|
||||
var $panels = root.find('.magesail-tab-panel');
|
||||
|
||||
function setHashForTab(name) {
|
||||
var frag = name === 'add' ? '#magesail-add' : '#magesail-list';
|
||||
if (window.history && window.history.replaceState) {
|
||||
var base = window.location.pathname + window.location.search;
|
||||
window.history.replaceState(null, '', base + frag);
|
||||
} else {
|
||||
window.location.hash = frag;
|
||||
}
|
||||
<script type="text/x-magento-init">
|
||||
{
|
||||
"#magesail-root": {
|
||||
"MageSail_Magesail/js/magesail-tunnel-ui": <?= /* @noEscape */ $block->getTunnelUiInitJson() ?>
|
||||
}
|
||||
|
||||
function setActiveTab(name) {
|
||||
if (name !== 'list' && name !== 'add') {
|
||||
return;
|
||||
}
|
||||
$tabs.each(function () {
|
||||
var $t = $(this);
|
||||
var isList = $t.attr('id') === 'magesail-tab-trigger-list';
|
||||
var active = (isList && name === 'list') || (!isList && name === 'add');
|
||||
$t.toggleClass('magesail-tab--active', active)
|
||||
.attr('aria-selected', active ? 'true' : 'false')
|
||||
.attr('tabindex', active ? '0' : '-1');
|
||||
});
|
||||
$panels.each(function () {
|
||||
var $p = $(this);
|
||||
var isListPanel = $p.attr('id') === 'magesail-tab-panel-list';
|
||||
var active = (isListPanel && name === 'list') || (!isListPanel && name === 'add');
|
||||
$p.toggleClass('magesail-tab-panel--active', active);
|
||||
if (active) {
|
||||
$p.removeAttr('hidden');
|
||||
} else {
|
||||
$p.attr('hidden', 'hidden');
|
||||
}
|
||||
});
|
||||
setHashForTab(name);
|
||||
}
|
||||
|
||||
function tabFromHash() {
|
||||
var h = (window.location.hash || '').toLowerCase();
|
||||
if (h === '#magesail-add' || h === '#add') {
|
||||
return 'add';
|
||||
}
|
||||
if (h === '#magesail-list' || h === '#list') {
|
||||
return 'list';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var initial = tabFromHash() || (root.data('default-tab') || 'list');
|
||||
setActiveTab(initial);
|
||||
|
||||
$tabs.on('click', function () {
|
||||
setActiveTab($(this).attr('id') === 'magesail-tab-trigger-list' ? 'list' : 'add');
|
||||
});
|
||||
|
||||
$tabs.on('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
$(this).trigger('click');
|
||||
return;
|
||||
}
|
||||
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var idx = $tabs.index(this);
|
||||
var dir = e.key === 'ArrowRight' ? 1 : -1;
|
||||
var $next = $tabs.eq((idx + dir + $tabs.length) % $tabs.length);
|
||||
setActiveTab($next.attr('id') === 'magesail-tab-trigger-list' ? 'list' : 'add');
|
||||
$next.focus();
|
||||
});
|
||||
|
||||
root.on('click', '.magesail-tab-goto-add', function () {
|
||||
setActiveTab('add');
|
||||
document.getElementById('magesail-tab-trigger-add').focus();
|
||||
});
|
||||
|
||||
var $busyOverlay = $('#magesail-busy-overlay');
|
||||
|
||||
function setBusy(on) {
|
||||
if (on) {
|
||||
root.attr('aria-busy', 'true');
|
||||
$busyOverlay.removeAttr('hidden');
|
||||
} else {
|
||||
root.removeAttr('aria-busy');
|
||||
$busyOverlay.attr('hidden', 'hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function reloadToListTab() {
|
||||
var base = window.location.pathname + window.location.search;
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState(null, '', base + '#magesail-list');
|
||||
} else {
|
||||
window.location.hash = '#magesail-list';
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function flash(msg, isError) {
|
||||
var el = $('#magesail-flash');
|
||||
el.removeClass('message-success success message-error error message-warning warning')
|
||||
.addClass(isError ? 'message-error error' : 'message-success success')
|
||||
.html('<span>' + $('<div/>').text(msg).html() + '</span>')
|
||||
.show();
|
||||
if (!isError) {
|
||||
setTimeout(function () { el.fadeOut(); }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function flashWarning(msg) {
|
||||
var el = $('#magesail-flash');
|
||||
el.removeClass('message-success success message-error error message-warning warning')
|
||||
.addClass('message-warning warning')
|
||||
.html('<span>' + $('<div/>').text(msg).html() + '</span>')
|
||||
.show();
|
||||
setTimeout(function () { el.fadeOut(); }, 20000);
|
||||
}
|
||||
|
||||
function refreshGroups() {
|
||||
var wid = $('#magesail-website').val();
|
||||
var $sg = $('#magesail-store-group');
|
||||
$sg.empty();
|
||||
if (!wid || !groupsByWebsite[wid]) {
|
||||
$sg.append($('<option/>').val('').text(<?= json_encode((string) __('— Select website first —')) ?>));
|
||||
$sg.prop('disabled', true);
|
||||
return;
|
||||
}
|
||||
$sg.prop('disabled', false);
|
||||
$sg.append($('<option/>').val('').text(<?= json_encode((string) __('— Select —')) ?>));
|
||||
(groupsByWebsite[wid] || []).forEach(function (g) {
|
||||
$sg.append($('<option/>').val(g.id).text(g.name));
|
||||
});
|
||||
}
|
||||
$('#magesail-website').on('change', refreshGroups);
|
||||
refreshGroups();
|
||||
|
||||
function toggleCustomPortField() {
|
||||
if ($('#magesail-local-port-preset').val() === 'custom') {
|
||||
$('#magesail-local-port-custom').show();
|
||||
} else {
|
||||
$('#magesail-local-port-custom').hide();
|
||||
}
|
||||
}
|
||||
$('#magesail-local-port-preset').on('change', toggleCustomPortField);
|
||||
toggleCustomPortField();
|
||||
|
||||
function getLocalPortForSubmit() {
|
||||
var preset = $('#magesail-local-port-preset').val();
|
||||
if (preset === 'custom') {
|
||||
return parseInt($('#magesail-local-port-custom').val(), 10);
|
||||
}
|
||||
return parseInt(preset, 10);
|
||||
}
|
||||
|
||||
function applyTunnelRow(tunnelId, data) {
|
||||
var row = $('tr[data-tunnel-id="' + tunnelId.replace(/"/g, '\\"') + '"]').first();
|
||||
if (!row.length) {
|
||||
reloadToListTab();
|
||||
return;
|
||||
}
|
||||
var statusCell = row.find('.magesail-status-cell');
|
||||
var keyCell = row.find('.magesail-key-cell');
|
||||
if (data.running && data.pid) {
|
||||
var pidMsg = <?= json_encode((string) __('Running (PID %1)', 'X')) ?>.replace('X', String(data.pid));
|
||||
statusCell.html('<span class="magesail-pid-msg">' + $('<div/>').text(pidMsg).html() + '</span>');
|
||||
}
|
||||
if (data.key && String(data.key).trim() !== '') {
|
||||
var k = String(data.key).trim();
|
||||
keyCell.html(
|
||||
'<code class="magesail-key">' + $('<div/>').text(k).html() + '</code>' +
|
||||
'<div class="admin__field-note"><?= $block->escapeJs(__('Client:')) ?> <code class="magesail-client-cmd">' + $('<div/>').text('holesail ' + k).html() + '</code></div>'
|
||||
);
|
||||
stopPoll(tunnelId);
|
||||
}
|
||||
}
|
||||
|
||||
function stopPoll(tunnelId) {
|
||||
if (pollTimers[tunnelId]) {
|
||||
clearInterval(pollTimers[tunnelId]);
|
||||
delete pollTimers[tunnelId];
|
||||
}
|
||||
}
|
||||
|
||||
function startPoll(tunnelId) {
|
||||
stopPoll(tunnelId);
|
||||
function tick() {
|
||||
$.ajax({
|
||||
url: statusUrl,
|
||||
type: 'GET',
|
||||
data: { tunnel_id: tunnelId },
|
||||
dataType: 'json',
|
||||
xhrFields: { withCredentials: true }
|
||||
}).done(function (data) {
|
||||
applyTunnelRow(tunnelId, data);
|
||||
});
|
||||
}
|
||||
tick();
|
||||
pollTimers[tunnelId] = setInterval(tick, 2000);
|
||||
}
|
||||
|
||||
function startLogPoll(tunnelId) {
|
||||
if (logPollTimers[tunnelId]) {
|
||||
clearInterval(logPollTimers[tunnelId]);
|
||||
}
|
||||
var logtailUrl = <?= json_encode($block->getLogtailUrl()) ?>;
|
||||
logPollTimers[tunnelId] = setInterval(function () {
|
||||
$.getJSON(logtailUrl, { tunnel_id: tunnelId }).done(function (res) {
|
||||
if (res.lines && res.lines.length) {
|
||||
$('tr[data-tunnel-id="' + tunnelId + '"]').next('.magesail-log-row').find('pre').text(res.lines.join('\n'));
|
||||
}
|
||||
});
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
$(document).on('click', '.magesail-btn-start-existing', function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'start', ajax: '1', tunnel_id: tid }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || 'Start failed', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Request failed';
|
||||
flash(m, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.magesail-btn-stop', function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'stop', ajax: '1', tunnel_id: tid }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || <?= json_encode((string) __('Stop failed')) ?>, true);
|
||||
}
|
||||
}).fail(function () {
|
||||
flash(<?= json_encode((string) __('Stop failed')) ?>, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.magesail-btn-remove', function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
if (!confirm(<?= json_encode((string) __('Remove this tunnel, delete its store view, and update the NGINX map?')) ?>)) {
|
||||
return;
|
||||
}
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'delete', ajax: '1', tunnel_id: tid }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || <?= json_encode((string) __('Remove failed')) ?>, true);
|
||||
}
|
||||
}).fail(function () {
|
||||
flash(<?= json_encode((string) __('Remove failed')) ?>, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#magesail-btn-add-start').on('click', function () {
|
||||
var baseUrl = ($('#magesail-tunnel-base-url').val() || '').trim();
|
||||
var wid = $('#magesail-website').val();
|
||||
var gid = $('#magesail-store-group').val();
|
||||
var port = getLocalPortForSubmit();
|
||||
if (!wid || !gid) {
|
||||
flash(<?= json_encode((string) __('Select website and store group.')) ?>, true);
|
||||
return;
|
||||
}
|
||||
if (!baseUrl) {
|
||||
flash(<?= json_encode((string) __('Enter the tunnel hostname.')) ?>, true);
|
||||
return;
|
||||
}
|
||||
if (!port || port < 1 || port > 65535) {
|
||||
flash(<?= json_encode((string) __('Enter a valid local port (1–65535).')) ?>, true);
|
||||
return;
|
||||
}
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: {
|
||||
form_key: formKey,
|
||||
action: 'start',
|
||||
ajax: '1',
|
||||
tunnel_label: ($('#magesail-tunnel-label').val() || '').trim(),
|
||||
website_id: wid,
|
||||
group_id: gid,
|
||||
local_port: port,
|
||||
tunnel_base_url: baseUrl,
|
||||
tunnel_secure_base_url: ($('#magesail-tunnel-secure-base-url').val() || '').trim(),
|
||||
tunnel_use_secure_urls: $('#magesail-tunnel-use-secure').is(':checked') ? '1' : '0',
|
||||
tunnel_holesail_secure: $('#magesail-tunnel-holesail-secure').is(':checked') ? '1' : '0'
|
||||
}
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
if (res.nginx_reload_required && res.nginx_reload_message) {
|
||||
flashWarning(res.nginx_reload_message);
|
||||
}
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || 'Failed', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Request failed';
|
||||
flash(m, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('tr[data-tunnel-id]').each(function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
var row = $(this);
|
||||
if (row.find('.magesail-generating').length) {
|
||||
startPoll(tid);
|
||||
}
|
||||
startLogPoll(tid);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* NGINX Global Map editor (parse, GUI blocks, raw, validate/reload).
|
||||
*/
|
||||
define(['jquery', 'domReady!'], function ($) {
|
||||
'use strict';
|
||||
|
||||
return function (config) {
|
||||
config = config || {};
|
||||
var t = config.translations || {};
|
||||
var mapUrl = config.mapUrl;
|
||||
var formKey = config.formKey;
|
||||
var mapConfigured = !!config.mapConfigured;
|
||||
var msgLoadingBlocks = t.msgLoadingBlocks || '';
|
||||
var msgParseFailed = t.msgParseFailed || '';
|
||||
var parsedData = null;
|
||||
var lastMapRaw = null;
|
||||
|
||||
function flash(msg, isError) {
|
||||
var el = $('#magesail-flash');
|
||||
el.removeClass('message-success success message-error error message-warning warning')
|
||||
.addClass(isError ? 'message-error error' : 'message-success success')
|
||||
.html('<span>' + $('<div/>').text(msg).html() + '</span>')
|
||||
.show();
|
||||
if (!isError) {
|
||||
setTimeout(function () { el.fadeOut(); }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function renderBlocks(blocks) {
|
||||
var html = '<div class="magesail-map-blocks-container">';
|
||||
blocks.forEach(function (block, blockIdx) {
|
||||
html += '<div class="admin__fieldset magesail-map-block" data-block-index="' + blockIdx + '">';
|
||||
html += '<div class="admin__fieldset-header">';
|
||||
html += '<strong>map $' + $('<div/>').text(block.source).html() + ' $' +
|
||||
$('<div/>').text(block.target).html() + ' {</strong>';
|
||||
html += '<button type="button" class="action-secondary magesail-add-entry" data-block="' +
|
||||
blockIdx + '">' + $('<div/>').text(t.addEntry || '').html() + '</button>';
|
||||
html += '</div>';
|
||||
html += '<table class="admin__table-primary magesail-map-entries">';
|
||||
html += '<thead><tr><th>' + $('<div/>').text(t.colHostname || '').html() + '</th><th>' +
|
||||
$('<div/>').text(t.colValue || '').html() + '</th><th>' +
|
||||
$('<div/>').text(t.colComment || '').html() + '</th><th>' +
|
||||
$('<div/>').text(t.colActions || '').html() + '</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
block.entries.forEach(function (entry, entryIdx) {
|
||||
html += '<tr data-entry-index="' + entryIdx + '">';
|
||||
html += '<td><input type="text" class="admin__control-text magesail-hostname" value="' +
|
||||
$('<div/>').text(entry.hostname).html() + '" /></td>';
|
||||
html += '<td><input type="text" class="admin__control-text magesail-value" value="' +
|
||||
$('<div/>').text(entry.value).html() + '" /></td>';
|
||||
html += '<td><input type="text" class="admin__control-text magesail-comment" value="' +
|
||||
$('<div/>').text(entry.comment || '').html() + '" /></td>';
|
||||
html += '<td><button type="button" class="action-delete magesail-delete-entry">' +
|
||||
$('<div/>').text(t.delete || '').html() + '</button></td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
html += '<div class="magesail-block-footer">}</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '<div class="magesail-actions" style="margin-top: 1rem;">';
|
||||
html += '<button type="button" class="action-primary" id="magesail-btn-save-blocks">';
|
||||
html += '<span>' + $('<div/>').text(t.saveBlocksValidate || '').html() + '</span>';
|
||||
html += '</button>';
|
||||
html += '<button type="button" class="action-secondary" id="magesail-btn-switch-raw">';
|
||||
html += '<span>' + $('<div/>').text(t.switchRaw || '').html() + '</span>';
|
||||
html += '</button>';
|
||||
html += '</div>';
|
||||
$('#magesail-map-blocks').html(html).show();
|
||||
$('#magesail-map-loading').hide();
|
||||
$('#magesail-map-raw-editor').hide();
|
||||
|
||||
$('.magesail-add-entry').on('click', function () {
|
||||
var blockIdx = $(this).data('block');
|
||||
var tbody = $(this).closest('.magesail-map-block').find('tbody');
|
||||
var row = '<tr data-entry-index="new">';
|
||||
row += '<td><input type="text" class="admin__control-text magesail-hostname" value="" /></td>';
|
||||
row += '<td><input type="text" class="admin__control-text magesail-value" value="" /></td>';
|
||||
row += '<td><input type="text" class="admin__control-text magesail-comment" value="" /></td>';
|
||||
row += '<td><button type="button" class="action-delete magesail-delete-entry">' +
|
||||
$('<div/>').text(t.delete || '').html() + '</button></td>';
|
||||
row += '</tr>';
|
||||
tbody.append(row);
|
||||
});
|
||||
|
||||
$('.magesail-delete-entry').on('click', function () {
|
||||
$(this).closest('tr').remove();
|
||||
});
|
||||
}
|
||||
|
||||
function updateDiagnostics() {
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'diagnostics', ajax: '1' }
|
||||
}).done(function (res) {
|
||||
if (res.success && res.diagnostics) {
|
||||
var d = res.diagnostics;
|
||||
var html = '<p><strong>' + $('<div/>').text(t.diagPath || '').html() + '</strong> ' +
|
||||
$('<div/>').text(d.path || t.diagNotConfigured || '').html() + '</p>';
|
||||
html += '<p><strong>' + $('<div/>').text(t.diagExists || '').html() + '</strong> ' +
|
||||
(d.exists ? (t.yes || '') : (t.no || '')) + '</p>';
|
||||
html += '<p><strong>' + $('<div/>').text(t.diagReadable || '').html() + '</strong> ' +
|
||||
(d.readable ? (t.yes || '') : (t.no || '')) + '</p>';
|
||||
html += '<p><strong>' + $('<div/>').text(t.diagWritable || '').html() + '</strong> ' +
|
||||
(d.writable ? (t.yes || '') : (t.no || '')) + '</p>';
|
||||
if (d.error) {
|
||||
html += '<p class="message message-error"><strong>' +
|
||||
$('<div/>').text(t.diagIssue || '').html() + '</strong> ' +
|
||||
$('<div/>').text(d.error).html() + '</p>';
|
||||
}
|
||||
$('#magesail-diagnostics-content').html(html);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showParseLoadingState() {
|
||||
$('#magesail-map-raw-editor').hide();
|
||||
$('#magesail-map-blocks').hide();
|
||||
$('#magesail-map-loading').removeClass('message-error error').addClass('message-info info');
|
||||
$('#magesail-map-loading span').text(msgLoadingBlocks);
|
||||
$('#magesail-map-loading').show();
|
||||
}
|
||||
|
||||
function showParseFailureBanner(detailMsg) {
|
||||
$('#magesail-map-loading').removeClass('message-info info').addClass('message-error error');
|
||||
$('#magesail-map-loading span').text(detailMsg || msgParseFailed);
|
||||
$('#magesail-map-loading').show();
|
||||
}
|
||||
|
||||
function parseAndLoadBlocks(silentSuccess) {
|
||||
showParseLoadingState();
|
||||
updateDiagnostics();
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'parse', ajax: '1' }
|
||||
}).done(function (res) {
|
||||
if (res.success && res.parsed) {
|
||||
parsedData = res.parsed;
|
||||
if (typeof res.raw === 'string') {
|
||||
lastMapRaw = res.raw;
|
||||
}
|
||||
renderBlocks(res.parsed.blocks);
|
||||
if (!silentSuccess) {
|
||||
flash(t.parseSuccess || '', false);
|
||||
}
|
||||
} else {
|
||||
showParseFailureBanner(res.message || '');
|
||||
flash(res.message || t.parseFailed || '', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
showParseFailureBanner(m);
|
||||
flash(m, true);
|
||||
});
|
||||
}
|
||||
|
||||
$('#magesail-btn-parse').on('click', function () {
|
||||
parseAndLoadBlocks(false);
|
||||
});
|
||||
|
||||
$(document).on('click', '#magesail-btn-save-blocks', function () {
|
||||
var blocks = [];
|
||||
$('.magesail-map-block').each(function () {
|
||||
var block = $(this);
|
||||
var headerText = block.find('.admin__fieldset-header strong').text();
|
||||
var matches = headerText.match(/\$(\w+)\s+\$(\w+)/);
|
||||
if (!matches || matches.length < 3) {
|
||||
flash(t.invalidHeader || '', true);
|
||||
return false;
|
||||
}
|
||||
var source = matches[1];
|
||||
var target = matches[2];
|
||||
var entries = [];
|
||||
block.find('tbody tr').each(function () {
|
||||
var hostname = $(this).find('.magesail-hostname').val().trim();
|
||||
var value = $(this).find('.magesail-value').val().trim();
|
||||
var comment = $(this).find('.magesail-comment').val().trim();
|
||||
if (hostname && value) {
|
||||
entries.push({
|
||||
hostname: hostname,
|
||||
value: value,
|
||||
comment: comment || null
|
||||
});
|
||||
}
|
||||
});
|
||||
blocks.push({
|
||||
source: source,
|
||||
target: target,
|
||||
entries: entries
|
||||
});
|
||||
});
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: {
|
||||
form_key: formKey,
|
||||
action: 'save_blocks',
|
||||
ajax: '1',
|
||||
blocks: JSON.stringify(blocks)
|
||||
}
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || t.blocksSaved || '', false);
|
||||
if (res.validation && !res.validation.valid) {
|
||||
flash((t.validationPrefix || '') + ' ' +
|
||||
(res.validation.error || t.nginxValidationFailed || ''), true);
|
||||
}
|
||||
} else {
|
||||
flash(res.message || t.saveBlocksFailed || '', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
flash(m, true);
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#magesail-btn-switch-raw', function () {
|
||||
$('#magesail-map-blocks').hide();
|
||||
$('#magesail-map-raw-editor').show();
|
||||
var $ta = $('#magesail-map-content');
|
||||
if ($ta.val() === '') {
|
||||
if (lastMapRaw !== null) {
|
||||
$ta.val(lastMapRaw);
|
||||
} else {
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'read', ajax: '1' }
|
||||
}).done(function (res) {
|
||||
if (res.success && typeof res.content === 'string') {
|
||||
lastMapRaw = res.content;
|
||||
$ta.val(res.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#magesail-btn-switch-gui').on('click', function () {
|
||||
parseAndLoadBlocks(false);
|
||||
});
|
||||
|
||||
if (mapConfigured) {
|
||||
parseAndLoadBlocks(true);
|
||||
}
|
||||
|
||||
$('#magesail-btn-save-raw').on('click', function () {
|
||||
var content = $('#magesail-map-content').val();
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'write', ajax: '1', content: content }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
if (typeof content === 'string') {
|
||||
lastMapRaw = content;
|
||||
}
|
||||
flash(res.message || t.rawSaved || '', false);
|
||||
if (res.validation && !res.validation.valid) {
|
||||
flash((t.validationPrefix || '') + ' ' +
|
||||
(res.validation.error || t.nginxValidationFailed || ''), true);
|
||||
}
|
||||
} else {
|
||||
flash(res.message || t.saveFailed || '', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
flash(m, true);
|
||||
});
|
||||
});
|
||||
|
||||
function ajaxAction(action, data, successMsg) {
|
||||
$.ajax({
|
||||
url: mapUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: $.extend({ form_key: formKey, action: action, ajax: '1' }, data || {})
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(successMsg || res.message || t.operationOk || '', false);
|
||||
if (res.validation && !res.validation.valid) {
|
||||
flash((t.validationPrefix || '') + ' ' +
|
||||
(res.validation.error || t.nginxValidationFailed || ''), true);
|
||||
}
|
||||
} else {
|
||||
flash(res.message || t.operationFailed || '', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : '';
|
||||
if (!m) {
|
||||
m = xhr.status ? ('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '')) : 'Request failed';
|
||||
if (xhr.responseText && xhr.responseText.length && xhr.responseText.charAt(0) !== '<') {
|
||||
m += ': ' + xhr.responseText.substring(0, 300);
|
||||
}
|
||||
}
|
||||
flash(m, true);
|
||||
});
|
||||
}
|
||||
|
||||
$('#magesail-btn-validate').on('click', function () {
|
||||
ajaxAction('validate', null, t.nginxValidated || '');
|
||||
});
|
||||
|
||||
$('#magesail-btn-reload').on('click', function () {
|
||||
ajaxAction('reload', null, t.nginxReloaded || '');
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* Holesail Tunnel admin UI (tabs, AJAX, polling).
|
||||
*/
|
||||
define(['jquery', 'domReady!'], function ($) {
|
||||
'use strict';
|
||||
|
||||
return function (config) {
|
||||
config = config || {};
|
||||
var t = config.translations || {};
|
||||
var root = $('#magesail-root');
|
||||
var statusUrl = config.statusUrl;
|
||||
var indexUrl = config.indexUrl;
|
||||
var formKey = config.formKey;
|
||||
var groupsByWebsite = config.groupsByWebsite || {};
|
||||
var pollTimers = {};
|
||||
var logPollTimers = {};
|
||||
|
||||
var $tabList = root.find('.magesail-tab-bar');
|
||||
var $tabs = $tabList.find('.magesail-tab');
|
||||
var $panels = root.find('.magesail-tab-panel');
|
||||
|
||||
function setHashForTab(name) {
|
||||
var frag = name === 'add' ? '#magesail-add' : '#magesail-list';
|
||||
if (window.history && window.history.replaceState) {
|
||||
var base = window.location.pathname + window.location.search;
|
||||
window.history.replaceState(null, '', base + frag);
|
||||
} else {
|
||||
window.location.hash = frag;
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveTab(name) {
|
||||
if (name !== 'list' && name !== 'add') {
|
||||
return;
|
||||
}
|
||||
$tabs.each(function () {
|
||||
var $tab = $(this);
|
||||
var isList = $tab.attr('id') === 'magesail-tab-trigger-list';
|
||||
var active = (isList && name === 'list') || (!isList && name === 'add');
|
||||
$tab.toggleClass('magesail-tab--active', active)
|
||||
.attr('aria-selected', active ? 'true' : 'false')
|
||||
.attr('tabindex', active ? '0' : '-1');
|
||||
});
|
||||
$panels.each(function () {
|
||||
var $p = $(this);
|
||||
var isListPanel = $p.attr('id') === 'magesail-tab-panel-list';
|
||||
var active = (isListPanel && name === 'list') || (!isListPanel && name === 'add');
|
||||
$p.toggleClass('magesail-tab-panel--active', active);
|
||||
if (active) {
|
||||
$p.removeAttr('hidden');
|
||||
} else {
|
||||
$p.attr('hidden', 'hidden');
|
||||
}
|
||||
});
|
||||
setHashForTab(name);
|
||||
}
|
||||
|
||||
function tabFromHash() {
|
||||
var h = (window.location.hash || '').toLowerCase();
|
||||
if (h === '#magesail-add' || h === '#add') {
|
||||
return 'add';
|
||||
}
|
||||
if (h === '#magesail-list' || h === '#list') {
|
||||
return 'list';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var initial = tabFromHash() || (config.defaultTab || 'list');
|
||||
setActiveTab(initial);
|
||||
|
||||
$tabs.on('click', function () {
|
||||
setActiveTab($(this).attr('id') === 'magesail-tab-trigger-list' ? 'list' : 'add');
|
||||
});
|
||||
|
||||
$tabs.on('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
$(this).trigger('click');
|
||||
return;
|
||||
}
|
||||
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var idx = $tabs.index(this);
|
||||
var dir = e.key === 'ArrowRight' ? 1 : -1;
|
||||
var $next = $tabs.eq((idx + dir + $tabs.length) % $tabs.length);
|
||||
setActiveTab($next.attr('id') === 'magesail-tab-trigger-list' ? 'list' : 'add');
|
||||
$next.focus();
|
||||
});
|
||||
|
||||
root.on('click', '.magesail-tab-goto-add', function () {
|
||||
setActiveTab('add');
|
||||
document.getElementById('magesail-tab-trigger-add').focus();
|
||||
});
|
||||
|
||||
var $busyOverlay = $('#magesail-busy-overlay');
|
||||
|
||||
function setBusy(on) {
|
||||
if (on) {
|
||||
root.attr('aria-busy', 'true');
|
||||
$busyOverlay.removeAttr('hidden');
|
||||
} else {
|
||||
root.removeAttr('aria-busy');
|
||||
$busyOverlay.attr('hidden', 'hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function reloadToListTab() {
|
||||
var base = window.location.pathname + window.location.search;
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState(null, '', base + '#magesail-list');
|
||||
} else {
|
||||
window.location.hash = '#magesail-list';
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function flash(msg, isError) {
|
||||
var el = $('#magesail-flash');
|
||||
el.removeClass('message-success success message-error error message-warning warning')
|
||||
.addClass(isError ? 'message-error error' : 'message-success success')
|
||||
.html('<span>' + $('<div/>').text(msg).html() + '</span>')
|
||||
.show();
|
||||
if (!isError) {
|
||||
setTimeout(function () { el.fadeOut(); }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function flashWarning(msg) {
|
||||
var el = $('#magesail-flash');
|
||||
el.removeClass('message-success success message-error error message-warning warning')
|
||||
.addClass('message-warning warning')
|
||||
.html('<span>' + $('<div/>').text(msg).html() + '</span>')
|
||||
.show();
|
||||
setTimeout(function () { el.fadeOut(); }, 20000);
|
||||
}
|
||||
|
||||
function refreshGroups() {
|
||||
var wid = $('#magesail-website').val();
|
||||
var $sg = $('#magesail-store-group');
|
||||
$sg.empty();
|
||||
if (!wid || !groupsByWebsite[wid]) {
|
||||
$sg.append($('<option/>').val('').text(t.selectWebsiteFirst || ''));
|
||||
$sg.prop('disabled', true);
|
||||
return;
|
||||
}
|
||||
$sg.prop('disabled', false);
|
||||
$sg.append($('<option/>').val('').text(t.selectDash || ''));
|
||||
(groupsByWebsite[wid] || []).forEach(function (g) {
|
||||
$sg.append($('<option/>').val(g.id).text(g.name));
|
||||
});
|
||||
}
|
||||
$('#magesail-website').on('change', refreshGroups);
|
||||
refreshGroups();
|
||||
|
||||
function toggleCustomPortField() {
|
||||
if ($('#magesail-local-port-preset').val() === 'custom') {
|
||||
$('#magesail-local-port-custom').show();
|
||||
} else {
|
||||
$('#magesail-local-port-custom').hide();
|
||||
}
|
||||
}
|
||||
$('#magesail-local-port-preset').on('change', toggleCustomPortField);
|
||||
toggleCustomPortField();
|
||||
|
||||
function getLocalPortForSubmit() {
|
||||
var preset = $('#magesail-local-port-preset').val();
|
||||
if (preset === 'custom') {
|
||||
return parseInt($('#magesail-local-port-custom').val(), 10);
|
||||
}
|
||||
return parseInt(preset, 10);
|
||||
}
|
||||
|
||||
function applyTunnelRow(tunnelId, data) {
|
||||
var row = $('tr[data-tunnel-id="' + tunnelId.replace(/"/g, '\\"') + '"]').first();
|
||||
if (!row.length) {
|
||||
reloadToListTab();
|
||||
return;
|
||||
}
|
||||
var statusCell = row.find('.magesail-status-cell');
|
||||
var keyCell = row.find('.magesail-key-cell');
|
||||
if (data.running && data.pid) {
|
||||
var tpl = t.runningPidTpl || '';
|
||||
var pidMsg = tpl.replace('X', String(data.pid));
|
||||
statusCell.html('<span class="magesail-pid-msg">' + $('<div/>').text(pidMsg).html() + '</span>');
|
||||
}
|
||||
if (data.key && String(data.key).trim() !== '') {
|
||||
var k = String(data.key).trim();
|
||||
var clientLabel = t.clientLabel || 'Client:';
|
||||
keyCell.html(
|
||||
'<code class="magesail-key">' + $('<div/>').text(k).html() + '</code>' +
|
||||
'<div class="admin__field-note">' + $('<div/>').text(clientLabel).html() +
|
||||
' <code class="magesail-client-cmd">' + $('<div/>').text('holesail ' + k).html() + '</code></div>'
|
||||
);
|
||||
stopPoll(tunnelId);
|
||||
}
|
||||
}
|
||||
|
||||
function stopPoll(tunnelId) {
|
||||
if (pollTimers[tunnelId]) {
|
||||
clearInterval(pollTimers[tunnelId]);
|
||||
delete pollTimers[tunnelId];
|
||||
}
|
||||
}
|
||||
|
||||
function startPoll(tunnelId) {
|
||||
stopPoll(tunnelId);
|
||||
function tick() {
|
||||
$.ajax({
|
||||
url: statusUrl,
|
||||
type: 'GET',
|
||||
data: { tunnel_id: tunnelId },
|
||||
dataType: 'json',
|
||||
xhrFields: { withCredentials: true }
|
||||
}).done(function (data) {
|
||||
applyTunnelRow(tunnelId, data);
|
||||
});
|
||||
}
|
||||
tick();
|
||||
pollTimers[tunnelId] = setInterval(tick, 2000);
|
||||
}
|
||||
|
||||
function startLogPoll(tunnelId) {
|
||||
if (logPollTimers[tunnelId]) {
|
||||
clearInterval(logPollTimers[tunnelId]);
|
||||
}
|
||||
var logtailUrl = config.logtailUrl;
|
||||
logPollTimers[tunnelId] = setInterval(function () {
|
||||
$.getJSON(logtailUrl, { tunnel_id: tunnelId }).done(function (res) {
|
||||
if (res.lines && res.lines.length) {
|
||||
$('tr[data-tunnel-id="' + tunnelId + '"]').next('.magesail-log-row').find('pre')
|
||||
.text(res.lines.join('\n'));
|
||||
}
|
||||
});
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
$(document).on('click', '.magesail-btn-start-existing', function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'start', ajax: '1', tunnel_id: tid }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || 'Start failed', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Request failed';
|
||||
flash(m, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.magesail-btn-stop', function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'stop', ajax: '1', tunnel_id: tid }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || t.stopFailed, true);
|
||||
}
|
||||
}).fail(function () {
|
||||
flash(t.stopFailed, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.magesail-btn-remove', function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
if (!confirm(t.removeConfirm)) {
|
||||
return;
|
||||
}
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: { form_key: formKey, action: 'delete', ajax: '1', tunnel_id: tid }
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || t.removeFailed, true);
|
||||
}
|
||||
}).fail(function () {
|
||||
flash(t.removeFailed, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#magesail-btn-add-start').on('click', function () {
|
||||
var baseUrl = ($('#magesail-tunnel-base-url').val() || '').trim();
|
||||
var wid = $('#magesail-website').val();
|
||||
var gid = $('#magesail-store-group').val();
|
||||
var port = getLocalPortForSubmit();
|
||||
if (!wid || !gid) {
|
||||
flash(t.selectWebsiteGroup, true);
|
||||
return;
|
||||
}
|
||||
if (!baseUrl) {
|
||||
flash(t.enterHostname, true);
|
||||
return;
|
||||
}
|
||||
if (!port || port < 1 || port > 65535) {
|
||||
flash(t.invalidPort, true);
|
||||
return;
|
||||
}
|
||||
var btn = $(this).prop('disabled', true);
|
||||
var willReload = false;
|
||||
setBusy(true);
|
||||
$.ajax({
|
||||
url: indexUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
data: {
|
||||
form_key: formKey,
|
||||
action: 'start',
|
||||
ajax: '1',
|
||||
tunnel_label: ($('#magesail-tunnel-label').val() || '').trim(),
|
||||
website_id: wid,
|
||||
group_id: gid,
|
||||
local_port: port,
|
||||
tunnel_base_url: baseUrl,
|
||||
tunnel_secure_base_url: ($('#magesail-tunnel-secure-base-url').val() || '').trim(),
|
||||
tunnel_use_secure_urls: $('#magesail-tunnel-use-secure').is(':checked') ? '1' : '0',
|
||||
tunnel_holesail_secure: $('#magesail-tunnel-holesail-secure').is(':checked') ? '1' : '0'
|
||||
}
|
||||
}).done(function (res) {
|
||||
if (res.success) {
|
||||
flash(res.message || '', false);
|
||||
if (res.nginx_reload_required && res.nginx_reload_message) {
|
||||
flashWarning(res.nginx_reload_message);
|
||||
}
|
||||
willReload = true;
|
||||
reloadToListTab();
|
||||
} else {
|
||||
flash(res.message || 'Failed', true);
|
||||
}
|
||||
}).fail(function (xhr) {
|
||||
var m = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Request failed';
|
||||
flash(m, true);
|
||||
}).always(function () {
|
||||
if (!willReload) {
|
||||
setBusy(false);
|
||||
btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('tr[data-tunnel-id]').each(function () {
|
||||
var tid = $(this).data('tunnel-id');
|
||||
var row = $(this);
|
||||
if (row.find('.magesail-generating').length) {
|
||||
startPoll(tid);
|
||||
}
|
||||
startLogPoll(tid);
|
||||
});
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user