Backup
This commit is contained in:
parent
21bc637ced
commit
7ea3d6cf11
@ -6,6 +6,7 @@ use PHPNative\Async\TaskManager;
|
|||||||
use PHPNative\Tailwind\Data\Icon as IconName;
|
use PHPNative\Tailwind\Data\Icon as IconName;
|
||||||
use PHPNative\Ui\Widget\Button;
|
use PHPNative\Ui\Widget\Button;
|
||||||
use PHPNative\Ui\Widget\Container;
|
use PHPNative\Ui\Widget\Container;
|
||||||
|
use PHPNative\Ui\Widget\Checkbox;
|
||||||
use PHPNative\Ui\Widget\Icon;
|
use PHPNative\Ui\Widget\Icon;
|
||||||
use PHPNative\Ui\Widget\Label;
|
use PHPNative\Ui\Widget\Label;
|
||||||
use PHPNative\Ui\Widget\TabContainer;
|
use PHPNative\Ui\Widget\TabContainer;
|
||||||
@ -65,6 +66,22 @@ class ServerListTab
|
|||||||
$this->refreshButton->setIcon($refreshIcon);
|
$this->refreshButton->setIcon($refreshIcon);
|
||||||
$headerRow->addComponent($this->refreshButton);
|
$headerRow->addComponent($this->refreshButton);
|
||||||
|
|
||||||
|
// Bulk action buttons for selected servers
|
||||||
|
$rebootButton = new Button(
|
||||||
|
'Reboot ausgewählte',
|
||||||
|
'px-3 py-2 bg-red-600 rounded hover:bg-red-700',
|
||||||
|
null,
|
||||||
|
'text-white',
|
||||||
|
);
|
||||||
|
$updateButton = new Button(
|
||||||
|
'Update ausgewählte',
|
||||||
|
'px-3 py-2 bg-amber-600 rounded hover:bg-amber-700',
|
||||||
|
null,
|
||||||
|
'text-white',
|
||||||
|
);
|
||||||
|
$headerRow->addComponent($rebootButton);
|
||||||
|
$headerRow->addComponent($updateButton);
|
||||||
|
|
||||||
// Loading indicator (top-right in the server tab header)
|
// Loading indicator (top-right in the server tab header)
|
||||||
$this->loadingIndicator = new LoadingIndicator('ml-auto');
|
$this->loadingIndicator = new LoadingIndicator('ml-auto');
|
||||||
$headerRow->addComponent($this->loadingIndicator);
|
$headerRow->addComponent($this->loadingIndicator);
|
||||||
@ -81,7 +98,13 @@ class ServerListTab
|
|||||||
// Table
|
// Table
|
||||||
$this->table = new Table(style: ' flex-1');
|
$this->table = new Table(style: ' flex-1');
|
||||||
$this->table->setColumns([
|
$this->table->setColumns([
|
||||||
['key' => 'id', 'title' => 'ID', 'width' => 100],
|
[
|
||||||
|
'key' => 'selected',
|
||||||
|
'title' => '',
|
||||||
|
'width' => 40,
|
||||||
|
'render' => [$this, 'renderSelectCell'],
|
||||||
|
],
|
||||||
|
['key' => 'id', 'title' => 'ID', 'width' => 80],
|
||||||
['key' => 'name', 'title' => 'Name'],
|
['key' => 'name', 'title' => 'Name'],
|
||||||
['key' => 'status', 'title' => 'Status', 'width' => 90],
|
['key' => 'status', 'title' => 'Status', 'width' => 90],
|
||||||
['key' => 'type', 'title' => 'Typ', 'width' => 80],
|
['key' => 'type', 'title' => 'Typ', 'width' => 80],
|
||||||
@ -203,10 +226,15 @@ class ServerListTab
|
|||||||
$this->tab->addComponent($detailPanel);
|
$this->tab->addComponent($detailPanel);
|
||||||
|
|
||||||
// Setup event handlers
|
// Setup event handlers
|
||||||
$this->setupEventHandlers($currentApiKey, $currentPrivateKeyPath);
|
$this->setupEventHandlers($currentApiKey, $currentPrivateKeyPath, $rebootButton, $updateButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setupEventHandlers(string &$currentApiKey, string &$currentPrivateKeyPath): void
|
private function setupEventHandlers(
|
||||||
|
string &$currentApiKey,
|
||||||
|
string &$currentPrivateKeyPath,
|
||||||
|
Button $rebootButton,
|
||||||
|
Button $updateButton,
|
||||||
|
): void
|
||||||
{
|
{
|
||||||
// Table row selection
|
// Table row selection
|
||||||
$serverListTab = $this;
|
$serverListTab = $this;
|
||||||
@ -493,6 +521,174 @@ class ServerListTab
|
|||||||
$serverListTab->loadingIndicator->setLoading(false);
|
$serverListTab->loadingIndicator->setLoading(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reboot selected servers
|
||||||
|
$rebootButton->setOnClick(function () use (&$currentPrivateKeyPath, $serverListTab) {
|
||||||
|
$selected = [];
|
||||||
|
foreach ($serverListTab->currentServerData as $index => $row) {
|
||||||
|
if (!empty($row['selected'])) {
|
||||||
|
$selected[] = ['index' => $index, 'row' => $row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($selected)) {
|
||||||
|
$serverListTab->statusLabel->setText('Keine Server ausgewählt für Reboot');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
|
||||||
|
$serverListTab->statusLabel->setText('Private Key Pfad nicht konfiguriert');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($selected as $item) {
|
||||||
|
$row = $item['row'];
|
||||||
|
$ip = $row['ipv4'] ?? '';
|
||||||
|
$name = $row['name'] ?? $ip;
|
||||||
|
$index = $item['index'];
|
||||||
|
|
||||||
|
if (empty($ip)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$task = TaskManager::getInstance()->runAsync(function () use ($ip, $currentPrivateKeyPath, $index) {
|
||||||
|
try {
|
||||||
|
$ssh = new \phpseclib3\Net\SSH2($ip);
|
||||||
|
$key = \phpseclib3\Crypt\PublicKeyLoader::loadPrivateKey(file_get_contents(
|
||||||
|
$currentPrivateKeyPath,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$ssh->login('root', $key)) {
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'SSH Login fehlgeschlagen',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssh->exec('reboot');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'success' => true,
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'SSH-Fehler: ' . $e->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$task->onComplete(function ($result) use (&$serverListTab, $name) {
|
||||||
|
if (!is_array($result) || !array_key_exists('success', $result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
$serverListTab->statusLabel->setText('Reboot ausgelöst für ' . $name);
|
||||||
|
} elseif (isset($result['error'])) {
|
||||||
|
$serverListTab->statusLabel->setText('Reboot Fehler bei ' . $name . ': ' . $result['error']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update selected servers
|
||||||
|
$updateButton->setOnClick(function () use (&$currentPrivateKeyPath, $serverListTab) {
|
||||||
|
$selected = [];
|
||||||
|
foreach ($serverListTab->currentServerData as $index => $row) {
|
||||||
|
if (!empty($row['selected'])) {
|
||||||
|
$selected[] = ['index' => $index, 'row' => $row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($selected)) {
|
||||||
|
$serverListTab->statusLabel->setText('Keine Server ausgewählt für Updates');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
|
||||||
|
$serverListTab->statusLabel->setText('Private Key Pfad nicht konfiguriert');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($selected as $item) {
|
||||||
|
$row = $item['row'];
|
||||||
|
$ip = $row['ipv4'] ?? '';
|
||||||
|
$name = $row['name'] ?? $ip;
|
||||||
|
$index = $item['index'];
|
||||||
|
|
||||||
|
if (empty($ip)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$task = TaskManager::getInstance()->runAsync(function () use ($ip, $currentPrivateKeyPath, $index) {
|
||||||
|
try {
|
||||||
|
$ssh = new \phpseclib3\Net\SSH2($ip);
|
||||||
|
$key = \phpseclib3\Crypt\PublicKeyLoader::loadPrivateKey(file_get_contents(
|
||||||
|
$currentPrivateKeyPath,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$ssh->login('root', $key)) {
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'SSH Login fehlgeschlagen',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run update & upgrade non-interactively
|
||||||
|
$ssh->exec(
|
||||||
|
'apt-get update >/dev/null 2>&1 && ' .
|
||||||
|
'DEBIAN_FRONTEND=noninteractive apt-get -y upgrade >/dev/null 2>&1',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Re-check number of available updates
|
||||||
|
$updatesOutput = trim($ssh->exec(
|
||||||
|
'apt-get -s upgrade 2>/dev/null | grep -c "^Inst " || echo 0',
|
||||||
|
));
|
||||||
|
$updatesCount = is_numeric($updatesOutput) ? (int) $updatesOutput : 0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'success' => true,
|
||||||
|
'updates' => $updatesCount,
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return [
|
||||||
|
'index' => $index,
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'SSH-Fehler: ' . $e->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$task->onComplete(function ($result) use (&$serverListTab, $name) {
|
||||||
|
if (!is_array($result) || !array_key_exists('success', $result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = $result['index'] ?? null;
|
||||||
|
if ($index !== null && isset($serverListTab->currentServerData[$index])) {
|
||||||
|
if (isset($result['updates'])) {
|
||||||
|
$updates = (int) $result['updates'];
|
||||||
|
$serverListTab->currentServerData[$index]['updates_available'] =
|
||||||
|
$updates > 0 ? ('ja (' . $updates . ')') : 'nein';
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverListTab->table->setData($serverListTab->currentServerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
$serverListTab->statusLabel->setText('Updates ausgeführt für ' . $name);
|
||||||
|
} elseif (isset($result['error'])) {
|
||||||
|
$serverListTab->statusLabel->setText('Update Fehler bei ' . $name . ': ' . $result['error']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContainer(): Container
|
public function getContainer(): Container
|
||||||
@ -575,4 +771,21 @@ class ServerListTab
|
|||||||
$this->detailDomainsContainer->addComponent($button);
|
$this->detailDomainsContainer->addComponent($button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function renderSelectCell(array $rowData, int $rowIndex): Checkbox
|
||||||
|
{
|
||||||
|
$isChecked = (bool) ($rowData['selected'] ?? false);
|
||||||
|
$checkbox = new Checkbox('', $isChecked);
|
||||||
|
|
||||||
|
$serverListTab = $this;
|
||||||
|
$checkbox->setOnChange(function (bool $checked) use ($serverListTab, $rowIndex) {
|
||||||
|
if (!isset($serverListTab->currentServerData[$rowIndex])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverListTab->currentServerData[$rowIndex]['selected'] = $checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $checkbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,18 @@ class Checkbox extends Component
|
|||||||
$this->onChange = $onChange;
|
$this->onChange = $onChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function layout(null|TextRenderer $textRenderer = null): void
|
||||||
|
{
|
||||||
|
parent::layout($textRenderer);
|
||||||
|
|
||||||
|
// Force a compact, fixed size for the checkbox
|
||||||
|
$checkboxSize = 20;
|
||||||
|
$this->viewport->width = $checkboxSize;
|
||||||
|
$this->viewport->height = $checkboxSize;
|
||||||
|
$this->contentViewport->width = $checkboxSize;
|
||||||
|
$this->contentViewport->height = $checkboxSize;
|
||||||
|
}
|
||||||
|
|
||||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||||
{
|
{
|
||||||
// Check if click is within checkbox bounds (not label)
|
// Check if click is within checkbox bounds (not label)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user