Files
server-manager/views/servers/edit.php
Agent 0cfed5bf27 feat: validate required SSH permissions before creating server
- New server_permissions table (migration 007) stores required SSH permissions per server
- PermissionValidator service connects via SSH and validates each permission before save
- ServerPermission model for CRUD on server_permissions table
- Web flow: create/edit forms include dynamic permission rows with quick-add presets
- API flow: serverAdd/serverUpdate accept permissions array, return 400 on failure
- Show view displays required permissions on server detail page
- Form input preserved on validation failure (credentials excluded)
- AGENTS.md updated with new conventions
2026-06-13 10:41:18 -04:00

200 lines
11 KiB
PHP
Executable File

<?php
$server = $server ?? [];
$groups = $groups ?? [];
$permissions = $permissions ?? [];
$hasPassword = !empty($server['ssh_password']);
$hasKey = !empty($server['ssh_key']);
?>
<div class="page-header">
<div class="page-header-left">
<div class="back-link">
<a href="/servers/<?= $server['id'] ?>"><i class="fas fa-arrow-left"></i> Back to Server</a>
</div>
<h1 class="page-title">Edit Server</h1>
<p class="page-subtitle"><?= htmlspecialchars($server['name'] ?? '') ?></p>
</div>
</div>
<div class="card">
<div class="card-body">
<form method="POST" action="/servers/<?= $server['id'] ?>/edit" class="form">
<input type="hidden" name="_csrf_token" value="<?= $this->csrfToken() ?>">
<div class="form-section">
<h3><i class="fas fa-info-circle"></i> General Information</h3>
<div class="form-row">
<div class="form-group">
<label for="name">Server Name *</label>
<input type="text" id="name" name="name" class="form-input"
value="<?= htmlspecialchars($server['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label for="group_name">Group / Category</label>
<input type="text" id="group_name" name="group_name" class="form-input"
value="<?= htmlspecialchars($server['group_name'] ?? '') ?>"
list="groupList">
<datalist id="groupList">
<?php foreach ($groups as $group): ?>
<option value="<?= htmlspecialchars($group) ?>">
<?php endforeach; ?>
</datalist>
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" name="description" class="form-input" rows="3"><?= htmlspecialchars($server['description'] ?? '') ?></textarea>
</div>
</div>
<div class="form-section">
<h3><i class="fas fa-plug"></i> Connection Details</h3>
<div class="form-row form-row-3">
<div class="form-group">
<label for="ip_address">IP Address / Hostname *</label>
<input type="text" id="ip_address" name="ip_address" class="form-input"
value="<?= htmlspecialchars($server['ip_address'] ?? '') ?>" required>
</div>
<div class="form-group">
<label for="ssh_port">SSH Port *</label>
<input type="number" id="ssh_port" name="ssh_port" class="form-input"
value="<?= $server['ssh_port'] ?? 22 ?>" min="1" max="65535" required>
</div>
<div class="form-group">
<label for="ssh_user">SSH User *</label>
<input type="text" id="ssh_user" name="ssh_user" class="form-input"
value="<?= htmlspecialchars($server['ssh_user'] ?? '') ?>" required>
</div>
</div>
</div>
<div class="form-section">
<h3><i class="fas fa-key"></i> Authentication (leave blank to keep current)</h3>
<div class="form-group">
<label for="ssh_password">New SSH Password</label>
<div class="input-group">
<input type="password" id="ssh_password" name="ssh_password" class="form-input"
placeholder="Enter new password or leave blank">
<button type="button" class="input-toggle-password" tabindex="-1">
<i class="fas fa-eye"></i>
</button>
</div>
<?php if ($hasPassword): ?>
<small class="form-hint">Currently using password authentication.</small>
<?php endif; ?>
</div>
<div class="form-group">
<label for="ssh_key">New SSH Private Key</label>
<textarea id="ssh_key" name="ssh_key" class="form-input font-mono" rows="6"
placeholder="Paste new private key or leave blank"><?= htmlspecialchars($server['ssh_key'] ?? '') ?></textarea>
<?php if ($hasKey): ?>
<small class="form-hint">Currently using key-based authentication.</small>
<?php endif; ?>
</div>
</div>
<div class="form-section">
<h3><i class="fas fa-shield-alt"></i> Required Permissions</h3>
<p class="form-hint">The system will verify these permissions on the remote server before saving changes.</p>
<div id="permissions-container">
<?php if (!empty($permissions)): ?>
<?php foreach ($permissions as $i => $perm): ?>
<div class="permission-row" data-index="<?= $i ?>">
<select name="permissions[<?= $i ?>][type]" class="form-input permission-type">
<option value="sudo" <?= ($perm['permission_type'] ?? '') === 'sudo' ? 'selected' : '' ?>>Sudo (passwordless)</option>
<option value="command" <?= ($perm['permission_type'] ?? '') === 'command' ? 'selected' : '' ?>>Command</option>
<option value="file_read" <?= ($perm['permission_type'] ?? '') === 'file_read' ? 'selected' : '' ?>>File Read</option>
<option value="file_write" <?= ($perm['permission_type'] ?? '') === 'file_write' ? 'selected' : '' ?>>File Write</option>
<option value="custom" <?= ($perm['permission_type'] ?? '') === 'custom' ? 'selected' : '' ?>>Custom Command</option>
</select>
<input type="text" name="permissions[<?= $i ?>][value]" class="form-input permission-value"
placeholder="e.g., systemctl" value="<?= htmlspecialchars($perm['permission_value'] ?? '') ?>">
<input type="text" name="permissions[<?= $i ?>][description]" class="form-input permission-desc"
placeholder="Description (optional)" value="<?= htmlspecialchars($perm['description'] ?? '') ?>">
<button type="button" class="btn btn-danger btn-sm" onclick="removePermissionRow(this)"><i class="fas fa-times"></i></button>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="permission-actions">
<button type="button" id="addPermissionBtn" class="btn btn-secondary">
<i class="fas fa-plus"></i> Add Permission
</button>
</div>
<div class="permission-presets">
<span class="preset-label">Quick add:</span>
<button type="button" class="btn btn-xs btn-outline" onclick="addPresetPermission('sudo', 'sudo -n true', 'Passwordless sudo for system management')">Sudo</button>
<button type="button" class="btn btn-xs btn-outline" onclick="addPresetPermission('command', 'systemctl', 'Service management')">systemctl</button>
<button type="button" class="btn btn-xs btn-outline" onclick="addPresetPermission('command', 'df', 'Disk usage monitoring')">df</button>
<button type="button" class="btn btn-xs btn-outline" onclick="addPresetPermission('command', 'free', 'Memory monitoring')">free</button>
<button type="button" class="btn btn-xs btn-outline" onclick="addPresetPermission('command', 'top', 'CPU monitoring')">top</button>
</div>
</div>
<div class="form-section">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="is_active" value="1" <?= ($server['is_active'] ?? 1) ? 'checked' : '' ?>>
<span>Server active</span>
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Update Server
</button>
<a href="/servers/<?= $server['id'] ?>" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
<script>
let permIndex = <?= count($permissions) ?>;
function createPermissionRow(type, value, description) {
const idx = permIndex++;
const container = document.getElementById('permissions-container');
const div = document.createElement('div');
div.className = 'permission-row';
div.dataset.index = idx;
div.innerHTML = `
<select name="permissions[${idx}][type]" class="form-input permission-type">
<option value="sudo" ${type === 'sudo' ? 'selected' : ''}>Sudo (passwordless)</option>
<option value="command" ${type === 'command' ? 'selected' : ''}>Command</option>
<option value="file_read" ${type === 'file_read' ? 'selected' : ''}>File Read</option>
<option value="file_write" ${type === 'file_write' ? 'selected' : ''}>File Write</option>
<option value="custom" ${type === 'custom' ? 'selected' : ''}>Custom Command</option>
</select>
<input type="text" name="permissions[${idx}][value]" class="form-input permission-value"
placeholder="e.g., systemctl" value="${escapeHtml(value || '')}">
<input type="text" name="permissions[${idx}][description]" class="form-input permission-desc"
placeholder="Description (optional)" value="${escapeHtml(description || '')}">
<button type="button" class="btn btn-danger btn-sm" onclick="removePermissionRow(this)"><i class="fas fa-times"></i></button>
`;
container.appendChild(div);
}
function removePermissionRow(btn) {
btn.closest('.permission-row').remove();
}
function addPresetPermission(type, value, description) {
createPermissionRow(type, value, description);
}
document.getElementById('addPermissionBtn').addEventListener('click', function() {
createPermissionRow('command', '', '');
});
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
</script>