Files
MageSail/Magesail/Block/Adminhtml/Tunnel.php
T
2026-03-21 03:01:08 -05:00

255 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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_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();
}
/**
* 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();
$default = 443;
foreach ($presets as $p) {
if ($p === $default) {
return ['preset' => (string) $p, 'custom_port' => $default];
}
}
return ['preset' => 'custom', 'custom_port' => $default];
}
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;
}
/**
* Config for RequireJS magesail-tunnel-ui (URLs, form key, i18n, store groups).
*
* @return array<string, mixed>
*/
public function getTunnelUiInitConfig(): array
{
$groupsJson = $this->getStoreGroupsByWebsiteJson();
$groups = json_decode($groupsJson, true);
return [
'statusUrl' => $this->getStatusUrl(),
'indexUrl' => $this->getIndexUrl(),
'logtailUrl' => $this->getLogtailUrl(),
'formKey' => $this->getFormKey(),
'groupsByWebsite' => \is_array($groups) ? $groups : [],
'defaultTab' => \count($this->listTunnelsFromRegistry()) === 0 ? 'add' : 'list',
'translations' => [
'copied' => (string) __('Copied!'),
'selectWebsiteFirst' => (string) __('— Select website first —'),
'selectDash' => (string) __('— Select —'),
'stopFailed' => (string) __('Stop failed'),
'removeFailed' => (string) __('Remove failed'),
'removeConfirm' => (string) __(
'Remove this tunnel, delete its store view, and update the NGINX map?'
),
'selectWebsiteGroup' => (string) __('Select website and store group.'),
'enterHostname' => (string) __('Enter the tunnel hostname.'),
'invalidPort' => (string) __('Enter a valid local port (165535).'),
'runningPidTpl' => (string) __('Running (PID %1)', 'X'),
'clientLabel' => (string) __('Client:'),
],
];
}
public function getTunnelUiInitJson(): string
{
return $this->json->serialize($this->getTunnelUiInitConfig());
}
}