TmuxLayoutBuilder::buildStrippedLayout()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 23
rs 9.9
cc 2
nc 2
nop 0
1
<?php
2
3
namespace App\Services\Tmux;
4
5
use App\Models\Settings;
6
7
/**
8
 * Service for building tmux window layouts based on sequential mode
9
 */
10
class TmuxLayoutBuilder
11
{
12
    protected TmuxSessionManager $sessionManager;
13
14
    protected TmuxPaneManager $paneManager;
15
16
    protected string $sessionName;
17
18
    public function __construct(TmuxSessionManager $sessionManager)
19
    {
20
        $this->sessionManager = $sessionManager;
21
        $this->sessionName = $sessionManager->getSessionName();
22
        $this->paneManager = new TmuxPaneManager($this->sessionName);
23
    }
24
25
    /**
26
     * Build the appropriate layout based on sequential mode
27
     */
28
    public function buildLayout(int $sequentialMode): bool
29
    {
30
        return match ($sequentialMode) {
31
            1 => $this->buildBasicLayout(),
32
            2 => $this->buildStrippedLayout(),
33
            default => $this->buildFullLayout(),
34
        };
35
    }
36
37
    /**
38
     * Build full non-sequential layout (mode 0)
39
     */
40
    protected function buildFullLayout(): bool
41
    {
42
        // Window 0: Monitor + Binaries + Backfill + Releases
43
        if (! $this->sessionManager->createSession('Monitor')) {
44
            return false;
45
        }
46
47
        // Select pane 0.0, then split for binaries (right side, 67%)
48
        $this->paneManager->selectPane('0.0');
49
        $this->paneManager->splitHorizontal('0', '67%', 'update_binaries');
50
51
        // Select pane 0.1 (binaries), then split for backfill (bottom, 67%)
52
        $this->paneManager->selectPane('0.1');
53
        $this->paneManager->splitVertical('0', '67%', 'backfill');
54
55
        // Split again for releases (bottom, 50%)
56
        $this->paneManager->splitVertical('0', '50%', 'update_releases');
57
58
        // Window 1: Fix names + Remove crap
59
        $this->paneManager->createWindow(1, 'utils');
60
        $this->paneManager->setPaneTitle('1.0', 'fixReleaseNames');
61
        $this->paneManager->selectPane('1.0');
62
        $this->paneManager->splitHorizontal('1', '50%', 'removeCrapReleases');
63
64
        // Window 2: Postprocessing (Left: Additional + TV + Amazon, Right: Movies + XXX)
65
        $this->paneManager->createWindow(2, 'post');
66
        $this->paneManager->setPaneTitle('2.0', 'postprocessing_additional');
67
68
        // Split horizontally to create left and right halves (don't set title yet)
69
        $this->paneManager->splitHorizontal('2', '50%', '');
70
71
        // Left side (2.0): split vertically for TV and Amazon
72
        $this->paneManager->selectPane('2.0');
73
        $this->paneManager->splitVertical('2', '67%', 'postprocessing_tv');
74
        $this->paneManager->splitVertical('2', '50%', 'postprocessing_amazon');
75
76
        // Right side: After left splits, right side is now 2.3
77
        $this->paneManager->selectPane('2.3');
78
        $this->paneManager->setPaneTitle('2.3', 'postprocessing_movies');
79
        $this->paneManager->splitVertical('2', '50%', 'postprocessing_xxx');
80
81
        // Window 3: IRC Scraper
82
        $this->createIRCScraperWindow();
83
84
        // Additional windows (optional monitoring tools)
85
        $this->createOptionalWindows();
86
87
        return true;
88
    }
89
90
    /**
91
     * Build basic sequential layout (mode 1)
92
     */
93
    protected function buildBasicLayout(): bool
94
    {
95
        // Window 0: Monitor + Releases
96
        if (! $this->sessionManager->createSession('Monitor')) {
97
            return false;
98
        }
99
100
        // Select pane 0.0, then split for releases
101
        $this->paneManager->selectPane('0.0');
102
        $this->paneManager->splitHorizontal('0', '67%', 'update_releases');
103
104
        // Window 1: Utils (Fix names + Remove crap)
105
        $this->paneManager->createWindow(1, 'utils');
106
        $this->paneManager->setPaneTitle('1.0', 'fixReleaseNames');
107
        $this->paneManager->selectPane('1.0');
108
        $this->paneManager->splitHorizontal('1', '50%', 'removeCrapReleases');
109
110
        // Window 2: Postprocessing (Left: Additional + TV + Amazon, Right: Movies + XXX)
111
        $this->paneManager->createWindow(2, 'post');
112
        $this->paneManager->setPaneTitle('2.0', 'postprocessing_additional');
113
114
        // Split horizontally to create left and right halves (don't set title yet)
115
        $this->paneManager->splitHorizontal('2', '50%', '');
116
117
        // Left side (2.0): split vertically for TV and Amazon
118
        $this->paneManager->selectPane('2.0');
119
        $this->paneManager->splitVertical('2', '67%', 'postprocessing_tv');
120
        $this->paneManager->splitVertical('2', '50%', 'postprocessing_amazon');
121
122
        // Right side: After left splits, right side is now 2.3
123
        $this->paneManager->selectPane('2.3');
124
        $this->paneManager->setPaneTitle('2.3', 'postprocessing_movies');
125
        $this->paneManager->splitVertical('2', '50%', 'postprocessing_xxx');
126
127
        // Window 3: IRC Scraper
128
        $this->createIRCScraperWindow();
129
130
        $this->createOptionalWindows();
131
132
        return true;
133
    }
134
135
    /**
136
     * Build stripped sequential layout (mode 2)
137
     */
138
    protected function buildStrippedLayout(): bool
139
    {
140
        // Window 0: Monitor + Sequential
141
        if (! $this->sessionManager->createSession('Monitor')) {
142
            return false;
143
        }
144
145
        // Select pane 0.0, then split for sequential
146
        $this->paneManager->selectPane('0.0');
147
        $this->paneManager->splitHorizontal('0', '67%', 'sequential');
148
149
        // Window 1: Amazon postprocessing
150
        $this->paneManager->createWindow(1, 'utils');
151
        $this->paneManager->setPaneTitle('1.0', 'fixReleaseNames');
152
        $this->paneManager->selectPane('1.0');
153
        $this->paneManager->splitHorizontal('1', '50%', 'postprocessing_amazon');
154
155
        // Window 2: IRC Scraper
156
        $this->createIRCScraperWindow();
157
158
        $this->createOptionalWindows();
159
160
        return true;
161
    }
162
163
    /**
164
     * Create IRC scraper window
165
     */
166
    protected function createIRCScraperWindow(): bool
167
    {
168
        $this->paneManager->createWindow(3, 'IRCScraper');
169
        $this->paneManager->setPaneTitle('3.0', 'scrapeIRC');
170
171
        return true;
172
    }
173
174
    /**
175
     * Create optional monitoring windows based on settings
176
     *
177
     * Creates separate tmux windows for enabled monitoring tools.
178
     * Each tool gets its own dedicated window starting from index 4.
179
     *
180
     * Window layout:
181
     * - Window 0: Monitor + Processing panes (binaries/backfill/releases)
182
     * - Window 1: Utilities (fix names, remove crap)
183
     * - Window 2: Postprocessing (additional, tv/anime, movies, amazon)
184
     * - Window 3: IRC Scraper
185
     * - Window 4+: Monitoring tools (htop, nmon, vnstat, tcptrack, bwm-ng, mytop, redis, console)
186
     */
187
    protected function createOptionalWindows(): void
188
    {
189
        $windowIndex = 4;
190
191
        // htop
192
        if ((int) Settings::settingValue('htop') === 1 && $this->commandExists('htop')) {
193
            $this->paneManager->createWindow($windowIndex, 'htop');
194
            $this->paneManager->respawnPane("{$windowIndex}.0", 'htop');
195
            $windowIndex++;
196
        }
197
198
        // nmon
199
        if ((int) Settings::settingValue('nmon') === 1 && $this->commandExists('nmon')) {
200
            $this->paneManager->createWindow($windowIndex, 'nmon');
201
            $this->paneManager->respawnPane("{$windowIndex}.0", 'nmon -t');
202
            $windowIndex++;
203
        }
204
205
        // vnstat
206
        if ((int) Settings::settingValue('vnstat') === 1 && $this->commandExists('vnstat')) {
207
            $vnstatArgs = Settings::settingValue('vnstat_args') ?? '';
208
            $this->paneManager->createWindow($windowIndex, 'vnstat');
209
            $this->paneManager->respawnPane("{$windowIndex}.0", "watch -n10 'vnstat {$vnstatArgs}'");
210
            $windowIndex++;
211
        }
212
213
        // tcptrack
214
        if ((int) Settings::settingValue('tcptrack') === 1 && $this->commandExists('tcptrack')) {
215
            $tcptrackArgs = Settings::settingValue('tcptrack_args') ?? '';
216
            $this->paneManager->createWindow($windowIndex, 'tcptrack');
217
            $this->paneManager->respawnPane("{$windowIndex}.0", "tcptrack {$tcptrackArgs}");
218
            $windowIndex++;
219
        }
220
221
        // bwm-ng
222
        if ((int) Settings::settingValue('bwmng') === 1 && $this->commandExists('bwm-ng')) {
223
            $this->paneManager->createWindow($windowIndex, 'bwm-ng');
224
            $this->paneManager->respawnPane("{$windowIndex}.0", 'bwm-ng');
225
            $windowIndex++;
226
        }
227
228
        // mytop
229
        if ((int) Settings::settingValue('mytop') === 1 && $this->commandExists('mytop')) {
230
            $this->paneManager->createWindow($windowIndex, 'mytop');
231
            $this->paneManager->respawnPane("{$windowIndex}.0", 'mytop -u');
232
            $windowIndex++;
233
        }
234
235
        // redis monitoring
236
        if ((int) Settings::settingValue('redis') === 1 && $this->commandExists('redis-cli')) {
237
            $redisHost = config('database.redis.default.host', '127.0.0.1');
238
            $redisPort = config('database.redis.default.port', 6379);
239
            $redisArgs = Settings::settingValue('redis_args') ?? '';
240
            $refreshInterval = 2;
241
242
            $this->paneManager->createWindow($windowIndex, 'redis');
243
244
            // Check if custom args provided for simple redis-cli output
245
            if (! empty($redisArgs) && $redisArgs !== 'NULL') {
246
                $this->paneManager->respawnPane("{$windowIndex}.0", "watch -n{$refreshInterval} -c 'redis-cli -h {$redisHost} -p {$redisPort} {$redisArgs}'");
247
            } else {
248
                // Use modern visual monitoring script
249
                $monitorScript = base_path('misc/redis-monitor.sh');
250
                if (file_exists($monitorScript)) {
251
                    $this->paneManager->respawnPane("{$windowIndex}.0", "bash {$monitorScript} {$redisHost} {$redisPort} {$refreshInterval}");
252
                } else {
253
                    // Fallback to basic redis-cli info with color
254
                    $this->paneManager->respawnPane("{$windowIndex}.0", "watch -n{$refreshInterval} -c 'redis-cli -h {$redisHost} -p {$redisPort} info'");
255
                }
256
            }
257
            $windowIndex++;
258
        }
259
260
        // bash console
261
        if ((int) Settings::settingValue('console') === 1) {
262
            $this->paneManager->createWindow($windowIndex, 'bash');
263
            $this->paneManager->respawnPane("{$windowIndex}.0", 'bash -i');
264
        }
265
    }
266
267
    /**
268
     * Check if a command exists
269
     */
270
    protected function commandExists(string $command): bool
271
    {
272
        $result = \Illuminate\Support\Facades\Process::timeout(5)
273
            ->run("which {$command} 2>/dev/null");
274
275
        return $result->successful() && str_contains($result->output(), $command);
276
    }
277
}
278