fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
222
src/Framework/Console/Components/Parsers/KeyboardEventParser.php
Normal file
222
src/Framework/Console/Components/Parsers/KeyboardEventParser.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Console\Components\Parsers;
|
||||
|
||||
use App\Framework\Console\Components\EventBuffer;
|
||||
use App\Framework\Console\Components\KeyEvent;
|
||||
|
||||
/**
|
||||
* Isolated keyboard event parser with comprehensive sequence support.
|
||||
* Handles all keyboard escape sequences and regular keys.
|
||||
*/
|
||||
final readonly class KeyboardEventParser
|
||||
{
|
||||
private const int TIMEOUT_MS = 10;
|
||||
|
||||
/**
|
||||
* Comprehensive key mapping for escape sequences
|
||||
*/
|
||||
private const array KEY_MAP = [
|
||||
// Arrow keys
|
||||
"\033[A" => 'ArrowUp',
|
||||
"\033[B" => 'ArrowDown',
|
||||
"\033[C" => 'ArrowRight',
|
||||
"\033[D" => 'ArrowLeft',
|
||||
|
||||
// Navigation
|
||||
"\033[H" => 'Home',
|
||||
"\033[F" => 'End',
|
||||
"\033[1~" => 'Home',
|
||||
"\033[4~" => 'End',
|
||||
|
||||
// Page navigation
|
||||
"\033[5~" => 'PageUp',
|
||||
"\033[6~" => 'PageDown',
|
||||
|
||||
// Function keys
|
||||
"\033OP" => 'F1',
|
||||
"\033OQ" => 'F2',
|
||||
"\033OR" => 'F3',
|
||||
"\033OS" => 'F4',
|
||||
"\033[15~" => 'F5',
|
||||
"\033[17~" => 'F6',
|
||||
"\033[18~" => 'F7',
|
||||
"\033[19~" => 'F8',
|
||||
"\033[20~" => 'F9',
|
||||
"\033[21~" => 'F10',
|
||||
"\033[23~" => 'F11',
|
||||
"\033[24~" => 'F12',
|
||||
|
||||
// Special keys
|
||||
"\033[3~" => 'Delete',
|
||||
"\033[2~" => 'Insert',
|
||||
"\033[Z" => 'ShiftTab',
|
||||
|
||||
// Control sequences
|
||||
"\033[1;5A" => 'Ctrl+ArrowUp',
|
||||
"\033[1;5B" => 'Ctrl+ArrowDown',
|
||||
"\033[1;5C" => 'Ctrl+ArrowRight',
|
||||
"\033[1;5D" => 'Ctrl+ArrowLeft',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private EventBuffer $eventBuffer
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keyboard event from escape sequence
|
||||
*/
|
||||
public function parse(string $sequence): KeyEvent
|
||||
{
|
||||
// Check for Enter key
|
||||
if ($sequence === "\n" || $sequence === "\r") {
|
||||
return new KeyEvent(key: 'Enter', code: "\n");
|
||||
}
|
||||
|
||||
// Check for Escape
|
||||
if ($sequence === "\033") {
|
||||
return new KeyEvent(key: 'Escape', code: "\033");
|
||||
}
|
||||
|
||||
// Try to read more characters for multi-character sequences
|
||||
$fullSequence = $this->readFullSequence($sequence);
|
||||
|
||||
// Check if we have a known mapping
|
||||
if (isset(self::KEY_MAP[$fullSequence])) {
|
||||
return new KeyEvent(key: self::KEY_MAP[$fullSequence], code: $fullSequence);
|
||||
}
|
||||
|
||||
// Check for modifier sequences (Ctrl+Key, Alt+Key, etc.)
|
||||
$modifierEvent = $this->parseModifierSequence($fullSequence);
|
||||
if ($modifierEvent !== null) {
|
||||
return $modifierEvent;
|
||||
}
|
||||
|
||||
// Unknown sequence, return as-is
|
||||
return new KeyEvent(key: $fullSequence, code: $fullSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read full escape sequence (handles multi-byte sequences)
|
||||
*/
|
||||
private function readFullSequence(string $initialSequence): string
|
||||
{
|
||||
$sequence = $initialSequence;
|
||||
$timeout = self::TIMEOUT_MS;
|
||||
$startTime = microtime(true) * 1000;
|
||||
|
||||
// Read additional characters if needed
|
||||
while (true) {
|
||||
$char = fgetc(STDIN);
|
||||
if ($char === false) {
|
||||
$elapsed = (microtime(true) * 1000) - $startTime;
|
||||
if ($elapsed > $timeout) {
|
||||
break;
|
||||
}
|
||||
usleep(1000); // 1ms
|
||||
continue;
|
||||
}
|
||||
|
||||
$sequence .= $char;
|
||||
|
||||
// Check if sequence is complete
|
||||
if ($this->isSequenceComplete($sequence)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Safety limit
|
||||
if (strlen($sequence) > 20) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if escape sequence is complete
|
||||
*/
|
||||
private function isSequenceComplete(string $sequence): bool
|
||||
{
|
||||
// Simple sequences end with a letter
|
||||
if (strlen($sequence) >= 3 && preg_match('/^\033\[[A-Za-z]$/', $sequence)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function keys end with ~
|
||||
if (preg_match('/^\033\[\d+~$/', $sequence)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Modifier sequences end with letter after numbers
|
||||
if (preg_match('/^\033\[\d+;\d+[A-Za-z]$/', $sequence)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse modifier sequences (Ctrl+, Alt+, etc.)
|
||||
*/
|
||||
private function parseModifierSequence(string $sequence): ?KeyEvent
|
||||
{
|
||||
// Pattern: \e[number;number;key
|
||||
if (!preg_match('/^\033\[(\d+);(\d+)([A-Za-z])$/', $sequence, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$modifierCode = (int) $matches[1];
|
||||
$keyCode = (int) $matches[2];
|
||||
$key = $matches[3];
|
||||
|
||||
$ctrl = false;
|
||||
$shift = false;
|
||||
$alt = false;
|
||||
$meta = false;
|
||||
|
||||
// Decode modifiers (standard xterm codes)
|
||||
if ($modifierCode === 5) {
|
||||
$ctrl = true;
|
||||
} elseif ($modifierCode === 3) {
|
||||
$alt = true;
|
||||
} elseif ($modifierCode === 2) {
|
||||
$shift = true;
|
||||
}
|
||||
|
||||
return new KeyEvent(
|
||||
key: $key,
|
||||
code: $sequence,
|
||||
shift: $shift,
|
||||
ctrl: $ctrl,
|
||||
alt: $alt,
|
||||
meta: $meta
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse regular character (non-escape sequence)
|
||||
*/
|
||||
public function parseRegularChar(string $char): KeyEvent
|
||||
{
|
||||
// Check for Ctrl+C (ASCII 3)
|
||||
if ($char === "\003") {
|
||||
return new KeyEvent(key: 'C', ctrl: true, code: "\003");
|
||||
}
|
||||
|
||||
// Check for other control characters
|
||||
$ord = ord($char);
|
||||
if ($ord < 32 && $ord !== 9 && $ord !== 10 && $ord !== 13) {
|
||||
// Control character
|
||||
$ctrlKey = chr($ord + 64);
|
||||
return new KeyEvent(key: $ctrlKey, ctrl: true, code: $char);
|
||||
}
|
||||
|
||||
// Regular character
|
||||
return new KeyEvent(key: $char, code: $char);
|
||||
}
|
||||
}
|
||||
|
||||
119
src/Framework/Console/Components/Parsers/MouseEventParser.php
Normal file
119
src/Framework/Console/Components/Parsers/MouseEventParser.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Console\Components\Parsers;
|
||||
|
||||
use App\Framework\Console\Components\EventBuffer;
|
||||
use App\Framework\Console\Components\MouseEvent;
|
||||
|
||||
/**
|
||||
* Isolated mouse event parser.
|
||||
* Handles SGR mouse events (\e[<b;x;yM or \e[<b;x;ym)
|
||||
*/
|
||||
final readonly class MouseEventParser
|
||||
{
|
||||
private const int MAX_BUFFER_SIZE = 20;
|
||||
private const int TIMEOUT_MS = 10;
|
||||
private const int MAX_X = 1000;
|
||||
private const int MAX_Y = 1000;
|
||||
|
||||
public function __construct(
|
||||
private EventBuffer $eventBuffer
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse mouse event from sequence prefix \e[<
|
||||
*/
|
||||
public function parse(string $prefix): ?MouseEvent
|
||||
{
|
||||
$buffer = '';
|
||||
$timeout = self::TIMEOUT_MS;
|
||||
$startTime = microtime(true) * 1000;
|
||||
|
||||
// Read until we get 'M' or 'm'
|
||||
while (true) {
|
||||
$char = fgetc(STDIN);
|
||||
if ($char === false) {
|
||||
// Check timeout
|
||||
$elapsed = (microtime(true) * 1000) - $startTime;
|
||||
if ($elapsed > $timeout) {
|
||||
// Timeout - store partial sequence for retry
|
||||
if (strlen($prefix . $buffer) > 0) {
|
||||
$this->eventBuffer->storePartialSequence($prefix . $buffer);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
usleep(1000); // 1ms
|
||||
continue;
|
||||
}
|
||||
|
||||
$buffer .= $char;
|
||||
|
||||
// Mouse event ends with 'M' (press) or 'm' (release)
|
||||
if ($char === 'M' || $char === 'm') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Safety: limit buffer size
|
||||
if (strlen($buffer) > self::MAX_BUFFER_SIZE) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse format: b;x;y where b is button code, x and y are coordinates
|
||||
$data = substr($buffer, 0, -1);
|
||||
$parts = explode(';', $data);
|
||||
|
||||
if (count($parts) < 3) {
|
||||
// Invalid mouse event format
|
||||
return null;
|
||||
}
|
||||
|
||||
$buttonCode = (int) $parts[0];
|
||||
$x = (int) $parts[1];
|
||||
$y = (int) $parts[2];
|
||||
|
||||
// Validate coordinates
|
||||
if ($x < 1 || $y < 1 || $x > self::MAX_X || $y > self::MAX_Y) {
|
||||
// Invalid coordinates, likely corrupted
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decode button and modifiers
|
||||
$button = $buttonCode & 0x03;
|
||||
$shift = ($buttonCode & 0x04) !== 0;
|
||||
$alt = ($buttonCode & 0x08) !== 0;
|
||||
$ctrl = ($buttonCode & 0x10) !== 0;
|
||||
|
||||
// Handle scroll events (button codes 64 and 65)
|
||||
if ($buttonCode >= 64 && $buttonCode <= 65) {
|
||||
$button = $buttonCode;
|
||||
} elseif (($buttonCode & 0x20) !== 0) {
|
||||
// Mouse move (button code 32 or bit 5 set)
|
||||
$button = $buttonCode;
|
||||
}
|
||||
|
||||
$pressed = $buffer[-1] === 'M';
|
||||
|
||||
return new MouseEvent(
|
||||
x: $x,
|
||||
y: $y,
|
||||
button: $button,
|
||||
pressed: $pressed,
|
||||
shift: $shift,
|
||||
ctrl: $ctrl,
|
||||
alt: $alt
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sequence is a mouse event prefix
|
||||
*/
|
||||
public function isMouseEventPrefix(string $sequence): bool
|
||||
{
|
||||
return str_starts_with($sequence, "\033[<");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user