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\Ui\Widget\Button;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Checkbox;
|
||||
use PHPNative\Ui\Widget\Icon;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
use PHPNative\Ui\Widget\TabContainer;
|
||||
@ -65,6 +66,22 @@ class ServerListTab
|
||||
$this->refreshButton->setIcon($refreshIcon);
|
||||
$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)
|
||||
$this->loadingIndicator = new LoadingIndicator('ml-auto');
|
||||
$headerRow->addComponent($this->loadingIndicator);
|
||||
@ -81,7 +98,13 @@ class ServerListTab
|
||||
// Table
|
||||
$this->table = new Table(style: ' flex-1');
|
||||
$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' => 'status', 'title' => 'Status', 'width' => 90],
|
||||
['key' => 'type', 'title' => 'Typ', 'width' => 80],
|
||||
@ -203,10 +226,15 @@ class ServerListTab
|
||||
$this->tab->addComponent($detailPanel);
|
||||
|
||||
// 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
|
||||
$serverListTab = $this;
|
||||
@ -493,6 +521,174 @@ class ServerListTab
|
||||
$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
|
||||
@ -575,4 +771,21 @@ class ServerListTab
|
||||
$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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// Check if click is within checkbox bounds (not label)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user