Testing/CI/ACL

This commit is contained in:
2026-03-21 03:01:08 -05:00
parent d1316c8fd6
commit dcebbceb88
60 changed files with 3141 additions and 1479 deletions
@@ -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 (165535).')) ?>, 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);
});
};
});