- Add Tunnels list / Add a tunnel tabs with hash navigation - Show busy overlay during start/stop/remove/create AJAX - Reload with #magesail-list after successful actions - Only reload stop/remove on success; fix error handling - Remove redundant Stores configuration link; drop getConfigUrl()
221 lines
6.3 KiB
PHP
221 lines
6.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace MageSail\Magesail\Block\Adminhtml;
|
|
|
|
use Magento\Backend\Block\Template;
|
|
use Magento\Backend\Block\Template\Context;
|
|
use Magento\Framework\App\Config\ScopeConfigInterface;
|
|
use Magento\Framework\Serialize\Serializer\Json;
|
|
use Magento\Store\Model\StoreManagerInterface;
|
|
use MageSail\Magesail\Model\TunnelRegistry;
|
|
use MageSail\Magesail\Model\TunnelStatus;
|
|
use MageSail\Magesail\Model\TunnelStoreProvisioner;
|
|
|
|
class Tunnel extends Template
|
|
{
|
|
private const LOG_TAIL_BYTES = 8192;
|
|
private const LOG_TAIL_LINES = 40;
|
|
|
|
private const XML_PORT = 'magesail/settings/port';
|
|
private const XML_MODE = 'magesail/settings/mode';
|
|
|
|
public function __construct(
|
|
Context $context,
|
|
private readonly TunnelStatus $tunnelStatus,
|
|
private readonly TunnelStoreProvisioner $tunnelStoreProvisioner,
|
|
private readonly StoreManagerInterface $storeManager,
|
|
private readonly ScopeConfigInterface $scopeConfig,
|
|
private readonly TunnelRegistry $tunnelRegistry,
|
|
private readonly Json $json,
|
|
array $data = []
|
|
) {
|
|
parent::__construct($context, $data);
|
|
}
|
|
|
|
/**
|
|
* @return list<array{tunnel_id: string, label: string, store_code: string, local_port: int, running: bool, pid: int|null, key: string|null}>
|
|
*/
|
|
public function getAllTunnelStatuses(): array
|
|
{
|
|
return $this->tunnelStatus->getAllTunnelStatuses();
|
|
}
|
|
|
|
/**
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function listTunnelsFromRegistry(): array
|
|
{
|
|
return $this->tunnelRegistry->listTunnels();
|
|
}
|
|
|
|
public function hasAnyNginxPendingReload(): bool
|
|
{
|
|
return $this->tunnelRegistry->anyTunnelHasNginxPendingReload();
|
|
}
|
|
|
|
public function getDefaultLocalPort(): int
|
|
{
|
|
$port = filter_var($this->scopeConfig->getValue(self::XML_PORT) ?: 80, FILTER_VALIDATE_INT);
|
|
return ($port && $port >= 1 && $port <= 65535) ? $port : 80;
|
|
}
|
|
|
|
/**
|
|
* Preset ports shown first in Admin (443 is typical for Holesail + HTTPS upstream).
|
|
*
|
|
* @return list<int>
|
|
*/
|
|
public function getLocalPortPresetValues(): array
|
|
{
|
|
return [443, 80, 8080];
|
|
}
|
|
|
|
/**
|
|
* Initial Add-tunnel UI: preset key '443'|'80'|'8080'|'custom' and optional custom port when not a preset.
|
|
*
|
|
* @return array{preset: string, custom_port: int}
|
|
*/
|
|
public function getInitialLocalPortUiState(): array
|
|
{
|
|
$presets = $this->getLocalPortPresetValues();
|
|
$cfg = $this->getDefaultLocalPort();
|
|
foreach ($presets as $p) {
|
|
if ($p === $cfg) {
|
|
return ['preset' => (string) $p, 'custom_port' => $cfg];
|
|
}
|
|
}
|
|
return ['preset' => 'custom', 'custom_port' => $cfg];
|
|
}
|
|
|
|
public function getDefaultHolesailSecure(): bool
|
|
{
|
|
return (bool) $this->scopeConfig->getValue(self::XML_MODE);
|
|
}
|
|
|
|
/**
|
|
* @return list<array{value: string, label: string}>
|
|
*/
|
|
public function getWebsiteOptions(): array
|
|
{
|
|
$options = [];
|
|
foreach ($this->storeManager->getWebsites() as $website) {
|
|
$options[] = [
|
|
'value' => (string) $website->getId(),
|
|
'label' => $website->getName() . ' (' . $website->getCode() . ')',
|
|
];
|
|
}
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* JSON map: websiteId string => list of {id, name}
|
|
*/
|
|
public function getStoreGroupsByWebsiteJson(): string
|
|
{
|
|
$map = [];
|
|
foreach ($this->storeManager->getWebsites() as $website) {
|
|
$wid = (string) $website->getId();
|
|
$map[$wid] = [];
|
|
foreach ($website->getGroups() as $group) {
|
|
$map[$wid][] = [
|
|
'id' => (string) $group->getId(),
|
|
'name' => $group->getName() . ' (' . $group->getCode() . ')',
|
|
];
|
|
}
|
|
}
|
|
return $this->json->serialize($map);
|
|
}
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
public function getTunnelStoreState(): ?array
|
|
{
|
|
return $this->tunnelStoreProvisioner->getPersistedState();
|
|
}
|
|
|
|
public function getTunnelStoreCodeConstant(): string
|
|
{
|
|
return TunnelStoreProvisioner::STORE_CODE;
|
|
}
|
|
|
|
/**
|
|
* Strip scheme for display so Admin shows hostname-only; persisted URLs stay full in state.
|
|
*/
|
|
public function getTunnelHostDisplay(?string $baseUrl): string
|
|
{
|
|
if ($baseUrl === null || $baseUrl === '') {
|
|
return '';
|
|
}
|
|
$parts = parse_url($baseUrl);
|
|
if ($parts === false || empty($parts['host'])) {
|
|
return $baseUrl;
|
|
}
|
|
$out = $parts['host'];
|
|
if (!empty($parts['port'])) {
|
|
$out .= ':' . $parts['port'];
|
|
}
|
|
if (!empty($parts['path']) && $parts['path'] !== '/' && $parts['path'] !== '') {
|
|
$out .= rtrim($parts['path'], '/');
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function getStatusUrl(): string
|
|
{
|
|
return $this->getUrl('*/*/status');
|
|
}
|
|
|
|
public function getIndexUrl(): string
|
|
{
|
|
return $this->getUrl('*/*/index');
|
|
}
|
|
|
|
public function getLogtailUrl(): string
|
|
{
|
|
return $this->getUrl('*/*/logtail');
|
|
}
|
|
|
|
public function getFormKey(): string
|
|
{
|
|
return $this->formKey->getFormKey();
|
|
}
|
|
|
|
public function getLogFilePathForTunnel(string $tunnelId): string
|
|
{
|
|
return $this->tunnelStatus->getLogFilePath($tunnelId);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getRecentLogLinesForTunnel(string $tunnelId): array
|
|
{
|
|
$path = $this->tunnelStatus->getLogFilePath($tunnelId);
|
|
if (!\is_readable($path)) {
|
|
return [];
|
|
}
|
|
$size = filesize($path);
|
|
if ($size === false || $size === 0) {
|
|
return [];
|
|
}
|
|
$fp = fopen($path, 'rb');
|
|
if ($fp === false) {
|
|
return [];
|
|
}
|
|
$start = max(0, $size - self::LOG_TAIL_BYTES);
|
|
fseek($fp, $start);
|
|
if ($start > 0) {
|
|
fgets($fp);
|
|
}
|
|
$chunk = stream_get_contents($fp) ?: '';
|
|
fclose($fp);
|
|
$lines = preg_split('/\R/', $chunk) ?: [];
|
|
$lines = array_values(array_filter($lines, static fn ($l) => $l !== ''));
|
|
if (\count($lines) > self::LOG_TAIL_LINES) {
|
|
$lines = \array_slice($lines, -self::LOG_TAIL_LINES);
|
|
}
|
|
return $lines;
|
|
}
|
|
}
|