255 lines
7.8 KiB
PHP
255 lines
7.8 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_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 (1–65535).'),
|
||
'runningPidTpl' => (string) __('Running (PID %1)', 'X'),
|
||
'clientLabel' => (string) __('Client:'),
|
||
],
|
||
];
|
||
}
|
||
|
||
public function getTunnelUiInitJson(): string
|
||
{
|
||
return $this->json->serialize($this->getTunnelUiInitConfig());
|
||
}
|
||
}
|